Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Meteor Blaze access Template.contentBlock inside Template.onCreated

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);
    });
});
like image 558
Jankapunkt Avatar asked Mar 07 '18 12:03

Jankapunkt


1 Answers

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.

like image 111
tomsp Avatar answered Oct 10 '22 19:10

tomsp