Nadawanie unikalnych atrybutów dla kolonowanych elementów formularza może przyprawić o ból głowy. Jeśli chodzi o atrybut id, to element nie musi go posiadać, ale jeśli powiążemy do niego etykietę (LABEL), która nie otacza tego elementu, to należałoby etykiecie nadać atrybut for, a co za tym idzie elementowi atrybut id. Co do atrybutu name, to oczywistym jest, że chcemy go mieć, jeśli dane będziemy przesyłać dalej. A jeśli to element klonowany, to pewnie wartości całej grupy takich elementów będziemy odbierać jako tablicę, czyli tu na pewno musimy mieć unikalne name w formie tablicowej.
Czy można się objeść bez wyrażeń regularnych?
Oczywiście! Wystarczy, że zamiast pobierać dany atrybut i wyłuskiwać z niego indeks danego elementu, przechowamy w innym atrybucie jego wartość bez numeracji. Mając "czystą" wartość dodamy do niej tylko kolejny indeks i gotowe!
Skoro wspominałem o atrybucie id, for i name, to nimi się zajmiemy. "Czystą" wartość danego atrybutu będziemy natomiast przechowywać w atrybucie data-base-***. W tym momencie wiesz, Czytelniku, że możemy ją nazwać dla uproszczenia wartością bazową.
Jak wygląda kod?
<form>
<fieldset data-index="1">
<label for="imie-1" data-base-for="imie">Imię</label>
<input type="text" id="imie-1" name="imie[1]" data-base-id="imie" data-base-name="imie" />
<label for="nazwisko-1" data-base-for="nazwisko">Imię</label>
<input type="text" id="nazwisko-1" name="nazwisko[1]" data-base-id="nazwisko" data-base-name="nazwisko" />
</fieldset>
<button type="button">Dodaj osobę</button>
</form>
Mamy tu prosty formularz, w którym blok pól przeznaczonych do klonowania, przechowujemy w elemencie FIELDSET. FIELDSET tego pierwszego, istniejącego na początku bloku, posiada atrybut data-index="1", dzięki czemu wiemy, że kolejny blok będzie mieć indeks 2. Wspomniane atrybuty mają również dodany indeks 1, a dodatkowo istnieją bazowe nazwy atrybutów. BUTTON będzie klonować blok.
Co dalej?
Trzeba jakoś oprogramować przycisk i dodać możliwość tytułowego klonowania. Oczywiście użyjemy JavaScript.
document.querySelector('form button').addEventListener('click', function() {
var fieldsets = document.querySelectorAll('form fieldset');
var lastFieldset = fieldsets[fieldsets.length - 1];
var newFieldset = lastFieldset.cloneNode(true);
lastFieldset.parentNode.insertBefore(newFieldset, lastFieldset.nextSibling);
var newIndex = parseInt(lastFieldset.getAttribute('data-index')) + 1;
newFieldset.setAttribute('data-index', newIndex);
newFieldset.querySelectorAll('[data-base-name],[data-base-id],[data-base-for]').forEach(function(el) {
if (el.getAttribute('data-base-name')) {
el.setAttribute('name', el.getAttribute('data-base-name') + '[' + newIndex + ']');
}
if (el.getAttribute('data-base-id')) {
el.setAttribute('id', el.getAttribute('data-base-id') + '-' + newIndex);
}
if (el.getAttribute('data-base-for')) {
el.setAttribute('for', el.getAttribute('data-base-for') + '-' + newIndex);
}
});
});
Całość dzieje się w obsłudze zdarzenia click na przycisku. Znajdujemy tam wszystkie bloki, a ostatni z nich klonujemy i wstawiamy właśnie za ostatnim. Z ostatniego bloku pobieramy atrybut data-index i zwiększony o 1 nadajemy sklonowanemu blokowi. W tym momencie mamy dodany nowy blok, jednak wartości atrybutów są identyczne jak w "starszym bracie". Aby je zmienić korzystamy z querySelectorAll, który wybiera nam z nowego bloku wszystkie elementy z bazowymi atrybutami (data-base-***). Jeśli element ma atrybut id, to pobieramy data-base-id, dodajemy indeks tego nowego bloku i zmieniamy wartość atrybutu. Analogicznie postępujemy z atrybutami for i name (ewentualna różnica występuje tylko w "doklejaniu" indeksu).
A co się dzieje przy usuwaniu bloków?
Tu co prawda nie ma tej możliwości, ale nic nie stoi na przeszkodzie, abyś ją dodał. Tu przy okazji wyjaśni się jedna kwestia. Mogło Ci się wydawać niepotrzebne dodawanie data-index do bloku, bo przecież w obecnej sytuacji atrybutom można było nadać numer na podstawie ilości bloków (skoro mamy jeden blok i dodajemy drugi, to atrybuty będą mieć indeks 2). To prawda, ale jeśli dodasz usuwanie bloków, to nie możesz posiłkowac się ilością bloków do nadawania indeksu atrybutom. Dlaczego? Ano dlatego, że jeśli dodasz pięć bloków i usuniesz jeden z nich (ale nie ostatni), to dodając kolejny, ten znów miałby indeks 5. W naszym przypadku używamy indeksu z ostatniego bloku, więc mamy pewność, że nowy zawsze będzie mieć numer większy od jakiegokolwiek innego bloku. W przypadku usuwania bloków, musisz pamiętać również o tym, że wartości tablicowe mogą mieć dziury w indeksach przy opcji usuwania.
Aby sprawdzić jak nadawane są wartości atrybutów, uzyj narzędzi deweloperskich z przeglądarki.
Mam nadzieję, że Twój ból głowy minął i dalej z uśmiechem na ustach będziesz klonował... elementy. I wykręcał numery.
Przydatne linki:
"Hello dolar", czyli po co mi jQuery?
Metoda cloneNode