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

Dynamic partials and partial collections #242

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open

Dynamic partials and partial collections #242

wants to merge 1 commit into from

Conversation

ghost
Copy link

@ghost ghost commented Aug 6, 2012

Hi,

This is a small patch that brings great flexibility when dealing with collections that are going to be rendered using partials. Please see the new documentation below:

With this patch, the following:

View:

{
  items: [
    { type: 'image', url: 'Some URL', is_image: true },
    { type: 'text', content: 'Some text', is_text: true }
  ]
}

Template:

base.mustache
{{#items}}
  {{#is_text}}
    {{>text}}
  {{/is_text}}
  {{#is_image}}
    {{>image}}
  {{/is_image?}}
{{/items}}

text.mustache
<p>{{content}}</p>

image.mustache
<p><img src="{{url}}"/></p>

...can be replaced with:

View:

{
  items: [
    { partial: 'text', content: 'Some text' },
    { partial: 'image', url: 'Some URL' }
  ]
}

Template:

base.mustache
{{@items}}

text.mustache
<p>{{content}}</p>

image.mustache
<p><img src="{{url}}"/></p>

...or if more fine grain control is needed:

Template:

base.mustache
{{#items}}
<p>{{>.}}</p>
{{^items}}

text.mustache
{{content}}

image.mustache
<img src="{{url}}"/>

Patch includes tests and documentation, addressing a recurring problem when using Mustache templates e.g. http://stackoverflow.com/questions/2932679/dynamically-render-partial-templates-using-mustache and #241.

I really hope the pull request is accepted as I am already utilising this in a couple of projects and it's a joy to use. I will look at implementing in the Ruby version of Mustache also if accepted.

Kind regards,

Jamie

@bobthecow
Copy link

Things like this should probably be taken up with the spec first, then added to individual implementations:

https://github.com/mustache/spec

@ghost
Copy link
Author

ghost commented Aug 6, 2012

Just created ticket here: mustache/spec#54

I do however think that it would be worth applying this patch anyway so that people can see just how useful it is. Think vendor prefixes in browsers, that's how template inheritance made it into hogan.js.

@bobthecow
Copy link

Vendor prefixes are great, but the analog in Mustache is a {{%PRAGMA}} tag. If it's a non-spec feature, it should be off-by-default and explicitly enabled via a toggle. Several great things came into the spec this way (e.g. dot notation, implicit iterators).

@bobthecow
Copy link

In fact, there's a "filters" feature which will prolly go out in the next Mustache.php release that does this too:

bobthecow/mustache.php#102

We don't need to stop implementing awesome new things, we just need to be spec-compliant by default and guard the new features with a {{%PRAGMA}} tag :)

@ghost
Copy link
Author

ghost commented Aug 7, 2012

I'm not sure how I'd implement pragma's in mustache.js ...I will investigate. In the meantime, what do you/others think of this patch?

I urge you to give it a try as I saw instantly the benefits in my projects.

@ghost
Copy link
Author

ghost commented Aug 9, 2012

I'm thinking we could do with some way of plugging in language features, that way the plugins (middleware) can be used and assessed as a plugin even if they don't make it into the core library. Something like:

Mustache.Renderer.register('@', function(name, context, options) {
  // My custom tag functionality

  return 'the output';
});

Not sure how to go about extend and existing symbol's functionality but something like before/after callbacks could work.

@devinrhode2
Copy link

Your syntax:

base.mustache
{{#items}}
<p>{{>.}}</p>
{{^items}}

and

base.mustache
{{@items}}

Assumes there is a key named 'partial' and then looks up that partial. The idea of dynamic partials is great, but I don't know about assuming a certain key to be present is the most developer friendly approach and the easiest to understand quickly. It could instead by {{>.partialName}} which, when the lookup for the literal key '.partialName' fails, it resolves .partialName to 'text' and looks that up.

This is interesting but I agree with bob that it should be off by default but have an option to turn it on. Further, the {{%PRAGMA}} idea should probably be in the spec itself... @bobthecow, would you agree?

@devinrhode2
Copy link

@thelucid In regards to:

Mustache.Renderer.register('@', function(name, context, options) {
  // My custom tag functionality

  return 'the output';
});

I don't know how you could think of something so terrible...

No I like the idea, I need to consider the implementation more, but I'd be interested in hearing what others think.

@ghost
Copy link
Author

ghost commented Sep 18, 2012

The reason I went with assuming a partial key is that I like the whole convention over configuration approach.

I am using this in production and the {{@some_collection}} syntax really cleans up templates, it basically says to me at a glance "render a bunch of objects with their own partials":

{
  articles: [
    { partial: 'basic_article', title: 'Title A', content: 'Some text' },
    { partial: 'image_article', title: 'Title B', src: 'image.jpg' }
  ]
}
base.mustache
<div class="articles">
{{@articles}}
</div>

basic_article.mustache
<h1>{{title}}</h1>
<p>{{content}}</p>

image_article.mustache
<h1>{{title}}</h1>
<p><img src="{{src}}"/ ></p>

I guess a common ground would be to use {{@collection}} as I have outlined and the longhand could either be:

Assuming a partial key:

{{#items}}
<p>{{>.}}</p>
{{^items}}

A partial based on my_partial_key:

{{#items}}
<p>{{>.my_partial_key}}</p>
{{^items}}

I'm not sure how I'd implement the {{%PRAGMA}} thing cleanly.

I'd arguably be more interested in the Mustache.Renderer.register functionality as it would allow app specific customisation without hacking Mustache itself. Not sure how you'd use this to add additional functionality to an existing symbol.

@ghost
Copy link
Author

ghost commented Sep 18, 2012

I would appreciate peoples opinions over at mustache/spec#54 if you get a sec as there have been a couple of suggestions that highlight further why a clean solution to "dynamic partials" is needed... namely a pretty ugly workaround using functions that return html (mustache/spec#54 (comment)).

@busticated
Copy link
Contributor

annnnd i think i'm all caught up on the history of this one (>_<)

so... what's the verdict on this dynamic partials stuff? it sure would be helpful to me.

at the least, the lamba-based approach shown here mustache/spec#54 (comment) and here #304 (comment)

@ghost
Copy link
Author

ghost commented May 31, 2013

@busticated I came up with a Ruby solution that I am happy with in my Mustache implementation (https://github.com/thelucid/tache) which allows for this type of behaviour without a change to the spec but not sure how it will translate to the Javascript version. See the tests starting with ~ for how it's achieved: https://github.com/thelucid/tache/tree/master/test/fixtures

@ghost ghost mentioned this pull request Apr 17, 2014
@vgracia
Copy link

vgracia commented Sep 22, 2015

This seems like a great feature. Was this functionality ever added to mustache in some way?

@neunato
Copy link

neunato commented Dec 27, 2017

I know I'm late to the party, but in case it helps someone - here's a simple function enabling dynamic partials on syntax level without polluting the view.

Parentheses in partial's name represent the dynamic property whose value determines the template to use.

// For every dynamic partial, a function which renders the subtemplate is added to the view's 
// prototype, and their occurrences in the template are replaced with `{{{partial-(name)}}}`, 
// now accessible through the prototype.

function render( template, view, partials = {} ){
   const prototype = {}
   template = template.replace(/\{\{>(.*?\(.+?\).*?)\}\}/g, function(_, name, property){
      if( !prototype[name] ){
         prototype[name] = function(){
            const computed = name.replace(/\((.*?)\)/, (_, property) => this[property])
            if( !partials[computed] )
               throw new Error(`Dynamic partial "${computed}" does not exist.`)
            return render(partials[computed], this, partials)
         }
      }
      return `{{{${name}}}}`
   })
   view = Object.assign(Object.create(prototype), view)
   return Mustache.render(template, view, partials)
}
const template = "{{#items}}{{>(type)}}{{/items}}"

const partials = {
   image: '<img title="{{title}}">',
   text:  '<p>{{content}}</p>'
}

const view = {
   items: [
      { "type": "image", title: "Some title" },
      { "type": "text", content: "Some text"}
   ]
}

render(template, view, partials)  // <img title="Some title"><p>Some text</p>

Using parentheses in property names of the view could shadow the prototype.

@stuartjnelson
Copy link

stuartjnelson commented Jun 26, 2020

@monkape thanks for sharing your solution. I can't get it to work...

Do you happen to have any repos/live code examples using your solution you'd be happy sharing?

@neunato
Copy link

neunato commented Jun 27, 2020

@stuartjnelson Looks like the example above still works fine https://jsbin.com/zejewasebe/edit

@stuartjnelson
Copy link

Thanks @monkape for creating the demo. I need to spend some more time getting it to work with my stack.

Express app which renders the templates based on JSON data and a .mst page. This page is made up of .mst partials. I was hoping I could get this magically working but it's proved unsuccessful so far.

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

Successfully merging this pull request may close these issues.

None yet

6 participants