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 渲染失效不会完成。

onCreatedonRendered 中使用 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 可能会更新或删除,完整代码将无法访问在这里,让其他人很难找到好的解决方案。