Muted Template <Review/Critique/Suggestions>

Coding Critique is the place to post source code for peer review by other members of DevNetwork. Any kind of code can be posted. Code posted does not have to be limited to PHP. All members are invited to contribute constructive criticism with the goal of improving the code. Posted code should include some background information about it and what areas you specifically would like help with.

Popular code excerpts may be moved to "Code Snippets" by the moderators.

Moderator: General Moderators

M2tM
Forum Commoner
Posts: 41
Joined: Sat Feb 27, 2010 12:35 pm

Re: Muted Template <Review/Critique/Suggestions>

Post 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.
Last edited by M2tM on Tue Mar 02, 2010 9:19 pm, edited 2 times in total.
josh
DevNet Master
Posts: 4872
Joined: Wed Feb 11, 2004 3:23 pm
Location: Palm beach, Florida

Re: Muted Template <Review/Critique/Suggestions>

Post 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
User avatar
Eran
DevNet Master
Posts: 3549
Joined: Fri Jan 18, 2008 12:36 am
Location: Israel, ME

Re: Muted Template <Review/Critique/Suggestions>

Post 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.
josh
DevNet Master
Posts: 4872
Joined: Wed Feb 11, 2004 3:23 pm
Location: Palm beach, Florida

Re: Muted Template <Review/Critique/Suggestions>

Post 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).
M2tM
Forum Commoner
Posts: 41
Joined: Sat Feb 27, 2010 12:35 pm

Re: Muted Template <Review/Critique/Suggestions>

Post 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.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: Muted Template <Review/Critique/Suggestions>

Post 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.
(#10850)
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: Muted Template <Review/Critique/Suggestions>

Post 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 ... ;)
(#10850)
josh
DevNet Master
Posts: 4872
Joined: Wed Feb 11, 2004 3:23 pm
Location: Palm beach, Florida

Re: Muted Template <Review/Critique/Suggestions>

Post 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
 
M2tM
Forum Commoner
Posts: 41
Joined: Sat Feb 27, 2010 12:35 pm

Re: Muted Template <Review/Critique/Suggestions>

Post 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.
Last edited by M2tM on Wed Mar 03, 2010 10:09 am, edited 3 times in total.
User avatar
Eran
DevNet Master
Posts: 3549
Joined: Fri Jan 18, 2008 12:36 am
Location: Israel, ME

Re: Muted Template <Review/Critique/Suggestions>

Post 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.
M2tM
Forum Commoner
Posts: 41
Joined: Sat Feb 27, 2010 12:35 pm

Re: Muted Template <Review/Critique/Suggestions>

Post 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.
User avatar
Weirdan
Moderator
Posts: 5978
Joined: Mon Nov 03, 2003 6:13 pm
Location: Odessa, Ukraine

Re: Muted Template <Review/Critique/Suggestions>

Post 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.
M2tM
Forum Commoner
Posts: 41
Joined: Sat Feb 27, 2010 12:35 pm

Re: Muted Template <Review/Critique/Suggestions>

Post 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.
Last edited by M2tM on Thu Mar 04, 2010 12:35 am, edited 1 time in total.
M2tM
Forum Commoner
Posts: 41
Joined: Sat Feb 27, 2010 12:35 pm

Re: Muted Template <Review/Critique/Suggestions>

Post 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>
 
M2tM
Forum Commoner
Posts: 41
Joined: Sat Feb 27, 2010 12:35 pm

Re: Muted Template <Review/Critique/Suggestions>

Post 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;
    }
}
?> 
Last edited by M2tM on Sat Mar 06, 2010 3:46 pm, edited 1 time in total.
Post Reply