Reputation: 5158
I'm working on a reactive dependence tree on Meteor (with Blaze), but I didn't found the best way to do it.
First, the problem
On this example I have container who set an item
Object, who have an attribute elements
(Array).
The elements
attribute is loop, and a child template is loaded for every instance of it.
The element can be update on the child template, but the update isn't propagate to the container.
Also, I added an Add
button to update the item
ReactiveVar (from the container
). Each field rendering is updated after item
is updated on container, so the object as been updated but not re-rendered.
HTML
<template name="container">
{{#let item=getItem}}
<h1>Update {{item._id}}</h1>
{{#each element in item.elements}}
<div>
{{> elementForm element}}
<small>{{element.name}}</small>
</div>
{{/each}}
{{/let}}
<button name="add">Add</button>
</template>
<template name="elementForm">
{{#let el=getEl}}
<input value="{{el.name}}"/>
{{/let}}
</template>
Parent Template
Template.container.onCreated(function onContainerCreated() {
// From data
let item = {
_id: '123abc',
elements: [
{name: "El #1"},
{name: "El #2"},
{name: "El #3"},
]
}
// Create master ReactiveVar
this.item = new ReactiveVar(item);
});
Template.container.helpers({
getItem() {
return Template.instance().item.get();
},
});
Template.container.events({
// Add a new element (Will refresh all elements render)
'click button[name="add"]'(e) {
let instance = Template.instance()
let item = instance.item.get()
item.elements.push({name: "new El"})
instance.item.set(item)
},
})
Child template
/* Element */
Template.elementForm.onCreated(function onElementCreated() {
this.el = new ReactiveVar(this.data)
});
Template.elementForm.helpers({
getEl() {
return Template.instance().el.get()
}
})
Template.elementForm.events({
'change input'(e) {
let instance = Template.instance()
let el = instance.el.get()
el.name = e.currentTarget.value
instance.el.set(el)
}
})
Preview
As meteor can't be integrate on Embed code, I make some images.
Default page
Update input didn't affect container render
Add a new item, will re render all the container and get new values
Solution 1 ? Send item
ReactiveVar to the child
I know than sending the entire ReactiveVar to a child can be a solution to use the .set()
method on the child.
But if I use this hack, I will have to send the entire item
to each elementForm
(not the best)
Exemple
/* Html */
<Template name="parentTemplate">
<small>{{getFoo}}</small>
{{> childTemplate getItem}}
</Template>
<Template name="childTemplate">
<input value="{{getFoo}}" />
</Template>
/* Parent */
Template.parentTemplate.onCreated(function onParentCreated() {
this.item = new ReactiveVar({ foo: bar})
});
Template.parentTemplate.helpers({
getFoo() {
return Template.instance().item.get().foo
},
getItem() {
return Template.instance().item // Send reactive var, net value (.get)
}
})
/* Child */
Template.childTemplate.onCreated(function onChildCreated() {
this.item = this.data.item // Use Parent Reactive var
});
Template.childTemplate.helpers({
getFoo() {
return Template.instance().item.get().foo
},
})
Template.childTemplate.events({
'change input'(e) {
let item = Template.instance().item.get()
item.foo = e.currentTarget.value
Template.instance().item.set(item) // Will update parent
},
})
Solution 2 ? Callback
An other solution is to send to the child template, in addition to element data, a callback for each action (like change) to inform the parent template to update the master ReactiveVar for a specific element.
Example
/* Html */
<Template name="parentTemplate">
<small>{{getFoo}}</small>
{{> childTemplate getData}}
</Template>
<Template name="childTemplate">
<input value="{{getFoo}}" />
</Template>
/* Parent */
Template.parentTemplate.onCreated(function onParentCreated() {
this.item = new ReactiveVar({ foo: bar})
});
Template.parentTemplate.helpers({
getFoo() {
return Template.instance().item.get().foo
},
getData() {
let instance = Template.instance()
return {
item: instance.item.get(),
onChange: function(val) {
// Update item here
let item = instance.item.get()
item.foo = val
instance.item.set(item)
}
}
}
})
/* Child */
Template.childTemplate.onCreated(function onChildCreated() {
});
Template.childTemplate.helpers({
getFoo() {
return Template.instance().data.item.foo
},
})
Template.childTemplate.events({
'change input'(e) {
// Call parent callback
Template.instance().data.onChange(e.currentTarget.value)
},
})
Final Question
What's the best way to handle Parent/Child Template to handle a list of element from a item object ?
Upvotes: 1
Views: 963
Reputation: 1687
Actually a good question.
IMHO depending on the case I mostly do kind of your solution 1.
Something like:
Template.container.onCreated(function(){
//each elements are ReactiveVar
let item = {
_id: '123abc',
elements: [
{name: new ReactiveVar("El #1")},
{name: new ReactiveVar("El #2")},
{name: new ReactiveVar("El #3")},
]
}
// I don't wrap the wole item on a ReactiveVar if I don't need it.
});
Then, when you iterate on it with your #each
, you will inject only the current element data into the datacontext of your child. From here, you can easily do:
Template.childTemplate.helpers({
getName() {
return this.name.get();
},
})
Template.childTemplate.events({
'change input'(e, tp) {
newVal = e.currentTarget.value
tp.currentData().name.set(newVal);
}
});
I used some "shortcut" as Template.currentData()
and direct datacontext access on helper with this.name.get()
.
You also can have direct access to the template instance as second parameter of an event:
'change input'(event, templateInstance) {}
I did not test it, I only give you the idea.
Hope this will help you.
Upvotes: 1