-
-
Save getify/3332023 to your computer and use it in GitHub Desktop.
<!-- 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 I don't worry too much about what other people may do to abuse a facility. In my experience, those intent on creating monstrosities will find their way clear, reliably and indefatigably, to do so despite the ingenuity of obstructions set up in their way.
My totally irrelevant $0.02:
It's impossible to complete markup and logic separate. Can't be done. It's just about where you draw the line. I draw the line somewhere around dictionaries of primitives and object field accesses, but no object methods. And that's that. Nothing that looks like ->() or .foo().
I draw the line at basic conditionals. Once you can string together three separate Boolean expressions you've gone too far. "if expr" and done. That expr should be determined by the thing rendering the template and passed in as a template var. And that's it.
So, in my world of markup/logic separation, you're doomed: yes, you're going to repeat that stupid check all over the place and it kind of sucks.
Let's talk different lines: where is the acceptable abstraction line? That check is going to execute code somewhere. It has to. What kind of code are you willing to accept? If it's not in the template itself, what about a helper function? If it's not a helper function that you write, is it a library function that's part of the template engine? What about a tag library? What about code in the object you're writing a template for? Should it know how to spit out the right attributes? But then your (potential) model knows how to write out its own views. That's not good. So this code has to exist above the model, probably in the controller, the view, or the "middle-end". ( ;
If you move the line to just above the markup you get something like this:
<% externalLink "url" %>
<% internalLink modelObject %>
...or, maybe just:
<% link modelObject %>
...and the "link" helper determines if its external or not. Nice and semantic, no repetitive-ness. But this brings us back to helper functions.
What about changing template languages, to something like HAML? What if you used something that compiled down to HTML but let you write less verbose tags? I'd argue that a well-populated custom tag library gets you close enough to this DSL-topia that you don't need to do that... but I think it's really about where you draw the line.
And, okay, yeah, that totally turned in to $0.14. ( =
Just as you mentioned "inline logic and function call are just two sides of the same coin". Then the real question is how hard it is to test your logic? Because at the end of day that's all that matters. I feel like the helper functions approach would be better in terms of testing.
One way to go about this problem is to have a different sub-template (aka, "partial") for each variation (aka, one with the "checked", one without it), then do a binary selection logic to pick the appropriate sub-template in each instance. This certainly reduces the logic that is embedded inline throughout the template markup. But it might create additional maintenance overhead in repeating the common markup parts twice.
Another way to handle things is to pre-compute (either in a helper function or in the controller) all logic down to boolean states, so that whereever you need to make a "decision", you're not actually making a decision but just consulting a pre-computed decision value. That also reduces the decision logic embedded in the template.
It still feels like there must be some other way to combine or vary such techniques.
the problem with the examples in this gist is that they are putting markup (in this case, just keywords really, but markup nonetheless) into strings and code. that feels like a violation of the spirit of a templating engine. you generally want all markup as, well, markup.
You almost want something 'automagical' where it can be inferred from it's context, something like this:
<select name="foobar">
<option value="bam" {{ =selected() }}Bam</option>
<option value="baz" {{ =selected() }}Baz</option>
</select>
<input type="radio" name="foobar" value="bam" {{ =checked() }} Bam
<input type="radio" name="foobar" value="baz" {{ =checked() }} Baz
@Pointy -- I think there's a big difference between giving someone a rope with which to hang themselves, and giving them the individual strands which they must choose first to weave into a rope before hanging themselves. Point being: you should make it so they have to try hard and intentionally to hang themselves, not make it a convenience path of least resistance.
Are you mainly concerned about populating forms with an object? If so, you can do the automagic stuff -- associate types (or metadata) with supported form tags.
Pseudocode for something like that would be
populateFormWithObject(myPerson,
<form>
<input type="text" name="firstName"/> <!-- populates automatically if myPerson.firstName is a string-like thing -->
<input type="checkbox" name="spamOptIn"/> <!-- myPerson.spamOptIn is boolean -->
<select name="accountType">
<option value="rich">Gold</option>
<option value="cheap">Silver</option>
<option value="poor">Free</option>
</select> <!-- accountType is an enum with rich,cheap,poor valid values ... the chosen one for myPerson gets selected="true" -->
</form>
)
@bhudgeons as "ex3.php" above shows, this problem exists in other contexts besides forms... they're just a convenient illustration.
I wouldn't necessarily say that the ultimate goal is auto-magical context binding. I dislike that kind of stuff. I think the idea I'm going for is as minimal a declarative (non-magical) form as possible. I'd like there to be very little for a markup'ist to be dealing with besides markup, and any logic they DO deal with should be clean and simple and purely presentational in nature.
That's one reason why handlegrips moves any logic from being embedded in the markup to the template section declaration header. It keeps the logic nearby, and accessible to the markup'ist, but it keeps as much of it as possible out of the inline markup. It's all a subjective balance game, yes. But the goal should be clear and indisputable.
@getify Sorry ... I mistakenly thought @andydavies' quote was yours. I think you're looking at the problem the right way -- you're going to have to manage the tradeoff between simplicity and verbosity (ex4) with power and conciseness (ex5). More power reduces both keystrokes and readability.
I've never thought about templating patterns from this context. I'll pay attention now and see if I can find other approaches that make sense.
I agree with @Pointy that templates do have logic. I do wish there was a better method for this particular situation though.
@polotek @Pointy i'm not trying to come up with logic-free templates. i think that's impossible/unuseful. I AM trying, with handlegrips specifically, to come up with the absolute minimal set of logic necessary (the "turing complete template DSL" if you will). For instance handlegrips has a looping construct, because without it a huge set of use-cases are impossible or awkward. Looping is a necessary "evil".
But handlegrips doesn't, at the moment, let you do inline decision making at all. By inline, I mean embedded directly inside the markup. Instead, you define (aka "pre-compute") your logic decisions (only binary decision logic, and variable aliases, and nothing else) in the header for each template section (aka "partial"). That's a compromise between having template logic buried in a separate document in a "helper function" (which I think is much harder to maintain) and just willy-nilly embedding the logic right inside your markup.
I don't see logic in templates as "evil". I think that's the point some are making. I totally get both sides of the argument. But I think this is a good example of the contortions you have to go through when trying to reach some ideal-sounding minimal syntax. I don't really see the upside.
I do see the downside though. 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. You then try to combat that by saying well the optional partial also constitutes logic so we should eliminate that somehow. It becomes a rabbit hole of chasing purity for little gain IMO. A quick if statement gets you where you need to go with minimal fuss.
That said, I'm not a fan of full blown programming in templates. In fact I think it's a terrible idea for the same initial reasons that lead some people to the conclusion that logic-less templates are the only way. But I think there's a nice middle ground that we should reach for. And so I'd love to see a nice syntax that supports the use case you're describing here. It would be a nice addition to several templating solutions all long the spectrum.
I know none of this is helpful. I just felt the need to expand on my actual position a bit. For what it's worth, the examples you gave for handlegrips syntax don't make any sense to me. I may too stuck in my ways though.
As it happens, I'm currently in the middle of converting a site from a very rich, server-side template language (JSP) to a data-only, client-side template system (using doT.js). And, @getify, to some extent I'm ending up doing a lot of what you're talking about. In fact I'm still going to use JSP, but only to construct the JSON. The process of doing that conversion is resulting in my discovery of many situations wherein the easiest, cleanest thing to do is distill some server-side state-based decisions into simple flags in the JSON. That leaves the template code with generally simple logic, and most of the lower-level stuff like data formatting (dates and currency for example) done in the JSP/JSON code. The resulting split makes everything much easier to read - the ugly JSP stuff is not jumbled up with real markup, and the power available there is nicely limited to the data preparation job. The template code is much, much cleaner.
Now that said, your point about the inherent mess of dealing with HTML radio buttons and select/option lists is an ugly problem with no clear solution. In a server-side solution, what I'm used to is the ability to leave that binding up to the server-side framework. In my case, that has meant that (for example) I can let the framework generate the options via a wrapper around the select element or the option list, and the framework implicitly knows how to bind values. Thus, the ugly repetitive logic to check each option element to see whether it's the one that should get the "selected" attribute is buried out of sight.
With a client-side template, that's not so easy. Of course, I could use the server-side system to drop the option list in my JSON, but that would be terribly wasteful; I'd much rather have the option list tucked safely away in the client-resident template code instead of shipping it over the wire redundantly. I could create wrappers around the HTML tags with the template language (which given the way doT works would maybe not be too ugly), but that's not very palatable either. I could do some weird trick with a "data-" attribute or something and defer the "checked"/"selected" setting to other client side code, but that also seems bad.
It seems to me that the problem here isn't so much a deficiency of any particular template solution, but rather a design problem with HTML itself. I'm not even an amateur HTML5 armchair syntax designer, but I am a programmer, and there just seems to be something "off" with the way that those kinds of form elements must be expressed.
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:
- embedded right in with the markup
- in the template section header declaration (like handlegrips)
- in a template "helper" function in a separate file
- 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.
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.
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
@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."
@Pointy I don't see how data-binding solves the question at issue here? Care to elaborate on what I'm missing?