Реализовать связанный select опций

121
28 ноября 2020, 13:50

На входе имеется строка типа:

processor_0_i3/chastota_0_1,5/ram_0_8/os_0_Windows/count_0_12/processor_1_i5/chastota_1_2,7/ram_1_16/os_1_Windows/count_1_10/processor_2_i3/chastota_2_1,5/ram_2_32/os_2_Linux/count_2_10

В ней записаны несколько опций одного товара.

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

Array
(
   [processor] => Array
       (
           [0] => i3
           [1] => i5
       )
   [chastota] => Array
       (
           [0] => 1,5
           [1] => 2,7
       )
   [ram] => Array
       (
           [0] => 8
           [1] => 16
           [2] => 32
       )
   [os] => Array
       (
           [0] => Windows
           [1] => Linux
       )
   [count] => Array
       (
           [0] => 12
           [1] => 10
       )
)

Из массива получаем селекты:

Теперь самое интересное: В полученных комбинациях селектов есть существующие комбинации опций, но есть и не существующие. На данном этапе, чтобы ограничить выбор неверных комбинаций, после каждого change селекта, на сервер отправляется полученная комбинация, и сравнивается и существующими. Но это не совсем правильно, к тому же, неверные комбинации в селектах могут формироваться много раз подряд. В идеале, активность последних селектов должна формироваться в зависимости от первого выбранного. Допустим, при выбранном в первом селекте processor i3, в последующих активными должны быть только те опции, которые состоят в комбинациях с первой выбранной опцией. Как это реализовать?

Answer 1

Для выполнения такой задачи, нужно всё таки изначально с сервера передавать массив всех версий продукта, а не только уникальных значений для полей. Что-то похожее на это:

<?php
// Исходная строка с характеристиками одного товара.
$product = 'processor_0_i3/chastota_0_1,5/ram_0_8/os_0_Windows/count_0_12/processor_1_i5/chastota_1_2,7/ram_1_16/os_1_Windows/count_1_10/processor_2_i3/chastota_2_1,5/ram_2_32/os_2_Linux/count_2_10';
// Сюда будем записывать версии товара.
$versions = [];
// Также сложим общее количество версий.
$count = 0;
// Создадим предварительно массив, разбив строку. Разделитель - `/`.
$array = explode('/', $product);
foreach ($array as $params) {
    // В данном примере проигнориваны проверки.
    // Опять разбиваем строку, чтобы выделить каждую характеристику.
    // Разделитель - `_`, количество - три.
    [$property, $index, $value] = explode('_', $params, 3);
    // Записываем версию товара.
    $versions[$index][$property] = $value;
    // Если характеристика == `count`,
    // то складываем в общее количество.
    if ('count' === $property) $count += (int) $value;
}
// Debug.
// Характеристики: процессоры, частоты, планки, оси. Уникальные значения.
// $processors = array_keys(array_column($versions, 'processor', 'processor'));
// $frequencies = array_keys(array_column($versions, 'chastota', 'chastota'));
// $memories = array_keys(array_column($versions, 'ram', 'ram'));
// $oses = array_keys(array_column($versions, 'os', 'os'));
// print_r(compact('versions', 'count', 'processors', 'frequencies', 'memories', 'oses'));

И основываясь на этот массив версий, и прибегая к функциям по работе с массивами map, filter, every, reduce динамически перестраивать выпадающие списки. Как один из вариантов:

// Данные для работы с `javascript` должны быть в `json` формате. 
// const VERSIONS = <?= json_encode($versions) ?>; 
 
// Массив версий товара. 
const VERSIONS = [{ 
  "processor": "i3", 
  "chastota": "1,5", 
  "ram": "8", 
  "os": "Windows", 
  "count": "12" 
}, { 
  "processor": "i5", 
  "chastota": "2,7", 
  "ram": "16", 
  "os": "Windows", 
  "count": "10" 
}, { 
  "processor": "i3", 
  "chastota": "1,5", 
  "ram": "32", 
  "os": "Linux", 
  "count": "10" 
}] 
 
// `NodeList` из выпадающих списков. 
const SELECTORS = document.querySelectorAll('select.linked__data') 
// Чтобы не нагружать постоянными пересчетами. 
const ARRAY_FROM_SELECTORS = Array.from(SELECTORS) 
// Поле вывода количества отфильтрованных версий товара. 
const SPAN_COUNT = document.getElementById('count') 
// Элемент для вывода отфильтрованных версий товара. 
const LIST_RESULT = document.getElementById('result') 
 
// Формирование запроса для выбранных значений всех `select`. 
// Пустые значения игнорируются. 
function selectedQuery(currentIndex) { 
  let search = {} 
 
  ARRAY_FROM_SELECTORS.map(function(select, index) { 
    // Сбрасываем `value` если index больше текущего. 
    if (index > currentIndex) select.value = '' 
 
    // Если в списке выбран пункт, добавляем в запрос. 
    if (!!select.value) search[select.name] = select.value 
  }) 
 
  return search 
} 
 
// Массив отфильтрованных объектов, основанных на 
// индексе текущего выбранного выпадающего списка 
// и сформированного запроса. 
function getFiltered(currentIndex) { 
  let query = selectedQuery(currentIndex) 
  let queryKeys = Object.keys(query) 
 
  return VERSIONS 
    .filter(version => queryKeys 
      .every(prop => version[prop] == query[prop]) 
    ) 
} 
 
// Построение таблицы. 
function renderTable(filtered) { 
  // Debug. 
  // console.clear() 
  // console.table(filtered) 
 
  // Очистим форму вывода. 
  LIST_RESULT.innerHTML = '' 
 
  // Пройдемся по отфильтрованным версиям. 
  filtered.map(function(item) { 
    let li = document.createElement('li') 
    li.append(document.createTextNode(JSON.stringify(item))) 
    LIST_RESULT.appendChild(li) 
  }) 
} 
 
// Отрисовка доступного количества. 
function renderCount(filtered) { 
  SPAN_COUNT.innerText = filtered 
    .map(item => item.count) 
    .reduce((total, current) => parseFloat(total) + parseFloat(current)) 
} 
 
// Перестроение выпадающих списков. 
function renderSelecotrs(filtered, currentIndex = 0) { 
  // По отфильтрованным данным формируем доступные выпадающие списки. 
  // Для текущего списка ничего не меняем, поэтому currentIndex + 1, 
  // то есть начинаем формирование только со следующего. 
  for (let i = currentIndex + 1; i < SELECTORS.length; i++) { 
    let name = SELECTORS[i].name 
 
    // Получим список всех возможных уникальных значений для текущего списка. 
    let unique = [...new Set(filtered.map(item => item[name]))] 
 
    Array.from(SELECTORS[i]) 
      .map(option => { 
        // Предварительно скроем все элементы в списке. 
        option.hidden = 'hidden' 
        // Если элемент списка не имеет `value` 
        // или `value` находится в списке уникальных. 
        // то покажем его. 
        if ('' == option.value || unique.includes(option.value)) { 
          option.hidden = '' 
        } 
      }) 
  } 
} 
 
// Функция фильтрации. 
function filterList(event = null, currentIndex = 0) { 
  // Получаем отфильтрованные данные. 
  let filtered = getFiltered(currentIndex) 
 
  // Построим список отфильтрованных версий. 
  renderTable(filtered) 
 
  // Обновим количество. 
  renderCount(filtered) 
 
  // Обновим выпадающие списки. 
  renderSelecotrs(filtered, currentIndex) 
} 
 
// Регистрируем обработчика события для каждого выпадающего списка. 
ARRAY_FROM_SELECTORS 
  .map(function(item, index) { 
    item.addEventListener('input', (event) => filterList(event, index)) 
  }) 
 
// Инициируем запуск фильтрации. 
filterList()
form { 
  display: flex; 
} 
 
.form-group { 
  width: 25%; 
  margin: 15px 0; 
} 
 
.form-control { 
  display: block; 
  width: 100%; 
  padding: .375rem .75rem; 
  border: 1px solid #ced4da; 
  box-sizing: border-box; 
}
<form action=""> 
  <div class="form-group"> 
    <label>processor</label> 
    <select name="processor" class="form-control linked__data"> 
      <option value="i3" selected>i3</option> 
      <option value="i5">i5</option> 
    </select> 
  </div> 
  <div class="form-group"> 
    <label>chastota</label> 
    <select name="chastota" class="form-control linked__data"> 
      <option value="" selected>Выберите</option> 
      <option value="1,5">1,5</option> 
      <option value="2,7">2,7</option> 
    </select> 
  </div> 
  <div class="form-group"> 
    <label>ram</label> 
    <select name="ram" class="form-control linked__data"> 
      <option value="" selected>Выберите</option> 
      <option value="8">8</option> 
      <option value="16">16</option> 
      <option value="32">32</option> 
    </select> 
  </div> 
  <div class="form-group"> 
    <label>os</label> 
    <select name="os" class="form-control linked__data"> 
      <option value="" selected>Выберите</option> 
      <option value="Windows">Windows</option> 
      <option value="Linux">Linux</option> 
    </select> 
  </div> 
</form> 
 
<h1 id="count"></h1> 
<ul id="result"></ul>

READ ALSO
laravel controller синтаксис ,

laravel controller синтаксис ,

как правильно написать что при каждом цикле получить последовательную цифру типа

115
Как создать генератор токена из данных по определённому шаблону

Как создать генератор токена из данных по определённому шаблону

Есть входные данные типа логин, пароль и тд

123
Поиск в базе данных PHP

Поиск в базе данных PHP

В БД есть такая таблица… Не получается реализовать поиск по таблице

117