Skip to content

Instantly share code, notes, and snippets.

@getify
Created August 12, 2012 14:15
Show Gist options
  • Save getify/3332023 to your computer and use it in GitHub Desktop.
Save getify/3332023 to your computer and use it in GitHub Desktop.
exploring inline decision-logic in templates
<!-- here's the PHP'ish way of making inline decisions while constructing HTML markup -->
<select name="foobar">
<option value="bam" <?=($foobar==="bam"?"selected":"")?>>Bam</option>
<option value="baz" <?=($foobar==="baz"?"selected":"")?>>Baz</option>
</select>
<input type="radio" name="foobar" value="bam" <?=($foobar==="bam"?"checked":"")?>> Bam
<input type="radio" name="foobar" value="baz" <?=($foobar==="baz"?"checked":"")?>> Baz
<!-- here's another PHP'ish approach using parameterized function calling -->
<?php
function selected($val,$test){ return ($val===$test?"selected":""); }
function checked($val,$test){ return ($val===$test?"checked":""); }
?>
<select name="foobar">
<option value="bam" <?=selected($foobar,"bam")?>>Bam</option>
<option value="baz" <?=selected($foobar,"baz")?>>Baz</option>
</select>
<input type="radio" name="foobar" value="bam" <?=checked($foobar,"bam")?>> Bam
<input type="radio" name="foobar" value="baz" <?=checked($foobar,"baz")?>> Baz
<!-- this illustrates the same problem but not in relation to forms. specifically,
the problem revolves around how to cleanly make conditional inclusion of small
snippets of markup inside other bigger markup templates. -->
<a href="<?=$bam?>" <?=isExternalLink($bam)?"target=_blank":""?>>Bam</a>
<a href="<?=$baz?>" <?=isExternalLink($baz)?"target=_blank":""?>>Baz</a>
<!--
one approach I'm considering for handlegrips:
http://github.com/getify/handlegrips
let you pre-compute a comparison hash for a data value (`data.foobar`) against a set
of values (`"bar","baz"`), and only make the assignment for the matching set value in
the comparison hash.
-->
{$: "#foobar" | data.foobar{"bar","baz"} = "checked" }
<input name="foobar" value="bar" {$=data.foobar{"bar"}$}>
<input name="foobar" value="baz" {$=data.foobar{"baz"}$}>
{$}
<!--
an alternative approach I'm considering for handlegrips:
http://github.com/getify/handlegrips
kind of like an array comprehension, where you can specify a set-literal as the
looping context of values, so your computation happens once for each iteration.
-->
{$: "#foobar" }
<!-- {$* is the looping construct. this example allows a set literal for the
looping context. you then evaluate the loop expression for each iteration,
setting a helper local variable `checked` to use in the markup.
-->
{$* ["bar","baz"] | checked = (data.foobar==.) ? "checked" }
<input name="foobar" value="{$=.$}" {$=checked$}>
{$}
{$}
@getify
Copy link
Author

getify commented Aug 16, 2012

@polotek

When you have to pre-compute trivial if statements like this, you end up doing a lot more pre-massaging of data just so you can pass them into templates. Also you end up doing more than strictly necessary because you pre-compute values that you don't actually need because they're part of some partial that doesn't even get rendered in this view.

Actually, I think that's reason I prefer the ability to specify your boolean selection logic in the declaration header of each template section. As such, you're not making those pre-computing decisions "too early" (tucked away in some controller that doesn't know ultimately how stuff is going to be visually presented) -- you're only doing the pre-computing at the point of the partial that needs to use it, and only computing what flags you need for THAT exact template partial. Shouldn't be any waste of effort there.

If you take as a given that these boolean decisions have to be made somewhere, seems like there's basically 4 choices as to where they are specified/computed:

  1. embedded right in with the markup
  2. in the template section header declaration (like handlegrips)
  3. in a template "helper" function in a separate file
  4. in the controller before the data is sent over to the View templates

I think #1 and #4 are the worst choices, and so between #2 and #3, I choose the simplicity of not having a separate file, and also not doing actual programming but really just boolean selection logic (a very small subset of programming).

Of course, the line that is important to distinguish here is that no matter where (#1 - #3) your template logic happens, that logic should only be purely presentational logic. You shouldn't be doing things there like Date formatting (that's often driven by business logic like the user's chosen TZ preference, etc), math calculations (summing up columns, etc), or other tasks. Those usually masquerade as presentational tasks, but I argue they are clearly tasks for a UI controller.

The types of logic that I see as OK to be associated with a template are basically things that be expressed as pure boolean selection (even composed boolean decisions). But calls to your M-model methods, math, formatting, etc... all that stuff needs to be handled in a controller step before the template engine gets ahold of the data.

I should also say that a "UI controller" is not necessarily the same thing as a back-end controller that handles data transfer between the M-model and the persistence layer, etc. Those are both controllers, but they are clearly different and each is specialized to his own tasks. In other words, there is no one C-controller, but many C-controllers.

@getify
Copy link
Author

getify commented Aug 16, 2012

@Pointy

Sounds like you are preferring the #4 approach from the comment I just made to @polotek, which is do all or most of the pre-computing in your controller layer, even for things that are purely presentational (like, should the "checked" flag appear, etc). I understand that approach, and I've done it before many times too.

But I think it's horribly brittle, and violates the spirit of what we're trying to achieve -- separation of concerns. You should not have the same snippet of code that's doing both: 1) consulting the M-model to figure out if you're User object is logged in; and 2) figuring out if a list of data is empty and so should display an alternate "no results found" type of message/markup. I see those as fundamentally different tasks, and they should be in distinctly different parts of the stack.

@rhysbrettbowen
Copy link

Don't you just have a function that sets the selected on the DOM given a model? you'd just bind a change in the model to that function so when a change happens it'll set the selected. Wll all you have to do is setup your template without any selected and fire off that function manually on the DOM structure before displaying it on the page.

If you've got a conditional that depends on data then you can make that data a model and update the DOM accordingly with a handler. No template logic is ever needed - it's just a quicker way to define what could go in code. If anything I'd argue that it's worse to put it in the template because you'll be tempted to just re-render the entire template rather than a small change, though it will make things faster on initial render

@getify
Copy link
Author

getify commented Aug 18, 2012

@rhysbrettbowen-

I think what you're pointing out is valid but orthagonal to the question at hand. The context of this question is how to handle a specific task in the template-string rendering approach.

Sure, dynamic live updating of simple properties probably should NOT occur with a whole re-rendering of a template partial for the surrounding element... that would be overkill.

But templating stands on its own as a valid approach for initial view rendering (especially on the server). I know some people would prefer that all UI is built via DOM manipulation, but that's also very distasteful to many others for lots of reasons. Look at the most recent updates from Twitter, where they've gone back to a server-rendered view (using template-strings, undoubtedly) approach, because they found that to be more performant for what they care about.

Essentially, there are two different paradigms: string-based template rendering, and DOM-manipulation based rendering. They are both equally valid, and depending on needs you would do either or both in a project.

But I don't think it's valid to blanket claim (without any supporting detail) "It should just be done in a totally different paradigm."

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment