Восстановление данных формы

Восстановление состояния формы с инициализированными экземплярами Choices.js требует учёта того, что библиотека заменяет стандартные <select> и <input> собственным управляемым состоянием. Простое восстановление значений через установку value у DOM-элемента часто не приводит к синхронизации интерфейса, поэтому требуется работа через API экземпляра и корректная последовательность инициализации.

Основная задача восстановления — корректная сериализация выбранных значений. В Choices.js данные могут представлять собой:

  • одиночное значение (single select)
  • массив значений (multiple select)
  • объекты с дополнительными полями (при использовании choices или remote data)

Наиболее устойчивый способ хранения — преобразование состояния в JSON.

const choices = new Choices('#city-select', {
  removeItemButton: true
});

function getState(instance) {
  return instance.getValue(true); // возвращает массив значений или одно значение
}

function saveState(instance, key) {
  const state = getState(instance);
  localStorage.setItem(key, JSON.stringify(state));
}

Метод getValue(true) предпочтителен, так как возвращает «чистые» значения без DOM-обвязки.


Восстановление состояния через API Choices

После загрузки страницы восстановление нельзя выполнять до инициализации экземпляра. Любая попытка установить значение до создания объекта Choices приводит к потере синхронизации UI.

const choices = new Choices('#city-select', {
  removeItemButton: true
});

function restoreState(instance, key) {
  const raw = localStorage.getItem(key);
  if (!raw) return;

  const values = JSON.parse(raw);

  instance.removeActiveItems();

  if (Array.isArray(values)) {
    values.forEach(value => instance.setChoiceByValue(value));
  } else {
    instance.setChoiceByValue(values);
  }
}

restoreState(choices, 'city-select-state');

Ключевой момент заключается в том, что Choices.js не предоставляет прямого метода setValue для массива, поэтому восстановление выполняется поэлементно через setChoiceByValue.


Очистка и предотвращение дублирования состояния

При повторной инициализации или восстановлении важно избегать дублирования выбранных элементов. Метод removeActiveItems() очищает текущее состояние экземпляра без разрушения структуры списка.

choices.removeActiveItems();

Если используется динамическая подгрузка данных, дополнительно может потребоваться:

choices.clearChoices();
choices.setChoices(data, 'value', 'label', true);

Восстановление при динамических источниках данных

Особая сложность возникает при использовании асинхронных источников (ajax, fetch, remote API). В этом случае восстановление состояния возможно только после загрузки списка опций.

const choices = new Choices('#city-select');

fetch('/api/cities')
  .then(res => res.json())
  .then(data => {
    choices.setChoices(data, 'id', 'name', true);

    const saved = JSON.parse(localStorage.getItem('city-select-state') || '[]');

    saved.forEach(id => {
      choices.setChoiceByValue(String(id));
    });
  });

Здесь критично соблюдение порядка:

  1. загрузка данных
  2. инициализация опций
  3. восстановление выбранных значений

Нарушение последовательности приводит к игнорированию восстановления, поскольку Choices.js не находит соответствующие значения в списке.


Восстановление состояния при перезагрузке формы

При работе с формами часто требуется восстановить не только отдельные поля, но и целый набор данных. Choices.js необходимо синхронизировать с сериализацией формы.

function serializeForm(form) {
  const data = {};
  const formData = new FormData(form);

  for (const [key, value] of formData.entries()) {
    if (!data[key]) data[key] = [];
    data[key].push(value);
  }

  return data;
}

Однако Choices.js не всегда обновляет <select> в DOM напрямую, поэтому восстановление должно идти через экземпляры:

function restoreForm(formMap, instances) {
  Object.keys(formMap).forEach(name => {
    const instance = instances[name];
    if (!instance) return;

    instance.removeActiveItems();

    formMap[name].forEach(value => {
      instance.setChoiceByValue(value);
    });
  });
}

Работа с reset формы

Стандартный form.reset() не восстанавливает состояние Choices.js, поскольку библиотека хранит собственный UI state.

form.addEventListener('reset', () => {
  setTimeout(() => {
    choices.removeActiveItems();
  }, 0);
});

Использование setTimeout необходимо, поскольку событие reset срабатывает до завершения обновления DOM-значений.


Сохранение состояния при изменениях

Для актуализации данных в реальном времени используется событие change. Choices.js проксирует его, позволяя фиксировать изменения без дополнительного polling.

choices.passedElement.element.addEventListener('change', () => {
  saveState(choices, 'city-select-state');
});

При мультивыборе это особенно важно, так как порядок добавления элементов может иметь значение.


Восстановление объектов (label + value)

Если данные представлены объектами, а не строками, требуется хранить не только value, но и метаданные.

choices.setChoices([
  { value: '1', label: 'Almaty' },
  { value: '2', label: 'Astana' }
], 'value', 'label', true);

При восстановлении достаточно использовать value, поскольку Choices.js сопоставляет его с уже загруженными объектами.


Частичные состояния и конфликт данных

При изменении списка опций между сохранением и восстановлением возможны расхождения. Например, сохранённое значение может отсутствовать в новом наборе данных.

Подход к обработке:

const availableValues = choices._store.choices.map(c => c.value);

savedValues
  .filter(v => availableValues.includes(v))
  .forEach(v => choices.setChoiceByValue(v));

Игнорирование этого шага приводит к «тихим» ошибкам восстановления, когда часть состояния теряется без уведомлений.


Гидратация состояния при серверном рендеринге

При использовании SSR (например, с шаблонами или фреймворками) важно синхронизировать начальное состояние до инициализации Choices.js.

<select id="city-select">
  <option value="1" selected>Almaty</option>
  <option value="2">Astana</option>
</select>

Choices.js при инициализации считывает selected, однако при динамической гидратации предпочтительнее явная передача состояния:

const initialValues = JSON.parse(document.getElementById('state').dataset.values);

const choices = new Choices('#city-select');

initialValues.forEach(v => choices.setChoiceByValue(v));

Очистка хранилища и сброс состояния

При необходимости полного сброса состояния формы необходимо синхронно очистить и UI, и хранилище.

function clearState(instance, key) {
  instance.removeActiveItems();
  localStorage.removeItem(key);
}

Несогласованность между DOM и localStorage приводит к повторному восстановлению «устаревших» данных при следующей загрузке.


Стабильность восстановления в сложных формах

В формах с несколькими экземплярами Choices.js ключевым становится централизованное управление состоянием. Обычно используется структура:

const instances = {
  city: new Choices('#city'),
  country: new Choices('#country'),
  tags: new Choices('#tags')
};

И соответствующее хранилище:

const state = {
  city: ['1'],
  country: ['kz'],
  tags: ['frontend', 'javascript']
};

Восстановление выполняется детерминированно:

Object.keys(state).forEach(key => {
  const instance = instances[key];
  if (!instance) return;

  instance.removeActiveItems();
  state[key].forEach(v => instance.setChoiceByValue(v));
});