javascript - nouislider 在某些事件中移动时消失
问题描述
这与我在这里关于 nousliders 的第一个问题非常相关:How to update div in Meteor without helper? (这个标题选得不好,因为它不是为了避免帮助)
Jankapunkt 提供的答案非常有效,例如我可以有 4 个滑块,并且重新排序不会丢失滑块状态,例如 min/max,如下所示:
现在我希望一些元素是非滑块,例如将 1 更改为下拉列表:
但是当我单击切换按钮时,1 个滑块消失(移动到下拉点的那个),并且在控制台中出现错误:
Exception in template helper: Error: noUiSlider (11.1.0): create requires a single element, got: undefined
我不明白为什么添加 if/else 会有什么不同...滑块助手正在等待准备就绪 {{#if ready}}...{{/if}} 所以它应该可以工作?任何人都明白为什么不?以及如何解决?
模板 onCreated 现在看起来像这样:
Template.MyTemplate.onCreated(function() {
const sliders = [{
id: 'slider-a',
prio: 1,
type: "NumberRange",
options: {
start: [0, 100],
range: {
'min': [0],
'max': [100]
},
connect: true
}
}, {
id: 'slider-b',
prio: 2,
type: "NumberRange",
options: {
start: [0, 100],
range: {
'min': [0],
'max': [100]
},
connect: true
}
}, {
id: 'dropdown-c',
prio: 3,
type: "Dropdown"
}, {
id: 'slider-d',
prio: 4,
type: "NumberRange",
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)
})
模板现在看起来像这样,有一个 if else 语句来显示 Dropdown 或 NumberRange(滑块):
<template name="MyTemplate">
{{#each sliders}}
<div class="range">
<div>
<span>id:</span>
<span>{{this.id}}</span>
</div>
{{#if $eq type 'Dropdown'}}
<select id="{{this.id}}" style="width: 200px;">
<option value="">a</option>
<option value="">b</option>
<option value="">c</option>
</select>
{{else if $eq type 'NumberRange'}}
<div id="{{this.id}}">
{{#if ready}}{{slider this}}{{/if}}
</div>
{{/if}}
{{#with values this.id}}
<div>
<span>values: </span>
<span>{{this}}</span>
</div>
{{/with}}
</div>
{{/each}}
<button class="test">Switch Sliders</button>
</template>
解决方案
首先,您应该了解以下情况:
- 您正在将经典 ui 渲染(滑块)与 Blaze 渲染(下拉菜单)混合。这会给你带来很多设计问题,下面的解决方案更像是一种 hack,而不是使用 Blaze 的 API 的干净方法
- 由于您的组件不再只是滑块,您应该重命名变量。否则外国人很难解码你的变量的上下文。
- 您的下拉列表当前没有保存任何值,切换按钮会重置下拉列表。
- 按下切换按钮时,您无法破坏
noUiSlider
下拉列表中的 a ,从而导致上述错误。
因此,我想先给你一些关于重构代码的建议。
1.重命名变量
您可以使用 IDE 的重构功能轻松重命名所有变量名。如果您的 IDE / 编辑器中没有这样的功能,我强烈建议您启动搜索引擎来获得一个。
由于您的输入类型比滑块多,因此您应该使用更通用的名称,例如inputs
表示更广泛的可能类型。
您的下拉列表中还应该有一个value
条目,重新渲染时确实能够恢复上次选择状态:
Template.MyTemplate.onCreated(function () {
const inputs = [{
id: 'slider-a',
prio: 1,
type: 'NumberRange',
options: {
start: [0, 100],
range: {
'min': [0],
'max': [100]
},
connect: true
}
}, {
id: 'slider-b',
prio: 2,
type: 'NumberRange',
options: {
start: [0, 100],
range: {
'min': [0],
'max': [100]
},
connect: true
}
}, {
id: 'dropdown-c',
prio: 3,
type: 'Dropdown',
value: '', // default none
}, {
id: 'slider-d',
prio: 4,
type: 'NumberRange',
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('inputs', inputs)
})
现在您还必须重命名您的助手和模板助手调用:
<template name="MyTemplate">
{{#each inputs}}
...
{{/each}}
</template>
Template.MyTemplate.helpers({
inputs () {
return Template.instance().state.get('inputs')
},
...
})
2. 处理切换事件的多种输入类型
您还应该在 switch 事件中重命名变量。此外,您需要在此处处理不同的输入类型。下拉列表没有.noUiSlider
属性,它们也接收不是一个数组而是一个字符串变量作为值:
'click .test': function (event, templateInstance) {
let inputs = templateInstance.state.get('inputs')
const values = templateInstance.state.get('values')
// remove current rendered inputs
// and their events / prevent memory leak
inputs.forEach(input => {
if (input.type === 'Dropdown') {
// nothing to manually remove
// Blaze handles this for you
return
}
if (input.type === 'NumberRange') {
const target = templateInstance.$(`#${input.id}`).get(0)
if (target && target.noUiSlider) {
target.noUiSlider.off()
target.noUiSlider.destroy()
}
}
})
// assign current values as
// start values for the next newly rendered
// inputs
inputs = inputs.map(input => {
const currentValues = values[input.id]
if (!currentValues) {
return input
}
if (input.type === 'Dropdown') {
input.value = currentValues
}
if (input.type === 'NumberRange') {
input.options.start = currentValues.map(n => Number(n))
}
return input
}).reverse()
templateInstance.state.set('inputs', inputs)
},
3.正确渲染/更新显示列表
现在出现了将 Blaze 渲染与经典 DOM 更新混合使用的问题:在此之前,您将遇到错误。这主要是因为现在我们的createSliders
函数将期望在按下开关之前div
在下拉列表已呈现的位置有一个具有特定 id 的元素。它不会在那里,因为此时 Blaze 渲染失效不会完成。
使用autorun
in onCreated
or来解决这个问题onRendered
很容易增加复杂性,甚至弄乱你的代码。一个更简单的解决方案是在这里使用短暂的超时:
Template.MyTemplate.helpers({
// ...
slider (source) {
const instance = Template.instance()
setTimeout(()=> {
createSliders(source.id, source.options, instance)
}, 50)
},
// ...
})
4. Bonus:保存下拉菜单的状态
为了保存下拉菜单的状态,您需要挂钩它的change
事件。因此,您需要为其分配一个类以独立于id
:
<select id="{{this.id}}" class="dropdown" style="width: 200px;">...</select>
现在您可以为其创建一个事件:
'change .dropdown'(event, templateInstance) {
const $target = templateInstance.$(event.currentTarget)
const value = $target.val()
const targetId = $target.attr('id')
const valuesObj = templateInstance.state.get('values')
valuesObj[targetId] = value
templateInstance.state.set('values', valuesObj)
}
现在您已经保存了当前的下拉值,但是为了在下一次渲染中恢复它,您需要options
在 html 中扩展:
<select id="{{this.id}}" class="dropdown" style="width: 200px;">
<option value="a" selected="{{#if $eq this.value 'a'}}selected{{/if}}">a</option>
<option value="b" selected="{{#if $eq this.value 'b'}}selected{{/if}}">b</option>
<option value="c" selected="{{#if $eq this.value 'c'}}selected{{/if}}">c</option>
</select>
这现在也应该显示下拉列表的最后选择状态。
概括
- 您可以使用此模式来包含更多输入组件。
- 请注意,将 Blaze 渲染与传统的 DOM 操作相结合会大大增加代码的复杂性。同样适用于那里的许多其他渲染系统/库/框架。
setTimeout
当其他方法更不可行时,该解决方案应该是最后使用的方法。- 变量和方法命名应该始终代表它们的上下文。如果上下文发生变化 -> 重命名/重构变量和方法。
- 请下次再次在此处发布完整代码。您的其他帖子可能会被更新或删除,并且无法再在此处访问完整的代码,这使得其他人很难找到一个好的解决方案。
推荐阅读
- python - Django ORM - 使用用户外键更新特定实例
- python - 将下一行的列值添加到 groupby 中的当前行的更快方法
- python - shapely object.buffer(0) 返回一个空多边形(坐标:[]),并丢弃主多边形
- xts - Quantstrat applystrategy 不正确的维度尝试使用手动 mktdata OHCLV 数据与 getSymbols
- javascript - React 路由:URL 正在更改,但页面未加载
- python - 无法使用批处理脚本激活 CONDA 环境
- c++ - 如何将参数传递给排序的比较函数?
- twilio - Twilio 可编程聊天用户频道限制 - 最佳实践?
- java - Android - 两位小数
- c++ - Prim的算法在c ++中使用邻接表