├── jQueryTmpl ├── Exception.php ├── Data │ ├── Exception.php │ └── Factory.php ├── Markup.php ├── Element │ ├── Exception.php │ ├── TypeControl.php │ ├── TypeInline.php │ ├── Comment.php │ ├── TypeBlock.php │ ├── NoOp.php │ ├── ValueNotEscaped.php │ ├── Base.php │ ├── ValueEscaped.php │ ├── Else.php │ ├── TypeRenderable.php │ ├── Factory.php │ ├── Tmpl.php │ ├── If.php │ └── Each.php ├── Markup │ ├── Exception.php │ ├── Factory.php │ ├── String.php │ └── File.php ├── Parser │ ├── Exception.php │ └── Factory.php ├── Tokenizer │ ├── Exception.php │ └── Factory.php ├── Token │ ├── TypeControl.php │ ├── TypeInline.php │ ├── Else.php │ ├── NoOp.php │ ├── Tmpl.php │ ├── Comment.php │ ├── ValueEscaped.php │ ├── ValueNotEscaped.php │ ├── Factory.php │ ├── TypeBlock.php │ ├── IfEnd.php │ ├── EachEnd.php │ ├── IfStart.php │ ├── EachStart.php │ └── Base.php ├── Factory.php ├── Tag │ ├── IfEnd.php │ ├── Comment.php │ ├── EachEnd.php │ ├── IfStart.php │ ├── ValueEscaped.php │ ├── ValueEscapedShorthand.php │ ├── ValueNotEscaped.php │ ├── Else.php │ ├── EachStart.php │ └── Tmpl.php ├── Element.php ├── Token.php ├── Tag.php ├── Parser.php ├── Tokenizer.php └── Data.php ├── test ├── phpunit.xml ├── Tag │ ├── TestCase.php │ ├── IfEndTest.php │ ├── EachEndTest.php │ ├── CommentTest.php │ ├── IfStartTest.php │ ├── ValueEscapedTest.php │ ├── ValueEscapedShorthandTest.php │ ├── ValueNotEscapedTest.php │ ├── EachStartTest.php │ ├── ElseTest.php │ └── TmplTest.php ├── Element │ ├── CommentTest.php │ ├── NoOpTest.php │ ├── TestCase.php │ ├── ValueEscapedTest.php │ ├── ValueNotEscapedTest.php │ ├── IfTest.php │ ├── EachTest.php │ └── TmplTest.php ├── TokenTest.php ├── TokenizerTest.php ├── DataTest.php └── ParserTest.php ├── example.js ├── LICENSE.txt ├── jQueryTmpl.php ├── example.php └── README.md /jQueryTmpl/Exception.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ./ 5 | 6 | 7 | -------------------------------------------------------------------------------- /jQueryTmpl/Element/TypeControl.php: -------------------------------------------------------------------------------- 1 | _parser = $parser; 10 | } 11 | 12 | abstract public function parseTokens(array $tokens); 13 | } 14 | 15 | -------------------------------------------------------------------------------- /jQueryTmpl/Markup/String.php: -------------------------------------------------------------------------------- 1 | _template = (string)$template; 10 | } 11 | 12 | public function getTemplate() 13 | { 14 | return $this->_template; 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /jQueryTmpl/Token/TypeBlock.php: -------------------------------------------------------------------------------- 1 | create(), 13 | $pFactory->create() 14 | ); 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /jQueryTmpl/Token/IfEnd.php: -------------------------------------------------------------------------------- 1 | _token = $token; 10 | } 11 | 12 | public function render() 13 | { 14 | return $this->_token->getRawContent(); 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /test/Tag/TestCase.php: -------------------------------------------------------------------------------- 1 | preg_match_all($regex, $string, $matches, PREG_OFFSET_CAPTURE), 14 | 'match' => $matches[0] 15 | ); 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /jQueryTmpl/Tag/IfEnd.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Movie Name 5 | Release Year 6 | Director 7 | Rating 8 | 9 | 10 | 11 | {{each movies}} 12 | 13 | ${this.name} 14 | ${this.year} 15 | ${this.director} 16 | {{tmpl "#ratingTemplate"}} 17 | 18 | {{/each}} 19 | 20 | 21 | -------------------------------------------------------------------------------- /jQueryTmpl/Element/ValueNotEscaped.php: -------------------------------------------------------------------------------- 1 | _token = $token; 10 | } 11 | 12 | public function render() 13 | { 14 | $options = $this->_token->getOptions(); 15 | return $this->_data->getValueOf($options['name']); 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /jQueryTmpl/Element/Base.php: -------------------------------------------------------------------------------- 1 | _data = $data; 11 | return $this; 12 | } 13 | 14 | public function setCompiledTemplates(array $compiledTemplates) 15 | { 16 | $this->_compiledTemplates = $compiledTemplates; 17 | return $this; 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /jQueryTmpl/Element/ValueEscaped.php: -------------------------------------------------------------------------------- 1 | _token = $token; 10 | } 11 | 12 | public function render() 13 | { 14 | $options = $this->_token->getOptions(); 15 | return htmlspecialchars($this->_data->getValueOf($options['name']), ENT_COMPAT, 'UTF-8'); 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /test/Element/CommentTest.php: -------------------------------------------------------------------------------- 1 | _elementFactory->createInline 10 | ( 11 | 'Comment', 12 | new jQueryTmpl_Token_Comment(0, array(), '{{! Some Comment !}}') 13 | ); 14 | 15 | $this->assertEquals 16 | ( 17 | '', 18 | $element->setData($this->_data)->render() 19 | ); 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /jQueryTmpl/Element/Else.php: -------------------------------------------------------------------------------- 1 | _token = $token; 10 | } 11 | 12 | public function displayNextBlock() 13 | { 14 | $options = $this->_token->getOptions(); 15 | 16 | if ($options == array()) 17 | { 18 | // Final {{else}} tag 19 | return true; 20 | } 21 | 22 | return (bool)$this->_data->getValueOf($options['name']); 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /test/Element/NoOpTest.php: -------------------------------------------------------------------------------- 1 | string and {{not a tag}}.'; 10 | 11 | $element = $this->_elementFactory->createInline 12 | ( 13 | 'NoOp', 14 | new jQueryTmpl_Token_NoOp(0, array(), $str) 15 | ); 16 | 17 | $this->assertEquals 18 | ( 19 | $str, 20 | $element->setData($this->_data)->render() 21 | ); 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /jQueryTmpl/Tag/IfStart.php: -------------------------------------------------------------------------------- 1 | trim($matches[1]) 28 | ); 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /jQueryTmpl/Tag/ValueEscaped.php: -------------------------------------------------------------------------------- 1 | trim($matches[1]) 28 | ); 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /jQueryTmpl/Tag/ValueEscapedShorthand.php: -------------------------------------------------------------------------------- 1 | trim($matches[1]) 28 | ); 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /jQueryTmpl/Tag/ValueNotEscaped.php: -------------------------------------------------------------------------------- 1 | trim($matches[1]) 28 | ); 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /jQueryTmpl/Token/Base.php: -------------------------------------------------------------------------------- 1 | _level = $level; 12 | $this->_options = $options; 13 | $this->_rawContent = $rawContent; 14 | } 15 | 16 | public function getLevel() 17 | { 18 | return $this->_level; 19 | } 20 | 21 | public function getOptions() 22 | { 23 | return $this->_options; 24 | } 25 | 26 | public function getRawContent() 27 | { 28 | return $this->_rawContent; 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /jQueryTmpl/Element/TypeRenderable.php: -------------------------------------------------------------------------------- 1 | trim($matches[1]) 35 | ); 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /jQueryTmpl/Element.php: -------------------------------------------------------------------------------- 1 | _getFileContents($filename); 10 | } 11 | 12 | public function getTemplate() 13 | { 14 | return $this->_template; 15 | } 16 | 17 | private function _getFileContents($filename) 18 | { 19 | try 20 | { 21 | $this->_template = file_get_contents($filename); 22 | } 23 | catch (Exception $e) 24 | { 25 | throw new jQueryTmpl_Markup_Exception($e->getMessage()); 26 | } 27 | 28 | if ($this->_template == FALSE) 29 | { 30 | throw new jQueryTmpl_Markup_Exception("Could not open markup file '$filename'."); 31 | } 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /jQueryTmpl/Element/Factory.php: -------------------------------------------------------------------------------- 1 | create()); 11 | $element 12 | ->parseTokens($tokens); 13 | 14 | return $element; 15 | } 16 | 17 | public function createControl($type, jQueryTmpl_Token $token) 18 | { 19 | $class = "jQueryTmpl_Element_$type"; 20 | $element = new $class(); 21 | $element 22 | ->parseToken($token); 23 | 24 | return $element; 25 | } 26 | 27 | public function createInline($type, jQueryTmpl_Token $token) 28 | { 29 | $class = "jQueryTmpl_Element_$type"; 30 | $element = new $class(); 31 | $element 32 | ->parseToken($token); 33 | 34 | return $element; 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /jQueryTmpl/Element/Tmpl.php: -------------------------------------------------------------------------------- 1 | _token = $token; 10 | } 11 | 12 | public function render() 13 | { 14 | $options = $this->_token->getOptions(); 15 | 16 | $elements = $this->_compiledTemplates[$options['template']]; 17 | $localData = $this->_data->getDataSice($options['data']); 18 | 19 | if (empty($elements) || empty($localData)) 20 | { 21 | return ''; 22 | } 23 | 24 | $rendered = ''; 25 | 26 | foreach ($elements as $element) 27 | { 28 | $rendered .= $element 29 | ->setData($localData) 30 | ->setCompiledTemplates($this->_compiledTemplates) 31 | ->render(); 32 | } 33 | 34 | return $rendered; 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /jQueryTmpl/Tag/EachStart.php: -------------------------------------------------------------------------------- 1 | trim($matches[1]) 30 | ); 31 | } 32 | 33 | // Matched optional params as well 34 | return array 35 | ( 36 | 'name' => trim($matches[4]), 37 | 'index' => trim($matches[2]), 38 | 'value' => trim($matches[3]) 39 | ); 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /jQueryTmpl/Tokenizer/Factory.php: -------------------------------------------------------------------------------- 1 | addTag(new jQueryTmpl_Tag_Comment()) 15 | // Each block 16 | ->addTag(new jQueryTmpl_Tag_EachStart()) 17 | ->addTag(new jQueryTmpl_Tag_EachEnd()) 18 | // If/Else Block 19 | ->addTag(new jQueryTmpl_Tag_IfStart()) 20 | ->addTag(new jQueryTmpl_Tag_Else()) 21 | ->addTag(new jQueryTmpl_Tag_IfEnd()) 22 | // Tmpl Tag 23 | ->addTag(new jQueryTmpl_Tag_Tmpl()) 24 | // Values 25 | ->addTag(new jQueryTmpl_Tag_ValueEscaped()) 26 | ->addTag(new jQueryTmpl_Tag_ValueEscapedShorthand()) 27 | ->addTag(new jQueryTmpl_Tag_ValueNotEscaped()); 28 | 29 | return $tokenizer; 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /test/TokenTest.php: -------------------------------------------------------------------------------- 1 | assertContainsOnly 23 | ( 24 | 'jQueryTmpl_Token', 25 | $tokens 26 | ); 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Xiao Yu, @HypertextRanch 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /jQueryTmpl/Data/Factory.php: -------------------------------------------------------------------------------- 1 | getMessage()); 14 | } 15 | 16 | if (!($obj instanceof stdClass)) 17 | { 18 | throw new jQueryTmpl_Data_Exception('Could not create data object from JSON string'); 19 | } 20 | 21 | return new jQueryTmpl_Data 22 | ( 23 | $obj, 24 | new jQueryTmpl_Data_Factory() 25 | ); 26 | } 27 | 28 | public function createFromArray(array $array) 29 | { 30 | return new jQueryTmpl_Data 31 | ( 32 | json_decode 33 | ( 34 | json_encode($array) 35 | ), 36 | new jQueryTmpl_Data_Factory() 37 | ); 38 | } 39 | 40 | public function createFromStdClass(stdClass $obj) 41 | { 42 | return new jQueryTmpl_Data 43 | ( 44 | $obj, 45 | new jQueryTmpl_Data_Factory() 46 | ); 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /jQueryTmpl/Token.php: -------------------------------------------------------------------------------- 1 | parseTag() 15 | * @param string $rawContent The entire tag or raw html content 16 | */ 17 | public function __construct($level, array $options, $rawContent); 18 | 19 | /** 20 | * The element type that should be created by this token type. 21 | * Closing tags will not create elements and should return an 22 | * empty string. 23 | * @return string Internal name for element type. 24 | */ 25 | public function getElementType(); 26 | 27 | /** 28 | * Simply gets the values set in the constructor. 29 | * @return integer Level of nesting. 30 | */ 31 | public function getLevel(); 32 | 33 | /** 34 | * Simply gets the values set in the constructor. 35 | * @return array Hash of options. 36 | */ 37 | public function getOptions(); 38 | 39 | /** 40 | * Simply gets the values set in the constructor. 41 | * @return string Raw string extracted from template. 42 | */ 43 | public function getRawContent(); 44 | } 45 | 46 | -------------------------------------------------------------------------------- /jQueryTmpl/Tag/Tmpl.php: -------------------------------------------------------------------------------- 1 | $this->_extractId($matches[1]), 30 | 'data' => null, 31 | 'options' => null 32 | ); 33 | } 34 | 35 | if (count($matches) == 7) 36 | { 37 | return array 38 | ( 39 | 'template' => $this->_extractId($matches[6]), 40 | 'data' => trim($matches[5]), 41 | 'options' => null 42 | ); 43 | } 44 | 45 | // Matched optional params as well 46 | return array 47 | ( 48 | 'template' => $this->_extractId($matches[4]), 49 | 'data' => trim($matches[2]), 50 | 'options' => trim($matches[3]) 51 | ); 52 | } 53 | 54 | private function _extractId($str) 55 | { 56 | return trim($str, "'\"# \t\n\r\0\x0B"); 57 | } 58 | } 59 | 60 | -------------------------------------------------------------------------------- /test/Tag/IfEndTest.php: -------------------------------------------------------------------------------- 1 | _cut = new jQueryTmpl_Tag_IfEnd(); 12 | } 13 | 14 | public function testShouldFindTag() 15 | { 16 | $str = 'Foo bar {{/if}}.'; 17 | 18 | $this->assertEquals 19 | ( 20 | array 21 | ( 22 | 'total' => 1, 23 | 'match' => array 24 | ( 25 | array 26 | ( 27 | '{{/if}}', 28 | 8 29 | ) 30 | ) 31 | ), 32 | $this->_evalRegex($this->_cut->getRegex(), $str) 33 | ); 34 | } 35 | 36 | public function testShouldGetAllTags() 37 | { 38 | $str = '{{/if}} and {{/If}}'; 39 | 40 | $this->assertEquals 41 | ( 42 | array 43 | ( 44 | 'total' => 2, 45 | 'match' => array 46 | ( 47 | array 48 | ( 49 | '{{/if}}', 50 | 0 51 | ), 52 | array 53 | ( 54 | '{{/If}}', 55 | 12 56 | ) 57 | ) 58 | ), 59 | $this->_evalRegex($this->_cut->getRegex(), $str) 60 | ); 61 | } 62 | } 63 | 64 | -------------------------------------------------------------------------------- /test/Tag/EachEndTest.php: -------------------------------------------------------------------------------- 1 | _cut = new jQueryTmpl_Tag_EachEnd(); 12 | } 13 | 14 | public function testShouldFindTag() 15 | { 16 | $str = 'Foo bar {{/each}}.'; 17 | 18 | $this->assertEquals 19 | ( 20 | array 21 | ( 22 | 'total' => 1, 23 | 'match' => array 24 | ( 25 | array 26 | ( 27 | '{{/each}}', 28 | 8 29 | ) 30 | ) 31 | ), 32 | $this->_evalRegex($this->_cut->getRegex(), $str) 33 | ); 34 | } 35 | 36 | public function testShouldGetAllTags() 37 | { 38 | $str = '{{/each}} and {{/EaCh}}'; 39 | 40 | $this->assertEquals 41 | ( 42 | array 43 | ( 44 | 'total' => 2, 45 | 'match' => array 46 | ( 47 | array 48 | ( 49 | '{{/each}}', 50 | 0 51 | ), 52 | array 53 | ( 54 | '{{/EaCh}}', 55 | 14 56 | ) 57 | ) 58 | ), 59 | $this->_evalRegex($this->_cut->getRegex(), $str) 60 | ); 61 | } 62 | } 63 | 64 | -------------------------------------------------------------------------------- /test/Element/TestCase.php: -------------------------------------------------------------------------------- 1 | Some Text & marks \"'\".", 55 | "\$wtfKey" : "It works!", 56 | "attn" : "!" 57 | } 58 | EOF; 59 | 60 | $this->_data = new jQueryTmpl_Data 61 | ( 62 | json_decode($testData), 63 | new jQueryTmpl_Data_Factory() 64 | ); 65 | 66 | $this->_elementFactory = new jQueryTmpl_Element_Factory(); 67 | } 68 | } 69 | 70 | -------------------------------------------------------------------------------- /jQueryTmpl/Tag.php: -------------------------------------------------------------------------------- 1 | _firstToken = array_shift($tokens); 12 | $lastToken = array_pop($tokens); 13 | 14 | // Make sure we have the right types of tokens 15 | if (!($this->_firstToken instanceof jQueryTmpl_Token_IfStart && $lastToken instanceof jQueryTmpl_Token_IfEnd)) 16 | { 17 | throw new jQueryTmpl_Element_Exception('Token mismatch, cannot create {{if}} element.'); 18 | } 19 | 20 | $this->_elements = $this->_parser->parse($tokens); 21 | } 22 | 23 | public function render() 24 | { 25 | $options = $this->_firstToken->getOptions(); 26 | 27 | $startDisplay = (bool)$this->_data->getValueOf($options['name']); 28 | 29 | $rendered = ''; 30 | 31 | foreach($this->_elements as $element) 32 | { 33 | if ($element instanceof jQueryTmpl_Element_Else) 34 | { 35 | if ($startDisplay) 36 | { 37 | break; 38 | } 39 | else 40 | { 41 | $startDisplay = $element->setData($this->_data)->displayNextBlock(); 42 | continue; 43 | } 44 | } 45 | 46 | if ($startDisplay) 47 | { 48 | $rendered .= $element 49 | ->setData($this->_data) 50 | ->setCompiledTemplates($this->_compiledTemplates) 51 | ->render(); 52 | } 53 | } 54 | 55 | return $rendered; 56 | } 57 | } 58 | 59 | -------------------------------------------------------------------------------- /test/Element/ValueEscapedTest.php: -------------------------------------------------------------------------------- 1 | _elementFactory->createInline 10 | ( 11 | 'ValueEscaped', 12 | new jQueryTmpl_Token_ValueEscaped(0, array('name'=>'key1'), '') 13 | ); 14 | 15 | $this->assertEquals 16 | ( 17 | 123.45, 18 | $element->setData($this->_data)->render() 19 | ); 20 | } 21 | 22 | public function testShouldReturnTagReplacedWithValueWithUncommonKey() 23 | { 24 | $element = $this->_elementFactory->createInline 25 | ( 26 | 'ValueEscaped', 27 | new jQueryTmpl_Token_ValueEscaped(0, array('name'=>'$wtfKey'), '') 28 | ); 29 | 30 | $this->assertEquals 31 | ( 32 | 'It works!', 33 | $element->setData($this->_data)->render() 34 | ); 35 | } 36 | 37 | public function testShouldReturnNonexistantTagReplacedWithEmptyString() 38 | { 39 | $element = $this->_elementFactory->createInline 40 | ( 41 | 'ValueEscaped', 42 | new jQueryTmpl_Token_ValueEscaped(0, array('name'=>'dneKey'), '') 43 | ); 44 | 45 | $this->assertEquals 46 | ( 47 | '', 48 | $element->setData($this->_data)->render() 49 | ); 50 | } 51 | 52 | public function testShouldReturnTagReplacedWithEscapedValue() 53 | { 54 | $element = $this->_elementFactory->createInline 55 | ( 56 | 'ValueEscaped', 57 | new jQueryTmpl_Token_ValueEscaped(0, array('name'=>'htmlKey'), '') 58 | ); 59 | 60 | $this->assertEquals 61 | ( 62 | "<span>Some Text & marks "'".</span>", 63 | $element->setData($this->_data)->render() 64 | ); 65 | } 66 | } 67 | 68 | -------------------------------------------------------------------------------- /test/Element/ValueNotEscapedTest.php: -------------------------------------------------------------------------------- 1 | _elementFactory->createInline 10 | ( 11 | 'ValueNotEscaped', 12 | new jQueryTmpl_Token_ValueNotEscaped(0, array('name'=>'key1'), '') 13 | ); 14 | 15 | $this->assertEquals 16 | ( 17 | 123.45, 18 | $element->setData($this->_data)->render() 19 | ); 20 | } 21 | 22 | public function testShouldReturnTagReplacedWithValueWithUncommonKey() 23 | { 24 | $element = $this->_elementFactory->createInline 25 | ( 26 | 'ValueNotEscaped', 27 | new jQueryTmpl_Token_ValueNotEscaped(0, array('name'=>'$wtfKey'), '') 28 | ); 29 | 30 | $this->assertEquals 31 | ( 32 | 'It works!', 33 | $element->setData($this->_data)->render() 34 | ); 35 | } 36 | 37 | public function testShouldReturnNonexistantTagReplacedWithEmptyString() 38 | { 39 | $element = $this->_elementFactory->createInline 40 | ( 41 | 'ValueNotEscaped', 42 | new jQueryTmpl_Token_ValueNotEscaped(0, array('name'=>'dneKey'), '') 43 | ); 44 | 45 | $this->assertEquals 46 | ( 47 | '', 48 | $element->setData($this->_data)->render() 49 | ); 50 | } 51 | 52 | public function testShouldReturnTagReplacedWithEscapedValue() 53 | { 54 | $element = $this->_elementFactory->createInline 55 | ( 56 | 'ValueNotEscaped', 57 | new jQueryTmpl_Token_ValueNotEscaped(0, array('name'=>'htmlKey'), '') 58 | ); 59 | 60 | $this->assertEquals 61 | ( 62 | "Some Text & marks \"'\".", 63 | $element->setData($this->_data)->render() 64 | ); 65 | } 66 | } 67 | 68 | -------------------------------------------------------------------------------- /jQueryTmpl/Element/Each.php: -------------------------------------------------------------------------------- 1 | _firstToken = array_shift($tokens); 12 | $lastToken = array_pop($tokens); 13 | 14 | // Make sure we have the right types of tokens 15 | if (!($this->_firstToken instanceof jQueryTmpl_Token_EachStart && $lastToken instanceof jQueryTmpl_Token_EachEnd)) 16 | { 17 | throw new jQueryTmpl_Element_Exception('Token mismatch, cannot create {{each}} element.'); 18 | } 19 | 20 | $this->_elements = $this->_parser->parse($tokens); 21 | } 22 | 23 | public function render() 24 | { 25 | $defaultOptions = array 26 | ( 27 | 'index' => '$index', 28 | 'value' => '$value' 29 | ); 30 | $options = array_merge($defaultOptions, $this->_firstToken->getOptions()); 31 | 32 | $blockData = $this->_data->getValueOf($options['name']); 33 | 34 | if (!(is_array($blockData) || $blockData instanceof stdClass)) 35 | { 36 | // If there is no valid data for this each block it becomes nothing. 37 | return ''; 38 | } 39 | 40 | $rendered = ''; 41 | 42 | foreach($blockData as $index => $value) 43 | { 44 | // Make a copy of the data to use as local 45 | $localData = $this->_data; 46 | 47 | // Add our local vars to copy of data 48 | $localData 49 | ->addDataPair($options['index'], $index) 50 | ->addDataPair($options['value'], $value) 51 | ->addDataPair('this', $value); 52 | 53 | // Now call each element and give them the data as well. 54 | foreach ($this->_elements as $element) 55 | { 56 | $rendered .= $element 57 | ->setData($localData) 58 | ->setCompiledTemplates($this->_compiledTemplates) 59 | ->render(); 60 | } 61 | } 62 | 63 | return $rendered; 64 | } 65 | } 66 | 67 | -------------------------------------------------------------------------------- /jQueryTmpl.php: -------------------------------------------------------------------------------- 1 | _tokenizer = $tokenizer; 16 | $this->_parser = $parser; 17 | 18 | $this->_outputBuffer = ''; 19 | } 20 | 21 | public function getHtml() 22 | { 23 | $ob = $this->_outputBuffer; 24 | $this->_outputBuffer = ''; 25 | return $ob; 26 | } 27 | 28 | public function renderHtml() 29 | { 30 | echo $this->getHtml(); 31 | return $this; 32 | } 33 | 34 | public function template($name, jQueryTmpl_Markup $markup) 35 | { 36 | $this->_compiledTemplates[$name] = $this->_compileTemplate($markup); 37 | return $this; 38 | } 39 | 40 | public function tmpl($nameOrMarkup, jQueryTmpl_Data $data) 41 | { 42 | if ($nameOrMarkup instanceof jQueryTmpl_Markup) 43 | { 44 | $elements = $this->_compileTemplate($nameOrMarkup); 45 | } 46 | else 47 | { 48 | $elements = $this->_compiledTemplates[$nameOrMarkup]; 49 | } 50 | 51 | if (!empty($elements)) 52 | { 53 | $this->_renderElements 54 | ( 55 | $elements, 56 | $data 57 | ); 58 | } 59 | 60 | return $this; 61 | } 62 | 63 | private function _compileTemplate(jQueryTmpl_Markup $markup) 64 | { 65 | return $this->_parser->parse 66 | ( 67 | $this->_tokenizer->tokenize 68 | ( 69 | $markup->getTemplate() 70 | ) 71 | ); 72 | } 73 | 74 | private function _renderElements(array $elements, jQueryTmpl_Data $data) 75 | { 76 | foreach ($elements as $element) 77 | { 78 | $this->_outputBuffer .= $element 79 | ->setData($data) 80 | ->setCompiledTemplates($this->_compiledTemplates) 81 | ->render(); 82 | } 83 | } 84 | } 85 | 86 | -------------------------------------------------------------------------------- /test/Tag/CommentTest.php: -------------------------------------------------------------------------------- 1 | _cut = new jQueryTmpl_Tag_Comment(); 12 | } 13 | 14 | public function testShouldFindTag() 15 | { 16 | $str = 'Some text{{! Some Comment}} and more text.'; 17 | 18 | $this->assertEquals 19 | ( 20 | array 21 | ( 22 | 'total' => 1, 23 | 'match' => array 24 | ( 25 | array 26 | ( 27 | '{{! Some Comment}}', 28 | 9 29 | ) 30 | ) 31 | ), 32 | $this->_evalRegex($this->_cut->getRegex(), $str) 33 | ); 34 | } 35 | 36 | public function testShouldFindTagSpanningLines() 37 | { 38 | $str = "Some text{{!Some{\n}Comment{{!}} and more text."; 39 | 40 | $this->assertEquals 41 | ( 42 | array 43 | ( 44 | 'total' => 1, 45 | 'match' => array 46 | ( 47 | array 48 | ( 49 | "{{!Some{\n}Comment{{!}}", 50 | 9 51 | ) 52 | ) 53 | ), 54 | $this->_evalRegex($this->_cut->getRegex(), $str) 55 | ); 56 | } 57 | 58 | public function testShouldGetAllTags() 59 | { 60 | $str = '{{!First Comment}} and {{! Second !}}'; 61 | 62 | $this->assertEquals 63 | ( 64 | array 65 | ( 66 | 'total' => 2, 67 | 'match' => array 68 | ( 69 | array 70 | ( 71 | '{{!First Comment}}', 72 | 0 73 | ), 74 | array 75 | ( 76 | '{{! Second !}}', 77 | 23 78 | ) 79 | ) 80 | ), 81 | $this->_evalRegex($this->_cut->getRegex(), $str) 82 | ); 83 | } 84 | } 85 | 86 | -------------------------------------------------------------------------------- /example.php: -------------------------------------------------------------------------------- 1 | array 8 | ( 9 | array 10 | ( 11 | 'name' => 'The Red Violin', 12 | 'year' => '1998', 13 | 'director' => 'François Girard', 14 | 'imdb' => 7.7 15 | ), 16 | array 17 | ( 18 | 'name' => 'Eyes Wide Shut', 19 | 'year' => '1999', 20 | 'director' => 'Stanley Kubrick', 21 | 'imdb' => 7.2 22 | ), 23 | array 24 | ( 25 | 'name' => 'The Inheritance', 26 | 'year' => '1976', 27 | 'director' => 'Mauro Bolognini', 28 | 'imdb' => 6.7 29 | ) 30 | ), 31 | 'greeting' => array 32 | ( 33 | 'name' => 'Xiao', 34 | 'from' => 'Boston' 35 | ) 36 | ); 37 | 38 | // Create factory classes 39 | $jQueryTmpl_Factory = new jQueryTmpl_Factory(); 40 | $jQueryTmpl_Markup_Factory = new jQueryTmpl_Markup_Factory(); 41 | $jQueryTmpl_Data_Factory = new jQueryTmpl_Data_Factory(); 42 | 43 | // Create jQueryTmpl object 44 | $jQueryTmpl = $jQueryTmpl_Factory->create(); 45 | 46 | // Create some data from our PHP array 47 | $jQueryTmpl_Data = $jQueryTmpl_Data_Factory->createFromArray($data); 48 | 49 | // Compile a template using a shared template file, or pass in text 50 | $jQueryTmpl 51 | ->template 52 | ( 53 | 'movieTemplate', 54 | $jQueryTmpl_Markup_Factory->createFromFile('example.js') 55 | ) 56 | ->template 57 | ( 58 | 'nameTemplate', 59 | $jQueryTmpl_Markup_Factory->createFromString('Hello {{=greeting.name}}!') 60 | ) 61 | ->template 62 | ( 63 | 'ratingTemplate', 64 | $jQueryTmpl_Markup_Factory->createFromString("I'd give it a {{=this.imdb}}") 65 | ); 66 | 67 | // Use pre compiled templates to render 68 | $jQueryTmpl 69 | ->tmpl('movieTemplate', $jQueryTmpl_Data) 70 | ->renderHtml(); 71 | 72 | echo "
\n"; 73 | 74 | // Mix in the use by non compiled templates as well 75 | $rendered = $jQueryTmpl 76 | ->tmpl('nameTemplate', $jQueryTmpl_Data) 77 | ->tmpl 78 | ( 79 | $jQueryTmpl_Markup_Factory->createFromString(' I hear ${greeting.from} is lovely.'), 80 | $jQueryTmpl_Data 81 | ) 82 | ->getHtml(); 83 | 84 | // Do whatever we want with the output, in this case just print it 85 | echo "
$rendered
\n"; 86 | 87 | -------------------------------------------------------------------------------- /test/Tag/IfStartTest.php: -------------------------------------------------------------------------------- 1 | _cut = new jQueryTmpl_Tag_IfStart(); 12 | } 13 | 14 | public function testShouldFindTag() 15 | { 16 | $str = 'Foo bar {{if myVar}}.'; 17 | 18 | $this->assertEquals 19 | ( 20 | array 21 | ( 22 | 'total' => 1, 23 | 'match' => array 24 | ( 25 | array 26 | ( 27 | '{{if myVar}}', 28 | 8 29 | ) 30 | ) 31 | ), 32 | $this->_evalRegex($this->_cut->getRegex(), $str) 33 | ); 34 | } 35 | 36 | public function testShouldFindTagSpanningLines() 37 | { 38 | $str = "Foo bar {{if\nmyVar}}."; 39 | 40 | $this->assertEquals 41 | ( 42 | array 43 | ( 44 | 'total' => 1, 45 | 'match' => array 46 | ( 47 | array 48 | ( 49 | "{{if\nmyVar}}", 50 | 8 51 | ) 52 | ) 53 | ), 54 | $this->_evalRegex($this->_cut->getRegex(), $str) 55 | ); 56 | } 57 | 58 | public function testShouldGetAllTags() 59 | { 60 | $str = '{{ifmyVar1}} and {{If myVar2 }}'; 61 | 62 | $this->assertEquals 63 | ( 64 | array 65 | ( 66 | 'total' => 2, 67 | 'match' => array 68 | ( 69 | array 70 | ( 71 | '{{ifmyVar1}}', 72 | 0 73 | ), 74 | array 75 | ( 76 | '{{If myVar2 }}', 77 | 17 78 | ) 79 | ) 80 | ), 81 | $this->_evalRegex($this->_cut->getRegex(), $str) 82 | ); 83 | } 84 | 85 | public function testShouldExtractSimpleVarName() 86 | { 87 | $this->assertEquals 88 | ( 89 | array 90 | ( 91 | 'name' => 'myVar' 92 | ), 93 | $this->_cut->parseTag('{{if myVar}}') 94 | ); 95 | } 96 | 97 | public function testShouldExtractAdvancedVarName() 98 | { 99 | $this->assertEquals 100 | ( 101 | array 102 | ( 103 | 'name' => "myVar.foo['bar baz'].length" 104 | ), 105 | $this->_cut->parseTag("{{if\nmyVar.foo['bar baz'].length }}") 106 | ); 107 | } 108 | } 109 | 110 | -------------------------------------------------------------------------------- /test/Tag/ValueEscapedTest.php: -------------------------------------------------------------------------------- 1 | _cut = new jQueryTmpl_Tag_ValueEscaped(); 12 | } 13 | 14 | public function testShouldFindTag() 15 | { 16 | $str = 'Foo bar {{= myVar}}.'; 17 | 18 | $this->assertEquals 19 | ( 20 | array 21 | ( 22 | 'total' => 1, 23 | 'match' => array 24 | ( 25 | array 26 | ( 27 | '{{= myVar}}', 28 | 8 29 | ) 30 | ) 31 | ), 32 | $this->_evalRegex($this->_cut->getRegex(), $str) 33 | ); 34 | } 35 | 36 | public function testShouldFindTagSpanningLines() 37 | { 38 | $str = "Foo bar {{=\nmyVar}}."; 39 | 40 | $this->assertEquals 41 | ( 42 | array 43 | ( 44 | 'total' => 1, 45 | 'match' => array 46 | ( 47 | array 48 | ( 49 | "{{=\nmyVar}}", 50 | 8 51 | ) 52 | ) 53 | ), 54 | $this->_evalRegex($this->_cut->getRegex(), $str) 55 | ); 56 | } 57 | 58 | public function testShouldGetAllTags() 59 | { 60 | $str = '{{=myVar1}} and {{= myVar2 }}'; 61 | 62 | $this->assertEquals 63 | ( 64 | array 65 | ( 66 | 'total' => 2, 67 | 'match' => array 68 | ( 69 | array 70 | ( 71 | '{{=myVar1}}', 72 | 0 73 | ), 74 | array 75 | ( 76 | '{{= myVar2 }}', 77 | 16 78 | ) 79 | ) 80 | ), 81 | $this->_evalRegex($this->_cut->getRegex(), $str) 82 | ); 83 | } 84 | 85 | public function testShouldExtractSimpleVarName() 86 | { 87 | $this->assertEquals 88 | ( 89 | array 90 | ( 91 | 'name' => 'myVar' 92 | ), 93 | $this->_cut->parseTag('{{=myVar}}') 94 | ); 95 | } 96 | 97 | public function testShouldExtractAdvancedVarName() 98 | { 99 | $this->assertEquals 100 | ( 101 | array 102 | ( 103 | 'name' => "myVar.foo['bar baz'].length" 104 | ), 105 | $this->_cut->parseTag("{{=\nmyVar.foo['bar baz'].length }}") 106 | ); 107 | } 108 | } 109 | 110 | -------------------------------------------------------------------------------- /jQueryTmpl/Parser.php: -------------------------------------------------------------------------------- 1 | _elementFactory = $elementFactory; 10 | } 11 | 12 | public function parse(array $tokens) 13 | { 14 | $elements = array(); 15 | $count = count($tokens); 16 | 17 | for ($i=0;$i<$count;$i++) 18 | { 19 | /** 20 | * If the type of token is a block we need to provide 21 | * the element an array of tokens, not just one. 22 | */ 23 | if ($tokens[$i] instanceof jQueryTmpl_Token_TypeBlock) 24 | { 25 | $startToken = $tokens[$i]; 26 | 27 | if (!$startToken->isBlockStart()) 28 | { 29 | throw new jQueryTmpl_Parser_Exception('A unmatched block end tag has been encountered.'); 30 | } 31 | 32 | $blockNest = $startToken->getLevel(); 33 | $endToken = 'jQueryTmpl_Token_'.$startToken->getBlockEndToken(); 34 | 35 | $childTokens = array(); 36 | 37 | for ($j=$i;$j<$count;$j++) 38 | { 39 | $childTokens[] = $tokens[$j]; 40 | 41 | if (($tokens[$j]->getLevel() == $blockNest) && ($tokens[$j] instanceof $endToken)) 42 | { 43 | $i = $j; 44 | break; 45 | } 46 | } 47 | 48 | $elements[] = $this->_elementFactory->createBlock 49 | ( 50 | $startToken->getElementType(), 51 | $childTokens 52 | ); 53 | 54 | continue; 55 | } 56 | 57 | /** 58 | * If the type of token is control call specific factory 59 | * create method. 60 | */ 61 | if ($tokens[$i] instanceof jQueryTmpl_Token_TypeControl) 62 | { 63 | $elements[] = $this->_elementFactory->createControl 64 | ( 65 | $tokens[$i]->getElementType(), 66 | $tokens[$i] 67 | ); 68 | 69 | continue; 70 | } 71 | 72 | /** 73 | * If the type of token is inline call specific factory 74 | * create method. 75 | */ 76 | if ($tokens[$i] instanceof jQueryTmpl_Token_TypeInline) 77 | { 78 | $elements[] = $this->_elementFactory->createInline 79 | ( 80 | $tokens[$i]->getElementType(), 81 | $tokens[$i] 82 | ); 83 | 84 | continue; 85 | } 86 | 87 | throw new jQueryTmpl_Parser_Exception('Uncaught token type detected.'); 88 | } 89 | 90 | return $elements; 91 | } 92 | } 93 | 94 | -------------------------------------------------------------------------------- /test/Tag/ValueEscapedShorthandTest.php: -------------------------------------------------------------------------------- 1 | _cut = new jQueryTmpl_Tag_ValueEscapedShorthand(); 12 | } 13 | 14 | public function testShouldFindTag() 15 | { 16 | $str = 'Foo bar ${myVar}.'; 17 | 18 | $this->assertEquals 19 | ( 20 | array 21 | ( 22 | 'total' => 1, 23 | 'match' => array 24 | ( 25 | array 26 | ( 27 | '${myVar}', 28 | 8 29 | ) 30 | ) 31 | ), 32 | $this->_evalRegex($this->_cut->getRegex(), $str) 33 | ); 34 | } 35 | 36 | public function testShouldFindTagSpanningLines() 37 | { 38 | $str = "Foo bar \${\nmyVar }."; 39 | 40 | $this->assertEquals 41 | ( 42 | array 43 | ( 44 | 'total' => 1, 45 | 'match' => array 46 | ( 47 | array 48 | ( 49 | "\${\nmyVar }", 50 | 8 51 | ) 52 | ) 53 | ), 54 | $this->_evalRegex($this->_cut->getRegex(), $str) 55 | ); 56 | } 57 | 58 | public function testShouldGetAllTags() 59 | { 60 | $str = '${myVar1} not {{= myVar2 }} and ${ myVar3 }'; 61 | 62 | $this->assertEquals 63 | ( 64 | array 65 | ( 66 | 'total' => 2, 67 | 'match' => array 68 | ( 69 | array 70 | ( 71 | '${myVar1}', 72 | 0 73 | ), 74 | array 75 | ( 76 | '${ myVar3 }', 77 | 32 78 | ) 79 | ) 80 | ), 81 | $this->_evalRegex($this->_cut->getRegex(), $str) 82 | ); 83 | } 84 | 85 | public function testShouldExtractSimpleVarName() 86 | { 87 | $this->assertEquals 88 | ( 89 | array 90 | ( 91 | 'name' => 'myVar' 92 | ), 93 | $this->_cut->parseTag('${myVar}') 94 | ); 95 | } 96 | 97 | public function testShouldExtractAdvancedVarName() 98 | { 99 | $this->assertEquals 100 | ( 101 | array 102 | ( 103 | 'name' => "myVar.foo['bar baz'].length" 104 | ), 105 | $this->_cut->parseTag("\${\nmyVar.foo['bar baz'].length }") 106 | ); 107 | } 108 | } 109 | 110 | -------------------------------------------------------------------------------- /test/Tag/ValueNotEscapedTest.php: -------------------------------------------------------------------------------- 1 | _cut = new jQueryTmpl_Tag_ValueNotEscaped(); 12 | } 13 | 14 | public function testShouldFindTag() 15 | { 16 | $str = 'Foo bar {{html myVar}}.'; 17 | 18 | $this->assertEquals 19 | ( 20 | array 21 | ( 22 | 'total' => 1, 23 | 'match' => array 24 | ( 25 | array 26 | ( 27 | '{{html myVar}}', 28 | 8 29 | ) 30 | ) 31 | ), 32 | $this->_evalRegex($this->_cut->getRegex(), $str) 33 | ); 34 | } 35 | 36 | public function testShouldFindTagSpanningLines() 37 | { 38 | $str = "Foo bar {{html\nmyVar}}."; 39 | 40 | $this->assertEquals 41 | ( 42 | array 43 | ( 44 | 'total' => 1, 45 | 'match' => array 46 | ( 47 | array 48 | ( 49 | "{{html\nmyVar}}", 50 | 8 51 | ) 52 | ) 53 | ), 54 | $this->_evalRegex($this->_cut->getRegex(), $str) 55 | ); 56 | } 57 | 58 | public function testShouldGetAllTags() 59 | { 60 | $str = '{{htmlmyVar1}} and {{HtMl myVar2 }}'; 61 | 62 | $this->assertEquals 63 | ( 64 | array 65 | ( 66 | 'total' => 2, 67 | 'match' => array 68 | ( 69 | array 70 | ( 71 | '{{htmlmyVar1}}', 72 | 0 73 | ), 74 | array 75 | ( 76 | '{{HtMl myVar2 }}', 77 | 19 78 | ) 79 | ) 80 | ), 81 | $this->_evalRegex($this->_cut->getRegex(), $str) 82 | ); 83 | } 84 | 85 | public function testShouldExtractSimpleVarName() 86 | { 87 | $this->assertEquals 88 | ( 89 | array 90 | ( 91 | 'name' => 'myVar' 92 | ), 93 | $this->_cut->parseTag('{{htmlmyVar}}') 94 | ); 95 | } 96 | 97 | public function testShouldExtractAdvancedVarName() 98 | { 99 | $this->assertEquals 100 | ( 101 | array 102 | ( 103 | 'name' => "myVar.foo['bar baz'].length" 104 | ), 105 | $this->_cut->parseTag("{{html\nmyVar.foo['bar baz'].length }}") 106 | ); 107 | } 108 | } 109 | 110 | -------------------------------------------------------------------------------- /test/Tag/EachStartTest.php: -------------------------------------------------------------------------------- 1 | _cut = new jQueryTmpl_Tag_EachStart(); 12 | } 13 | 14 | public function testShouldFindTag() 15 | { 16 | $str = 'Foo bar {{each myVar}}.'; 17 | 18 | $this->assertEquals 19 | ( 20 | array 21 | ( 22 | 'total' => 1, 23 | 'match' => array 24 | ( 25 | array 26 | ( 27 | '{{each myVar}}', 28 | 8 29 | ) 30 | ) 31 | ), 32 | $this->_evalRegex($this->_cut->getRegex(), $str) 33 | ); 34 | } 35 | 36 | public function testShouldFindTagSpanningLines() 37 | { 38 | $str = "Foo bar {{each\nmyVar}}."; 39 | 40 | $this->assertEquals 41 | ( 42 | array 43 | ( 44 | 'total' => 1, 45 | 'match' => array 46 | ( 47 | array 48 | ( 49 | "{{each\nmyVar}}", 50 | 8 51 | ) 52 | ) 53 | ), 54 | $this->_evalRegex($this->_cut->getRegex(), $str) 55 | ); 56 | } 57 | 58 | public function testShouldGetAllTags() 59 | { 60 | $str = '{{eachmyVar1}} and {{EaCh myVar2 }}'; 61 | 62 | $this->assertEquals 63 | ( 64 | array 65 | ( 66 | 'total' => 2, 67 | 'match' => array 68 | ( 69 | array 70 | ( 71 | '{{eachmyVar1}}', 72 | 0 73 | ), 74 | array 75 | ( 76 | '{{EaCh myVar2 }}', 77 | 19 78 | ) 79 | ) 80 | ), 81 | $this->_evalRegex($this->_cut->getRegex(), $str) 82 | ); 83 | } 84 | 85 | public function testShouldExtractSimpleVarName() 86 | { 87 | $this->assertEquals 88 | ( 89 | array 90 | ( 91 | 'name' => 'myVar' 92 | ), 93 | $this->_cut->parseTag('{{each myVar}}') 94 | ); 95 | } 96 | 97 | public function testShouldExtractAdvancedVarName() 98 | { 99 | $this->assertEquals 100 | ( 101 | array 102 | ( 103 | 'name' => "myVar.foo['bar baz'].length", 104 | 'index' => 'idx', 105 | 'value' =>'$val' 106 | ), 107 | $this->_cut->parseTag("{{each(idx, \$val)\nmyVar.foo['bar baz'].length }}") 108 | ); 109 | } 110 | } 111 | 112 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | jQuery-tmpl-PHP 2 | =============== 3 | 4 | A PHP library for rendering jQuery templates server-side. Inspired by 5 | [jQuery-tmpl.Net][1] and of course [jQuery Templates][2]. 6 | 7 | Usage 8 | ----- 9 | 10 | jQuery-tmpl-PHP was written to mimic to a certain extent the jQuery Templates 11 | method calls. An example file has been included showing various usage cases. 12 | 13 | The `jQueryTmpl()` object supports the following method calls: 14 | 15 | * **`getHtml()`** 16 | * Parameters: None 17 | * Purpose: Returns the generated HTML in the buffer and clears it. 18 | * **`renderHtml()`** 19 | * Parameters: None 20 | * Purpose: Prints the generated HTML in the buffer and clears it. 21 | * **`template(name, jQueryTmpl_Markup)`** 22 | * Parameters: 23 | * String name for the template. 24 | * The markup to be compiled. 25 | * Purpose: Compiles the given template markup. 26 | * **`tmpl(name/jQueryTmpl_Markup, jQueryTmpl_Data)`** 27 | * Parameters: 28 | * String name of a precompiled template or the markup to be compiled. 29 | * The data to be applied to the template. 30 | * Purpose: Renders the template with the given data and stores it in the 31 | output buffer. 32 | 33 | Supported Tags 34 | -------------- 35 | 36 | * **`${property}` and `{{= property}}`** 37 | 38 | Both the shorthand `${}` and `{{= }}` are supported. Will print out the 39 | value of the indicated property on the provided data object. Nested property 40 | resolution is supported. However expression/function evaluation is not 41 | currently supported. (See roadmap.) 42 | 43 | * **`{{html property}}`** 44 | 45 | Renders the value of the property without HTML encoding. Otherwise identical 46 | to `${}`. 47 | 48 | * **`{{each(index, value) property}}...{{/each}}`** 49 | 50 | Renders an instance of the tag contents for each item in the property value 51 | on the provided data object. Custom index and value variables can be 52 | optionally passed in. 53 | 54 | * **`{{if property}}...{{/if}`** 55 | 56 | Renders the content of the tag if the property value on the provided data 57 | object evaluates to `true`. This is javascript-style evaluation so 0, null, 58 | empty string are all `false`. 59 | 60 | * **`{{else property}}`** 61 | 62 | Used within the `{{if}}` tag to evaluate else conditions. The property value 63 | is optional. 64 | 65 | * **`{{tmpl(data, options) template}}`** 66 | 67 | This tag takes data and options as optional parameters. The tag will render a 68 | existing rendered template (using the `template()` method) in place. When a 69 | data property is passed in only the portion of data referenced by that 70 | property is passed to the template specified. Options is currently not 71 | supported. 72 | 73 | * **`{{! comments}}`** 74 | 75 | This tag does not appear to be documented on the official jQuery site 76 | however it does exist in code. The same functionality is preserved here, 77 | comments are simply discarded in rendered output. 78 | 79 | Roadmap 80 | ------- 81 | 82 | The following is on my todo list. 83 | 84 | * Support for JavaScript expresion evaluation. 85 | * Support for `{{wrap}}` 86 | 87 | [1]: http://github.com/awhatley/jquery-tmpl.net 88 | [2]: http://github.com/jquery/jquery-tmpl 89 | -------------------------------------------------------------------------------- /test/Element/IfTest.php: -------------------------------------------------------------------------------- 1 | _elementFactory->createBlock 10 | ( 11 | 'If', 12 | array 13 | ( 14 | new jQueryTmpl_Token_IfStart(0, array('name'=>'key5.child3'), ''), 15 | new jQueryTmpl_Token_NoOp(1, array(), 'TEST'), 16 | new jQueryTmpl_Token_IfEnd(0, array(), '') 17 | ) 18 | ); 19 | 20 | $this->assertEquals 21 | ( 22 | '', 23 | $element->setData($this->_data)->render() 24 | ); 25 | } 26 | 27 | public function testShouldReturnWhenCondIsTrue() 28 | { 29 | $element = $this->_elementFactory->createBlock 30 | ( 31 | 'If', 32 | array 33 | ( 34 | new jQueryTmpl_Token_IfStart(0, array('name'=>'key3'), ''), 35 | new jQueryTmpl_Token_NoOp(1, array(), 'TEST'), 36 | new jQueryTmpl_Token_Else(0, array(), ''), 37 | new jQueryTmpl_Token_NoOp(1, array(), 'TEST 2'), 38 | new jQueryTmpl_Token_IfEnd(0, array(), '') 39 | ) 40 | ); 41 | 42 | $this->assertEquals 43 | ( 44 | 'TEST', 45 | $element->setData($this->_data)->render() 46 | ); 47 | } 48 | 49 | public function testShouldReturnElseWhenCondIsFalse() 50 | { 51 | $element = $this->_elementFactory->createBlock 52 | ( 53 | 'If', 54 | array 55 | ( 56 | new jQueryTmpl_Token_IfStart(0, array('name'=>'key5.child3'), ''), 57 | new jQueryTmpl_Token_NoOp(1, array(), 'TEST 1'), 58 | new jQueryTmpl_Token_Else(0, array('name'=>'key5.child3'), ''), 59 | new jQueryTmpl_Token_NoOp(1, array(), 'TEST 2'), 60 | new jQueryTmpl_Token_Else(0, array(), ''), 61 | new jQueryTmpl_Token_NoOp(1, array(), 'TEST 3'), 62 | new jQueryTmpl_Token_IfEnd(0, array(), '') 63 | ) 64 | ); 65 | 66 | $this->assertEquals 67 | ( 68 | 'TEST 3', 69 | $element->setData($this->_data)->render() 70 | ); 71 | } 72 | 73 | public function testShouldReturnElseIfWhenCondIsTrue() 74 | { 75 | $element = $this->_elementFactory->createBlock 76 | ( 77 | 'If', 78 | array 79 | ( 80 | new jQueryTmpl_Token_IfStart(0, array('name'=>'key5.child3'), ''), 81 | new jQueryTmpl_Token_NoOp(1, array(), 'TEST 1'), 82 | new jQueryTmpl_Token_Else(0, array('name'=>'key3'), ''), 83 | new jQueryTmpl_Token_NoOp(1, array(), 'TEST 2'), 84 | new jQueryTmpl_Token_Else(0, array(), ''), 85 | new jQueryTmpl_Token_NoOp(1, array(), 'TEST 3'), 86 | new jQueryTmpl_Token_IfEnd(0, array(), '') 87 | ) 88 | ); 89 | 90 | $this->assertEquals 91 | ( 92 | 'TEST 2', 93 | $element->setData($this->_data)->render() 94 | ); 95 | } 96 | } 97 | 98 | -------------------------------------------------------------------------------- /jQueryTmpl/Tokenizer.php: -------------------------------------------------------------------------------- 1 | _tokenFactory = $tokenFactory; 11 | } 12 | 13 | public function addTag(jQueryTmpl_Tag $tag) 14 | { 15 | $this->_tags[] = $tag; 16 | return $this; 17 | } 18 | 19 | public function tokenize($template) 20 | { 21 | $markers = $this->_findAllTokens($template); 22 | return $this->_createTokensFromMarkers($template, $markers); 23 | } 24 | 25 | private function _findAllTokens($template) 26 | { 27 | $markers = array(); 28 | 29 | foreach ($this->_tags as $tag) 30 | { 31 | $matches = array(); 32 | preg_match_all($tag->getRegex(), $template, $matches, PREG_OFFSET_CAPTURE); 33 | 34 | foreach ($matches[0] as $match) 35 | { 36 | $markers[$match[1]] = array 37 | ( 38 | 'rawMatch' => $match[0], 39 | 'tokenType' => $tag->getTokenType(), 40 | 'nestingValue' => $tag->getNestingValue(), 41 | 'options' => $tag->parseTag($match[0]) 42 | ); 43 | } 44 | } 45 | 46 | ksort($markers); 47 | 48 | return $markers; 49 | } 50 | 51 | private function _createTokensFromMarkers($template, array $markers) 52 | { 53 | $tokens = array(); 54 | $currPos = 0; 55 | $currNest = 0; 56 | 57 | foreach ($markers as $pos => $marker) 58 | { 59 | /** 60 | * If next marker is beyond where we are the delta is 61 | * just a NoOp token. 62 | */ 63 | if ($currPos < $pos) 64 | { 65 | $tokens[] = $this->_tokenFactory->create 66 | ( 67 | 'NoOp', 68 | $currNest, 69 | array(), 70 | substr($template, $currPos, $pos-$currPos) 71 | ); 72 | } 73 | 74 | /** 75 | * Add the tag that was found as a token advancing the 76 | * nesting and position as needed. 77 | */ 78 | $currNest += $marker['nestingValue'][0]; 79 | 80 | $tokens[] = $this->_tokenFactory->create 81 | ( 82 | $marker['tokenType'], 83 | $currNest, 84 | $marker['options'], 85 | substr($template, $pos, strlen($marker['rawMatch'])) 86 | ); 87 | 88 | $currNest += $marker['nestingValue'][1]; 89 | $currPos = $pos + strlen($marker['rawMatch']); 90 | } 91 | 92 | if ($currPos < strlen($template)) 93 | { 94 | $tokens[] = $this->_tokenFactory->create 95 | ( 96 | 'NoOp', 97 | $currNest, 98 | array(), 99 | substr($template, $currPos) 100 | ); 101 | } 102 | 103 | if ($currNest != 0) 104 | { 105 | throw new jQueryTmpl_Tokenizer_Exception('jQuery Template can not be tokenized, there exists an unclosed tag.'); 106 | } 107 | 108 | return $tokens; 109 | } 110 | } 111 | 112 | -------------------------------------------------------------------------------- /test/Tag/ElseTest.php: -------------------------------------------------------------------------------- 1 | _cut = new jQueryTmpl_Tag_Else(); 12 | } 13 | 14 | public function testShouldFindTag() 15 | { 16 | $str = 'Foo bar {{else myVar}}.'; 17 | 18 | $this->assertEquals 19 | ( 20 | array 21 | ( 22 | 'total' => 1, 23 | 'match' => array 24 | ( 25 | array 26 | ( 27 | '{{else myVar}}', 28 | 8 29 | ) 30 | ) 31 | ), 32 | $this->_evalRegex($this->_cut->getRegex(), $str) 33 | ); 34 | } 35 | 36 | public function testShouldFindTagSpanningLines() 37 | { 38 | $str = "Foo bar {{else\nmyVar}}."; 39 | 40 | $this->assertEquals 41 | ( 42 | array 43 | ( 44 | 'total' => 1, 45 | 'match' => array 46 | ( 47 | array 48 | ( 49 | "{{else\nmyVar}}", 50 | 8 51 | ) 52 | ) 53 | ), 54 | $this->_evalRegex($this->_cut->getRegex(), $str) 55 | ); 56 | } 57 | 58 | public function testShouldGetAllTags() 59 | { 60 | $str = '{{elsemyVar1}} and {{ElSe myVar2 }} and {{else}}'; 61 | 62 | $this->assertEquals 63 | ( 64 | array 65 | ( 66 | 'total' => 3, 67 | 'match' => array 68 | ( 69 | array 70 | ( 71 | '{{elsemyVar1}}', 72 | 0 73 | ), 74 | array 75 | ( 76 | '{{ElSe myVar2 }}', 77 | 19 78 | ), 79 | array 80 | ( 81 | '{{else}}', 82 | 40 83 | ) 84 | ) 85 | ), 86 | $this->_evalRegex($this->_cut->getRegex(), $str) 87 | ); 88 | } 89 | 90 | public function testShouldExtractSimpleVarName() 91 | { 92 | $this->assertEquals 93 | ( 94 | array 95 | ( 96 | 'name' => 'myVar' 97 | ), 98 | $this->_cut->parseTag('{{else myVar}}') 99 | ); 100 | } 101 | 102 | public function testShouldExtractAdvancedVarName() 103 | { 104 | $this->assertEquals 105 | ( 106 | array 107 | ( 108 | 'name' => "myVar.foo['bar baz'].length" 109 | ), 110 | $this->_cut->parseTag("{{else\nmyVar.foo['bar baz'].length }}") 111 | ); 112 | } 113 | 114 | public function testShouldExtractNothingForFinalElse() 115 | { 116 | $this->assertEquals 117 | ( 118 | array 119 | ( 120 | ), 121 | $this->_cut->parseTag('{{else}}') 122 | ); 123 | 124 | $this->assertEquals 125 | ( 126 | array 127 | ( 128 | ), 129 | $this->_cut->parseTag('{{else }}') 130 | ); 131 | } 132 | } 133 | 134 | -------------------------------------------------------------------------------- /jQueryTmpl/Data.php: -------------------------------------------------------------------------------- 1 | _data = $data; 12 | $this->_dataFactory = $dataFactory; 13 | } 14 | 15 | public function addDataPair($key, $value) 16 | { 17 | $this->_data->$key = $value; 18 | return $this; 19 | } 20 | 21 | public function getValueOf($jsNotation) 22 | { 23 | $dataTokens = $this->_parseJsNotation($jsNotation); 24 | 25 | $localData = $this->_data; 26 | 27 | foreach ($dataTokens as $token) 28 | { 29 | $localData = $this->_getDataPart($localData, $token); 30 | } 31 | 32 | return $localData; 33 | } 34 | 35 | public function getDataSice($jsNotation) 36 | { 37 | if (empty($jsNotation)) 38 | { 39 | return $this; 40 | } 41 | 42 | $dataSlice = $this->getValueOf($jsNotation); 43 | 44 | if ($dataSlice instanceof stdClass) 45 | { 46 | return $this->_dataFactory->createFromStdClass($dataSlice); 47 | } 48 | else 49 | { 50 | return null; 51 | } 52 | } 53 | 54 | private function _parseJsNotation($js) 55 | { 56 | $dataTokens = array(); 57 | 58 | // Split based on '.' to traverse into object. Could be smarter... 59 | $loc = explode('.', $js); 60 | 61 | foreach($loc as $idx => $name) 62 | { 63 | $match = array(); 64 | 65 | if (!preg_match('/^([a-z_$][0-9a-z_$]*)(\[.*\])?$/i', $name, $match)) 66 | { 67 | throw new jQueryTmpl_Data_Exception('jQueryTmpl_Data can not evaluate expressions.'); 68 | } 69 | 70 | $dataTokens[] = $match[1]; 71 | 72 | if (!empty($match[2])) 73 | { 74 | $subparts = explode('][', $match[2]); 75 | 76 | foreach ($subparts as $subname) 77 | { 78 | $dataTokens[] = trim($subname, ' \'"[]'); 79 | } 80 | } 81 | } 82 | 83 | return $dataTokens; 84 | } 85 | 86 | private function _getDataPart($data, $token) 87 | { 88 | if ($data instanceof stdClass) 89 | { 90 | return $this->_getDataPartObject($data, $token); 91 | } 92 | 93 | if (is_array($data)) 94 | { 95 | return $this->_getDataPartArray($data, $token); 96 | } 97 | 98 | if (is_string($data)) 99 | { 100 | return $this->_getDataPartString($data, $token); 101 | } 102 | 103 | // At this point we are told to go into a primitive type, this is undefined 104 | return ''; 105 | } 106 | 107 | private function _getDataPartObject(stdClass $data, $token) 108 | { 109 | if ($token == 'length') 110 | { 111 | $i = 0; 112 | 113 | foreach ($data as $element) 114 | { 115 | $i++; 116 | } 117 | 118 | return $i; 119 | } 120 | 121 | return $data->$token; 122 | } 123 | 124 | private function _getDataPartArray(array $data, $token) 125 | { 126 | if ($token == 'length') 127 | { 128 | return count($data); 129 | } 130 | 131 | if (!$this->_isInt($token)) 132 | { 133 | return ''; 134 | } 135 | 136 | return $data[$token]; 137 | } 138 | 139 | private function _getDataPartString($data, $token) 140 | { 141 | if ($token == 'length') 142 | { 143 | return strlen($data); 144 | } 145 | 146 | if (!$this->_isInt($token)) 147 | { 148 | return ''; 149 | } 150 | 151 | return $data[$token]; 152 | } 153 | 154 | private function _isInt($var) 155 | { 156 | return ((int)$var == $var); 157 | } 158 | } 159 | 160 | -------------------------------------------------------------------------------- /test/Tag/TmplTest.php: -------------------------------------------------------------------------------- 1 | _cut = new jQueryTmpl_Tag_Tmpl(); 12 | } 13 | 14 | public function testShouldFindTag() 15 | { 16 | $str = 'Foo bar {{tmpl "#myTemplate"}}.'; 17 | 18 | $this->assertEquals 19 | ( 20 | array 21 | ( 22 | 'total' => 1, 23 | 'match' => array 24 | ( 25 | array 26 | ( 27 | '{{tmpl "#myTemplate"}}', 28 | 8 29 | ) 30 | ) 31 | ), 32 | $this->_evalRegex($this->_cut->getRegex(), $str) 33 | ); 34 | } 35 | 36 | public function testShouldFindTagSpanningLines() 37 | { 38 | $str = "Foo bar {{tmpl\n'#myTemplate'}}."; 39 | 40 | $this->assertEquals 41 | ( 42 | array 43 | ( 44 | 'total' => 1, 45 | 'match' => array 46 | ( 47 | array 48 | ( 49 | "{{tmpl\n'#myTemplate'}}", 50 | 8 51 | ) 52 | ) 53 | ), 54 | $this->_evalRegex($this->_cut->getRegex(), $str) 55 | ); 56 | } 57 | 58 | public function testShouldGetAllTags() 59 | { 60 | $str = '{{tmpl "#myTemplate1"}} and {{TmPl "#myTemplate2" }}'; 61 | 62 | $this->assertEquals 63 | ( 64 | array 65 | ( 66 | 'total' => 2, 67 | 'match' => array 68 | ( 69 | array 70 | ( 71 | '{{tmpl "#myTemplate1"}}', 72 | 0 73 | ), 74 | array 75 | ( 76 | '{{TmPl "#myTemplate2" }}', 77 | 28 78 | ) 79 | ) 80 | ), 81 | $this->_evalRegex($this->_cut->getRegex(), $str) 82 | ); 83 | } 84 | 85 | public function testShouldExtractSimpleVarName() 86 | { 87 | $this->assertEquals 88 | ( 89 | array 90 | ( 91 | 'template' => 'myTemplate', 92 | 'data' => null, 93 | 'options' => null 94 | ), 95 | $this->_cut->parseTag('{{tmpl "#myTemplate"}}') 96 | ); 97 | } 98 | 99 | public function testShouldExtractAdvancedVarName() 100 | { 101 | $this->assertEquals 102 | ( 103 | array 104 | ( 105 | 'template' => "some-template-name", 106 | 'data' => 'data', 107 | 'options' => null 108 | ), 109 | $this->_cut->parseTag 110 | ( 111 | "{{tmpl(data) '#some-template-name'}}" 112 | ) 113 | ); 114 | 115 | $this->assertEquals 116 | ( 117 | array 118 | ( 119 | 'template' => "some-template-name", 120 | 'data' => 'myVar.foo["bar baz"]', 121 | 'options' => null 122 | ), 123 | $this->_cut->parseTag 124 | ( 125 | '{{tmpl(myVar.foo["bar baz"]) "#some-template-name"}}' 126 | ) 127 | ); 128 | 129 | $this->assertEquals 130 | ( 131 | array 132 | ( 133 | 'template' => "some-template-name", 134 | 'data' => 'data', 135 | 'options' =>'options' 136 | ), 137 | $this->_cut->parseTag 138 | ( 139 | "{{tmpl(data, options) '#some-template-name'}}" 140 | ) 141 | ); 142 | 143 | $this->assertEquals 144 | ( 145 | array 146 | ( 147 | 'template' => "some-template-name", 148 | 'data' => 'myVar.foo["bar baz"]', 149 | 'options' =>'options' 150 | ), 151 | $this->_cut->parseTag 152 | ( 153 | '{{tmpl(myVar.foo["bar baz"], options) "#some-template-name"}}' 154 | ) 155 | ); 156 | } 157 | } 158 | 159 | -------------------------------------------------------------------------------- /test/TokenizerTest.php: -------------------------------------------------------------------------------- 1 | getMock('jQueryTmpl_Token_Factory', array('create')); 12 | $tokenFactory 13 | ->expects($this->any()) 14 | ->method('create') 15 | ->will($this->returnCallback('jQueryTmpl_TokenizerTest__FactoryCallback')); 16 | 17 | $tag1 = $this->getMock('jQueryTmpl_Tag', array('getTokenType', 'getRegex', 'getNestingValue', 'parseTag')); 18 | $tag1 19 | ->expects($this->any()) 20 | ->method('getTokenType') 21 | ->will($this->returnValue('>')); 22 | $tag1 23 | ->expects($this->any()) 24 | ->method('getRegex') 25 | ->will($this->returnValue('/>/')); 26 | $tag1 27 | ->expects($this->any()) 28 | ->method('getNestingValue') 29 | ->will($this->returnValue(array(0,1))); 30 | $tag1 31 | ->expects($this->any()) 32 | ->method('parseTag') 33 | ->will($this->returnValue(array())); 34 | 35 | $tag2 = $this->getMock('jQueryTmpl_Tag', array('getTokenType', 'getRegex', 'getNestingValue', 'parseTag')); 36 | $tag2 37 | ->expects($this->any()) 38 | ->method('getTokenType') 39 | ->will($this->returnValue('<')); 40 | $tag2 41 | ->expects($this->any()) 42 | ->method('getRegex') 43 | ->will($this->returnValue('/expects($this->any()) 46 | ->method('getNestingValue') 47 | ->will($this->returnValue(array(-1,0))); 48 | $tag2 49 | ->expects($this->any()) 50 | ->method('parseTag') 51 | ->will($this->returnValue(array())); 52 | 53 | $tag3 = $this->getMock('jQueryTmpl_Tag', array('getTokenType', 'getRegex', 'getNestingValue', 'parseTag')); 54 | $tag3 55 | ->expects($this->any()) 56 | ->method('getTokenType') 57 | ->will($this->returnValue('=')); 58 | $tag3 59 | ->expects($this->any()) 60 | ->method('getRegex') 61 | ->will($this->returnValue('/=/')); 62 | $tag3 63 | ->expects($this->any()) 64 | ->method('getNestingValue') 65 | ->will($this->returnValue(array(0,0))); 66 | $tag3 67 | ->expects($this->any()) 68 | ->method('parseTag') 69 | ->will($this->returnValue(array())); 70 | 71 | $this->_cut = new jQueryTmpl_Tokenizer($tokenFactory); 72 | $this->_cut 73 | ->addTag($tag1) 74 | ->addTag($tag2) 75 | ->addTag($tag3); 76 | } 77 | 78 | public function testShouldTokenizeSingleTag() 79 | { 80 | $tokens = $this->_cut->tokenize('='); 81 | 82 | $this->assertEquals 83 | ( 84 | array 85 | ( 86 | array('=', 0, array(), '=') 87 | ), 88 | $tokens 89 | ); 90 | } 91 | 92 | public function testShouldTokenizeAllTags() 93 | { 94 | $tokens = $this->_cut->tokenize('123=456=789'); 95 | 96 | $this->assertEquals 97 | ( 98 | array 99 | ( 100 | array('NoOp', 0, array(), '123'), 101 | array('=', 0, array(), '='), 102 | array('NoOp', 0, array(), '456'), 103 | array('=', 0, array(), '='), 104 | array('NoOp', 0, array(), '789') 105 | ), 106 | $tokens 107 | ); 108 | } 109 | 110 | public function testShouldNestTokens() 111 | { 112 | $tokens = $this->_cut->tokenize('12=34>56=78<90='); 113 | 114 | $this->assertEquals 115 | ( 116 | array 117 | ( 118 | array('NoOp', 0, array(), '12'), 119 | array('=', 0, array(), '='), 120 | array('NoOp', 0, array(), '34'), 121 | array('>', 0, array(), '>'), 122 | array('NoOp', 1, array(), '56'), 123 | array('=', 1, array(), '='), 124 | array('NoOp', 1, array(), '78'), 125 | array('<', 0, array(), '<'), 126 | array('NoOp', 0, array(), '90'), 127 | array('=', 0, array(), '=') 128 | ), 129 | $tokens 130 | ); 131 | } 132 | 133 | /** 134 | * @expectedException jQueryTmpl_Tokenizer_Exception 135 | */ 136 | public function testShouldThrowExceptionWithNonMatchedBlocks() 137 | { 138 | $this->_cut->tokenize('12=34>56=78=90='); 139 | } 140 | } 141 | 142 | function jQueryTmpl_TokenizerTest__FactoryCallback() 143 | { 144 | return func_get_args(); 145 | } 146 | 147 | -------------------------------------------------------------------------------- /test/Element/EachTest.php: -------------------------------------------------------------------------------- 1 | _elementFactory->createBlock 10 | ( 11 | 'Each', 12 | array 13 | ( 14 | new jQueryTmpl_Token_EachStart(0, array('name'=>'key3'), ''), 15 | new jQueryTmpl_Token_NoOp(1, array(), 'TEST'), 16 | new jQueryTmpl_Token_EachEnd(0, array(), '') 17 | ) 18 | ); 19 | 20 | $this->assertEquals 21 | ( 22 | '', 23 | $element->setData($this->_data)->render() 24 | ); 25 | } 26 | 27 | public function testShoudLoopThroughArray() 28 | { 29 | $element = $this->_elementFactory->createBlock 30 | ( 31 | 'Each', 32 | array 33 | ( 34 | new jQueryTmpl_Token_EachStart(0, array('name'=>'array'), ''), 35 | new jQueryTmpl_Token_NoOp(1, array(), '
  • '), 36 | new jQueryTmpl_Token_ValueEscaped(1, array('name'=>'$index'), ''), 37 | new jQueryTmpl_Token_NoOp(1, array(), ': '), 38 | new jQueryTmpl_Token_ValueEscaped(1, array('name'=>'$value'), ''), 39 | new jQueryTmpl_Token_NoOp(1, array(), '
  • '), 40 | new jQueryTmpl_Token_EachEnd(0, array(), '') 41 | ) 42 | ); 43 | 44 | $this->assertEquals 45 | ( 46 | '
  • 0: av1
  • 1: av2
  • 2: av3
  • ', 47 | $element->setData($this->_data)->render() 48 | ); 49 | } 50 | 51 | public function testShoudLoopThroughArrayWithCustomIndex() 52 | { 53 | $element = $this->_elementFactory->createBlock 54 | ( 55 | 'Each', 56 | array 57 | ( 58 | new jQueryTmpl_Token_EachStart(0, array('name'=>'array','index'=>'idx','value'=>'val'), ''), 59 | new jQueryTmpl_Token_NoOp(1, array(), '
  • '), 60 | new jQueryTmpl_Token_ValueEscaped(1, array('name'=>'idx'), ''), 61 | new jQueryTmpl_Token_NoOp(1, array(), ': '), 62 | new jQueryTmpl_Token_ValueEscaped(1, array('name'=>'val'), ''), 63 | new jQueryTmpl_Token_NoOp(1, array(), '
  • '), 64 | new jQueryTmpl_Token_EachEnd(0, array(), '') 65 | ) 66 | ); 67 | 68 | $this->assertEquals 69 | ( 70 | '
  • 0: av1
  • 1: av2
  • 2: av3
  • ', 71 | $element->setData($this->_data)->render() 72 | ); 73 | } 74 | 75 | public function testShoudLoopThroughNestedEach() 76 | { 77 | $element = $this->_elementFactory->createBlock 78 | ( 79 | 'Each', 80 | array 81 | ( 82 | new jQueryTmpl_Token_EachStart(0, array('name'=>'array'), ''), 83 | new jQueryTmpl_Token_NoOp(1, array(), ''), 92 | new jQueryTmpl_Token_EachEnd(0, array(), '') 93 | ) 94 | ); 95 | 96 | $this->assertEquals 97 | ( 98 | '', 99 | $element->setData($this->_data)->render() 100 | ); 101 | } 102 | 103 | public function testShoudLoopThroughObject() 104 | { 105 | $element = $this->_elementFactory->createBlock 106 | ( 107 | 'Each', 108 | array 109 | ( 110 | new jQueryTmpl_Token_EachStart(0, array('name'=>'object'), ''), 111 | new jQueryTmpl_Token_NoOp(1, array(), '
  • '), 114 | new jQueryTmpl_Token_ValueEscaped(1, array('name'=>'$value.gender'), ''), 115 | new jQueryTmpl_Token_NoOp(1, array(), ': '), 116 | new jQueryTmpl_Token_ValueEscaped(1, array('name'=>'this.name'), ''), 117 | new jQueryTmpl_Token_NoOp(1, array(), '('), 118 | new jQueryTmpl_Token_ValueEscaped(1, array('name'=>'attn'), ''), 119 | new jQueryTmpl_Token_NoOp(1, array(), ')
  • '), 120 | new jQueryTmpl_Token_EachEnd(0, array(), '') 121 | ) 122 | ); 123 | 124 | $this->assertEquals 125 | ( 126 | '
  • F: Sophia(!)
  • M: Zack(!)
  • F: Zoe(!)
  • : Morgan(!)
  • ', 127 | $element->setData($this->_data)->render() 128 | ); 129 | } 130 | } 131 | 132 | -------------------------------------------------------------------------------- /test/Element/TmplTest.php: -------------------------------------------------------------------------------- 1 | _data = new jQueryTmpl_Data 31 | ( 32 | json_decode($testData), 33 | new jQueryTmpl_Data_Factory() 34 | ); 35 | 36 | $this->_elementFactory = new jQueryTmpl_Element_Factory(); 37 | 38 | $this->_compiledTemplates['person'] = array 39 | ( 40 | $this->_elementFactory->createInline 41 | ( 42 | 'NoOp', 43 | new jQueryTmpl_Token_NoOp(0, array(), 'Name: ') 44 | ), 45 | $this->_elementFactory->createInline 46 | ( 47 | 'ValueEscaped', 48 | new jQueryTmpl_Token_ValueEscaped(0, array('name'=>'name'), '') 49 | ), 50 | $this->_elementFactory->createInline 51 | ( 52 | 'NoOp', 53 | new jQueryTmpl_Token_NoOp(0, array(), ' (') 54 | ), 55 | $this->_elementFactory->createInline 56 | ( 57 | 'ValueEscaped', 58 | new jQueryTmpl_Token_ValueEscaped(0, array('name'=>'location'), '') 59 | ), 60 | $this->_elementFactory->createInline 61 | ( 62 | 'NoOp', 63 | new jQueryTmpl_Token_NoOp(0, array(), ')') 64 | ) 65 | ); 66 | 67 | $this->_compiledTemplates['tree'] = array 68 | ( 69 | $this->_elementFactory->createInline 70 | ( 71 | 'NoOp', 72 | new jQueryTmpl_Token_NoOp(0, array(), '
  • ') 73 | ), 74 | $this->_elementFactory->createInline 75 | ( 76 | 'ValueEscaped', 77 | new jQueryTmpl_Token_ValueEscaped(0, array('name'=>'name'), '') 78 | ), 79 | $this->_elementFactory->createInline 80 | ( 81 | 'Tmpl', 82 | new jQueryTmpl_Token_Tmpl(0, array('template'=>'tree', 'data'=>'child'), '') 83 | ), 84 | $this->_elementFactory->createInline 85 | ( 86 | 'NoOp', 87 | new jQueryTmpl_Token_NoOp(0, array(), '
  • ') 88 | ) 89 | ); 90 | } 91 | 92 | public function testShouldNotPrintNonExistantTemplate() 93 | { 94 | $element = $this->_elementFactory->createInline 95 | ( 96 | 'Tmpl', 97 | new jQueryTmpl_Token_Tmpl(0, array('template'=>'dne'), '') 98 | ); 99 | 100 | $this->assertEquals 101 | ( 102 | '', 103 | $element 104 | ->setData($this->_data) 105 | ->setCompiledTemplates($this->_compiledTemplates) 106 | ->render() 107 | ); 108 | } 109 | 110 | public function testShouldPrintTemplate() 111 | { 112 | $element = $this->_elementFactory->createInline 113 | ( 114 | 'Tmpl', 115 | new jQueryTmpl_Token_Tmpl(0, array('template'=>'person'), '') 116 | ); 117 | 118 | $this->assertEquals 119 | ( 120 | 'Name: Sophia (Denver, CO)', 121 | $element 122 | ->setData($this->_data) 123 | ->setCompiledTemplates($this->_compiledTemplates) 124 | ->render() 125 | ); 126 | } 127 | 128 | public function testShouldPrintNestedTemplates() 129 | { 130 | $element = $this->_elementFactory->createInline 131 | ( 132 | 'Tmpl', 133 | new jQueryTmpl_Token_Tmpl(0, array('template'=>'person', 'data' => 'child'), '') 134 | ); 135 | 136 | $this->assertEquals 137 | ( 138 | 'Name: Zack (Portland, OR)', 139 | $element 140 | ->setData($this->_data) 141 | ->setCompiledTemplates($this->_compiledTemplates) 142 | ->render() 143 | ); 144 | 145 | $element = $this->_elementFactory->createInline 146 | ( 147 | 'Tmpl', 148 | new jQueryTmpl_Token_Tmpl(0, array('template'=>'person', 'data' => 'child.child'), '') 149 | ); 150 | 151 | $this->assertEquals 152 | ( 153 | 'Name: Zoe (Boston, MA)', 154 | $element 155 | ->setData($this->_data) 156 | ->setCompiledTemplates($this->_compiledTemplates) 157 | ->render() 158 | ); 159 | } 160 | 161 | public function testShouldPrintTemplateAndSliceData() 162 | { 163 | $element = $this->_elementFactory->createInline 164 | ( 165 | 'Tmpl', 166 | new jQueryTmpl_Token_Tmpl(0, array('template'=>'tree'), '') 167 | ); 168 | 169 | $this->assertEquals 170 | ( 171 | '
  • Sophia
  • Zack
  • Zoe
  • ', 172 | $element 173 | ->setData($this->_data) 174 | ->setCompiledTemplates($this->_compiledTemplates) 175 | ->render() 176 | ); 177 | } 178 | } 179 | 180 | -------------------------------------------------------------------------------- /test/DataTest.php: -------------------------------------------------------------------------------- 1 | _cut = new jQueryTmpl_Data 50 | ( 51 | json_decode($testData), 52 | new jQueryTmpl_Data_Factory() 53 | ); 54 | 55 | $this->_cutPart = new jQueryTmpl_Data 56 | ( 57 | json_decode($testDataPart), 58 | new jQueryTmpl_Data_Factory() 59 | ); 60 | } 61 | 62 | public function testShouldAddDataToObject() 63 | { 64 | $this->_cut 65 | ->addDataPair('addedKey1', 'value 1') 66 | ->addDataPair('addedKey2', 'value 2'); 67 | 68 | $this->assertEquals 69 | ( 70 | 'a string', 71 | $this->_cut->getValueOf('key2') 72 | ); 73 | 74 | $this->assertEquals 75 | ( 76 | 'value 2', 77 | $this->_cut->getValueOf('addedKey2') 78 | ); 79 | } 80 | 81 | /** 82 | * @expectedException jQueryTmpl_Data_Exception 83 | */ 84 | public function testShouldNotParseExpresion() 85 | { 86 | $this->_cut->getValueOf('someFunc()'); 87 | } 88 | 89 | public function testShouldReturnEmptyStringForDne() 90 | { 91 | $this->assertEquals 92 | ( 93 | '', 94 | $this->_cut->getValueOf('dne') 95 | ); 96 | 97 | $this->assertEquals 98 | ( 99 | '', 100 | $this->_cut->getValueOf('key5.dne') 101 | ); 102 | 103 | $this->assertEquals 104 | ( 105 | '', 106 | $this->_cut->getValueOf('key1[0]') 107 | ); 108 | } 109 | 110 | public function testShouldGetFromFirstLevel() 111 | { 112 | $this->assertEquals 113 | ( 114 | 123.45, 115 | $this->_cut->getValueOf('key1') 116 | ); 117 | 118 | $this->assertEquals 119 | ( 120 | "a string", 121 | $this->_cut->getValueOf('key2') 122 | ); 123 | 124 | $this->assertEquals 125 | ( 126 | true, 127 | $this->_cut->getValueOf('key3') 128 | ); 129 | 130 | $this->assertEquals 131 | ( 132 | array('item1','item2','item3'), 133 | $this->_cut->getValueOf('key4') 134 | ); 135 | } 136 | 137 | public function testShouldGetFromSecondLevel() 138 | { 139 | $this->assertEquals 140 | ( 141 | 543.21, 142 | $this->_cut->getValueOf('key5.child1') 143 | ); 144 | 145 | $this->assertEquals 146 | ( 147 | "another string", 148 | $this->_cut->getValueOf('key5.child2') 149 | ); 150 | 151 | $this->assertEquals 152 | ( 153 | false, 154 | $this->_cut->getValueOf('key5.child3') 155 | ); 156 | 157 | $this->assertEquals 158 | ( 159 | array('item4','item5','item6'), 160 | $this->_cut->getValueOf('key5.child4') 161 | ); 162 | } 163 | 164 | public function testShouldGetArrayElements() 165 | { 166 | $this->assertEquals 167 | ( 168 | 'item2', 169 | $this->_cut->getValueOf('key4[1]') 170 | ); 171 | 172 | $this->assertEquals 173 | ( 174 | 'item4', 175 | $this->_cut->getValueOf('key5.child4[0]') 176 | ); 177 | } 178 | 179 | public function testShouldGetHashElements() 180 | { 181 | $this->assertEquals 182 | ( 183 | 'Naomi', 184 | $this->_cut->getValueOf('key5.child5["Grand Child 2"]') 185 | ); 186 | 187 | $this->assertEquals 188 | ( 189 | 'Rachel', 190 | $this->_cut->getValueOf('key5[child5]["Grand Child 1"]') 191 | ); 192 | } 193 | 194 | public function testShouldGetCharInString() 195 | { 196 | $this->assertEquals 197 | ( 198 | "e", 199 | $this->_cut->getValueOf('key5.child2[5]') 200 | ); 201 | } 202 | 203 | public function testShouldGetLengths() 204 | { 205 | // Array length 206 | $this->assertEquals 207 | ( 208 | 3, 209 | $this->_cut->getValueOf('key5.child4.length') 210 | ); 211 | 212 | // Object length 213 | $this->assertEquals 214 | ( 215 | 3, 216 | $this->_cut->getValueOf('key5.child5.length') 217 | ); 218 | 219 | // String length 220 | $this->assertEquals 221 | ( 222 | 5, 223 | $this->_cut->getValueOf('key5.child5["Grand Child 2"].length') 224 | ); 225 | } 226 | 227 | public function testShouldReturnFullObjectIfNothingToSlice() 228 | { 229 | // Property not specified 230 | $this->assertEquals 231 | ( 232 | $this->_cut, 233 | $this->_cut->getDataSice('') 234 | ); 235 | } 236 | 237 | public function testShouldReturnNullIfSliceNotObject() 238 | { 239 | // Property is string 240 | $this->assertEquals 241 | ( 242 | null, 243 | $this->_cut->getDataSice('key2') 244 | ); 245 | 246 | // Property is array 247 | $this->assertEquals 248 | ( 249 | null, 250 | $this->_cut->getDataSice('key4') 251 | ); 252 | } 253 | 254 | public function testShouldSliceOutPartOfData() 255 | { 256 | $this->assertEquals 257 | ( 258 | $this->_cutPart, 259 | $this->_cut->getDataSice('key5') 260 | ); 261 | } 262 | } 263 | 264 | -------------------------------------------------------------------------------- /test/ParserTest.php: -------------------------------------------------------------------------------- 1 | getMock('jQueryTmpl_Element_Factory', array('createBlock','createControl','createInline')); 12 | $elementFactory 13 | ->expects($this->any()) 14 | ->method('createBlock') 15 | ->will($this->returnCallback('jQueryTmpl_ParserTest__FactoryBlockCallback')); 16 | $elementFactory 17 | ->expects($this->any()) 18 | ->method('createControl') 19 | ->will($this->returnCallback('jQueryTmpl_ParserTest__FactoryControlCallback')); 20 | $elementFactory 21 | ->expects($this->any()) 22 | ->method('createInline') 23 | ->will($this->returnCallback('jQueryTmpl_ParserTest__FactoryInlineCallback')); 24 | 25 | $this->_cut = new jQueryTmpl_Parser($elementFactory); 26 | } 27 | 28 | public function testShouldCreatInlineElements() 29 | { 30 | $elements = $this->_cut->parse 31 | ( 32 | array 33 | ( 34 | new jQueryTmpl_Token_NoOp(0, array(), 'a'), 35 | new jQueryTmpl_Token_Comment(0, array(), 'b'), 36 | new jQueryTmpl_Token_NoOp(0, array(), 'c'), 37 | new jQueryTmpl_Token_ValueEscaped(0, array(), 'd') 38 | ) 39 | ); 40 | 41 | $this->assertEquals 42 | ( 43 | array 44 | ( 45 | array 46 | ( 47 | 'Inline', 48 | 'NoOp', 49 | new jQueryTmpl_Token_NoOp(0, array(), 'a') 50 | ), 51 | array 52 | ( 53 | 'Inline', 54 | 'Comment', 55 | new jQueryTmpl_Token_Comment(0, array(), 'b') 56 | ), 57 | array 58 | ( 59 | 'Inline', 60 | 'NoOp', 61 | new jQueryTmpl_Token_NoOp(0, array(), 'c') 62 | ), 63 | array 64 | ( 65 | 'Inline', 66 | 'ValueEscaped', 67 | new jQueryTmpl_Token_ValueEscaped(0, array(), 'd') 68 | ) 69 | ), 70 | $elements 71 | ); 72 | } 73 | 74 | public function testShouldCreatNestedElements() 75 | { 76 | $elements = $this->_cut->parse 77 | ( 78 | array 79 | ( 80 | new jQueryTmpl_Token_NoOp(0, array(), 'a'), 81 | new jQueryTmpl_Token_Comment(0, array(), 'b'), 82 | new jQueryTmpl_Token_IfStart(0, array(), 'c1'), 83 | new jQueryTmpl_Token_NoOp(1, array(), 'c2'), 84 | new jQueryTmpl_Token_Else(0, array(), 'c3'), 85 | new jQueryTmpl_Token_ValueEscaped(1, array(), 'c4'), 86 | new jQueryTmpl_Token_IfEnd(0, array(), 'c5'), 87 | new jQueryTmpl_Token_ValueEscaped(0, array(), 'd') 88 | ) 89 | ); 90 | 91 | $this->assertEquals 92 | ( 93 | array 94 | ( 95 | array 96 | ( 97 | 'Inline', 98 | 'NoOp', 99 | new jQueryTmpl_Token_NoOp(0, array(), 'a') 100 | ), 101 | array 102 | ( 103 | 'Inline', 104 | 'Comment', 105 | new jQueryTmpl_Token_Comment(0, array(), 'b') 106 | ), 107 | array 108 | ( 109 | 'Block', 110 | 'If', 111 | array 112 | ( 113 | new jQueryTmpl_Token_IfStart(0, array(), 'c1'), 114 | new jQueryTmpl_Token_NoOp(1, array(), 'c2'), 115 | new jQueryTmpl_Token_Else(0, array(), 'c3'), 116 | new jQueryTmpl_Token_ValueEscaped(1, array(), 'c4'), 117 | new jQueryTmpl_Token_IfEnd(0, array(), 'c5'), 118 | ) 119 | ), 120 | array 121 | ( 122 | 'Inline', 123 | 'ValueEscaped', 124 | new jQueryTmpl_Token_ValueEscaped(0, array(), 'd') 125 | ) 126 | ), 127 | $elements 128 | ); 129 | } 130 | 131 | public function testShouldCreatControlElements() 132 | { 133 | $elements = $this->_cut->parse 134 | ( 135 | array 136 | ( 137 | new jQueryTmpl_Token_NoOp(1, array(), 'c2'), 138 | new jQueryTmpl_Token_Else(0, array(), 'c3'), 139 | new jQueryTmpl_Token_ValueEscaped(1, array(), 'c4'), 140 | ) 141 | ); 142 | 143 | $this->assertEquals 144 | ( 145 | array 146 | ( 147 | array 148 | ( 149 | 'Inline', 150 | 'NoOp', 151 | new jQueryTmpl_Token_NoOp(1, array(), 'c2') 152 | ), 153 | array 154 | ( 155 | 'Control', 156 | 'Else', 157 | new jQueryTmpl_Token_Else(0, array(), 'c3') 158 | ), 159 | array 160 | ( 161 | 'Inline', 162 | 'ValueEscaped', 163 | new jQueryTmpl_Token_ValueEscaped(1, array(), 'c4') 164 | ) 165 | ), 166 | $elements 167 | ); 168 | } 169 | 170 | /** 171 | * @expectedException jQueryTmpl_Parser_Exception 172 | */ 173 | public function testShouldThrowExceptionWithNonMatchedEndBlocks() 174 | { 175 | $elements = $this->_cut->parse 176 | ( 177 | array 178 | ( 179 | new jQueryTmpl_Token_NoOp(0, array(), 'a'), 180 | new jQueryTmpl_Token_Comment(0, array(), 'b'), 181 | new jQueryTmpl_Token_IfEnd(0, array(), 'c1'), 182 | new jQueryTmpl_Token_NoOp(1, array(), 'c2'), 183 | new jQueryTmpl_Token_Else(0, array(), 'c3'), 184 | new jQueryTmpl_Token_ValueEscaped(1, array(), 'c4'), 185 | new jQueryTmpl_Token_IfStart(0, array(), 'c5'), 186 | new jQueryTmpl_Token_ValueEscaped(0, array(), 'd') 187 | ) 188 | ); 189 | } 190 | } 191 | 192 | function jQueryTmpl_ParserTest__FactoryBlockCallback() 193 | { 194 | return array_merge(array('Block'), func_get_args()); 195 | } 196 | 197 | function jQueryTmpl_ParserTest__FactoryControlCallback() 198 | { 199 | return array_merge(array('Control'), func_get_args()); 200 | } 201 | 202 | function jQueryTmpl_ParserTest__FactoryInlineCallback() 203 | { 204 | return array_merge(array('Inline'), func_get_args()); 205 | } 206 | 207 | --------------------------------------------------------------------------------