BorisMoore / jquery-tmpl

The original official jQuery Templates plugin. This project was maintained by the jQuery team as an official jQuery plugin. It is no longer in active development, and has been superseded by JsRender.
3.23k stars 1.01k forks source link

{{if conditionalProperty1 || conditionalProperty2}} fails #70

Closed mattfysh closed 13 years ago

mattfysh commented 13 years ago

Given the following template data:

[{
    conditional1: true
}, {
    conditional2: true
}, {
    conditional1: true,
    conditional2: true
}, {

}];

These two both work

{{if conditional1}}

{{if conditional2}}

However, this silently fails and nothing is rendered

{{if conditional1 || conditional2}}

I've only been able to get this to work so far through the use of $data

A conditional value {{if $data.conditional1 || $data.conditional2}} does {{else}} doesn't {{/if}} exist
BorisMoore commented 13 years ago

That is because you are passing a javascript expression containing variables which may be undefined. The template engine can check if you pass a single variable that is undefined: if conditional1, but cannot help the javascript engine to figure what to do with an expression that contains undefined variables within it: if conditional1 || conditional2.

So the solution is either:

{{if conditional1}}
{Success
{{else conditional2}}
Success
{{/if}}

or use this for you data:

[{
    conditional1: true,
    conditional2: false
}, {
    conditional1: false,
    conditional2: true
}, {
    conditional1: true,
    conditional2: true
}, {
    conditional1: false,
    conditional2: false
}]
mattfysh commented 13 years ago

It seems rather counter-intuitive to repeat the same 'Success' content twice, especially with extensive markup. There are two reasons why this should work as expected:

  1. The following works

    {{if $data.conditional1 || $data.conditional2}}
  2. It is a valid JavaScript expression, and the API doc doesn't mention any restrictions for

    {{if expression}}

I've forked the project, so I'll take a look this weekend. Besides this small issue, the entire project is nothing short of amazing. I can't even begin to imagine how many hours I've saved using it :)

BorisMoore commented 13 years ago

Sure, you would use {{if $data.conditional1 || $data.conditional2}} to avoid repeated content. Just showed you the other form to show how the templates engine can be smart about top-level undefined, but cannot prevent the javascript engine from throwing if you have expressions containing undefined variables. The javascript engine will consider that if (foo.undefinedField) is equivalent to if (false). But if ( undefinedVariable ) will fail to execute - and will throw:

a={x:1}

if (a.x) {"yes"} else { "no" }
"yes"

if (a.foo) {"yes"} else { "no" }
"no"

if (foo) {"yes"} else { "no" }
'foo' is undefined      

So if ( foo ) is a valid expression but will still throw...

The only alternative is for the tmpl implementation to wrap things in try catch blocks. We'll be looking at this kind of possibility for better error handling in a future (Beta2) release...

mattfysh commented 13 years ago

Right, that makes much more sense. I get where you're coming from now, but shouldn't all variables in a template be resolved against the current data object? That is,

{{if conditional1 || conditional2}}

Should automatically be interpreted as

{{if $data.conditional1 || $data.conditional2}}

I guess the cleanest way to implement this would be

{{if $item.hasConditional()}}

Initiating the template like so

.tmpl( data, {hasConditional: function(return this.data.conditional1 || this.data.conditional2 ) {} } )
BorisMoore commented 13 years ago

You can provide the hasConditional function on $item, as you suggest, yes, or you can include $data.conditional in your template, or you can ensure conditional1 and conditional2 are defined (true or false) on your data...

But the template engine can't tell the JavaScript interpreter to interpret {{if conditional1 || conditional2}} as {{if $data.conditional1 || $data.conditional2}}. And we don't want to be in the game of parsing JavaScript expressions, and figuring out where the variables are, and appending "$data." to them. Once you provide an expression, it will run as is...

mattfysh commented 13 years ago

True, the parsing of expressions to search for identifiers should be kept well out of scope. I'll use the $item extension instead, much more readable :) Thanks Boris!