Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Could we please have a *standard* if conditional? #616

Closed
machineghost opened this issue Sep 24, 2013 · 25 comments
Closed

Could we please have a *standard* if conditional? #616

machineghost opened this issue Sep 24, 2013 · 25 comments

Comments

@machineghost
Copy link

If you look at this Stack Overflow post:

http://stackoverflow.com/questions/8853396/logical-operator-in-a-handlebars-js-if-conditional

You will see that, based on the answer votes, there were over a hundred people who went looking for a proper value-comparing if conditional (ie. an "if x == y"). And those are just the people who bothered to vote after finding the answer ... and just the people on that specific Stack Overflow question (there are several others).

The need for such a helper is obvious when one considers even simple cases, such as trying to render an HTML tag. One has to resort to: <option value="1" {{if option1IsSelected}}selected{{/if}}> <option value="2" {{if option2IsSelected}}selected{{/if}}> ... etc. which can get ugly fast on the value-providing-side of the code if you have a decent number of options. Now of course, one could just build all the options in Javascript, but that rather defeats the point of using a templating system, doesn't it? It would seem far better to be able to do: <option value="1" {{if value == 1}}selected{{/if}}> <option value="2" {{if value == 2}}selected{{/if}}> Now I understand (from reading other issues) that there is a philosophical opposition to such a conditional, but could you please reconsider your stance based on the overwhelming desire for this feature? Don't the hundreds (literally, if you count all of the Stack Overflow posts, forum posts, issues on this GitHub repo, etc.) of people all asking for the same thing suggest that the library has an opportunity for improvement? P.S. At the same time, the fact that Handlebars has hundreds of users all requesting any feature at all is a testament to how great Handlebars is, so regardless of the outcome thanks for making such a useful library.

@georgefrick
Copy link

There are many options, including using any of the standard sets of helpers people have been publishing.
You could write your own helper. Like: https://github.com/assemble/handlebars-helpers

I agree with the core argument that Handlebars is bare and you add these things. It means you can add an implementation appropriate for you.

Here is a simple version:
Handlebars.registerHelper('ifCond', function(v1, v2, options) {
if (v1 === v2) {
return options.fn(this);
}
return options.inverse(this);
});

BUT. Some people will want ==, some people will want to pass a comparator. Then there will be 'not if'. My vote as a Handlebars user and teacher is to refrain from adding it.

Also, for your example; I'd generate the select with a helper {{selectMaker options selectedValue }}

@machineghost
Copy link
Author

Right but look if hundreds of people are all asking a library for the same thing, might it not make sense to incorporate that thing in to the library, rather than making each one of those hundred people roll their own solution?

@variousauthors
Copy link

-1

I respect you guys keeping it lean, and I enjoy the spirit of the handlebars "if". It doesn't evaluate logical expressions, it evaluates the answer to a question asked on the back end. If the answer was "yes" do this, otherwise don't. Definitely don't give in: don't let people do their calculations in their HTML!

@sorentwo
Copy link
Contributor

sorentwo commented Oct 2, 2013

Another 👎. Evaluating expressions and keeping logic in templates is diametrically opposed to the goal of Mustache, and thereby Handlebars.

mustache -- Logic-less templates

People need to learn how to avoid the need for logic in templates, not how to hack it in.

@nickkitto
Copy link

-1
I wrote the accepted answer for that question. I agree that you should generally do the computation inside the js code before rendering the template. All that you can achieve with a helper like this you can achieve with the standard if helper and setting up the template parameters correctly. Based on this I don't think there is a way you can justify making it a default helper. I find it useful to do quick comparing sometimes though so I have added it as a custom helper.

@machineghost
Copy link
Author

So I respect those three negative votes, but I really think they should be taken in consideration with the hundreds of positive votes for this feature that one will find if they just do a simple Google search (as I mentioned above).

Where does the opposition to a "real" if come from? Principles. Everyone knows that Handlebars is a super-principled library, and that thou shalt never mix presentation logic and non-presentation logic. Since the best way to do that is to keep all logic out of templates, Handlebars does so.

Er ... except for existence-only if statements. And inverted existence-only if statements. And with statements. And for/each loops. And of course whatever people want to introduce with their own helpers. Actually, come to think of it, Handlebars isn't a library that slavishly adheres to ideology.

On the contrary, it seems to me that it's a library that strikes a balance between principles (which are important for good/clean code) and practicality. What I'm trying to argue here is that Handlebars should continue striking that careful balance, but just move the line an inch more towards the practical side. I'm trying to argue that keeping non-existence-only if statements out of Handlebars results in worse, uglier code.

Consider a very simple <select> element's construction. We start with a JS array and a Handlebars template:

var someList = [
    {name: 'Aardvarks' val: 'a'},
    {name: 'Beetles', val: 'b'},
    {name: 'Cantaloupe', val: 'c'
];
var template = Handlebars.compile(
    '<select>{{#each list}}' +
    '<option value="{{val}}{{#if selected}} selected{{/if}}</option>' +
    '</select>');

Now here's what we have to do without a proper if:

var someListForHandlebars = _(someList).clone(); // leaving cloning logic to Underscore library
var selectedValue = 'b';
for (var i = 0; i < someListForHandlebars.length; i++) {
    someListForHandlebars[i].selected = someListForHandlebars[i].val == selectedValue;
}
template({list: someListForHandlebars})

But if instead we had a real if, all that could be avoided with just a simple change to the template

var template = Handlebars.compile(
    '<select>{{#each list}}' +
    '<option value="{{val}}{{#if val == 'b'}} selected{{/if}}</option>' +
    '</select>');
template({list: someList})

Now I can hear the response already: "just make a helper for generating selects". My response to that is "sure, you can do that ... but if you have to keep telling a very significant number who use your library to write the exact same helper, isn't that a sign that the library itself is deficient?"

Adding a proper if statement won't make the walls between presentation and real logic come crashing down. It won't make every Handlebars users code instantly harder to maintain. What it will do is let people eliminate stupid, boilerplate code like the lines I pasted above, and it would make Handlebars a more useful, and more practical, library.

@sorentwo
Copy link
Contributor

Surely nobody believes an if statement will make the "walls between presentation and logic come crashing down." What it would do is blur the line between what something logically is and how something is logically evaluated. When you evaluate a single property like this:

{{#if isSomething}}
  <p>Yes, it is something</p>
{{/if}}

There is no need for anybody modifying the template to understand how isSomething has been evaluated. No context or understanding of the business logic is necessary. On the other hand, were you to evaluate a property like this:

{{#if something == "a"}}
  <p>Yes, it is something</p>
{{/if}}

You would need to have an understanding of the possible values of "a".

More importantly I think allowing a single if equality operator would introduce a very slippery slope. Why not also provide!=, &&, ||, or parenthetical grouping? The same argument could be made for every one of those operators and grouping constructs.

@mcw0933
Copy link

mcw0933 commented Oct 10, 2013

I just took this construct out of an XSLT template that populates an HTML
select:

<xsl:for-each select="user[not(@id = /repositories/administration/repository[not(@deleted)][@ownertype

= 1]/@ownerid)]">
<xsl:attribute name="value">
<xsl:value-of select="id" />
/xsl:attribute<xsl:value-of select="name" />
/xsl:for-each

and replaced it with this snippet of HB:

{{#each userNotAlreadyOwner}}
    {{> option .}}
{{/each}}

Consequently I've made it readable and maintainable; cut down the data
going to the client (no repositories or the entire user list anymore); and
pushed boilerplate presentation logic into a partial.

Handlebars' philosophy is good. Complex expressions and helpers should be
taken as a sign that you're on the slippery slope. The limited
implementation forces you to be thoughtful.

I have yet to see in this thread a realistic example of logic that is 1)
appropriate for the template to tackle and 2) thorny enough to warrant more
than a basic helper.

As for the argument that languages need to support a standard
arbitrary-comparison syntax... several well-known languages seem to be
doing just fine without it:
http://stackoverflow.com/questions/1937362/can-you-write-any-algorithm-without-an-if-statement

See also: http://www.antiifcampaign.com/get-started.html

@machineghost
Copy link
Author

Ok, here's something we can all agree on: XSLT (well really XPath) syntax is god awful :-D

"I have yet to see in this thread a realistic example of logic that is 1)
appropriate for the template to tackle and 2) thorny enough to warrant more
than a basic helper."

Here's where you lose me. Why is it acceptable to make every Handlebars user write their own version of the same basic helper? Doesn't the sheer un-DRY-ness of that (not to mention all the wasted human effort) bother anyone besides me?

Ultimately, I think all of the following statements are true; if they're not, can someone please correct me?

  1. rendering a <select> tag using "out of the box" Handlebars (ie. without a helper) is impossible without a hideous amount of extra boolean variables that exist solely for Handlebar's benefit

  2. As a result of 1), currently, every Handlebar user must:

  • not render <select> tags
  • build and pass a hideous number of booleans to their template
  • write their own version of a helper that's already been written thousands of times before
  1. a very large chunk of Handlebars users are going to want to render <select> tags in their templates ... which means they have to chose between the two latter options I just mentioned ... neither of which is ideal

  2. These limited options are unnecessary: if there was a standard Handlebars way of rendering <select>s, every user would be able to use it, without writing a redundant helper or making hideous amounts of booleans

If all of those statements (which completely leave out all of the non-<select> cases for a proper if) are correct, then doesn't that beg the question I keep asking: if you have to keep telling a very significant number of your users (everyone who wants to render a <select>) to write the exact same helper, isn't that a sign that the library itself should include that helper?"

@BrewDawg
Copy link

{{#xIf Age ">=" 42}}
   This dude's gettin' up there ...
{{else}}
    I got socks older than you ...
{{/xIf}}

or

{{#xIf Name "===" "Mike}}
   it matches !!
{{/xIf}}

Here is the code, you can see all the operators it supports

Handlebars.registerHelper('xIf', function (lvalue, operator, rvalue, options) {

    var operators, result;

    if (arguments.length < 3) {
        throw new Error("Handlerbars Helper 'compare' needs 2 parameters");
    }

    if (options === undefined) {
        options = rvalue;
        rvalue = operator;
        operator = "===";
    }

    operators = {
        '==': function (l, r) { return l == r; },
        '===': function (l, r) { return l === r; },
        '!=': function (l, r) { return l != r; },
        '!==': function (l, r) { return l !== r; },
        '<': function (l, r) { return l < r; },
        '>': function (l, r) { return l > r; },
        '<=': function (l, r) { return l <= r; },
        '>=': function (l, r) { return l >= r; },
        'typeof': function (l, r) { return typeof l == r; }
    };

    if (!operators[operator]) {
        throw new Error("'xIf' doesn't know the operator " + operator);
    }

    result = operators[operator](lvalue, rvalue);

    if (result) {
        return options.fn(this);
    } else {
        return options.inverse(this);
    }
});

I've got a bunch of useful ones, including one that always gets you to the root ...
http://www.my2ndgeneration.com/TemplateLanguageDoc.aspx

@machineghost
Copy link
Author

@BrewDawg I think it's great that you made your own if ... like thousands of other Handlebars users before you. My point though is that you (or the other thousands of users) shouldn't need to write one in the first place. If most of the users of a library are trying to reproduce a missing feature of a library on their own (as you have), it makes sense for that library to provide that feature ... and since rendering a <select> tag requires a proper if helper (or else an ugly workaround of one boolean variable per <option> tag) that really does mean that most Handlebars users need a proper if.

@georgefrick
Copy link

Again:

Your requirement to render a select tag is a requirement for a helper to create a select tag; it doesn't logically suggest 'if' is a requirement.

The 'if' provided above is yet another example of exactly why one shouldn't be provided. Nobody is going to agree on an implementation.

We're probably all working on projects and probably all had to add whatever 'if' our group agreed on. Meaning that not only is it already done; but worse - if added now it will create upgrade problems for some groups.

Select tag helper exists and has been posted; you shouldn't be doing Select tags with 'if' because you shouldn't have your options hard coded in your html anyways.
Example ~100 of a pretty good 'if' implementation is further proof of the fragmentation that would be caused by trying to provide a single standard. Future posts would be about how the 'if' should work and we'd be back to square one.
Due to the lack of original 'if' and fragmentation of implementation among groups, introducing it would introduce an upgrade path problem.

It's not about votes or popularity; it's a bad decision for the library because it has no technical merit that you have been able to explain.

@HoffmannP
Copy link
Contributor

I think it would be a good strategy to accept the decission and move on to a second best solution. Why not open a handlebars-helpers-lib that contains if and probably some other often used functions. There is no need to bloat handlebars as it does what it does fine.

@nickkitto
Copy link

@HoffmannP a popular helper collection is https://github.com/elving/swag

@jonschlinkert
Copy link
Contributor

@sizzlemctwizzle
Copy link

I'm against this as a user of Mustache and a potential future user of Handlebars. I chose Mustache precisely because it minimizes logic in the template (logic-less). Handlebars provides extra features in the same vein, but this feature would open a whole can of worms. You might as well save some time and copy EJS's source and change the delimiters to mustaches.

@rpocklin
Copy link

rpocklin commented Nov 4, 2013

-1, i'm with @sizzlemctwizzle. not having verbose if logic in the template is one of the biggest features HB has. If you don't like it, choose any number of other template engines. HB forces you to ask what the intent is behind the comparison, and to consider creating modular building blocks that are reusable. All good practices.

@BrewDawg
Copy link

BrewDawg commented Nov 4, 2013

Well, in the same vain, if you don't like it don't use the If statement. But I agree, keep Handlebars small and let these other handlebar helper libraries fill the gap. In our case, we are using Handlebars as full code generation for business objects and everything, so we have created lots of handlebar extensions, we have complicated if/then/else logic in our templates and we need to. All that being said, I think there are some good extensions libraries for handlebars for those that need them and writing helpers is pretty easy.

@Starefossen
Copy link

It's plain stupid not having conditionals like the one OP is proposing in a templating language like handlebars. I have to make a seperate handler to render a simple select? No, thank you! There are already conditionals like if and unless so claiming that this will add logic to an otherwise logic-less templating language is a mute point.

+1 from me

@sorentwo
Copy link
Contributor

The debate on this isn't getting anywhere. Conditionals in Mustache / Handlebars templates go against the core principals of the project. I'd like it if a core contributor such as @kpdecker would weigh in here and start to get this issue closed.

@kpdecker
Copy link
Collaborator

Logicless is a very fuzzy thing that has many different meanings to many people as this thread shows. For me the debate comes not down to functionality that a feature allows for and the associated overhead. If those two work out you can always choose to not use a feature or even explicitly disable it.

I think that a limited set of comparisons is worthwhile but ensuring that the feature is sane and sound for all situations and keeping library bloat down is important. For this to be implemented the impact that this would have on the parser and the overall lexical complexity of the language (and resulting binary distributions) would have to be reasonable.

Any implementation of this, if done, would be of course optional and not required by anyone. I've left this issue open as to me it's worth the investigation of what such a system would look like, what the cost would be, and how that would integrate, or not, with the core tenants of the project.

@BrewDawg
Copy link

This argument kind of reminds me why Unix doesn't rule the world. It was by far the best OS light-years before anybody was doing serious multi-threading and had a killer graphical UI however, everyone branched off and built their own flavor of Unix and eventually version x couldn't talk to version y and Unix was quickly become the OS of a thousand flavors and you couldn't have vanilla and chocolate in the same cone. This could be where Handlebars is heading, each shop takes and adds a ton of helpers and then their templates only work in their shop, which might be okay since they're likely building a website and that code wouldn't be shared by some other company.

To me logicless means it doesn't execute Javascript directly inline which makes it far safer than say dOT and some of the others.

I was glad to see @FIRST and @last which might have been inspired by our implementations. Also, we have the xRoot operator which lets you get to the root object at any time no matter how deeply nested you are in an {{each}} loop, it's a trivial 5 line change to the the handlbars code. I love using handlebars and we'll use it no matter what the group decides.

There are the operators we've added (we use the "x" prefix to indicate our additions)

{{ xNotFirst }}
{{ xLast }}
{{ xNotLast }}
{{ xCamelCase }}
{{ xPascalCase }}
{{ xLowerCase }}
{{ xUpperCase }}
{{ xIf }}
{{ xIfAny }}
{{ xIfNotAny }}
{{ xIfAll }}
{{ xIfNotAll }}
{{ xMath }}
{{ xHashtable }}
{{ xRoot }}
{{ xBlock }}
{{ xFile }}

https://www.my2ndgeneration.com/TemplateLanguageDoc.aspx

@kpdecker
Copy link
Collaborator

#690 is going to provide the official support for this. This will allow for things like {{#if (eql a b) }} at any location that a value can be used currently.

@jonschlinkert
Copy link
Contributor

👍

@morphar
Copy link

morphar commented Jan 7, 2014

I've left this issue open as to me it's worth the investigation of what such a system would look like, what the cost would be, and how that would integrate, or not, with the core tenants of the project.

@kpdecker Awesome response! That kind of openness and exploration is really admirable!
I wish we could all be better at being open, exploring possibilities before writing them of and first then apply logic (pun intended) to make a much more informed decision!

Maybe the solution to all of this, would be to have a place to share handlebars helpers?
This would eliminate some of the "wasted human effort" - I can only agree with you @machineghost, everybody creating the same code over and over again is nothing but waste that should be eliminated!

Such a solution would:

  • Eliminate the wasted human effort
  • Let people decide if they want logic in their templates or not
  • Create a place to study what functionality people actually need and use

That became a rant... Sorry :)

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests