如何在没有助手的情况下更新 Meteor 中的 div?
How to update div in Meteor without helper?
我可以使用 JavaScript 更新 div 没有问题。例如,左侧图像显示了两个 div 的初始值,右侧显示了单击测试按钮后的值。
当我点击测试按钮时,计数器加 1,div 的值改变如下:
var ela = document.getElementById("a");
var elb = document.getElementById("b");
$("#" + ela.id).html("new value " + ela.id + " ");
$("#" + elb.id).html("new value " + elb.id + " ");
到目前为止一切顺利,但现在我想更改 divs 的顺序:当计数器为偶数时,我希望 div a = yellow 位于顶部(div b = 底部灰色), 计数器不均匀时反之.
为简单起见,我在示例中为每个可能的顺序 (a,b) 和 (b,a) 使用了 2 个小数组,并且助手 'order' 将 return 1 of the 2情况取决于计数器值(x%2 是否为零)
不幸的是,结果不是我想要和期望的。 div 正确地改变了位置,但它们的文本不是。第一次点击后计数器从 5 变为 6,所以黄色的 div 从底部移动到顶部,但是 divs 中的文本是错误的,我希望 'new value a' 在黄色 div 向上移动,但我得到 'new value b a' (其他 div 也一样)
此外,当我检查控制台输出中的 divs 时,我看到了奇怪的结果,Meteor 似乎对 div id 感到困惑?看下图,第一个div既是a又是b,同时是黄色和灰色...
有人知道这是为什么吗?我该如何解决?
我知道我可以使用帮助程序在正确的 div 中获取正确的文本。但我的最终目标不是更改 divs 中的文本,而是我想像这样使用 nouislider 创建一个滑块:
var noUiSlider = require('nouislider');
var sliderHtmlElement = document.getElementById("a");
var options = {
start: [0, 100],
range: {
'min': [0],
'max': [100]
}
};
noUiSlider.create(sliderHtmlElement, options);
我的完整测试代码:
<template name="MyTemplate">
{{x}}
<br>
<br>
{{#each order}}
<div class="{{label}}" id="{{label}}"
style="background-color: {{color}};">
start value {{label}}
</div>
<br>
{{/each}}
<button class="test">test</button>
</template>
var order1;
var order2;
Template.MyTemplate.onCreated(function() {
Session.set("x", 5);
var or0 = [];
or0["label"] = "a";
or0["color"] = "yellow";
var or1 = [];
or1["label"] = "b";
or1["color"] = "grey";
order1 = [];
order1[0] = or0;
order1[1] = or1;
order2 = [];
order2[0] = or1;
order2[1] = or0;
});
Template.MyTemplate.events({
'click .test': function(event) {
var varx = Session.get("x") + 1;
Session.set("x", varx);
createSliders();
}
});
Template.MyTemplate.helpers({
x: function() {
return Session.get("x");
},
order: function() {
if (Session.get("x") % 2 === 0) {
return order1;
} else {
return order2;
}
}
});
function createSliders() {
var ela = document.getElementById("a");
var elb = document.getElementById("b");
console.log(ela);
console.log(elb);
$("#" + ela.id).html("new value " + ela.id + " ");
$("#" + elb.id).html("new value " + elb.id + " ");
}
对于 Blaze,您还必须显式导入 .css
文件才能应用样式:
// no slider without css
import 'nouislider/distribute/nouislider.css'
import noUiSlider from 'nouislider'
然后您可以轻松地使用模板的内置 jQuery 来获取目标 div nouislider 将在其中呈现。
考虑以下模板:
<template name="MyTemplate">
<div>
<div id="range"></div>
</div>
{{#if values}}
<div>
<span>values: </span>
<span>{{values}}</span>
</div>
{{/if}}
<button class="test">Show Slider</button>
</template>
现在让我们通过单击按钮将一个新的 nouislider 渲染到 ID 为 range
的 div 中:
Template.MyTemplate.events({
'click .test': function (event, templateInstance) {
createSliders(templateInstance)
},
})
function createSliders (templateInstance) {
// get the target using the template's jQuery
const range = templateInstance.$('#range').get(0)
noUiSlider.create(range, {
start: [0, 100],
range: {
'min': [0],
'max': [100]
},
connect: true
})
}
现在您也可以在此处轻松添加一些反应性数据(但要避免 Session
):
Template.MyTemplate.onCreated(function () {
const instance = this
instance.state = new ReactiveDict()
instance.state.set('values', null)
})
... 并将其与一些数据连接起来。 noUiSlider
允许您连接到它的更新事件,从那里您可以将值传递到状态:
function createSliders (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)
})
}
使用助手将值渲染到模板中:
Template.MyTemplate.helpers({
values () {
return Template.instance().state.get('values')
}
})
导入您自己的 .css
文件以静态设置滑块样式:
#range {
width: 300px;
margin: 14px;
}
或使用 jQuery 的 css 动态设置样式。
更新:在更新的显示列表上正确呈现
您描述的问题是正确的,可以重现。但是也可以避免,使用Template.onRendered
来控制可能发生渲染的点。
我将模板扩展为以下代码:
<template name="MyTemplate">
{{#each sliders}}
<div class="range">
<div>
<span>id:</span>
<span>{{this.id}}</span>
</div>
<div id="{{this.id}}">
{{#if ready}}{{slider this}}{{/if}}
</div>
{{#with values this.id}}
<div>
<span>values: </span>
<span>{{this}}</span>
</div>
{{/with}}
</div>
{{/each}}
<button class="test">Switch Sliders</button>
</template>
现在查看目标 div,它之前只分配了一个 id。现在有一个 {{#if ready}}
标志和一个函数,调用 {{slider this}}
.
现在为了只在 DOM 最初被渲染时渲染,我们需要 Template.onRendered
:
Template.MyTemplate.onRendered(function () {
const instance = this
instance.state.set('ready', true)
})
并将其添加到(更新的)助手中:
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')
}
})
现在我们还有一些问题需要解决。我们只想在开关发生变化时渲染,而不是在值更新时渲染。但是我们需要最新的值,以便将它们重新分配为下一次渲染中的起始位置(否则滑块将设置为起始值 0,100)。
为此,我们稍微更改 onCreated
代码:
Template.MyTemplate.onCreated(function () {
// initial slider states
const 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)
})
现在,如果我们按下切换按钮,想要 a) 删除所有当前滑块及其事件等,以及 b) 将 sliders
数据更新为我们的新(反转)状态:
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)
}
})
因为我们有多个滑块值要分别更新,所以我们还需要更改createSlider
函数中的一些代码:
function createSliders (sliderId, options, templateInstance) {
const $target = $(`#${sliderId}`)
const target = $target.get(0)
//skip if slider is already in target
if ($target.hasClass('noUi-target')) {
return
}
noUiSlider.create(target, options)
target.noUiSlider.on('update', function (values, handle) {
// update values by sliderId
const valuesObj = templateInstance.state.get('values')
valuesObj[sliderId] = values
templateInstance.state.set('values', valuesObj)
})
}
通过使用这种方法,您既有优点也有缺点。
- (+) 模式可用于许多类似的问题
- (+) 不需要
autorun
- (+) 将滑块状态与值状态分开
- (-) 重新渲染可以多次渲染,用户不会注意到,但它会浪费资源,这在移动设备上可能是个问题。
- (-) 在较大的模板上可能会变得过于复杂。 Templates的封装在这里很重要
我可以使用 JavaScript 更新 div 没有问题。例如,左侧图像显示了两个 div 的初始值,右侧显示了单击测试按钮后的值。
当我点击测试按钮时,计数器加 1,div 的值改变如下:
var ela = document.getElementById("a");
var elb = document.getElementById("b");
$("#" + ela.id).html("new value " + ela.id + " ");
$("#" + elb.id).html("new value " + elb.id + " ");
到目前为止一切顺利,但现在我想更改 divs 的顺序:当计数器为偶数时,我希望 div a = yellow 位于顶部(div b = 底部灰色), 计数器不均匀时反之.
为简单起见,我在示例中为每个可能的顺序 (a,b) 和 (b,a) 使用了 2 个小数组,并且助手 'order' 将 return 1 of the 2情况取决于计数器值(x%2 是否为零)
不幸的是,结果不是我想要和期望的。 div 正确地改变了位置,但它们的文本不是。第一次点击后计数器从 5 变为 6,所以黄色的 div 从底部移动到顶部,但是 divs 中的文本是错误的,我希望 'new value a' 在黄色 div 向上移动,但我得到 'new value b a' (其他 div 也一样)
此外,当我检查控制台输出中的 divs 时,我看到了奇怪的结果,Meteor 似乎对 div id 感到困惑?看下图,第一个div既是a又是b,同时是黄色和灰色...
有人知道这是为什么吗?我该如何解决?
我知道我可以使用帮助程序在正确的 div 中获取正确的文本。但我的最终目标不是更改 divs 中的文本,而是我想像这样使用 nouislider 创建一个滑块:
var noUiSlider = require('nouislider');
var sliderHtmlElement = document.getElementById("a");
var options = {
start: [0, 100],
range: {
'min': [0],
'max': [100]
}
};
noUiSlider.create(sliderHtmlElement, options);
我的完整测试代码:
<template name="MyTemplate">
{{x}}
<br>
<br>
{{#each order}}
<div class="{{label}}" id="{{label}}"
style="background-color: {{color}};">
start value {{label}}
</div>
<br>
{{/each}}
<button class="test">test</button>
</template>
var order1;
var order2;
Template.MyTemplate.onCreated(function() {
Session.set("x", 5);
var or0 = [];
or0["label"] = "a";
or0["color"] = "yellow";
var or1 = [];
or1["label"] = "b";
or1["color"] = "grey";
order1 = [];
order1[0] = or0;
order1[1] = or1;
order2 = [];
order2[0] = or1;
order2[1] = or0;
});
Template.MyTemplate.events({
'click .test': function(event) {
var varx = Session.get("x") + 1;
Session.set("x", varx);
createSliders();
}
});
Template.MyTemplate.helpers({
x: function() {
return Session.get("x");
},
order: function() {
if (Session.get("x") % 2 === 0) {
return order1;
} else {
return order2;
}
}
});
function createSliders() {
var ela = document.getElementById("a");
var elb = document.getElementById("b");
console.log(ela);
console.log(elb);
$("#" + ela.id).html("new value " + ela.id + " ");
$("#" + elb.id).html("new value " + elb.id + " ");
}
对于 Blaze,您还必须显式导入 .css
文件才能应用样式:
// no slider without css
import 'nouislider/distribute/nouislider.css'
import noUiSlider from 'nouislider'
然后您可以轻松地使用模板的内置 jQuery 来获取目标 div nouislider 将在其中呈现。
考虑以下模板:
<template name="MyTemplate">
<div>
<div id="range"></div>
</div>
{{#if values}}
<div>
<span>values: </span>
<span>{{values}}</span>
</div>
{{/if}}
<button class="test">Show Slider</button>
</template>
现在让我们通过单击按钮将一个新的 nouislider 渲染到 ID 为 range
的 div 中:
Template.MyTemplate.events({
'click .test': function (event, templateInstance) {
createSliders(templateInstance)
},
})
function createSliders (templateInstance) {
// get the target using the template's jQuery
const range = templateInstance.$('#range').get(0)
noUiSlider.create(range, {
start: [0, 100],
range: {
'min': [0],
'max': [100]
},
connect: true
})
}
现在您也可以在此处轻松添加一些反应性数据(但要避免 Session
):
Template.MyTemplate.onCreated(function () {
const instance = this
instance.state = new ReactiveDict()
instance.state.set('values', null)
})
... 并将其与一些数据连接起来。 noUiSlider
允许您连接到它的更新事件,从那里您可以将值传递到状态:
function createSliders (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)
})
}
使用助手将值渲染到模板中:
Template.MyTemplate.helpers({
values () {
return Template.instance().state.get('values')
}
})
导入您自己的 .css
文件以静态设置滑块样式:
#range {
width: 300px;
margin: 14px;
}
或使用 jQuery 的 css 动态设置样式。
更新:在更新的显示列表上正确呈现
您描述的问题是正确的,可以重现。但是也可以避免,使用Template.onRendered
来控制可能发生渲染的点。
我将模板扩展为以下代码:
<template name="MyTemplate">
{{#each sliders}}
<div class="range">
<div>
<span>id:</span>
<span>{{this.id}}</span>
</div>
<div id="{{this.id}}">
{{#if ready}}{{slider this}}{{/if}}
</div>
{{#with values this.id}}
<div>
<span>values: </span>
<span>{{this}}</span>
</div>
{{/with}}
</div>
{{/each}}
<button class="test">Switch Sliders</button>
</template>
现在查看目标 div,它之前只分配了一个 id。现在有一个 {{#if ready}}
标志和一个函数,调用 {{slider this}}
.
现在为了只在 DOM 最初被渲染时渲染,我们需要 Template.onRendered
:
Template.MyTemplate.onRendered(function () {
const instance = this
instance.state.set('ready', true)
})
并将其添加到(更新的)助手中:
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')
}
})
现在我们还有一些问题需要解决。我们只想在开关发生变化时渲染,而不是在值更新时渲染。但是我们需要最新的值,以便将它们重新分配为下一次渲染中的起始位置(否则滑块将设置为起始值 0,100)。
为此,我们稍微更改 onCreated
代码:
Template.MyTemplate.onCreated(function () {
// initial slider states
const 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)
})
现在,如果我们按下切换按钮,想要 a) 删除所有当前滑块及其事件等,以及 b) 将 sliders
数据更新为我们的新(反转)状态:
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)
}
})
因为我们有多个滑块值要分别更新,所以我们还需要更改createSlider
函数中的一些代码:
function createSliders (sliderId, options, templateInstance) {
const $target = $(`#${sliderId}`)
const target = $target.get(0)
//skip if slider is already in target
if ($target.hasClass('noUi-target')) {
return
}
noUiSlider.create(target, options)
target.noUiSlider.on('update', function (values, handle) {
// update values by sliderId
const valuesObj = templateInstance.state.get('values')
valuesObj[sliderId] = values
templateInstance.state.set('values', valuesObj)
})
}
通过使用这种方法,您既有优点也有缺点。
- (+) 模式可用于许多类似的问题
- (+) 不需要
autorun
- (+) 将滑块状态与值状态分开
- (-) 重新渲染可以多次渲染,用户不会注意到,但它会浪费资源,这在移动设备上可能是个问题。
- (-) 在较大的模板上可能会变得过于复杂。 Templates的封装在这里很重要