Meteor Blaze access Template.contentBlock inside Template.onCreated
Asked Answered
T

1

7

I am writing a custom Blaze block helper with children:

<template name="parent">
    {{> Template.contentBlock ..}}
</template>

<template name="child">
    {{> Template.contentBlock ..}}
</template>

My intended use case would be to have a Template with arbitrary child nodes, that I define in the html file.

{{#parent}}

  {{#child id="child1" title="Child 1"}}
    <p>This is content of child 1</p>
  {{/child}}

  {{#child id="child2" title="Child 2"}}
    <p>This is content of child 2</p>
  {{/child}}

  {{#child id="childN" title="Child N"}}
    <p>This is content of child N</p>
  {{/child}}

{{/parent}}

No problem so far. However, in the parent Template's onCreated / autorun I want to have access to child templates. I want to use this data to dynamically create in the parent Template elements, based

Template.parent.onCreated(function () {
    const instance = this;
    instance.state = new ReactiveDict();

    instance.autorun(function () {
        const contentBlocks = // how?
        instance.state.set("children", contentBlocks);
    });
});

Template.parent.helpers({
    children() {
        return Template.instance().state.get("children");
    }
});

Where children would be used in the parent template as following:

{{#parent}}

  {{#each children}}
    do something with {{this.value}}
  {{/each}}      

  {{#child id="child1" title="Child 1"}}
    <p>This is content of child 1</p>
  {{/child}}

  {{#child id="child2" title="Child 2"}}
    <p>This is content of child 2</p>
  {{/child}}

  {{#child id="childN" title="Child N"}}
    <p>This is content of child N</p>
  {{/child}}

{{/parent}}

What I don't want is to access the contentBlock's content (the <p>) but rather get a list of the added child Templates.

Is that possible with the current Template / Blaze API? The documentation is a bit thin on that point.

It is basically the opposite of this post: How to get the parent template instance (of the current template)


Edit 1: Use parent View's Renderfunction (only partially working)

I found a way to get the parent Template's children but not their data reactively:

// in Template.parant.onCreated -> autorun
const children = instance.view.templateContentBlock.renderFunction()
    .filter(child => typeof child === 'object')
    .map(el => Blaze.getData(el._render()));
console.log(children);
// null, null, null because Blaze.getData(view) does return null

Another approach I found is to used a shared ReactiveVar but both seem to me not clean enough. I just want to get the list of Template instances in the parent's js code.


Edit 2: Use a shared ReactiveVar (only partially working)

It is possible to use a shared ReactiveVar as long as it is in the scope of both Templates:

const _cache = new ReactiveVar({});

Template.parent.onCreated(function () {
    const instance = this;
    instance.state = new ReactiveDict();

    instance.autorun(function () {
        const children = Object.values(_cache.get());
        instance.state.set("children", children);
    });
});

Template.parent.helpers({
    children() {
        return Template.instance().state.get("children");
    }
});

Working (but only rendered once, not reactive):

Template.child.onCreated(function () {
    const instance = this;
    const data = Template.currentData();
    const cache = _cache.get();
    cache[data.id] = data;
    _cache.set(cache);
});

Not working (child autorun is setting values, but new values are not rendered):

Template.child.onCreated(function () {
    const instance = this;
    instance.autorun(function() {
        const instance = this;
        const data = Template.currentData();
        const cache = _cache.get();
        cache[data.id] = data;
        _cache.set(cache);
    });
});
Thanatos answered 7/3, 2018 at 12:35 Comment(0)
J
6

here is what I came up with. Pls let me know if that is what you wanted or if I misunderstood.

main.html:

<body>
    {{> content}}
</body>

<template name="content">
    {{#parent}}

        {{#each children}}
            <p>do something with {{this.id}}</p>
            <p>data: {{this.tmpl.data.title}}</p>
        {{/each}}

        {{#child id="child1" title="Child 1" parentTemplate=this.parentTemplate}}
            <p>This is content of child 1</p>
        {{/child}}

        {{#child id="child2" title="Child 2" parentTemplate=this.parentTemplate }}
            <p>This is content of child 2</p>
        {{/child}}

        {{#child id="childN" title="Child N" parentTemplate=this.parentTemplate }}
            <p>This is content of child N</p>
        {{/child}}

    {{/parent}}
</template>

<template name="parent">
    {{> Template.contentBlock parentTemplate=template}}
</template>

<template name="child">
    {{> Template.contentBlock }}
</template>

main.js

import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';

import './main.html';

Template.content.helpers({
    children() {
        return this.parentTemplate.children.get();
    },
});

Template.parent.onCreated(function () {
    this.children = new ReactiveVar([]);
});

Template.parent.helpers({
    template() {
        return Template.instance();
    }
});

Template.child.onRendered(function () {
    const children = this.data.parentTemplate.children.get();
    children.push({ id: this.data.id, tmpl: this });
    this.data.parentTemplate.children.set(children);
});

Output:

enter image description here

Although it uses ReactiveVar which is not ideal it does not rely on any global and you can put your code in different files, no problem.

Jutta answered 17/3, 2018 at 9:30 Comment(1)
I checked your solution. It is running fine, also with the required reactivity in the . But it also introduces a third template instance. For the fairness I will wait until the bounty period days.Thanatos

© 2022 - 2024 — McMap. All rights reserved.