Fork me on GitHub
Current release is 2.1.6 (21 Nov 2014).

Users who have not yet upgraded to 2.1 should get security release 2.0.18.14 (1 Nov 2014). We will stop providing these security releases to 2.0 at the end of this year.

Garden and Vanilla Idea 1: Views clean and lean

x00x00 MVP
edited April 2009 in Vanilla 1.0 Help
I have enjoyed messing around with vanilla so far. It is a great little app and not half bad for development. I am looking forward to Garden and Vanilla 2 which should be easier. I have been following its development keenly, and interested to know how various things are going to be done.

One of the areas the vanilla 1 is not so good on is the themes/templates. They are just messy, loads of code, and echoes all over the place, or appending variables, etc. In accordance to good MCV practice views should have minimal amount of code. You should avoid putting too much logic in views as this is not the place for it. However sometimes it is unavoidable, but it should always be done thoughtfully if it is necessary.

PHP is considered an "embedded" language. However it can be both an eye sore and practically difficult to mange XHTML with a bunch of PHP throughout.

One of my ideas is a simpler notation, that is unobtrusive an intuitive. I have just come up with this quick and dirty example. I have been using a more basic version of this for mini aps and extensions for a while now. Here is a Template class:

class Template {
var $evalMode;

//the chained together parts exploded
function properties($string){
$a=0;$b=0;$seperator='.';$exploded = array();
while(($a=strpos($string,$seperator,$a))!==false){
$exploded[]=substr($string,$b,$a-$b);
$a+=1;
$b=$a;
}
$exploded[]=substr($string,$b);
return $exploded;

}

//get the required parameter
function getParameter($txt,$params){
$properties = $this->properties($txt);
foreach($properties As $property){
$type = gettype($params);
if(in_array($type,array('array','object'))){
if($type == 'object'){
$params = get_object_vars($params);
}
if(array_key_exists($property,$params)){
$params = $params[$property];
}else{
return false;
}
}else{
return false;
}
}
return $params;
}

//get the file contents
//php eval modes: 0=don't evaluate, 1=force evaluate, 2=auto evaluate as required
function getTemplFile($templFile,$params){
extract($params);
$templ = file_get_contents($templFile);
if($this->evalMode==1 || ($this->evalMode==2 && strpos($templ,'<?php')!==false)){
ob_start();
eval('?>'.$templ);
return ob_get_clean();
}
return $templ;
}

//Simple parser for a simple notation
//one method 'each' with a corresponding control word 'end'
//for iteration and static blocks
function parse($templ,$params){
$a=0;
while(($a=strpos($templ,"#{",$a))!==false && ($b=strpos($templ,"}",$a))!==false && ($a==0 || substr($templ,$a-1,1)!='\\')){
$p = substr($templ,$a+2,$b-$a-2);
if(($d=strrpos($p,'.'))!==false){
$t ='';
$f=strlen($p)-1;
if(substr($p,$d+1,5)=='each|' && substr($p,$f)=='|' && ($g=strpos($templ,'#{end}',$b))!==false){

if(!($c=$this->getParameter(substr($templ,$a+2,$d),$params))){$a=$g+6;continue;}
$blocklabel = substr($p,$d+6,$f-$d-6);
$block = substr($templ,$b+1,$g-$b-1);
$type = gettype($c);
if(!in_array($type,array('array','object'))){
$c = array($c);
}

foreach($c As $v){
$params[$blocklabel] = $v;
$t .= $this->parse($block,$params);
unset($params[$blocklabel]);

}
}

if($t){
$d=substr($templ,0,$a).$t;
$e=substr($templ,$g+6);
$a=strlen($d);
$templ=$d.$e;
continue;
}
}
if(!($c=$this->getParameter(substr($templ,$a+2,$b-$a-2),$params))){$a=$b;continue;}
$type = gettype($c);
if(in_array($type,array('array','object','resource'))){$a=$b;continue;}
$d=substr($templ,0,$a).$c;
$e=substr($templ,$b+1);
$a=strlen($d);
$templ=$d.$e;

}
return $templ;
}

//load a template
function Template($templFile,$params=array(),$evalMode=2){
$this->evalMode=$evalMode;
$templ = $this->getTemplFile($templFile,$params);
echo $this->parse($templ,$params);

}
}


The notation is parses (as well as php) is influenced by embedded ruby and ruby string parsing, but it is not even as complicated.

here is an example of a template:

<?php
if(true){
echo "<p>hi</p>";
}
?>
<p>#{test.one}</p>
<input type="text" value="#{test.0}"/>
#{test.each|x|}
<p>This is a value: #{x}</p>
#{end}

#{test.one.each|y|}
<p>This is just a static block: #{y} #{test.one}</p>
#{end}

<p>#{other.bla.special}</p>

#{other.bla.each|item|}
<p>#{item}</p>
#{end}


You can create a new Template like so:
$params = array('test'=> array('one'=>'one','two'=>'two','three'=>'three','four'),
'other'=>array('bla'=>array('special'=>'whooo!','duckling')));
new Template('test.php',$params);


which gives you:

hi

one

This is a value: one

This is a value: two

This is a value: three

This is a value: four

This is just just a static block: one one

whooo!

whooo!

duckling



It doesn't worry too much about types. It is not a real language or even a domain specific, micro or macro language. It only has two pseudo types. Things you can output directly like strings and numbers, and things that you can iterate or access its properties or index like arrays. It just uses the simple dot notation. It matches this to whatever you put in you params array.

If it can't find what you want, or doesn't know what to do, at the moment it just hops over. It could equally rub itself out, or raise an error.

Currently it only has one method 'each', which has a corresponding control word 'end'. This can be used can be used to iterate or if the object is not the iteration type it just creates a block so you can tidy things up a bit.

The simple notation does not itself have logic, However if were to rub out block when properties that don't exist then it could work like if true situation.

Sometimes you want php so you can insert that true. By default it will evaluate php if it detects php in the template file, or to can force it to or not.

I don't think I will be making it a great deal more complicated than that otherwise you need more syntax rules which is unnecessary. It is very literal, so if there is a space in your property it will look for the property including the space. There are no escape rules besides \#{...}, but with good conventions this shouldn’t be a problem.

Two more methods/control words that could be useful are 'fire', which will fire an event in an event handler that is passed tot he constructor, and also 'fragment', which could use a caching hander for fragment caching. Anther thing that could be useful is 'yield'. Instead of having say header and footer, etc you just have the main container page, view and partial views.

Anyway that is just a suggestion that may be useful in the framework and would also be available for extensions to use as well.

grep is your friend.

Comments

  • Another area which would be good to get right is that fact that creating extension is not a problem but the chance of extensions conflicting is quite high. I do like the freedom and wouldn’t want to sacrifice that. I understand things will be improving in that respect but I am still keen to know whether the many ways extension conflict with one another are being considered. I would propose ideas like conventions and polaroid scoping, but I don’t want to tread on any toes until I fully understand how it is going to work.

    Ok am ok with not having proper ORM as that is not strictly necessary so long and the query builder works nicely.

    I have mixed feeling about JQuery being included in as standard. I do think we need certain functionality to be standardised for conflict reasons if nothing else like domready, etc. But I am not sure I want JQuery all the time, especially as I managed to write concise scripts (I haven’t bothered to minify yet) without a framework so far. As amazing as JQuery is, sometimes it simply isn’t needed. I mean JavaScript is not that bad, or else you wouldn’t have frameworks like JQuery. Even though you can load individual modules, those modules are nowhere near as small as you want them if you are only doing something very simple. JavaScript frameworks are not created to on save space. In order to get that benefit you would have to write something very big. JQuery, etc are created for people who hate JavaScript. Also it is myth that frameworks help you with quirks and compatibility completely. Framework teams have to make decisions with the direction the framework is going. If you don’t know about that it may be the opposite of what you want. While they do come up with innovative methods that can be efficient some times, they are also may include extra functionality, which you don’t need and that can negate the benefit.

    grep is your friend.

  • x00x00 MVP
    edited April 2009
    The above example hasn't got support for nested blocks yet, but i could do that. You could imply label the end like so: #{end|x|} So it knows where the block ends.

    grep is your friend.

  • That's a nice bit of code there. However, I really don't understand the utility of having 100+ lines of code doing something that foreach () can do faster, natively, and with more functionality. What I can agree with though is that programming is art, and when it comes to art, I am a minimalist ;)
  • x00x00 MVP
    edited April 2009
    because you really shouldn't do anything complicated in the views anyway. If you subscribe to MCV that is.

    107 lines of code isn't for foreach. If you are talking about 'each' that is basically dealt with in 17 line of code.

    The length is actually down to speed I could do make shorter say using regular expressions call back, but that would be slower

    grep is your friend.

  • basically if all you want to do is insert variable you have already sorted you can use a simple function such as this:
    function insert_params($txt,$params){
    $a=0;
    while(($a=strpos($txt,"#{",$a))!==false && ($b=strpos($txt,"}",$a))!==false && ($a==0 || substr($txt,$a-1,1)!='\\')){
    if(!($c=$params[substr($txt,$a+2,$b-$a-2)])){$a=$b;continue;}
    $d=substr($txt,0,$a).$c;
    $e=substr($txt,$b+1);
    $a=strlen($d);
    $txt=$d.$e;
    }
    return $txt;
    }


    The previous example just give you a bit more flexibility that is all.

    grep is your friend.

  • Here is an improved version to demonstrates nesting, yields and chaining:
    class Template {
    var $evalMode;
    var $templStr;
    var $yeild;

    //the chained together parts exploded
    function properties($string){
    $a=0;$b=0;$seperator='.';$exploded = array();
    while(($a=strpos($string,$seperator,$a))!==false){
    $exploded[]=substr($string,$b,$a-$b);
    $a+=1;
    $b=$a;
    }
    $exploded[]=substr($string,$b);
    return $exploded;

    }

    //get the required parameter
    function getParameter($txt,$params){
    $properties = $this->properties($txt);
    foreach($properties As $property){
    $type = gettype($params);
    if(in_array($type,array('array','object'))){
    if($type == 'object'){
    $params = get_object_vars($params);
    }
    if(array_key_exists($property,$params)){
    $params = $params[$property];
    }else{
    return false;
    }
    }else{
    return false;
    }
    }
    return $params;
    }

    //get the file contents
    //php eval modes: 0=don't evaluate, 1=force evaluate, 2=auto evaluate as required
    function getTemplFile($templFile,$params){

    extract($params);
    $templ = file_get_contents($templFile);
    if($this->evalMode==1 || ($this->evalMode==2 && strpos($templ,'<?php')!==false)){
    ob_start();
    eval('?>'.$templ);
    $templ = ob_get_clean();
    }
    $this->insertOnYeild($this->parse($templ,$params));
    }

    //insert into parent on yeild
    function insertOnYeild($txt){

    if(!$this->templStr){
    $this->templStr=$txt;
    return;
    }
    if($this->yeild){
    $a=0;
    $l=strlen($this->yeild);
    while(($a=strpos($this->templStr,'#{yeild|'.$this->yeild.'|}',$a))!==false && ($a==0 || substr($this->templStr,$a-1,1)!='\\')){
    $d=substr($this->templStr,0,$a).$txt;
    $e=substr($this->templStr,$a+10+$l);
    $a=strlen($d);
    $this->templStr=$d.$e;
    }
    }else{

    $this->templStr=$this->templStr.$txt;

    }
    }

    //Simple parser for a simple notation
    //one method 'each' with a corresponding control word 'end'
    //for iteration and static blocks
    function parse($templ,$params){
    $a=0;
    while(($a=strpos($templ,"#{",$a))!==false && ($b=strpos($templ,"}",$a))!==false && ($a==0 || substr($templ,$a-1,1)!='\\')){
    $p = substr($templ,$a+2,$b-$a-2);
    $f=strlen($p)-1;
    if(($d=strrpos($p,'.'))!==false){
    $t ='';
    $h = substr($p,$d+6,$f-$d-6);
    if(substr($p,$d+1,5)=='each|' && substr($p,$f)=='|' && ($g=strpos($templ,'#{end|'.$h.'|}',$b))!==false){

    if(!($c=$this->getParameter(substr($templ,$a+2,$d),$params))){$a=$g+6;continue;}
    $blocklabel = $h;
    $block = substr($templ,$b+1,$g-$b-1);
    $type = gettype($c);
    if(!in_array($type,array('array','object'))){
    $c = array($c);
    }

    foreach($c As $v){
    $params[$blocklabel] = $v;
    $t .= $this->parse($block,$params);
    unset($params[$blocklabel]);

    }
    }

    if($t){
    $d=substr($templ,0,$a).$t;
    $e=substr($templ,$g+8+strlen($h));
    $a=strlen($d);
    $templ=$d.$e;
    continue;
    }
    }

    if(!($c=$this->getParameter(substr($templ,$a+2,$b-$a-2),$params))){$a=$b;continue;}
    $type = gettype($c);
    if(in_array($type,array('array','object','resource'))){$a=$b;continue;}
    $d=substr($templ,0,$a).$c;
    $e=substr($templ,$b+1);
    $a=strlen($d);
    $templ=$d.$e;

    }
    return $templ;
    }

    //print Template
    function tPrint(){
    echo $this->templStr;
    }

    //load a template
    function tAdd($templFile,$params=array(),$yeild=null,$evalMode=2){
    $params = is_array($params) ? $params:array();
    $this->evalMode=$evalMode;
    $this->yeild=$yeild;
    $this->getTemplFile($templFile,$params);
    return $this;
    }

    //constructor
    function Template(){
    $this->evalMode=null;
    $this->templStr=null;
    $this->yeild=null;
    }
    }


    Say you have the following template files:
    <?php
    echo "hi";
    ?>
    #{yeild|bla|}
    #{yeild|bla|}
    #{yeild|bla|}
    <input type="text" value="#{test.0}"/>
    #{test.each|x|}
    <p>This is a value: #{x}</p>
    #{end|x|}

    #{test.one.each|y|}
    <p>This is just a static block: #{y} #{test.one}</p>
    #{end|y|}

    <p>Nested blocks:</p>

    #{other.each|item|}

    #{item.each|z|}
    <p>#{z}</p>
    #{end|z|}

    #{end|item|}

    <p>#{test.one}</p>
    <div>#{yeild|sillybar|}</div>
    <p />

    Some other content!

    You do this:
    $params = array('test'=> array('one'=>'one','two'=>'two','three'=>'three','four'),
    'other'=>array('bla'=>array('special'=>'whooo!','duckling')));

    $tmpl = new Template;
    $tmpl->tAdd('test.php',$params)->tAdd('test1.php',$params,'bla')->tAdd('test2.php',null,'sillybar')->tPrint();


    ...and you will get this:

    hi

    one

    Some other content!

    one

    Some other content!

    one

    Some other content!

    This is a value: one

    This is a value: two

    This is a value: three

    This is a value: four

    This is just a static block: one one

    Nested blocks:

    whooo!

    duckling



    All this is this just a very simple, lean, template engine example. You could also use more comprehensive template engine like ->smarty-<

    Regardless of whether you use one or not clean views are a must.

    grep is your friend.

  • [-Stash-][-Stash-] New
    edited April 2009
    Maybe I'm just simple, but if you really want to impress me, make Vanilla/Garden work with Haml and Sass :) That's the first time I've ever seen a template that looks easy enough for me to edit quickly and without really learning anything more than I already have (XHTML and CSS). I'm a designer, not a programmer, so what you've shown here is already on the technical side, and therefore a little less interesting to me. that's not to put down anything you've written about, it is just so complex that I can't appreciate it. Hope this designer perspective is valuable to you!
  • x00x00 MVP
    edited April 2009
    [-Stash-] I totally agree with you. I actually went off what I'd posted here not long after as well as smarty and a most of them. It is not that I don't to create a great template engine, it is just I think it will need a little work. Haml is great but like what I posted and probably the majority of template engine and embedded code such a php it doesn't work very well in wysiwyg editors. I like the look of TinyButStrong and PHPTAL, but I think they are still not how I would want them yet. I also think that "precompiled" templates are a must.

    grep is your friend.

  • That's kinda why I suggested Haml and Sass since (under Ruby at least) they are "precompiled" to standard XHTML (and I guess Ruby...).
  • Just to clarify my position. I don’t think Garden should use a template engine at this stage. It was just a pie in sky thing.

    [-Stash-] that is true. However some designers like to use visual editors such as Dreamweaver. As a coder i don't, but if you go off traditional mark-up some people will be lost.

    The solution may actually be need visual editor for template language rather trying to stuff a template language into XML and hope it comes out unscathed.

    Then again I think xml and xml like things like (X)HTML are completely overrated. W3C didn't some how anticipate how template would be used, or didn't care enough. If I see anyone using xml just to serialise data without need for a definition, I give them a lecture on sterilization, and what a parser has to go though to process something overly complex like xml.

    grep is your friend.

  • I would have thought that if you're using a templating engine that "compiles" the templates before they're used, that would kinda by design allow people to design templates in a visual editor - no?
  • No because that is *after* the thought, and you have still end up with the ugly (and invalid) embedded code breaking up the code.

    If you have

    <table>
    code here <- invalid
    <tr>
    <td>
    code here <- possibly still invalid and still not universally supported
    </td>
    </tr>
    </table>

    grep is your friend.

  • "because you really shouldn't do anything complicated in the views anyway."

    So don't. Just because you can make a DB call inside of a view doesn't mean that you must. Shove all of your data into a bunch of nested arrays and restrict your templates to only use echo, if, select and foreach statements. Any template system is just another way of forcing you to do this. But it's like training wheels on a bicycle, what's the point of using it for the rest of your life when it's pretty easy to just learn how use the real thing correctly?

    I don't get why you're so down on XML, it offers some major advantages. You're worried about overhead? With XML+XSLT you don't have to process the template on the server side at all. Just send the XML and XSL files to the browser and let the client handle it. And because the goal of XSLT is the ability to arbitrarily translate any kind of XML into any other kind of XML, it also gives you a solution for RSS or SOAP or any other XML-based trick you want to pull.

    I scratch my head when you say, "W3C didn't some how anticipate how template would be used, or didn't care enough." I would say that Smarty and HAML and most other template engines are the ones that don't anticipate enough. They all seem to focus narrowly on the task of putting data into HTML. But really, how much does that help? It seems to me that most of the work in coding a web theme is making sure that your HTML and CSS work correctly across a million different browsers, not getting at the data.
  • x00x00 MVP
    edited April 2009
    squirrelI don't get why you're so down on XML, it offers some major advantages.
    By the time that XML+XSLT is fully viable hopefully something better will come replace it.

    Most of the mess is to do with trying to work around the mark-up design issues. It is pretty obvious people would want to put data in their pages (shock horror who have thought it). The web is hardly the first example where you are pulling raw data and outputting it in the desired format. You can't exactly blame template engines for providing a stop gap for a niche market (which is not that small).

    The main point is XML, without a definition, is pointless and a waste of time to parse. You are better off with a decent serialisation language like YAML, yet people persist with this out of ignorance. Even when it has a definition, it quite often used in a situation that des not really require.
    squirrelIt seems to me that most of the work in coding a web theme is making sure that your HTML and CSS work correctly across a million different browsers, not getting at the data.
    Of course can't have data cluttering up the pages.

    grep is your friend.

  • It took many years before the internet truly adopted CSS and it will probably take just as many for XSLT to become as widely accepted.

    x00, you're not backing up any of your statements. If we're going to have a discussion, start referencing reasons. Do some research.

    As for XSLT. I've not found a better CMS for it than with Symphony. If anyone finds something better, let me know :)
  • x00x00 MVP
    edited April 2009
    Interesting stuff mentioned here on both side of the argument:
    http://c2.com/cgi/wiki?XmlSucks

    grep is your friend.

  • Forget XML for a minute. My main argument is that template systems don't do anything that PHP doesn't do already. All they do is obscure PHP and create a hobbled subset of the language. The rationale for this given on the Why Use Smarty page seems to boil down to "designers are too stupid to mess with PHP", which I don't buy.

    More code = more bugs, period. If you want to introduce more code then you should have a compelling reason for it. I haven't yet heard a compelling argument in favor of template engines like Smarty. Any argument that it reduces bugs tends to ignore the extra bugs introduced by the template engine itself. I guess it could make the HTML easier to write if your HTML editor doesn't understand PHP snippets, but is that really still a problem in 2009?
    x00The main point is XML, without a definition, is pointless and a waste of time to parse. You are better off with a decent serialisation language like YAML, yet people persist with this out of ignorance. Even when it has a definition, it quite often used in a situation that des not really require.
    You're talking about serializing data to pass it between two modules of an application. I'm saying there's no need to do that at all. As long as you build your data structures correctly you can keep the application logic quite well separated from the display logic without having to treat the view like it's the application's retarded little brother.

    But back to XML, you still didn't address the big advantage of XML+XSLT over other template systems. With your code or Smarty or HAML or anything else, your code must generate the content all over again whenever you apply a new template. With XML+XSLT, you're always generating the same XML (so it's easier to cache) and each XSL template is a static file. The work of applying the template is done by the client. That does mean the client must have an XML parser built-in, but that shouldn't be a problem since every modern web browser already has that.
    x00Interesting stuff mentioned here on both side of the argument:
    http://c2.com/cgi/wiki?XmlSucks
    Define irony: An unstructured, stream-of-consciousness rant about how XML sucks because it's too wordy and too hard to parse.
Sign In or Register to comment.