при перемещении их на какое-либо событие нулайдеры исчезают - PullRequest
0 голосов
/ 15 сентября 2018

Это очень сильно связано с моим первым вопросом о нуслайдерах: Как обновить div в Meteor без помощника? (этот заголовок был выбран неправильно, потому что речь шла не о том, чтобы избегать помощников)

Ответ, предоставленный Jankapunkt, работает очень хорошо, например, у меня может быть 4 ползунка, и переупорядочение работает без потери состояний ползунка, таких как min / max, например:

switching slider works ok

теперь я хочу, чтобы некоторые элементы не были ползунками, например, измените 1 на выпадающий список:

enter image description here

но когда я нажимаю кнопку переключателя, исчезает 1 ползунок (тот, который перемещается в точку раскрывающегося списка), и в консоли появляется ошибка:

Exception in template helper: Error: noUiSlider (11.1.0): create requires a single element, got: undefined

Я не понимаю, почему добавление if / else имеет какое-то значение ... Помощник ползунка ожидает готовности {{#if ready}} ... {{/ if}}, поэтому он должен работать? Кто-нибудь понимает, почему это не так? и как это исправить?

шаблон на создан теперь выглядит так:

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>

1 Ответ

0 голосов
/ 15 сентября 2018

Прежде всего вы должны знать о ситуации:

  • Вы смешиваете классический рендеринг пользовательского интерфейса (ползунки) с рендерингом Blaze (выпадающий список).Это принесет вам много проблем с дизайном, и приведенное ниже решение - скорее хак, чем чистая аппроксимация с использованием 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 с классическими обновлениями DOM: до этого момента вы столкнетесь с ошибкой.Это происходит главным образом потому, что сейчас наша функция createSliders ожидает элемент div с определенным идентификатором в том месте, где раскрывающийся список был отображен до нажатия переключателя.Этого не будет, потому что на этом этапе недействительность рендеринга Blaze не будет завершена.

Исправление этого с использованием 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>

. Теперь это должно также отображать последнее выбранное состояние раскрывающегося списка.

Сводка

  • Вы можете использовать этот шаблон для включения еще большего количества компонентов ввода.
  • Имейте в виду, что смешивание рендеринга Blaze с традиционными манипуляциями с DOM может усложнитьВаш код много.То же самое относится ко многим другим системам рендеринга / библиотекам / инфраструктурам.
  • Решение setTimeout должно быть последним подходом, который следует использовать, когда другие подходы еще менее осуществимы.
  • Переменная и методнаименование должно всегда представлять их контекст.Если контекст изменится -> переименовать / изменить факторы и методы.
  • Пожалуйста, в следующий раз опубликуйте полный код здесь снова. Ваш другой пост может быть обновлен или удален, а полный код не будетбыть доступным здесь больше, мешая другим находить хорошее решение.
...