How To Update Div In Meteor Without Helper?
Solution 1:
With Blaze you have to explicitly import the .css
file as well in order to get the styles applied:
// no slider without cssimport'nouislider/distribute/nouislider.css'import noUiSlider from'nouislider'
Then you can easily use the Template's builtin jQuery to get a target div where nouislider will render.
Consider the following template:
<template name="MyTemplate">
<div><divid="range"></div></div>
{{#if values}}
<div>
<span>values: </span><span>{{values}}</span>
</div>
{{/if}}
<button class="test">ShowSlider</button>
</template>
Now let's render a new nouislider into the div with id range
by clicking the button:
Template.MyTemplate.events({
'click .test': function (event, templateInstance) {
createSliders(templateInstance)
},
})
functioncreateSliders (templateInstance) {
// get the target using the template's jQueryconst range = templateInstance.$('#range').get(0)
noUiSlider.create(range, {
start: [0, 100],
range: {
'min': [0],
'max': [100]
},
connect: true
})
}
Now you could also easily add some reactive data here (but avoid Session
):
Template.MyTemplate.onCreated(function () {
const instance = this
instance.state = newReactiveDict()
instance.state.set('values', null)
})
... and connect it with some data. The noUiSlider
allows you to hook into it's update event, from where you can pass the values into the state:
functioncreateSliders (templateInstance) {
// get slider, render slider...// ...
range.noUiSlider.on('update', function (values, handle) {
// update values, for instance use a reactive dict
templateInstance.state.set('values', values)
})
}
Render the value into the template using a helper:
Template.MyTemplate.helpers({
values () {
returnTemplate.instance().state.get('values')
}
})
Import your own .css
files to statically style the slider like so:
#range {
width: 300px;
margin: 14px;
}
or style it dynamically using jQuery's css.
UPDATE: Correct rendering on updated display list
The issue you described is correct and can be reproduced. However it can also be prevented, using Template.onRendered
to control the point when rendering may occur.
I extended the Template to the following code:
<templatename="MyTemplate">
{{#each sliders}}
<divclass="range"><div><span>id:</span><span>{{this.id}}</span></div><divid="{{this.id}}">
{{#if ready}}{{slider this}}{{/if}}
</div>
{{#with values this.id}}
<div><span>values: </span><span>{{this}}</span></div>
{{/with}}
</div>
{{/each}}
<buttonclass="test">Switch Sliders</button></template>
Now look inside the target div, which previously had only an id assigned. Now there is a {{#if ready}}
flag and a function, call to {{slider this}}
.
Now in order to render only when the DOM has initially been rendered, we need Template.onRendered
:
Template.MyTemplate.onRendered(function () {
const instance = this
instance.state.set('ready', true)
})
and add it to the (updated) helpers:
Template.MyTemplate.helpers({
sliders () {
return Template.instance().state.get('sliders')
},
values (sliderId) {
return Template.instance().state.get('values')[sliderId]
},
slider (source) {
createSliders(source.id, source.options, Template.instance())
},
ready() {
return Template.instance().state.get('ready')
}
})
Now we have some more issues here that need to be resolved. We only want to render if the switch changes but not if the values update. But we need the latest values in order to re-assign them as start position in the next render (otherwise the sliders would be set with the start values 0,100).
To do that we change the onCreated
code a bit:
Template.MyTemplate.onCreated(function () {
// initial slider statesconst sliders = [{
id: 'slider-a',
options: {
start: [0, 100],
range: {
'min': [0],
'max': [100]
},
connect: true
}
}, {
id: 'slider-b',
options: {
start: [0, 100],
range: {
'min': [0],
'max': [100]
},
connect: true
}
},
]
const instance = this
instance.state = new ReactiveDict()
instance.state.set('values', {}) // mapping values by sliderId
instance.state.set('sliders', sliders)
})
Now if we press the switch button want a) delete all current sliders with their events etc. and b) update the sliders
data to our new (reversed) state:
Template.MyTemplate.events({
'click .test': function (event, templateInstance) {
let sliders = templateInstance.state.get('sliders')
const values = templateInstance.state.get('values')
// remove current rendered sliders// and their events / prevent memory leak
sliders.forEach(slider => {
const target = templateInstance.$(`#${slider.id}`).get(0)
if (target && target.noUiSlider) {
target.noUiSlider.off()
target.noUiSlider.destroy()
}
})
// assign current values as// start values for the next newly rendered// sliders
sliders = sliders.map(slider => {
const currentValues = values[slider.id]
if (currentValues) {
slider.options.start = currentValues.map(n =>Number(n))
}
return slider
}).reverse()
templateInstance.state.set('sliders', sliders)
}
})
Because we have multiple slider values to be updated separately, we also need to change some code in the createSlider
function:
functioncreateSliders (sliderId, options, templateInstance) {
const$target = $(`#${sliderId}`)const target = $target.get(0)
//skip if slider is already in targetif ($target.hasClass('noUi-target')) {
return
}
noUiSlider.create(target, options)
target.noUiSlider.on('update', function (values, handle) {
// update values by sliderIdconst valuesObj = templateInstance.state.get('values')
valuesObj[sliderId] = values
templateInstance.state.set('values', valuesObj)
})
}
By using this approach you some advantages and some disadvantages.
- (+) pattern can be used for many similar problems
- (+) no
autorun
required - (+) separated slider state from value state
- (-) rendering can occur more than once for a re-render, users wont notice but it wastes resources, which can be an issue on mobile.
- (-) can become overly complex on larger templates. Encapsulation of Templates is very important here.
Post a Comment for "How To Update Div In Meteor Without Helper?"