janl / mustache.js

Minimal templating with {{mustaches}} in JavaScript
https://mustache.github.io
MIT License
16.47k stars 2.39k forks source link

Partials in nested objects don't work #627

Open danielcobo opened 7 years ago

danielcobo commented 7 years ago

It seems partials don't work if they are stored in a nested object.

I haven't seen docs detailing that partials are not working if they are stored in a nested object. Is this a bug (in either mustache or my example) or is this expected behaviour?

EXAMPLES:

The following works (see https://runkit.com/58ffc7fbd7f7860012732802/591c49a29a86bd00110e565f):

var mustache = require("mustache")
    var view = {names : [
            {name : 'Foo'},
            {name : 'Bar'}
            ]};
    var template = [
                '<h2>Names</h2>',
            '{{#names}}',
                '{{>hello}}',
            '{{/names}}',
            ].join('');
    var partials = {
            hello : '<strong>{{name}}</strong>'
            }

mustache.render(template, view, partials);

while this does not work (see https://runkit.com/58db86c6eae8b80014630525/58eb71ee82d3930015030dad):

var mustache = require("mustache")
    var view = {names : [
            {name : 'Foo'},
            {name : 'Bar'}
            ]};
    var template = [
                '<h2>Names</h2>',
            '{{#names}}',
                '{{>hello.world}}',
            '{{/names}}',
            ].join('');
    var partials = {
            hello : {
                world : '<strong>{{name}}</strong>'
                }
            }

mustache.render(template, view, partials);

(SORT OF) SOLUTION I've solved it by using a function to return partial template instead of the object directly. (See: https://runkit.com/58db86c6eae8b80014630525/58ebd3fbd862590014ec615e)

var mustache = require("mustache")
var objRef = require('objref');

var view = {names : [
                    {name : 'Foo'},
                    {name : 'Bar'}
                    ]};
var template =  [
                '<h2>Names</h2>',
                '{{#names}}',
                    '{{>hello.world}}',
                '{{/names}}',
                ].join('');
var partialTemplates = {
                hello : {
                        world : '<strong>{{name}}</strong>'
                        }
                };

var partials = function(name){
    return objRef(partialTemplates, name, '.');
};

var result = mustache.render(template, view, partials);

This however is not the most elegant way. If the object of partials worked directly, it would be a much nicer solution.

ifgx commented 7 years ago

@danielcobo Note: Your two first links to runkit are the same location.

danielcobo commented 7 years ago

@ifgx sorry about that (the code examples here were correct though)

I've updated the link to reflect the code examples

KnutRyagerInmeta commented 5 years ago

The context is never set to the deepest partial's. Here I am evaluating name: 'obj' in the lookup function (line 428), in a template {{>tsExpression}} expanded in a {{>tsReturn}}:

intermediateValue = context.view[name]; // undefined

How? Well here is the context.view:

{"templateName":"tsReturn","tsExpression":{"templateName":"tsExpression","obj":{"templateName":"tsObject","properties":[{"name":"template","value":"t","isLastOfList":true}]}}}

I am in context of the outer tsReturn, and yes, tsExpression, where obj is located, is never expanded! From here, only parent views are checked for obj...

KnutRyagerInmeta commented 5 years ago

Note: I have made the following fix

Switch line 600 (return of function renderPartial of mustache.js ) from

return this.renderTokens(this.parse(value, tags), context, partials, value);

to

return this.renderTokens(this.parse(value, tags), context.view[token[1]] ? context.push(context.view[token[1]]) : context, partials, value);

This pushes the context during my mentioned problem. And in case it do this in a case it is not supposed to, it will still look up the regular context by the usual parent context traversal.

KnutRyagerInmeta commented 5 years ago

I have uncovered an issue with my fix. You can easily create infinite recursion if you go through the same template expansion twice (due to inheriting context variables that cause unintended expansions). In my setup I will automatically pass complete JSON objects that close any unintended paths, however it seems partial context expansion runs contrary to the general context inheriting nature of Mustache. So I think this answers the question of why it's not supported.