nouislider 在某些事件上移动时消失
nouisliders dissapear when moving them on some event
这与我在这里关于nousliders的第一个问题非常相关:(这个标题选得不好,因为它不是关于避免帮助)
Jankapunkt 提供的答案非常有效,例如我可以有 4 个滑块,并且重新排序不会丢失像 min/max 这样的滑块状态:
现在我希望某些元素是 non-sliders,例如将 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>
首先你应该了解的情况是:
- 您正在混合使用 classic ui 渲染(滑块)和 Blaze 渲染(下拉菜单)。这会给你带来很多设计问题,下面的解决方案更像是一个 hack 而不是使用 Blaze 的 API
的干净方法
- 由于您的组件不再只是滑块,您应该重命名变量。否则外国人很难解码你变量的上下文。
- 您的下拉菜单目前没有保存任何值,切换按钮会重置下拉菜单。
- 您无法在按下切换按钮时破坏下拉菜单中的
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。在切换事件上处理多种输入类型
您还应该在切换事件中重命名您的变量。此外,您需要在此处处理不同的输入类型。下拉菜单没有 .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 渲染与 classic DOM 更新混合的问题:直到此时您将 运行 陷入错误。这主要是因为 now 我们的 createSliders
函数将在 之前呈现下拉列表的地方期望具有特定 id 的 div
元素 开关已按下。它不会在那里,因为此时 Blaze 渲染失效不会完成。
在 onCreated
或 onRendered
中使用 autorun
修复此问题很容易增加复杂性甚至弄乱您的代码。一个更简单的解决方案是在这里使用短超时:
Template.MyTemplate.helpers({
// ...
slider (source) {
const instance = Template.instance()
setTimeout(()=> {
createSliders(source.id, source.options, instance)
}, 50)
},
// ...
})
4。奖励:保存下拉菜单的状态
为了保存下拉菜单的状态,您需要挂钩它的 change
事件。因此,您需要为其分配 class 以独立于 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)
}
现在您已经保存了当前的下拉值,但为了在下一次渲染中恢复它,您需要在 html:
中扩展 options
<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
解决方案应该是最后使用的方法。
- 变量和方法命名应始终代表它们的上下文。如果上下文发生变化 -> 重命名/重构变量和方法。
- 请下次再post 完整代码。您的其他 post 可能会更新或删除,完整代码将无法访问在这里,让其他人很难找到好的解决方案。
这与我在这里关于nousliders的第一个问题非常相关:
Jankapunkt 提供的答案非常有效,例如我可以有 4 个滑块,并且重新排序不会丢失像 min/max 这样的滑块状态:
现在我希望某些元素是 non-sliders,例如将 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>
首先你应该了解的情况是:
- 您正在混合使用 classic ui 渲染(滑块)和 Blaze 渲染(下拉菜单)。这会给你带来很多设计问题,下面的解决方案更像是一个 hack 而不是使用 Blaze 的 API 的干净方法
- 由于您的组件不再只是滑块,您应该重命名变量。否则外国人很难解码你变量的上下文。
- 您的下拉菜单目前没有保存任何值,切换按钮会重置下拉菜单。
- 您无法在按下切换按钮时破坏下拉菜单中的
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。在切换事件上处理多种输入类型
您还应该在切换事件中重命名您的变量。此外,您需要在此处处理不同的输入类型。下拉菜单没有 .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 渲染与 classic DOM 更新混合的问题:直到此时您将 运行 陷入错误。这主要是因为 now 我们的 createSliders
函数将在 之前呈现下拉列表的地方期望具有特定 id 的 div
元素 开关已按下。它不会在那里,因为此时 Blaze 渲染失效不会完成。
在 onCreated
或 onRendered
中使用 autorun
修复此问题很容易增加复杂性甚至弄乱您的代码。一个更简单的解决方案是在这里使用短超时:
Template.MyTemplate.helpers({
// ...
slider (source) {
const instance = Template.instance()
setTimeout(()=> {
createSliders(source.id, source.options, instance)
}, 50)
},
// ...
})
4。奖励:保存下拉菜单的状态
为了保存下拉菜单的状态,您需要挂钩它的 change
事件。因此,您需要为其分配 class 以独立于 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)
}
现在您已经保存了当前的下拉值,但为了在下一次渲染中恢复它,您需要在 html:
中扩展options
<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
解决方案应该是最后使用的方法。 - 变量和方法命名应始终代表它们的上下文。如果上下文发生变化 -> 重命名/重构变量和方法。
- 请下次再post 完整代码。您的其他 post 可能会更新或删除,完整代码将无法访问在这里,让其他人很难找到好的解决方案。