Having worked a lot with AngularJS over the past 2+ years and starting to get into MeteorJS 1.2 at the moment there are some aspects of the blaze templating layer that feel quite unintuitive to me but probably have an easy solution.
I want to have a "Create poll" form which has the following fields:
<li>s based on a local options arrayIn essence I want to be able to populate the poll option field, click "add option" and have this option render in the list repeater below the poll option text field in a readonly fashion (plain text).
My issue currently is that without using something like ReactiveArray the blaze template does not rerender when I push items in the local array variable options.
What is the correct way of going about this?
Code below:
poll.create.html template:
<template name="pollCreate">
<form class="form form--create">
<section class="form__section">
<!-- Question Field -->
<input class="form__field" type="text" name="question" required>
<!-- Poll Options -->
<ul class="form__list">
<!-- Poll option form field -->
<li class="form__list-item form__list-item--creator">
<input class="form__field" type="text" name="pollOption">
<button class="form__button--add" type="button">+</button>
</li>
<!-- Render the options added above -->
{{#each option in optionList}}
<li class="form__list-item--option" data-index="{{option.index}}">
<span class="form__list-item-value">{{option.label}}</span>
<button type="button" class="form__button--delete">×</button>
</li>
{{/each}}
</ul>
</section>
<button class="form__button--submit" type="submit">Create Poll</button>
<button class="form__button--reset" type="reset">Clear Fields</button>
</form>
</template>
poll.create.client.js (Polls = new Mongo.Collection("polls") is in a server file)
// Setup
Template.pollCreate.onCreated(function () {
// Local poll variable to capture the options in.
this.options = [
{ index: 0, label: 'BurgerBurger' },
{ index: 1, label: 'BetterBurger' }
];
});
// Events
Template.pollCreate.events({
/**
* Adds an option to the local array for saving as part of the submit in the Posts.insert call.
* @param event
* @param instance
*/
'click .form__button--add': function ( event, instance ) {
event.preventDefault();
// Get current option element
var option = instance.find('[name="pollOption"]');
// Store in a local array
instance.options.push({
index: instance.options.length,
label: option.value
});
// Clear field so it's ready for another option
option.value = '';
},
/**
* Delete an existing option.
* @param event
* @param instance Template.instance
*/
'click .form__button--delete': function ( event, instance ) {
event.preventDefault();
var listItemElement = event.target.parentNode;
var itemIndex = instance.$(listItemElement).data('index');
// Delete option from reactive source array
instance.options.splice(itemIndex, 1);
},
/**
* Submit the Poll Create form which will run Polls.insert with the question and options
* @param event
* @param instance
*/
'submit .form--create': function ( event, instance ) {
event.preventDefault();
var question = instance.find('[name="question"]');
var options = instance.options;
var poll = {
question: question.value,
options: options
};
Polls.insert(poll);
}
});
// Helpers
Template.pollCreate.helpers({
optionList: function () {
const instance = Template.instance();
return instance.options;
}
});
I can see the instance.options array values changing as I push or splice options in and out of it but it doesn't seem to re-render the template and the list of options never changes.
What am I doing wrong here?
Whenever you want the ui to reactively update you need to have two things, (1) a reactive data source and (2) a reactive context.
Right now you've got the reactive context - that's the Template helper optionList which will re-render any time a reactive data source referenced by it changes. The problem is the option list is not a reactive data source, it's just a vanilla array. So when the Templace.instance().options array changes, the helper does not know to rerender it.
The fix is very easy - just make Template.instance.options a reactive data source by using one of Meteor's built in reactive structures ReactiveVar or ReactiveDict or by using MiniMongo. ReactiveArray which you mentioned would work too - I believe that's community maintained and not a core package, but it's the same idea.
The quickest and easiest solution here is to save the whole array to one ReactiveVar:
Template.pollCreate.onCreated(function(){
this.options = new ReactiveVar([
{ index: 0, label: 'BurgerBurger' },
{ index: 1, label: 'BetterBurger' }
]);
});
Template.pollCreate.helpers({
optionList({
return Template.instance().options.get();
})
});
That ought to do it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With