这是非常相关的,以我的第一个问题关于nousliders这里: 如何流星更新DIV没有帮手? (这个称号没有得到很好的选择,因为它不是关于避免佣工)
通过Jankapunkt提供答案的作品非常好,比如我可以有4个滑块,并重新排序工作没有松动的滑块国家如最小值/最大值是这样的:
现在我要一些元件的是非滑块,例如改变1至下拉:
但是当我点击切换按钮,滑块1个自败(即在移动到下拉点之一),我也得到控制台的错误:
Exception in template helper: Error: noUiSlider (11.1.0): create requires a single element, got: undefined
我不明白为什么增加的if / else有什么差别......滑块助手正在等待准备{{#如果准备}} ... {{/ 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)
})
和模板现在看起来是这样,有一个如果else语句显示下拉或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渲染(下拉列表)。 这会为你带来很多的设计问题和下面的解决方案更多的是使用火焰的API比干净appraoch黑客
- 由于您的组件不仅滑了,你应该重命名变量。 否则,就很难对外籍人士来解码你的变量的情况下。
- 您的下拉目前已没有保存值,开关按钮复位下拉。
- 你不能破坏
noUiSlider
击中开关按钮时,给你造成上述错误的下拉列表。
因此,我想给你的第一重组你的代码一些建议。
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.手柄上开关事件的多个输入类型
你也应该在切换事件重命名变量。 Furhtermore,你需要在这里处理不同的输入类型。 下拉菜单没有.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.正确的渲染/更新显示列表
现在到混合火焰与经典的DOM更新渲染的问题:直到此时,你会遇到一个错误。 这主要是因为我们现在的createSliders
功能将期待一个div
元素与其中已经按下开关前的下拉菜单已经呈现了地方一定的ID。 它不会在那里,因为火焰渲染失效不会在此时完成。
修复此使用autorun
在onCreated
或onRendered
容易增加复杂性,甚至打乱了你的代码。 一个简单的办法就是在这里使用一个短超时:
Template.MyTemplate.helpers({
// ...
slider (source) {
const instance = Template.instance()
setTimeout(()=> {
createSliders(source.id, source.options, instance)
}, 50)
},
// ...
})
4,奖励:节约下拉的状态
为了节省下拉的状态,你需要挂接到它的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>
现在,这应该也显示下拉的最后选择的状态。
摘要
- 您可以使用这一模式甚至包括多个输入组件。
- 要知道,混合火焰呈现与传统的DOM操作可以提高你的代码有很多的复杂性。 这同样适用于许多其他渲染系统/库/框架存在。
- 该
setTimeout
解决方案应该是要使用的最后一种方法,当其他方法甚至不太可行。 - 变量和方法命名要始终代表他们的背景。 >重命名/重构变量和方法 - 如果环境的变化。
- 请下次在这里再次张贴的完整代码。 您的其他职位可能会被更新或删除和完整的代码将无法访问在这里了,很难为别人找到一个很好的解决方案。