Почему это выдает "Не удается прочитать свойство 'длина' из неопределенного"? - PullRequest
0 голосов
/ 27 января 2020

У меня есть две колонки формы, в которых есть выпадающие меню слева и справа.

В зависимости от того, что пользователь выберет в выпадающем меню слева, выпадающее меню справа будет отображаться как простой выпадающий список (<select>) или множественный выбор (<select multiple="multiple">) и заполнение значений.

В основном это работает, но я получаю ошибку Uncaught TypeError: Cannot read property 'length' of undefined в консоли при добавлении строки и изменении левого выпадающего списка во вновь добавленной строке , Код работает как задумано (он обновляет правильный выпадающий список), но я не уверен, почему он выдает ошибку в первую очередь.

Пример:

const regex = /^(.+?)(\d+)$/i;
let cloneIndex = $(".operation").length;

bindToSelector($('.operation-keys'));

function setIndex(elements) {
  elements.each(function(index, element) {
    $(element).find("select.operation-keys").attr("name", "operation-keys[" + index + "]");
    $(element).find("select.operation-values").attr("name", "operation-values[" + index + "]");
  });
}

function clone() {

  let $removeButton = $(this).closest(".actions").find(".remove-operation");
  let $closestOperation = $(this).closest(".actions").prev(".operation").first();

  $removeButton.show();

  let selector = $closestOperation.clone()
    .insertAfter($closestOperation).attr("id", "operation-" + cloneIndex)
    .find("*")
    .each(function() {
      let id = this.id || "";
      let match = id.match(regex) || [];
      if (match.length == 3) {
        this.id = match[1] + '-' + (cloneIndex);
      }
    });
  cloneIndex++;

  let $Operations = $(this).closest(".operation-parent").find(".operation");

  setIndex($Operations);

  bindToSelector(selector);
}

function remove() {
  $(this).closest(".actions").prev(".operation").remove();

  let $Operations = $(this).closest(".operation-parent").find(".operation");
  setIndex($Operations);
  let totalElements = $(this).closest(".operation-parent").find(".operation").length;
  if (totalElements === 1) {
    $(this).hide();
  }
  cloneIndex--;
}

$(".add-operation").on("click", clone);
$(".remove-operation").on("click", remove);

// select logic

function buildSelect(operationKey) {
  let result = {};
  switch (operationKey) {
    case 'continents':
      result['operationValues'] = ['North America', 'South America', 'Europe'];
      result['type'] = 'multiple';
      break;
    case 'languages':
      result['operationValues'] = ['English', 'French', 'Spanish'];
      result['type'] = 'multiple';
      break;
    case 'eye_color':
      result['operationValues'] = ['Brown', 'Blue', 'Green', 'Hazel'];
      result['type'] = 'single';
      break;
    case 'age':
      result['operationValues'] = ['18-24', '25-32', '33-40', '>41'];
      result['type'] = 'single';
      break;
  }
  return result;
}

function bindToSelector(selector) {
  $(selector).on('change', function() {
    let operationKey = $(this).val()
    let valueSelect = $(this).parent().next().find('.operation-values');
    
    // get the values + type of select we should populate
    let result = buildSelect(operationKey);
    // set the select type, if needed
    if (result['type'] == 'multiple') {
      valueSelect.attr("multiple", "multiple");
    }
    else
    {
      valueSelect.removeAttr("multiple", "multiple");
    }

    // populate the values
    let options = result['operationValues'];
    console.log(options);
    
    // empty the dropdown in the cloned row (not working - empties all value dropdowns)
    valueSelect.empty();
    for (var i = 0; i < options.length; i++) {
      valueSelect.append(`<option value="${options[i].toLowerCase()}" >${options[i]}</option>`);
    }

  });

}
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"><script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://github.com/davidstutz/bootstrap-multiselect/blob/master/dist/css/bootstrap-multiselect.css" type="text/css"/>
<script type="text/javascript" src="https://github.com/davidstutz/bootstrap-multiselect/blob/master/dist/js/bootstrap-multiselect.js"></script>
<script type="text/javascript" src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<div class="form-group row">
  <label class="col-4">Operation Key</label>
  <label class="col-6">Operation Value</label>
</div>
<div class="form-group">

  <div class="operation-parent row">
    <div class="col-8 operation">
      <div class="row">
        <div class="col-6">
          <select class="form-control form-control-lg operation-keys" name="operation_keys[]">
            <option value="" selected disabled>Select One</option>
            <option value="continents">Continents Visited</option>
            <option value="languages">Languages Spoken</option>
            <option value="eye_color">Eye Color</option>
            <option value="age">Age</option>
          </select>
        </div>

        <div class="col-6">
          <select class="form-control form-control-lg mb-30 operation-values" name="operation_values[]">

          </select>
        </div>

      </div>
    </div>
    <div class="col-2 actions">
      <a class="btn btn-alt-success add-operation">+</a>
      <a class="btn btn-alt-danger remove-operation" style="display:none;">-</a>
    </div>
  </div>

Неправильная строка:

for (var i = 0; i < options.length; i++) {

Я просто не понимаю, почему выдает ошибку в первое место.

Шаги для воспроизведения:

  1. Запустите фрагмент, сделайте выбор в раскрывающемся списке слева.
  2. Нажмите +, чтобы добавить новый row.
  3. Сделайте выбор в выпадающем списке слева новой строки. Наблюдайте ошибку в консоли.

Бонусные баллы : При добавлении новой строки (шаг № 2 выше), как очистить значения в правом выпадающем списке новой строки ( так что это не то же самое, что клонированный ряд)? Я знаю, что мне нужно вызвать .empty(); для селектора где-то ближе к концу clone(), но не уверен, какой селектор мне следует использовать.

1 Ответ

0 голосов
/ 27 января 2020

Я считаю, что проблема в вашей функции клонирования:

let selector = $closestOperation.clone()
.insertAfter($closestOperation).attr("id", "operation-" + cloneIndex)
.find("*")
.each(function() {
  let id = this.id || "";
  let match = id.match(regex) || [];
  if (match.length == 3) {
    this.id = match[1] + '-' + (cloneIndex);
  }
});
// ...
bindToSelector(selector);

Когда вы запускаете .find ("*"), ваш селектор становится большим массивом из 10 элементов. Каждый из них затем отправляется вашей функции выбора и связывается с обработчиком событий.

Когда вы изменяете значение, эти элементы запускаются через вашу функцию buildSelect, но ничего не совпадают, так как вы ожидали только, что элемент select будет отправлен через него. Таким образом возвращается значение по умолчанию {}. Этот пустой объект НЕ имеет значения "operationValues", к которому вы пытаетесь обратиться с помощью result['operationValues'], поэтому он не определен и, следовательно, не имеет длины.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...