【问题标题】:nouisliders dissapear when moving them on some eventnouislider 在某些事件中移动它们时消失
【发布时间】:2018-09-14 22:40:09
【问题描述】:

这与我在这里关于 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>

【问题讨论】:

    标签: javascript meteor nouislider


    【解决方案1】:

    首先你应该了解情况:

    • 您正在混合使用经典 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。在切换事件上处理多种输入类型

    您还应该在 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 函数将在按下开关之前在下拉列表被渲染的地方期待一个具有特定ID 的div 元素.它不会在那里,因为此时 Blaze 渲染失效不会完成。

    onCreatedonRendered 中使用autorun 修复此问题会很容易增加复杂性,甚至会弄乱您的代码。一个更简单的解决方案是在这里使用短暂的超时:

    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)
    }
    

    现在您已经保存了当前的下拉值,但为了在下一次渲染中恢复它,您需要在 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 解决方案应该是在其他方法更不可行时使用的最后一种方法。
    • 变量和方法命名应始终代表它们的上下文。如果上下文发生变化 -> 重命名/重构变量和方法。
    • 请下次再在这里发布完整代码。您的其他帖子可能会被更新或删除,完整代码将无法再在这里访问,其他人很难找到好的解决方案。李>

    【讨论】:

    • 添加超时有效,所以我将此标记为正确答案。但我不喜欢它(你自己提到它更像是一种黑客攻击,也是最后的手段)。我想我不应该将经典 UI 和 blaze 混合使用,但因为我真的想使用 nouisliders,所以我现在将使用超时。
    猜你喜欢
    • 2020-04-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-07-23
    • 1970-01-01
    • 2013-04-24
    • 1970-01-01
    • 2019-03-26
    相关资源
    最近更新 更多