У меня есть две колонки формы, в которых есть выпадающие меню слева и справа.
В зависимости от того, что пользователь выберет в выпадающем меню слева, выпадающее меню справа будет отображаться как простой выпадающий список (<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++) {
Я просто не понимаю, почему выдает ошибку в первое место.
Шаги для воспроизведения:
- Запустите фрагмент, сделайте выбор в раскрывающемся списке слева.
- Нажмите
+
, чтобы добавить новый row. - Сделайте выбор в выпадающем списке слева новой строки. Наблюдайте ошибку в консоли.
Бонусные баллы : При добавлении новой строки (шаг № 2 выше), как очистить значения в правом выпадающем списке новой строки ( так что это не то же самое, что клонированный ряд)? Я знаю, что мне нужно вызвать .empty();
для селектора где-то ближе к концу clone()
, но не уверен, какой селектор мне следует использовать.