jQuery, странное поведение .each()
Столкнулся с проблемой не совсем корректного (в моём понимании) исполнения метоа .each() в jQuery.
В общем, есть большая табличная форма, куда динамически добавляются строки.
Далее нужно перебрать все input в форме, и назначить им соответствующие атрибуты имён, установить номер строки и тд.
Пример того, что добавляется в форму:
<tr>
<td rowspan="4">1</td>
<td rowspan = "4"><textarea class = "form-control" data-id = "naim" name = "tovs[0][naim]"></textarea></td>
<td style = "text-align: center; vertical-align: middle;">А (до изменения)</td>
<td><input type = "text" class = "form-control" data-id = "a_at" name = "tovs[0][a_at]"></td>
<td><input type = "text" class = "form-control" data-id = "a_code" name = "tovs[0][a_code]"></td>
<td><input type = "text" class = "form-control" data-id = "a_obozn" name = "tovs[0][a_obozn]"></td>
<td><input type = "text" class = "form-control" data-id = "a_col" name = "tovs[0][a_col]"></td>
<td><input type = "text" class = "form-control" data-id = "a_price" name = "tovs[0][a_price]"></td>
<td><input type = "text" class = "form-control" data-id = "a_sum" name = "tovs[0][a_sum]"></td>
<td><input type = "text" class = "form-control" data-id = "a_akc" name = "tovs[0][a_akc]"></td>
<td rowspan="4"></td>
</tr>
<tr>
<td>Б (после изменения)</td>
<td><input type = "text" class = "form-control" data-id = "b_past" name = "tovs[0][b_past]"></td>
<td><input type = "text" class = "form-control" data-id = "b_code" name = "tovs[0][b_code]"></td>
<td><input type = "text" class = "form-control" data-id = "b_obozn" name = "tovs[0][b_obozn]"></td>
<td><input type = "text" class = "form-control" data-id = "b_col" name = "tovs[0][b_col]"></td>
<td><input type = "text" class = "form-control" data-id = "b_price" name = "tovs[0][b_price]"></td>
<td><input type = "text" class = "form-control" data-id = "b_sum" name = "tovs[0][b_sum]"></td>
<td><input type = "text" class = "form-control" data-id = "b_akc" name = "tovs[0][b_akc]"></td>
</tr>
<tr>
<td style = "text-align: center; vertical-align: middle;">В ( увеличение )</td>
<td><input type = "text" class = "form-control" data-id = "v_grow" name = "tovs[0][v_grow]"></td>
<td><p style = "text-align: center;">X</p></td>
<td><p style = "text-align: center;">X</p></td>
<td><p style = "text-align: center;">X</p></td>
<td><p style = "text-align: center;">X</p></td>
<td><input type = "text" class = "form-control" data-id = "v_sum" name = "tovs[0][v_sum]"></td>
<td><input type = "text" class = "form-control" data-id = "v_akc" name = "tovs[0][v_akc]"></td>
</tr>
<tr>
<td style = "text-align: center; vertical-align: middle;">Г ( уменьшение )</td>
<td><input type = "text" class = "form-control" data-id = "g_um" name = "tovs[0][g_um]"></td>
<td><p style = "text-align: center;">X</p></td>
<td><p style = "text-align: center;">X</p></td>
<td><p style = "text-align: center;">X</p></td>
<td><p style = "text-align: center;">X</p></td>
<td><input type = "text" class = "form-control" data-id = "g_sum" name = "tovs[0][g_sum]"></td>
<td><input type = "text" class = "form-control" data-id = "g_akc" name = "tovs[0][g_akc]"></td>
</tr>
Пытаюсь перебрать целиком всю таблицу, привязавшись к textare, так как для каждой заполняемой "строки" он будет один:
$('textarea').each(function(index) {
p = $(this).parents('tr');
p.find('.rowCount').html(index);
tag = $(this).parents('table').attr('id') + '[' + index + ']';
$(this).attr('name', tag + '[' + $(this).attr('data-id') + ']');
iter = index;
$('.lastIns').each(function(index) {
console.log(index);
ins = $(this).find('input');
ins.attr('name', tag + '[' + $(ins).attr('data-id') + ']');
$(this).removeAttr('class');
});
});
В итоге получается, что вложенная переборка (по input), перебирает разом все поля при первой итерации, и для всех последующих для input устанавливает индекc 0, хотя, если следовать логике, то должен быть установлен индекс соответствующе "строки" (что и устанавливается для textarea), однако, туда всегда попадает 0.
В чём может быть проблема? куда копать?
Заранее спасибо.
Ответы (2 шт):
В общем, на данный момент пришёл к решению следующему:
$('textarea').each(function(index){
p = $(this).parents('tr');
p.find('.rowCount').html(index);
tag = $(this).parents('table').attr('id')+'['+index+']';
$(this).attr('name', tag+'['+$(this).attr('data-id')+']');
iter = index;
});
$('.lastIns').find('input').each(function(){
$(this).attr('name', tag+'['+$(this).attr('data-id')+']')
});
$('.lastIns').removeAttr('class');
Просто вынес вторую переборку наружу. Решение костыльное, как по мне, но работает и ладно.
Во-первых не забывайте писать let (var), когда объявляете переменную. Js коварен! Он ищет необъявленную переменную во внешнем скоупе, потом во внешнем скоупе внешнего скоупа и так до самого верха. Если он не нашёл переменную на самом верху, он создаёт её. Это может приводить к опасным ошибкам.
Попробуйте угадать что сделает код:
function foo() {
for (i = 0; i < 2; ++i) {
console.log(bar());
}
console.log(i);
}
function bar() {
i = 100500;
return 'hello';
}
foo();
Во-вторых, с jQuery или без него старайтесь разбивать вашу задачу на части, выделяя соответствующие методы. Тогда код будет гораздо проще.
В вашем случае логически связанные куски html никак не объединены. Хорошо было бы это исправить, изменив вёрстку. Но попробуем расплести ваш код и так (я использую нативный js, вы легко перепишете это на jQuery).
Сперва решим абстрактную проблему. Надо научиться получать все строки таблицы, которые объединяются какой-то ячейкой. То есть нам нужна функция, которая на вход получит ячейку, а на выход отдаст массив строк, которые эта ячейка объединяет.
function getRows(td) {
let rowspan = td.getAttribute('rowspan');
let row = td.parentElement;
let rows = [
row,
];
for (let i = 0; i < (rowspan - 1); ++i) {
row = row.nextElementSibling;
rows.push(row);
}
return rows;
}
Теперь получим id таблицы, он не меняется в цикле, поэтому выбирать его каждый раз не обязательно:
const table = document.querySelector('form table'); // уточните селектор в зависимости от вашего html
const tableId = table.getAttribute('id');
Теперь выберем textarea и найдём строки таблицы, которые объединяет соответствующая ячейка:
let textareas = table.querySelectorAll('textarea'); // обратите внимание, что я ищу не по всему документу, а только в таблице (jQuery тоже так может)
textareas.forEach((textarea, i) => {
let tag = `${tableId}[${i}]`;
let textareaId = textarea.dataset.id; // jQuery тоже так может (https://api.jquery.com/data/#data-html5)
textarea.setAttribute('name', `${tag}[${textareaId}]`);
// получаем все строки, которые объединяет ячейка с textarea
trs = getRows(textarea.parentElement);
trs.forEach(tr => {
// для каждой строчки проставим имена input'ам
let inputs = tr.querySelectorAll('input');
inputs.forEach(input => input.setAttribute('name', `${tag}[${input.dataset.id}]`));
});
});
Вот в эту сторону вам стоит копать.
P.S. А each в jQuery работает предсказуемо хорошо! :-)