[Shark] Meteor UI - Template.name.withData removed

618 views
Skip to first unread message

Thomas Haferlach

unread,
Feb 21, 2014, 7:03:31 PM2/21/14
to meteo...@googlegroups.com
i'm having some trouble with the recent shark branch. it seems the Template.name.withData() has been removed. I can't figure out how to pass in a data context to the template now.

this also breaks the until recently compatible iron-router shark.

any ideas?

Avital Oliver

unread,
Feb 21, 2014, 7:07:42 PM2/21/14
to meteo...@googlegroups.com
We eliminated it because it was an anti-pattern. It made dynamically chosen templates re-render completely any time the helper that called `withData` fired. There was no way to actually preserve elements within a dynamically chosen template using withData.

Instead, you should use the following pattern:

    {{> dynamicTemplate dataContext}}

which is just a short-hand version of:

    {{#with dataContext}}
      {{> dynamicTemplate}}
    {{/with}}



--
You received this message because you are subscribed to the Google Groups "meteor-talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to meteor-talk...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

David Burles

unread,
Feb 22, 2014, 6:08:31 AM2/22/14
to meteo...@googlegroups.com
Hey Avital I have a kind of related question, I was integrating code from your reorderable example, however the the component.data() method appeared to be missing? Is there any reason why this would be so?  Not too concerned if there is not a simple answer to this as I have solved it another way.

Tarang Patel

unread,
Feb 22, 2014, 6:53:53 AM2/22/14
to meteo...@googlegroups.com
What about calling it using the return Template.hello pattern, would this be recommended:

Template.parent.something = function() {
    return Template.hello.extend(data, { .. });
}

<template name="parent">
    {{>something}}
</template>

It makes a bit easier when being able to serve different template with the {{>something}} helper

Tarang Patel

unread,
Feb 22, 2014, 11:28:31 AM2/22/14
to meteo...@googlegroups.com
Sorry just noticed there's a typo in that extend. Its supposed to be .extend({data: .. });

Avital Oliver

unread,
Feb 22, 2014, 12:07:59 PM2/22/14
to meteo...@googlegroups.com
Tarang, as described: Your suggestion would lead to overly aggressive re-rendering which leads to both worse performance and losing values in input fields.

Avital Oliver

unread,
Feb 22, 2014, 12:08:34 PM2/22/14
to meteo...@googlegroups.com
David, we're still finalizing the API around the reorderable list example. We'll publish a newly working example when we're ready.

Sam Hatoum

unread,
Feb 22, 2014, 8:22:06 PM2/22/14
to meteo...@googlegroups.com
Hey Avital

I'm getting re-rendering of sub-templates and I'm assuming it's because of the withData you've mentioned. Below is a simplified example of our scenario:

I have the following objects:

{
    _id: "9F2J3QHLLgXt9JgJE”,
    type: "heading",
    text: "I'm rendered by the heading template"
}

{
    _id: "5khZGuLAtmgReDkGc”,
    type: "paragraph",
    text: "I'm rendered by the paragraph template"
}

and this markup:

<body>
    {{#each objects}}
        {{> object}}
    {{/each}}
</body>

<template name="object">
    <div id="{{_id}}" class="{{type}}">
        {{#_ this}}
        {{/_}}
    </div>
</template>

<template name="heading">
    <h1>{{text}}</h1>
</template>

<template name="paragraph">
    <p>{{text}}</p>
</template>

and this helper:

Handlebars.registerHelper('_', function (context) {
    return Template[context.type].withData(context);
});

At the moment, if I update any value in the object, the heading / paragraph markup gets re-rendered. How do I move away from the withData anti-pattern in this scenario?

I basically need to the choose the template to use at runtime, based on data within the parent template's object.

Sam

Avital Oliver

unread,
Feb 22, 2014, 9:41:51 PM2/22/14
to meteo...@googlegroups.com
Sam,

A few things:

1. No need to pass this to {{#_}}. So you could have done {{#_}} {{/_}}
2. {{#_}} {{/_}} is the exact same thing as {{> _}}
3. Since by default template take the same data context as where they were called, there's no need for your withData at all.

So, do this:

Handlebars.registerHelper('_', function () {
  return Template[this.type]; // `this` is the data context here.
});

And then just use {{> _}} when you want to include the template dynamically.

Let me know how this works for you.

David Burles

unread,
Feb 23, 2014, 3:56:35 AM2/23/14
to meteo...@googlegroups.com
Thanks Avital

Tarang Patel

unread,
Feb 23, 2014, 3:24:01 PM2/23/14
to meteo...@googlegroups.com
Thanks Avital,

Is there a better way of doing this so that Its possible to decide what template is rendered using a helper, such that it has a context? Something that could replace withData.

Would I have to refactor my code away to try and use {{#with data}}{{>templatehelper}}{{/with}} instead?

Avital Oliver

unread,
Feb 23, 2014, 11:29:15 PM2/23/14
to meteo...@googlegroups.com
As I described, withData is an anti-pattern.

Instead of {{#with data}}{> templateHelper}}{{/with}} you can use the short-hand {{> templateHelper data}} which does the exact same thing.

Eric Dobbertin

unread,
Feb 24, 2014, 12:17:18 PM2/24/14
to meteo...@googlegroups.com
Hi Avi,

I've noticed a related issue. Say you have a template defined by a helper that returns a template:

Handlebars.registerHelper("foo", function fooHelper() {
  console.log(this);
  var templateName = figureOutNameDynamically(this.criticalInfo);
  return Template[templateName];
});

So I need the "criticalInfo" property from the calling context to determine which template to render.

Scenario 1 (works fine):

{{> foo objWithCriticalInfo}}

Scenario 2 (works fine):

{{#with objWithCriticalInfo}}
{{> foo}}
{{/with}}

Scenario 3 (does not work):

{{#with objWithCriticalInfo}}
{{> foo extraKeyword=bar}}
{{/with}}

Scenario 3 doesn't work because as soon as keywords are detected, they are merged into an object and that object becomes `this` rather than the calling context. In fact, there is currently no way at all to get the calling context from within the helper function if keywords are used.

A workaround is to do this:

{{#with objWithCriticalInfo}}
{{> foo extraKeyword=bar context=..}}
{{/with}}

Or this (in this particular example):

{{#with objWithCriticalInfo}}
{{> foo extraKeyword=bar criticalInfo=../criticalInfo}}
{{/with}}

However, this should not be necessary, and in my case it would cause confusion for users of my package. My proposal is that when `this` is overridden by a keywords object, the helper function should receive the data context in an argument. I made a quick change to this effect that works in a branch on my fork, but I'm almost positive my change is not the ideal way to solve, so I'm bringing this to your attention rather than doing a PR.

Another option would be to have keywords extend the default context rather than replace it. This could lead to some interesting abilities, but it might be confusing.

Thanks,
Eric

David Greenspan

unread,
Feb 24, 2014, 1:37:58 PM2/24/14
to meteo...@googlegroups.com
Hi Eric,

Your points are valid, and we will probably change the calling convention one last time before we lock it down for 1.0.  We just needed something simple and flexible with good re-rendering properties for 0.8.0 so we can land this branch without blocking on the next round of changes, which will improve the experience of creating components.

For a package author concerned about the calling convention of your helper, the solution is to create a wrapper template and expose that as your public API, and in this template do whatever rejiggering of the data context and arguments is necessary to feed them into the helper that selects the dynamic template.  Does that make sense?

Thanks,
David

Eric Dobbertin

unread,
Feb 24, 2014, 2:54:25 PM2/24/14
to meteo...@googlegroups.com
Thanks, David. I had already been trying the template wrapper thing, but once I read your answer, the way to do it “clicked” for some reason. In case it helps others, what I think you are saying (and what seems to work) is this:

Handlebars.registerHelper(“_foo", function fooHelper() {
  var templateName = figureOutNameDynamically(this.context.criticalInfo);
  return Template[templateName];
});

<template name=“foo”>
  {{> _foo context=.. atts=this}}
</template>


You received this message because you are subscribed to a topic in the Google Groups "meteor-talk" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/meteor-talk/F9XivhCqWvY/unsubscribe.
To unsubscribe from this group and all its topics, send an email to meteor-talk...@googlegroups.com.

Sam Hatoum

unread,
Feb 25, 2014, 11:07:10 AM2/25/14
to meteo...@googlegroups.com
Avi, just circling back to say thank you, that worked a charm. 

Sam

Sam Hatoum | Founder | xolvi.o | +1 (415) 857 1317 | LinkedIn

Avital Oliver

unread,
Feb 25, 2014, 1:34:21 PM2/25/14
to meteo...@googlegroups.com
Awesome :)

Jan Hansen

unread,
Mar 3, 2014, 5:40:08 PM3/3/14
to meteo...@googlegroups.com
Hi

How would one go about replacing this with something equivalent?

UI.insert(
    UI.render(
        Template.popup.withData({...})
    ),
    document.body
);

Jan

Eric Dobbertin

unread,
Mar 4, 2014, 8:31:40 AM3/4/14
to meteo...@googlegroups.com
Jan,

Here's a quick overview of how I solved a similar case. If others have better ways, chime in.

1. Render Template.popup (without data).
2. Wrap the "popup" template's inner with {{#with popupData}}, where popupData is a helper that returns the data context object.

Since this is apparently to display a popup, I would suggest that the easiest is just to put a `{{#with popupData}}` block containing popup markup directly in your HTML file, as a child of <body>. Then the `popupData` helper can return `null` to hide the popup or a data object to show it. No need to directly use `UI.insert` or `UI.render` (unless it's a package that's inserting into an app's HTML).

--Eric

Gadi Cohen

unread,
Mar 4, 2014, 8:51:50 AM3/4/14
to meteo...@googlegroups.com
I went partially along with Tarang's suggestion:

    return Template.blah.extend({ data: context });

It's true that's anti pattern for a Template Helper (don't use it for that!), but if we're just inserting something into the DOM once off with UI.insert, I think this is ok, no? :)

So my final code is like:

    UI.DomRange.insert(UI.render(tpl.extend({data: context})).dom, document.body);

That's a big longer than necessary since UI.insert is missing in blaze-rc0 but was added back into Shark, where it could be:

    UI.insert(UI.render(tpl.extend({data: context})), document.body);

Obviously the use case varies... if a popup is specific to a particular template, I'd definitely makes more sense to embed.

Jan Hansen

unread,
Mar 5, 2014, 5:22:15 PM3/5/14
to meteo...@googlegroups.com
Hi Eric

Thank for the hint. Not that hard when I got my head in gear :-)

I do need the UI.insert etc. though, as this is a general popup spawning thing...

Jan

Gadi Cohen

unread,
Mar 6, 2014, 6:39:27 AM3/6/14
to meteo...@googlegroups.com
This thread unfortunately got split and should be continued here:

Reply all
Reply to author
Forward
0 new messages