olado / doT

The fastest + concise javascript template engine for nodejs and browsers. Partials, custom delimiters and more.
Other
5.01k stars 1.02k forks source link

Feature request: Context delimeter for prefix free output #29

Closed dannyc closed 9 years ago

dannyc commented 12 years ago

Hi, It would be nice if there was a context delimeter. Instead of needing to add "it" as a prefix for every data reference.

<div>Hi {{=it.name}}!</div>
<div>{{=it.age || ''}}</div>

becomes

{{$ it}}
<div>Hi {{=name}}!</div>
<div>{{=age || ''}}</div>
{{$}}

Or in a more complex scenario with nesting

var data = {
  "name": "Jake",
  "age":31,
  "mother":"Kate",
  "father":"John",
  "interests": ["basketball","hockey","photography"],
  "contact": {
    "email":"jake@xyz.com",
    "phone":"999999999"
  }
}

Could be used like this:

{{$ it}}
  <div>I'm {{=name}} and I love..</div>
  <ul>
    {{~interests :value}}
      <li>{{=value}}!</li>
    {{~}}
  </ul>
  <div>Please contact me</div>
  {{$ contact}}
    Phone: {{=phone}}
    Email: {{=email}}
  {{$}}
{{$}}

I roughly tried something along these lines in my fork some time back but never really tested or used it. This give a few big advantages. 1) Easier to read the templates without "it" everywhere. 2) If the data structure ever changes- (i.e. something gets nested inside another object when previously it had not) fixing the template is simply a matter of adding a new context delimiter.

Thoughts? Thanks!

LarryBattle commented 12 years ago

Just use the with keyword in Javascript. Your feature would be really easy to implement since it's almost the same as the if condition, {{~}}. I didn't have any time so I didn't try but here's a working solution without hacking doT.js.

Use {{ with ( it ){ }}, {{ } }} in place of {{ $ it }}, {{ $ }} respectively.

Which gives the following.

var template = "{{with (it){ }}";
template += "<div>I'm {{=name}} and I love..</div>"
template += "<ul>"
template += "{{~interests :value}}"
template += "<li>{{=value}}!</li>"
template += "{{~}}"
template += "</ul>"
template += "<div>Please contact me</div>"
template += "{{ with ( contact ) { }}"
template += "Phone: {{=phone}}<br/>"
template += "Email: {{=email}}"
template += "{{ } }}"
template += "{{ } }}"

Live demo: http://jsfiddle.net/UVFnr/

drkibitz commented 12 years ago

Personally the auto context in mustache, is the most annoying part about it. I like the option of completely ignoring it, but I also think there are plenty of ways to get it in there without the use of the 'with' keyword. Maybe auto generating closures that accept arguments (varname) that are equivalent to the keys of the context object.

Object it.contact {name: "John", age: 40} can be converted to (function (name, age) { return // template string }(it.contact.john, it.contact.age))

drkibitz commented 12 years ago

Probably would be simpler and faster if just prepended the context in the compilation process.

drkibitz commented 12 years ago

Not sure about the $ character either, but I still like the idea of just concatenating scope automagically to variable names in the compilation.

olado commented 12 years ago

@LarryBattle 'with' would work though with is better avoided. @drkibitz auto-closures would be better. Concatenating automatically - I don't think this would work as we don't know which variables to concatenate to. One more option is to do something similar to array iterator {{~ it name, age }} where you would have to list properties that you want to use.

drkibitz commented 12 years ago

@olado I thought concatenation could work something like the following:

{{$ it}}
    This is {{foo}} // concatenates to "it.foo"
    {{$ bar}}
        This is {{foo}} // concatenates to "it.bar.foo"
        This is {{it.bar.foo}} // Matched scope variable name, no concatenation
    {{$ bar}}
{{$ it}}
This is {{it.baz}} // top level, no concatenation

Basically scope is a stack that always concatenates to the front of variable names. We know the variable to concatenate from tags that must exist with the variable name. There are some weird instances here, with the scope matching thing, that might be a bad idea because it.it.it is not possible with that type of functionality.

Alternatively I do like your Object iteration solution:

{{~ it foo, bar}}
    This is {{foo}} // should be value of it.foo
    {{~ bar foo}}
        This is {{foo}} // should be value of it.bar.foo
        This is {{it.bar.foo}} // should also be value of it.bar.foo
    {{~ bar}}
{{~ it}}
This is {{it.baz}} // should be value of it.baz

I'm on the fence here. The first is more like other popular template engines that people are use to. Then second is about the same in ease of use, but slightly more characters needed in the template, although very slightly in my comparison.

olado commented 12 years ago

We don't know which variables to concatenate to because inside of {{ }} can be ANY javascript, not only reference to a variable. This is valid: {{= (function(foo) { return foo; })("hello") }}, same for all other delimiters. Code is allowed in all delimiters, access to global variables is allowed to. Basically all of Javascript is allowed. That's why I don't think automatic would work.

Pointy commented 9 years ago

The configurable varname is one of the absolute best features of doT. Dropping that for the questionable convenience of implicit context reference would be a tragedy (to me).

Another problem with this is that doT syntax allows the expression inside {{= }} or {{! }} to be any JavaScript expression. Where would the implicit context go? I suppose that the implicit context thing could apply only to {{= }} expressions where there's nothing but an identifier, but then that'd mean that for more complicated situations you'd have to remember on your own to add the prefixes.