Search code examples
templatesmeteoriron-router

Best practice for dynamically rendering element outside of template


What is the best way of toggling the presence of some element (represented by a template) that is a direct child of <body>?

I'm talking about a modal box, notification, light-box etc. that is triggered either by some user event or by a route.

Examples: Dialog for newsletter sign-up that is shown after a user clicks a button. Overlay for content editing that is triggered by appending /edit to the item's route


Edit:

The first solution I though of was using Session to control state and then lining up all the application's modals and messages inside #if statements at the end of my main layout template like this:

<template name="layout">
  <!-- yields and stuff -->

  {{#if isActiveModal 'editArticle'}}{{> editArticle}}{{/if}}
  {{#if ...
</template>

The problem is modularity; if a teammate is working inside some page template and needs to display a specific message or dialog, he or she has to edit the main layout to add it. I would have liked some easy way of conditionally appending a template to <body>.

Should be possible with the tools at hand, shouldn't it?


Solution

  • Since I posted the question, Blaze has become more sophisticated. Elements can now be rendered anywhere in the DOM while keeping a logical view hierarchy. See this discussion:

    https://forums.meteor.com/t/most-useful-meteor-apis/1560/8

    I've inserted the main outline below – the full thread also has code examples.


    ...

    [Use] Blaze.render to output a template anywhere in the DOM – such as in a dimmable wrapper directly inside <body> – while maintaining a parent-child relationship between the view of the opening template and the view of the modal. This relationship is required for them to communicate directly.

    (Bear in mind that a Session variable, among other limitations, can only hold serializable data...)

    With the hierarchy intact, it's possible to set up shared reactivity between the modal and the background and call methods on the other, too. If we volunteered to use Blaze.renderWithData, then the two templates can even share the same data context!

    Manually traversing the view hierarchy is pretty tedious, but with the parent() method from aldeed:template-extension, it isn't an issue.

    Depending on whether one likes to use template instances as a 'view-model' or prefers to pass stuff around, reciprocal reactivity between the two templates can either be mediated by a new ReactiveVar assigned to a property on the parent template instance or view, or by a more retrieve-on-demand TemplateVar.

    The entire exercise with Blaze.render can be duly incapsulated by using tricks like block helper templates and keyword arguments, so that everything is kept declarative.