Page 2 of 3

Re: Muted Template <Review/Critique/Suggestions>

Posted: Tue Mar 02, 2010 7:20 pm
by M2tM
pytrin wrote:By the way, I hope we aren't stealing this thread from the OP, maybe this discussion should continue elsewhere...
Possibly, but I'd love to get some more feedback on my project and I'm just glad people are posting in this thread, so don't just go. :)

*EDIT: For anyone skimming this and skipping the last page I just wanted to point out two fairly long posts I submitted in response to questions.

Re: Muted Template <Review/Critique/Suggestions>

Posted: Tue Mar 02, 2010 7:21 pm
by josh
pytrin wrote: But when it's a one/two-liner, I really don't see the benefit.
I don't think anyone implied there is

Re: Muted Template <Review/Critique/Suggestions>

Posted: Tue Mar 02, 2010 7:28 pm
by Eran
That's my point then - you can't take out control statements completely from a template, as some indicated they attempt to. That would be shooting yourself in the foot, or making yourself do cartwheels in order to render any kind of dynamic markup.

Re: Muted Template <Review/Critique/Suggestions>

Posted: Tue Mar 02, 2010 7:35 pm
by josh
I wouldn't disagree. You can remove most though. Example, you don't write a foreach loop when you need to output a select box, you use a view helper.

"My" technique is really no different then a view helper, in that they both remove presentation logic from the template to "somewhere else". My technique just makes it so that the "somewhere else" is different.

View helpers get messy when you want the same "function name" to do different things in different templates. You start depending on the brittle "prefix paths" instead of a solid view object.

View Helpers are used by me when I *do* want the same behavior in many templates (only when I have a tangible need to make it cross cutting that is).

Re: Muted Template <Review/Critique/Suggestions>

Posted: Tue Mar 02, 2010 9:51 pm
by M2tM
Sorry for continuing to ramble (I've just been thinking of examples as one poser requested I post some), I just realized I didn't really talk about one other thing I wanted to mention. I've made a "call" tag which can call pre-registered php functions. This means that someone who doesn't want to muck about with making a built-in language tag can actually just write a few functions and call them with that tag! This offers premium flexibility with a very low learning curve.

Example:

Code: Select all

 
function unorderedList($valueArray, $parameterCount, &$mutedTemplate){
    $returnString = '<ul>'."\n";
    foreach($valueArray as $value){
        $returnString.='<li>'.$value.'</li>'."\n";
    }
    return $returnString.'</ul>'."\n";
}
 
class listOutputObject{
    function listOutputObject($containerTag, $elementTag){
        $this->containerTag = $containerTag;
        $this->elementTag = $elementTag;
    }
    function getListOutput($valueArray, $parameterCount, &$mutedTemplate){
        $returnString = '<'.$this->containerTag.'>'."\n";
        foreach($valueArray as $value){
            $returnString.='<'.$this->elementTag.'>'.$value.'</'.$this->elementTag.'>'."\n";
        }
        return $returnString.'</'.$this->containerTag.'>'."\n"; 
    }
    var $containerTag;
    var $elementTag;
}
 
$mutedTemplate = new MutedTemplate();
 
$mutedTemplate->setTagOption('call', 'UL', 'unorderedList');
 
$orderedListObject = new listOutputObject('ol', 'li');
$mutedTemplate->setTagOption('call', 'OL', array('getListOutput', $orderedListObject));
 
$mutedTemplate->parse('
{call|UL|(apple)(pear)(bananna)(orange)(apricot)}
<br/>
{call|OL|(apple)(pear)(bananna)(orange)(apricot)}
');
 
The results would be:

Code: Select all

 
<ul>
<li>apple</li>
<li>pear</li>
<li>bananna</li>
<li>orange</li>
<li>apricot</li>
</ul>
 
<br/>
<ol>
<li>apple</li>
<li>pear</li>
<li>bananna</li>
<li>orange</li>
<li>apricot</li>
</ol>
 
In cases where a call tag may only expect one argument you can safely omit the parentheses:
{call|function|argument}
or keep them:
{call|function|(argument)}

And, of course, it supports nested tags (everything does unless the tag is specifically programmed to turn off that default behavior):

{call|function|({math|5 + 5})({$variable})}

Let's say we want to directly access variables in the template instance, we could do that with the following:

Code: Select all

 
function callback1($valueArray, $parameterCount, &$mutedTemplate){
    foreach($valueArray as $value){
        if($value[0] == '$'){$value = substr($value, 1);} //cut the $
        $returnValue.=$mutedTemplate->get($value).'<br/>'
    }
    return $returnValue;
}
function callback2($valueArray, $parameterCount, &$mutedTemplate){
    foreach($valueArray as $value){
        $returnValue.=$value.'<br/>'
    }
    return $returnValue;
}
 
Given the above definitions and assuming the callbacks have been registered with the template engine we can expect the following to produce the same output:

Code: Select all

{set|($a)(Hello), ($b)(World)}
{call|callback1|($a)($b)}
{call|callback2|({$a})({$b})}
{call|callback2|($a)($b)}
Should output:
Hello
World
Hello
World
$a
$b

This has been a quick tour of the call tag, hopefully it begins to illustrate a little bit of what this language can do and how it works.

Re: Muted Template <Review/Critique/Suggestions>

Posted: Wed Mar 03, 2010 2:22 am
by Christopher
M2tM wrote:I've extracted two primary questions for me and I'll do my best to respond to them.
"Why use a template language at all?"
"Why this over Smarty?"
Unfortunately, those are probably two questions that only you are asking. :( You have an alternate syntax, so really the big question is whether your syntax in interesting to programmers.

Re: Muted Template <Review/Critique/Suggestions>

Posted: Wed Mar 03, 2010 2:27 am
by Christopher
josh wrote:
pytrin wrote:I'll use a small example for demonstration.
:-/

This is an example of too much logic in the template for my tastes.
My guess is that that template has exactly as much logic as necessary in it. Certainly we strive for "tasteful" code, but often code needs to be what it is. The code in that code is perfectly clear, so if all it does is hurt your feelings then it's not so bad ... ;)

Re: Muted Template <Review/Critique/Suggestions>

Posted: Wed Mar 03, 2010 3:54 am
by josh
arborint wrote:My guess is that that template has exactly as much logic as necessary in it. Certainly we strive for "tasteful" code, but often code needs to be what it is. The code in that code is perfectly clear, so if all it does is hurt your feelings then it's not so bad ... ;)
What about this one then?

Code: Select all

 
<?php
/**
 * Magento
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the Academic Free License (AFL 3.0)
 * that is bundled with this package in the file LICENSE_AFL.txt.
 * It is also available through the world-wide-web at this URL:
 * http://opensource.org/licenses/afl-3.0.php
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@magentocommerce.com so we can send you a copy immediately.
 *
 * DISCLAIMER
 *
 * Do not edit or add to this file if you wish to upgrade Magento to newer
 * versions in the future. If you wish to customize Magento for your
 * needs please refer to http://www.magentocommerce.com for more information.
 *
 * @category   design_default
 * @package    Mage
 * @copyright  Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com)
 * @license    http://opensource.org/licenses/afl-3.0.php  Academic Free License (AFL 3.0)
 */
?>
 
<?php
/**
 * @see Mage_Catalog_Block_Product_View
 */
$_product = $this->getProduct();
$_tierPrices = $this->getTierPrices();
$_finalPriceInclTax = $this->helper('tax')->getPrice($_product, $_product->getFinalPrice(), true);
 
$_weeeTaxAmount = Mage::helper('weee')->getAmountForDisplay($_product);
if (Mage::helper('weee')->typeOfDisplay($_product, array(1,2,4))) {
    $_weeeTaxAttributes = Mage::helper('weee')->getProductWeeeAttributesForDisplay($_product);
}
 
?>
<?php if (count($_tierPrices) > 0): ?>
    <ul class="<?php echo ($this->getInGrouped() ? 'product-pricing-grouped' : 'product-pricing'); ?>">
    <?php if ($this->getInGrouped()): ?>
        <?php $_tierPrices = $this->getTierPrices($_product); ?>
    <?php endif; ?>
    <?php Mage::helper('weee')->processTierPrices($_product, $_tierPrices); ?>
 
    <?php foreach ($_tierPrices as $_price): ?>
        <?php if ($this->helper('tax')->displayBothPrices() && $_price['formated_price'] != $_price['formated_price_incl_tax']): ?>
            <?php if (Mage::helper('weee')->typeOfDisplay($_product, 0)): ?>
                <li><?php echo $this->__('Buy %1$s for %2$s (%3$s incl. tax) each', $_price['price_qty'], $_price['formated_price_incl_weee_only'], $_price['formated_price_incl_weee']) ?>
            <?php elseif(Mage::helper('weee')->typeOfDisplay($_product, 1)): ?>
                <li><?php echo $this->__('Buy %1$s for %2$s', $_price['price_qty'], $_price['formated_price_incl_weee_only']); ?>
                    <?php if ($_weeeTaxAttributes): ?>
                    (<small>
                    <?php echo $this->__('%1$s incl tax.', $_price['formated_price_incl_weee']); ?>
                    <?php $separator = ' + '; foreach ($_weeeTaxAttributes as $_attribute): ?>
                        <?php echo $separator; ?>
                        <?php echo $_attribute->getName(); ?>: <?php echo Mage::helper('core')->currency($_attribute->getAmount()); ?>
                    <?php endforeach; ?>
                    </small>)
                    <?php endif; ?>
                    <?php echo $this->__('each') ?>
            <?php elseif(Mage::helper('weee')->typeOfDisplay($_product, 4)): ?>
                <li><?php echo $this->__('Buy %1$s for %2$s', $_price['price_qty'], $_price['formated_price_incl_weee_only']); ?>
                    <?php if ($_weeeTaxAttributes): ?>
                    (<small>
                    <?php echo $this->__('%1$s incl tax.', $_price['formated_price_incl_weee']); ?>
                    <?php $separator = ' + '; foreach ($_weeeTaxAttributes as $_attribute): ?>
                        <?php echo $separator; ?>
                        <?php echo $_attribute->getName(); ?>: <?php echo Mage::helper('core')->currency($_attribute->getAmount()+$_attribute->getTaxAmount()); ?>
                    <?php endforeach; ?>
                    </small>)
                    <?php endif; ?>
                    <?php echo $this->__('each') ?>
            <?php elseif(Mage::helper('weee')->typeOfDisplay($_product, 2)): ?>
                <li><?php echo $this->__('Buy %1$s for %2$s', $_price['price_qty'], $_price['formated_price']); ?>
                    <?php if ($_weeeTaxAttributes): ?>
                    (<small>
                    <?php foreach ($_weeeTaxAttributes as $_attribute): ?>
                        <?php echo $_attribute->getName(); ?>: <?php echo Mage::helper('core')->currency($_attribute->getAmount()); ?>
                    <?php endforeach; ?>
                    <?php echo $this->__('Total incl. Tax: %1$s', $_price['formated_price_incl_weee']); ?>
                    </small>)
                    <?php endif; ?>
                    <?php echo $this->__('each') ?>
            <?php else: ?>
                <li><?php echo $this->__('Buy %1$s for %2$s (%3$s incl. tax) each', $_price['price_qty'], $_price['formated_price'], $_price['formated_price_incl_tax']) ?>
            <?php endif; ?>
        <?php else: ?>
            <?php if ($this->helper('tax')->displayPriceIncludingTax()): ?>
                <?php if (Mage::helper('weee')->typeOfDisplay($_product, 0)): ?>
                    <li><?php echo $this->__('Buy %1$s for %2$s each', $_price['price_qty'], $_price['formated_price_incl_weee']) ?>
                <?php elseif(Mage::helper('weee')->typeOfDisplay($_product, 1)): ?>
                    <li><?php echo $this->__('Buy %1$s for %2$s', $_price['price_qty'], $_price['formated_price_incl_weee']); ?>
                        <?php if ($_weeeTaxAttributes): ?>
                        (</small>
                        <?php $separator = ''; foreach ($_weeeTaxAttributes as $_attribute): ?>
                            <?php echo $separator; ?>
                            <?php echo $_attribute->getName(); ?>: <?php echo Mage::helper('core')->currency($_attribute->getAmount()); ?>
                        <?php $separator = ' + '; endforeach; ?>
                        <small>)
                        <?php endif; ?>
                        <?php echo $this->__('each') ?>
                <?php elseif(Mage::helper('weee')->typeOfDisplay($_product, 4)): ?>
                    <li><?php echo $this->__('Buy %1$s for %2$s', $_price['price_qty'], $_price['formated_price_incl_weee']); ?>
                        <?php if ($_weeeTaxAttributes): ?>
                        (</small>
                        <?php $separator = ''; foreach ($_weeeTaxAttributes as $_attribute): ?>
                            <?php echo $separator; ?>
                            <?php echo $_attribute->getName(); ?>: <?php echo Mage::helper('core')->currency($_attribute->getAmount()+$_attribute->getTaxAmount()); ?>
                        <?php $separator = ' + '; endforeach; ?>
                        <small>)
                        <?php endif; ?>
                        <?php echo $this->__('each') ?>
                <?php elseif(Mage::helper('weee')->typeOfDisplay($_product, 2)): ?>
                    <li><?php echo $this->__('Buy %1$s for %2$s', $_price['price_qty'], $_price['formated_price_incl_tax']); ?>
                        <?php if ($_weeeTaxAttributes): ?>
                        (<small>
                        <?php foreach ($_weeeTaxAttributes as $_attribute): ?>
                            <?php echo $_attribute->getName(); ?>: <?php echo Mage::helper('core')->currency($_attribute->getAmount()); ?>
                        <?php endforeach; ?>
                        <?php echo $this->__('Total incl. Tax: %1$s', $_price['formated_price_incl_weee']); ?>
                        </small>)
                        <?php endif; ?>
                        <?php echo $this->__('each') ?>
                <?php else: ?>
                    <li><?php echo $this->__('Buy %1$s for %2$s each', $_price['price_qty'], $_price['formated_price_incl_tax']) ?>
                <?php endif; ?>
            <?php else: ?>
                <?php if (Mage::helper('weee')->typeOfDisplay($_product, 0)): ?>
                    <li><?php echo $this->__('Buy %1$s for %2$s each', $_price['price_qty'], $_price['formated_price_incl_weee_only']) ?>
                <?php elseif(Mage::helper('weee')->typeOfDisplay($_product, 1)): ?>
                    <li><?php echo $this->__('Buy %1$s for %2$s', $_price['price_qty'], $_price['formated_price_incl_weee_only']); ?>
                        <?php if ($_weeeTaxAttributes): ?>
                        (<small>
                        <?php $separator = ''; foreach ($_weeeTaxAttributes as $_attribute): ?>
                            <?php echo $separator; ?>
                            <?php echo $_attribute->getName(); ?>: <?php echo Mage::helper('core')->currency($_attribute->getAmount()); ?>
                        <?php $separator = ' + '; endforeach; ?>
                        </small>)
                        <?php endif; ?>
                        <?php echo $this->__('each') ?>
                <?php elseif(Mage::helper('weee')->typeOfDisplay($_product, 4)): ?>
                    <li><?php echo $this->__('Buy %1$s for %2$s', $_price['price_qty'], $_price['formated_price_incl_weee_only']); ?>
                        <?php if ($_weeeTaxAttributes): ?>
                        (<small>
                        <?php $separator = ''; foreach ($_weeeTaxAttributes as $_attribute): ?>
                            <?php echo $separator; ?>
                            <?php echo $_attribute->getName(); ?>: <?php echo Mage::helper('core')->currency($_attribute->getAmount()+$_attribute->getTaxAmount()); ?>
                        <?php $separator = ' + '; endforeach; ?>
                        </small>)
                        <?php endif; ?>
                        <?php echo $this->__('each') ?>
                <?php elseif(Mage::helper('weee')->typeOfDisplay($_product, 2)): ?>
                    <li><?php echo $this->__('Buy %1$s for %2$s', $_price['price_qty'], $_price['formated_price']); ?>
                        <?php if ($_weeeTaxAttributes): ?>
                        (<small>
                        <?php foreach ($_weeeTaxAttributes as $_attribute): ?>
                            <?php echo $_attribute->getName(); ?>: <?php echo Mage::helper('core')->currency($_attribute->getAmount()); ?>
                        <?php endforeach; ?>
                        <?php echo $this->__('Total incl. Tax: %1$s', $_price['formated_price_incl_weee_only']); ?>
                        </small>)
                        <?php endif; ?>
                        <?php echo $this->__('each') ?>
                <?php else: ?>
                    <li><?php echo $this->__('Buy %1$s for %2$s each', $_price['price_qty'], $_price['formated_price']) ?>
                <?php endif; ?>
            <?php endif; ?>
        <?php endif; ?>
        <?php if (!$this->getInGrouped()): ?>
            <?php if(($_product->getPrice() == $_product->getFinalPrice() && $_product->getPrice() > $_price['price'])
            || ($_product->getPrice() != $_product->getFinalPrice() &&  $_product->getFinalPrice() > $_price['price'])): ?>
                <?php echo $this->__('and') ?>&nbsp;<strong class="benefit"><?php echo $this->__('save')?>&nbsp;<?php echo $_price['savePercent']?>%
            <?php endif ?></strong>
        <?php endif; ?>
        </li>
    <?php endforeach ?>
    </ul>
<?php endif;?>
 
By "as much logic as necessary" you must mean "as much logic as possible" lol. In all fairness I did a pretty poor job on the first example.

You guys should check out #haml, it was an attempt to remove the need for html. Personally I don't like it at all. To each his own. http://haml-lang.com/
Documentation - so called "html abstraction for ruby on rails"
http://haml-lang.com/docs/yardoc/file.H ... RENCE.html

Instead of

Code: Select all

<div id="profile">
  <div class="left column">
    <div id="date"><%= print_date %></div>
    <div id="address"><%= current_user.address %></div>
  </div>
  <div class="right column">
    <div id="email"><%= current_user.email %></div>
    <div id="bio"><%= current_user.bio %></div>
  </div>
</div>
They would write

Code: Select all

#profile
  .left.column
    #date= print_date
    #address= current_user.address
  .right.column
    #email= current_user.email
    #bio= current_user.bio
 

Re: Muted Template <Review/Critique/Suggestions>

Posted: Wed Mar 03, 2010 9:33 am
by M2tM
arborint wrote:
M2tM wrote:I've extracted two primary questions for me and I'll do my best to respond to them.
"Why use a template language at all?"
"Why this over Smarty?"
Unfortunately, those are probably two questions that only you are asking. :( You have an alternate syntax, so really the big question is whether your syntax in interesting to programmers.
Well, considering every post in here is talking about frameworks, the first question has been asked by every person here.

The second question was asked specifically by basically the only other person to comment directly on the project I posted for review in this thread.

Functions and classes are "alternative syntax", why not write everything in machine language? What I have here is a layer of abstraction. It is a similar layer of abstraction to other templating languages and in that regard it is an alternative syntax, but it provides more flexibility and has a different focus than other template systems I am aware of and in that regard it is more useful.

The fact is, I posted this for consideration by peers and I've got more posts talking about unrelated stuff, I count 2 posts directly about the project posted for review (other than my own) and I am responding to the few questions I've found in an attempt to cut through the static. If people don't find it interesting I suppose that's fine, but I was hoping for some honest feedback about the project itself rather than a detailed debate on unrelated frameworks.

Re: Muted Template <Review/Critique/Suggestions>

Posted: Wed Mar 03, 2010 9:40 am
by Eran
Most posts here discuss patterns, not frameworks. We did consider a specific implementation of a pattern by a specific framework, but that was not one of the contention points discussed. Also, no templating system was mentioned in the sense of alternative markup - only different approaches to logic in templates was discussed.

Re: Muted Template <Review/Critique/Suggestions>

Posted: Wed Mar 03, 2010 9:47 am
by M2tM
pytrin wrote:Most posts here discuss patterns, not frameworks. We did consider a specific implementation of a pattern by a specific framework, but that was not one of the contention points discussed. Also, no templating system was mentioned in the sense of alternative markup - only different approaches to logic in templates was discussed.
The discussion seems to have focused around the MVC patterns common to most PHP presentation frameworks. The difference in terminology here is pedantic as I never specified a framework in my generalization. You could technically build such a framework within the Muted Template language (though it would be silly to.)

#haml was briefly mentioned, and the very first poster asked why I would use this over something like Smarty. That poster then asked for more examples to see the scope of the project. I posted these reasons and examples.

___

#haml seems like a very neat project, by the way. It doesn't really look like it has the same goals I have set and it isn't how I would approach building a website, but it is a cool project. I've not abandoned the use of HTML in my templates, I embrace it.
___

In light of the new question/comment about if other programmers find the syntax interesting, I don't know. I wrote a language and hope that it is useful to others, but if it is not I suppose that it will remain a personal project. This is the first time I've posted this publicly in such a way to get feedback to answer this very question.

I am quite proud of the quality I have invested into this project and my reasons for posting it for review were to:
a) Gague my actual ability to program. How would I know if I were doing things completely goofy without any feedback? Typically programming on hobby projects is a very solitary and feedback-void activity and I was honestly hoping for critique and suggestions to help me get better. Or, if I'm already doing well, hearing that is always nice.
b) Get suggestions for the project in terms of direction, syntax, capability and more.
c) See what people had to say about the project. Like/dislike/prefer alternative. This is why I don't really mind the off-topic comments very much, but when it is suggested I'm the only one asking questions that I've answered based on this thread that's a bit silly.

Re: Muted Template <Review/Critique/Suggestions>

Posted: Wed Mar 03, 2010 10:27 am
by Weirdan
M2tM, to steer the topic from meta-discussion and offtopic, how about showing a bit more complex template? You know, smarty also looks nice in examples posted on their website, but after all the real-life monstrosities written in smarty I've seen (and had to maintain) I, for one, do not believe simple examples.

For example, you might take one of the templates josh posted here and rewrite it using Muted syntax. It would give everyone much better understanding of what the language is like.

Re: Muted Template <Review/Critique/Suggestions>

Posted: Wed Mar 03, 2010 10:29 am
by M2tM
Weirdan wrote:M2tM, to steer the topic from meta-discussion and offtopic, how about showing a bit more complex template? You know, smarty also looks nice in examples posted on their website, but after all the real-life monstrosities written in smarty I've seen (and had to maintain) I, for one, do not believe simple examples.

For example, you might take one of the templates josh posted here and rewrite it using Muted syntax. It would give everyone much better understanding of what the language is like.
Excellent, I will do that!

Let's tackle this beast of a template as was posted by Josh earlier:

Code: Select all

 
form id="co-shipping-method-form" action="<?php echo $this->getUrl('checkout/cart/estimateUpdatePost') ?>">
            <dl class="sp-methods">
                <?php foreach ($_shippingRateGroups as $code => $_rates): ?>
                    <dt><?php echo $this->getCarrierName($code) ?></dt>
                    <dd>
                        <ul>
                        <?php foreach ($_rates as $_rate): ?>
                            <li<?php if ($_rate->getErrorMessage()) echo ' class="error-msg"';?>>
                               <?php if ($_rate->getErrorMessage()): ?>
                                    <?php echo $_rate->getErrorMessage() ?>
                               <?php else: ?>
                                    <input name="estimate_method" type="radio" value="<?php echo $this->htmlEscape($_rate->getCode()) ?>" id="s_method_<?php echo $_rate->getCode() ?>"<?php if($_rate->getCode()===$this->getAddressShippingMethod()) echo ' checked="checked"' ?> class="radio" />
                                    <label for="s_method_<?php echo $_rate->getCode() ?>"><?php echo $_rate->getMethodTitle() ?>
                                    <?php $_excl = $this->getShippingPrice($_rate->getPrice(), $this->helper('tax')->displayShippingPriceIncludingTax()); ?>
                                    <?php $_incl = $this->getShippingPrice($_rate->getPrice(), true); ?>
                                    <?php echo $_excl; ?>
                                    <?php if ($this->helper('tax')->displayShippingBothPrices() && $_incl != $_excl): ?>
                                        (<?php echo $this->__('Incl. Tax'); ?> <?php echo $_incl; ?>)
                                    <?php endif; ?>
                                    </label>
                               <?php endif ?>
                            </li>
                        <?php endforeach; ?>
                        </ul>
                    </dd>
                <?php endforeach; ?>
            </dl>
            <div class="buttons-set">
                <button type="submit" title="<?php echo $this->__('Update Total') ?>" class="button" name="do" value="<?php echo $this->__('Update Total') ?>"><span><span><?php echo $this->__('Update Total') ?></span></span></button>
            </div>
        </form>
 
Now, keep in mind this would be something like the originally supplied template. However this is a really ugly example to begin with. I've only modified the organization a bit by extracting the logic into named sections (which are similar to true functions, but share a global scope. If a template gets complicated enough that scoping issues play a part I think it is time to seriously consider doing more processing from PHP before generating the template.) Keep in mind, some of the functionality is a bit fuzzy to me as I don't really know what any of the functions do and can only kind of guess.

This code looks prettier without line-wrapping... But it's kind of like decorating a turd. Sometimes it is actually -ok- to have a complicated template for something you'll probably not need to re-use provided you offshore some of that complexity into other files and keep it out of the flow of the document.

Assumptions/Info: I assume some of the relevant functions are attached to the template language, wherever you see a {call} tag for instance. This is the most basic way of hooking extra functionality from an external system into MutedTemplate. I've attempted to preserve as much of the decision logic as possible simply to offer a contrast to the verbose and jarring PHP template.

outputShippingRates.html

Code: Select all

 
{section|outputSingleShippingRate|
    {set|($rate[EscapedCode])=({quote|{$rate[Code]}})}
    <input name="estimate_method" type="radio" value="{$rate[EscapedCode]}" id="s_method_{$rate[EscapedCode]}" class="radio" {if|$rate[Selected]|checked="checked"}/>
    <label for="s_method_{$rate[EscapedCode]}">
        {$rate[MethodTitle]}
        {set|
            ($ShippingRate) = ({call|getShippingPrice|({$rate[Price]})($rate[IncludeTax])}),
            ($ShippingRateWithTax) = ({call|getShippingPrice|({$rate[Price]})(true)})
        }
        {$ShippingRate}
        {if|$DisplayBothPrices && $ShippingRate != $ShippingRateWithTax|
            Incl. Tax: {$ShippingRateWithTax}
        }
    </label>
}
 
{each|$shippingRateGroups|($code)($rates)|
    <dt>{call|getCarrierName|({$code})}</dt>
    <dd>
        <ul>
        {each|$rates|($rate)|
            <li{if|$rate[ErrorMessage]| class="error-msg"}>
               {if|$rate[ErrorMessage]|
                    {$rate[ErrorMessage]}
               ||
                    {load|outputSingleShippingRate}
               }
            </li>
        }
        </ul>
    </dd>
}
[/code]  
       
       

Code: Select all

 
<form id="co-shipping-method-form" action="{$Paths/SiteUrl}checkout/cart/estimateUpdatePost">
    <dl class="sp-methods">
        {load|Templates/Functions/outputShippingRates.html}
    </dl>
    <div class="buttons-set">
        <button type="submit" title="{$UpdateTotal}" class="button" name="do" value="{$UpdateTotal}"><span><span>{$UpdateTotal}</span></span></button>
    </div>
</form>
 
 
 
Now, let's say we were a bit more pro-active in our choice and developed some tags in PHP for this, our template may simply become:
 

Code: Select all

 
<form id="co-shipping-method-form" action="{$Paths/SiteUrl}checkout/cart/estimateUpdatePost">
    <dl class="sp-methods">
        {ShippingRates|RateListHandle|
            <dt>{$codeName}</dt>
            <dd>
                <ul>
                {RateList|{$code}|
                    {$RateRadioButton}
                    <label for="s_method_{$RateCode}">
                        {$RatePriceOutput}
                    </label>
                }
                </ul>
            </dd>
        }
    </dl>
    <div class="buttons-set">
        <button type="submit" title="{$UpdateTotal}" class="button" name="do" value="{$UpdateTotal}"><span><span>{$UpdateTotal}</span></span></button>
    </div>
</form>
 
 
 
 
 
 
This assumes we've set up a tag called ShippingRates which executes its block over the shipping rate list specified by RateListHandle (and supplied to the templating system via setTagOption before parsing) and sets up some pre-determined variables. RateList is a second custom tag which would take a code and then iterate over that specific rate list setting up more pre-determined variables based on the rate calculations. We separate the template from the values in this fashion and mostly strip the logic.

Now, contrast the final template with the original.

Re: Muted Template <Review/Critique/Suggestions>

Posted: Wed Mar 03, 2010 11:58 am
by M2tM
Here is a second example of a real sign-up and edit form:

promoterSignUp.html

Code: Select all

 
{if|$signupSuccess|
    <h1>Success</h1>
    
    Thank you for signing up for an account!  You can now <a href = "promoterLogin.php">sign in</a> and create listings.<br/><br/>
    
    If you have any problems and require assistance, please feel free to <a href = "contact.php">contact us</a>.
||
    <h1>Promoter Sign Up</h1>
    {isset|$signupMessage|<span style = "color:red;">{$signupMessage}</span>}
    <form method = "POST">
        <ul class = "SignupForm">
            {load|promoterFields.html}
        </ul>
        <input type = "submit" name = "signUpSubmit" value = "Submit" style = "margin-left:10px;"/>
    </form>
}
 
promoterEdit.html

Code: Select all

 
    <h1>Edit Profile</h1>
    {isset|$editMessage|<span style = "color:{if|$isError|red||blue};">{$editMessage}</span>}
    <form method = "POST">
        <ul class = "SignupForm">
            {load|promoterFields.html}
        </ul>
        <input type = "submit" name = "editSubmit" value = "Submit" style = "margin-left:10px;"/>
    </form>
 
promoterFields.html

Code: Select all

 
            <li><h3>Login Information</h3></li>
            <li>
                <label for = "Email">Email:</label>
                <input type = "text" name = "Email" value = "{isset|$UserFields[Email]|{$UserFields[Email]}}"/>
            </li>
            <li>
                <label for = "Password">Password:</label>
                <input type = "password" name = "Password"/>
            </li>
            <li>
                <label for = "PasswordRepeat">Repeat Password:</label>
                <input type = "password" name = "PasswordRepeat"/>
            </li>
            <li><h3>Contact Information</h3></li>
            <li>
                <label for = "Company">Company:</label>
                <input type = "text" name = "Company" value = "{isset|$UserFields[ExtraDetails][Company]|{$UserFields[ExtraDetails][Company]}}"/>
            </li>
            <li>
                <label for = "ContactName">Contact Name:</label>
                <input type = "text" name = "ContactName" value = "{isset|$UserFields[ExtraDetails][ContactName]|{$UserFields[ExtraDetails][ContactName]}}"/>
            </li>
            <li>
                <label for = "Phone">Phone:</label>
                <input type = "text" name = "Phone" value = "{isset|$UserFields[ExtraDetails][Phone]|{$UserFields[ExtraDetails][Phone]}}"/>
            </li>
 

Re: Muted Template <Review/Critique/Suggestions>

Posted: Wed Mar 03, 2010 3:50 pm
by M2tM
Final post in a row. This is meant to illustrate the tag creation process so that you can (hopefully) see it is not too terribly complicated to parse a new tag.

In this example I settled on how I wanted to handle escaping basic html attribute values such as <tagname attribute = "attribute value"/>. The problem is that we cannot allow new lines or quotes to appear in these areas, and the solution must be clear, but short as it should be easy to type and yet flexible enough to meet the problem.

I settled on the tag-name: "quote" because it describes exactly where the tag should be used. Its function should be to replace " with " and ' with ' which allows javascript to execute correctly still and correctly escapes those characters.

Newline handling posed to be the most difficult problem. There may be cases where you want to replace them with \n, or cases where you want to replace them with <br/> or even cases where you simply want to ignore them entirely.

I decided that this tag should take one optional parameter which is the string to replace newlines with. If this parameter is not present the default behavior will be to simply ignore newlines.

So I wrote up two very simple unit tests which cover the features I'm aiming for:

Code: Select all

 
function TEST_QuoteTest1(){
    $engine = new MutedTemplate(false);
    $result = $engine->parse('1{quote|\'Hello'."\n".' World"}2');
    $expected = '1'Hello World"2';
    return $this->getResult($result, $expected);
}
 
function TEST_QuoteTest2(){
    $engine = new MutedTemplate(false);
    $result = $engine->parse('1{quote|<br/>|\'Hello'."\n".' World"}2');
    $expected = '1'Hello<br/> World"2';
    return $this->getResult($result, $expected);
}
 
Another practical situation to use this may be in assigning a string to a variable in javascript.

Code: Select all

 
var value = "{quote|\n|A duck
And a truck
Are out of luck}";
 
So I have a plan, I have some good use-cases and unit tests which I want to pass. I'll do a rough description of how the control flow of the template language works:

For every character from a source we parse to see if it follows the {tagname| syntax, as soon as something like that is encountered it detects if the tag has been registered to a class, and if it has been then the template engine creates an object of that type. Now, every time it loops over a character the template function calls the "parentCallback" function which the tag class inherited from the default tag director class called "MutedTag". The parentCallback function has a few purposes, it basically handles escape character sequences, new tags being opened, and then in the case of an unhandled character (not escaped, not a new tag) it passes that along to the childCallback function (the specific function that it calls can be changed by the child class.) When the tag has determined it is done parsing it wraps up and signals that it is complete and the template engine destroys the object and returns to its basic parsing routine.

Output is put into a multi-level buffer via the $this->output function which allows for arbitrarily deep tag nesting (output from one tag is passed down to the next buffer). The top level of the buffer is what is eventually displayed.

On to the actual tag. I've added a number of comments detailing the purpose of each line.

Code: Select all

 
<?PHP
if(!defined('_M2tM_QUOTETAG_PHP_')){
    define('_M2tM_QUOTETAG_PHP_', true);
    //Use Examples:
    //<a onclick = "{quote|alert("Hello World");}">Execute</a>
    //or
    //<a onclick = "{quote|\n|alert("Hello 
    //World");}">Execute</a>
    //The first parameter in the quote tag is what to replace new lines with.
    //The first parameter is optional.
    class MutedTag_Quote extends MutedTag{
        function MutedTag_Quote(&$mutedTemplate, $tagName, $returnCallback = NULL){
            parent::MutedTag($mutedTemplate, $tagName, $returnCallback);
            $this->startEscape(array('{', '\\', '|', '}')); //Here we set up the escape-able characters.
            $this->startBuffer(); //Initiate a buffer space so we can store output for processing
            $this->newlineReplace = '';
            $this->needEndEscape = true;
        }
 
        //This is the tag parse entry point, and characters are passed through this callback by default
        function childCallback(&$stream){
            if($stream->getCurrent() === '|'){ //Detect the optional parameter
                $this->newlineReplace = $this->endBuffer(); //get the newline replace string
                $this->endEscape(); //We no longer need to worry about '|' so we can go to default escape chars
                $this->startBuffer(); //set up a new buffer for our output.
                $this->needEndEscape = false;
            }else if($stream->getCurrent() === '}'){ //this case means there was no newline replacement
                //here we take the contents of the buffer and replace the desired characters.
                $this->output(str_replace(array('"',"'","\n"), array('"',''',$this->newlineReplace), $this->endBuffer()));
                if($this->needEndEscape){ //if we had an optional parameter we don't want to end the escape
                    $this->endEscape();
                }
                parent::closeTag(); //clean up and return control
            }else{
                $this->output($stream->getCurrent()); //default case, dump contents into buffer.
            }
        }
 
        var $newlineReplace;
        var $needEndEscape;
    }
}
?>