Dynamiczne formularze z użyciem jQuery

jQueryWszyscy deweloperzy prędzej czy później stają przed problemem stworzenia dynamicznego formularza. Dynamiczny formularz to taki, w którym ilość pół może być zmieniana przez użytkownika. Może to być dowolna ilość plików wysyłanych do serwera, czy zmieniające się pola formularza, w zależności od udzielanych odpowiedzi. Na przykładzie prostego formularza, zajmę się tym tematem.

Na początek tworzę formularz z możliwością wpisania danych jednej osoby, obrazkiem + odpowiedzialnym za dodanie kolejnej osoby oraz nagłówek i przycisk wysyłania formularza:

        <form action="parser.php" method="post">
            <table id="listaOsob">
                <thead>
                    <tr>
                        <th>Lp</th>
                        <th>Imię</th>
                        <th>Nazwisko</th>
                        <th>+/-</th>
                    </tr>
                </thead>
                <tfoot>
                    <tr>
                        <th colspan="4">
                            <input type="submit" name="wyslij" value="Wyślij">
                        </th>
                    </tr>
                </tfoot>
                <tbody>
                    <tr>
                        <td>1.</td>
                        <td><input type="text" name="imie[]"></td>
                        <td><input type="text" name="nazwisko[]"></td>
                        <td><img src="http://antczak.org/source/dynamic_forms/11.png" id="dodajOsobe" alt="dodaj"></td>
                    </tr>
                </tbody>
            </table>
        </form>

Ideą jest, aby kliknięcie obrazka + powodowało dodanie do części tabelki <tbody> kolejnego wiersza, czyli tagu <tr> wraz z treścią. Potrzebujemy do tego szablonu tagu <tr>, czyli pól formularza do których będziemy mogli wpisać dane kolejnej osoby:

        <textarea id="szablon" style="display:none;" cols="1" rows="1">
            <tr id="wiersz_{0}">
                <td>{0}.</td>
                <td><input type="text" name="imie[]"></td>
                <td><input type="text" name="nazwisko[]"></td>
                <td><img src="http://antczak.org/source/dynamic_forms/12.png" id="usunOsobe_{0}" alt="usun"></td>
            </tr>
        </textarea>

Szablon tagu <tr> opakowujemy w tag <textarea>, za pomocą CSS ukrywany <textarea>. Wiersz ten różni się od pierwszego wiersza w tabeli kilkoma rzeczami. Zamiast obrazka + mamy -, który będzie usuwał dany wiersz. Uwagę zwracają także tokeny {0}. Są to miejsca, w które później nasz mechanizm będzie wstawiał kolejne numery. Numerowanie jest potrzebne, ponieważ każde pole formularza powinno mieć unikatową nazwę.
Przejdźmy do kolejnego kroku. Mamy już formularz, mamy szablon kolejnego wiersza, pozostało to poskładać.

        <script type="text/javascript">
            $(document).ready(function(){
                // tworzymy zmienną i, wykorzystamy ją do identyfikowania wierszy
                // ustawiamy jej wartość 2, ponieważ jeden wiersz jest już w tabeli
                var i = 2;
                // tworzymy zmienną szablonWiersza i wczytujemy do niej szablon z textarea #szablon
                var szablonWiersza = jQuery.format($("#szablon").val());
                // tworzymy funkcję, która dodaje szablon do właściwej tabeli
                function dodajWiersz() {
                    var ii = i++;
                    // dodajemy wiersz do właściwej tabeli
                    $("#listaOsob tbody").append(szablonWiersza(ii));
                    // Nowy wiersz jest już widoczny.
                    // Każdy element nowego wiersza posiada swoje id
                    // tr ma id: wiersz_2, ikona minus ma id: usunOsobe_2
                    // Do nowo dodanej ikony - dodajmy zdarzenie.
                    // W momencie kliknięcia minusa, wiersz zostanie usunięty.
                    $("#usunOsobe_" + ii).click(function(){
                        $("#wiersz_" + ii).remove();
                    });
                }
                // do przycisku #dodajOsobe dodajemy zdarzenie,
                // kliknięcie na przycisk plus wywoła fukcję dodajWiersz
                $("#dodajOsobe").click(dodajWiersz);
            });
        </script>

Ponieważ tu zaczęły się odrobinę poważniejsze rzeczy, skomentowałem kod linia po linii. Mechanizm działania jest następujący: każde kliknięcie w obrazek + powoduje wywołanie funkcji dodajWiersz. Wewnątrz niej dzieje się kilka rzeczy. Funkcja jQuery.format odczytuje zawartość szablonu wiersza. Użycie w postaci

$(szablonWiersza(ii)).appendTo("#listaOsob tbody");

powoduje wygenerowanie wiersza według szablonu oraz podstawienie w miejsce tokena {0} parametru wywołania – w naszym wypadku zmiennej ii, czyli kolejnego numeru. Druga część tej linii powoduje dodanie do tabeli listaOsob, do części tbody wygenerowanego wiersza. Następne linie dodają zdarzenie do obrazka -, kliknięcie spowoduje usunięcie danego wiersza.
Ostatnia linia dodaje zdarzenie do obrazka +, kliknięcie spowoduje wywołanie opisanego wyżej mechanizmu.

Efekt końcowy wygląda następująco:
dynamic_form_example

Przygotujemy jeszcze skrypt PHP, który odczyta dane wysłane z formularza.

// tworzymy tablice która będzie zawierała wszystkie osoby
$osoby = array();

// przepisujemy dane do wspolnej tablicy
$osoby['imiona'] = $_POST['imie'];
$osoby['nazwiska'] = $_POST['nazwisko'];

// wypisujemy tablicę zawierającą wszystkie osoby
$wynik = print_r($osoby,true);
echo "<pre>$wynik</pre>";

Działający przykład: http://antczak.org/source/dynamic_forms/
Kod źródłoy: https://github.com/pawelantczak/antczak.org/tree/master/dynamic_forms

37 komentarzy

  1. Wygląda to bardzo ładnie, ja jednak skusił bym się na renumerację pól formularza.

    Po stworzeniu np. dwudziestu pól i usunięciu od dziesiątego do osiemnastego, pola wcześniej oznaczone jako dziewiętnaście i dwadzieścia mogły by zamienić się na kolejne istniejące, w tym przypadku jedenaście i dwanaście bo w tym momencie pozostają w formie jak by poprzednie pola nadal istniały.

  2. Witam.
    Dzięki za komentarz.

    Rzeczywiście. W produkcyjnej aplikacji można by to zmienić.
    Zrobiłem tak dlatego, żeby było widać, że usuwamy pola, a nie tylko zmniejszamy ich ilość.

  3. A i jeszcze trochę bezsensu zrobiłeś te nazewnictwo pól input text.

    Mógłbyś zrobić tak:

    name="[1][imie]"
    name="[1][nazwisko]"
    
    next:
    
    name="[2][imie]"
    name="[2][nazwisko]"
    
    next:
    
    name="[3][imie]"
    name="[3][nazwisko]"
    

    a później w php miałbyś już od razu posegregowane bez potrzeby przerabiania przesłanych danych.

    😉 pozdro.

  4. No nie do końca poprawione, bo teraz nie masz par imię nazwisko. Tylko masz osobną tablicę imiona i nazwiska, a nam chodzi o to by były pary array { imie, nazwisko } musisz dodać jeszcze jedną tablice tak jak Ci napisałem komentarz wyżej. Chyba że taki sposób Cie zadowala. Bo mnie osobiście nie. W taki sposób jak zastosowanie par można bezpośrednio przesłać jako argument do funkcji w postgresql np przy użyciu typu ANNYARAY i później już łatwo na loopie powrzucać dane do bazy bez problemu. 😉

  5. Kod PHP to tylko przykład. Artykuł jest nt. dynamicznych formularzach, a nie tablic w PHP. Poza tym Twój przykład nie zadziała, brakuje nazwy tablicy. Poprawnie mogłoby to wyglądać tak: name=”osoby[1][imie]” i name=”osoby[1][nazwisko]”.

  6. wszystko fajnie działa tylko jak zrobię tak: dodam 10 nowych pól, potem usunę sobie z 4 dodam znów kilka usunę kilka dodam po raz kolejny 🙂 to psuje się numerowanie poszczególnych, taki drobiazg jednak wielu ludzi zwraca uwagę na usability, no a to jest tak trochę anty 😉

  7. Tak, to prawda. Zacytuję siebie z komentarza wyżej: „W produkcyjnej aplikacji można by to zmienić. Zrobiłem tak dlatego, żeby było widać, że usuwamy pola, a nie tylko zmniejszamy ich ilość.”

  8. Witam! Bardzo fajnie zrobiona „lekcja”… gratuluję! Mam tylko pytanie/prośbę… zapewne banalna, ale nie jestem znawcą javascript i nie wiem końca jak zrobić, żeby przemnażać wartości liczbowe z pierwszego pola (tutaj imię) przez wartości liczbowe z drugiego pola (tut. nazwisko), a wynik wyświetlałby się w trzecim (utworzonym przeze mnie) polu (input field). Udało mi się zrobić coś zbliżonego, do zamierzenia na podstawie prostego skryptu, ale działa tylko w jednym wierszu, nie działa natomiast w wierszach dodanych w dalszej kolejności. Będę ogromnie wdzięczny za pomoc. Pozdrawiam.

  9. @Paweł Antczak
    Witam ponownie… Przeglądam forum na php.pl, jeszcze nie udało mi sie trafić na odpowiedni temat… jest tego bardzo dużo, ale bardzo dziękuję za odpowiedź. Pozdrawiam.

  10. Ok już sobie poradziłem z wysyłaniem a jak zrobić pole pod tabelą w którym zmieniała by się liczba w zależności od ilości dodanych wierszy np: 200 potem 400 (po dodaniu 2 wiersza) itd

  11. Witam serdecznie! Opieram się trochę na sposobie wykorzystanym przez Ciebie na dodwanie dynamicznych pól. Lecz powiedz mi jak na podstawie Twojego skryptu mogę wykorzystać działania na inputach. Np powielanie w innym poprzedniego, tak jakbym wpisując w imie autotycznie widział to samo w polu naziwsko: teoretycznie w jquery powinno to wyglądać następująco:
    $(document).ready(function(){
    $(„input[name=imie[]]”).keyup(function () {
    var value = $(this).val();
    $(„input[name=nazwisko[]]”).val(value);
    }).keyup();
    });

    Niestety ta metoda działa tylko dla pierwszego wiersza!

  12. pytanie czy jest to wykonalne na tablicach i jak się do nich odwołać w jquery jeśli są tak jak w tym przykładzie numerowane przez zmienną i – lecz ona dodaje textarea a nie poszczególnego inputa

  13. Poradziłem sobie z problemem opierając się o id=”nazwa_pola_{0}”, a później wywołując je w funkcji przez zmienna $(„#nazwa_pola_” + ii) gdzie ii występuje w funkcji podczas tworzenia nowego wiersza. Chyba tylko w taki sposób możemy odwołać się do konkretnych pól w tym formularzu. Niestety metoda ta nie działa to dla pierwszego wiersza tylko skopiowanego szablonu.
    Może komuś się przyda ta wiadomość. Pozdrawiam!

  14. Witam, też mam pytanie w jaki sposób można odwołać się do poszczególnych indeksów danego pola korzystając z tej metody. Normalnie robi się to za pomocą metody index() jednak tutaj jak w przypadku kolegi powyżej działa ona tylko dla 1 wiersza

  15. A ja mam inny problem. Chciałbym dodać pole radio. Nadałem mu name=”coś_tam_{0}” żeby każdy wiersz miał osobny wybór. Jednak w tym momencie nie wiem jak się do tego odwołać przy zapisie danych np. do sql. Mógłby ktoś pomóc?

  16. Bardzo proszę mi powiedzieć w jaki sposób mogę zapisać dane z formularza do bazy.

  17. Witam.

    Mam dla ciebie fajne wyzwanie. Otóż to co stworzyłeś gwarantuje ci tylko i wyłacznie zapisywanie pol. Sprobuj tak zapisana liste potem wczytac ( np po odswiezeniu strony ) zaktualizowac i oprocz tego dodac nowe pola. Glownym problemem bedzie rozwiazanie identyfikatorow. Nie bedziesz mogl juz uzywac rzeczy w stylu name=”imie[]” beda to musialy byc pola z identyfikatorami np imie[1]. Nowo dodane pole w formularzu nie moze uzywac juz tej samej nazwy jako ze po przekazaniu jej do php nada sie jej index. imie[]=”czesiek”, imie[]=wiesiek stanie sie tablica imie(0=>”czesiek” , 1=>”wiesiek”)

    Pozdrawiam

  18. @skoczman

    // tworzymy tablice która będzie zawierała wszystkie rekordy
    $dane = array();
    // przepisujemy dane do wspolnej tablicy
    $dane_0[’nr_reklamacji’] = $_POST[’nr_reklamacji’];
    $dane_0[’data_dostawy’] = $_POST[’data_dostawy’];
    $dane_0[’kontrahent’] = $_POST[’kontrahent’];
    $dane_1[] = $_POST[’asortyment’];
    $dane_1[] = $_POST[’ilosc’];
    $dane_1[] = $_POST[’uznana’];
    $dane_1[] = $_POST[’nieuznana’];
    $dane_1[] = $_POST[’powod’];

    $ile = count($dane_1[0])-1;

    for ($i = 0; $i <= $ile; $i++)
    {
    $x = $i;
    $wartosci[$i] = "('$dane_0[nr_reklamacji]','$dane_0[data_dostawy]',$dane_0[kontrahent]".','.$dane_1[0][$i].','.$dane_1[1][$i].','.$dane_1[2][$i].','.$dane_1[3][$i].','.$dane_1[4][$i].')';
    }

    $sql = "INSERT INTO `reklamacje`.`protokoly` ";
    // implode keys of $array…
    $sql .= " (`".implode("`, `", array_keys($_POST))."`)";
    // implode values of $array…
    $sql .= " VALUES ".implode(",", $wartosci)." ";
    $insert = mysql_query($sql) or die(mysql_error() );

    if($insert =='1')
    {
    header('Location: drukuj.php?nr_reklamacji='.$dane_0['nr_reklamacji'].'&komunikat=ok');
    exit;
    }
    else
    {
    print'’.$komunikat[11].”;
    exit;
    }

  19. Cześć,
    od pewnego czasu pracuję nad własnym systemem zamówień, wystawiania faktur itp.
    Szukałem rozwiązania które jest tu zamieszczone i bardzo za nie dziękuję.
    W Java S. jestem bardzo początkujący mimo, że już nie taki młody 🙂
    Czy zadane na początku w sekcji linki do zewnętrznych plików są „pewne”?
    Czy może się zdarzyć tak, że kiedyś nie zostaną odnalezione itp?
    Czy gdzieś można poczytać co z tych plików jest wykorzystywane na poziomie tego kodu?

  20. Trochę się zastanawiałem dlaczego tam jest textarea 😉
    Jakby ktoś miał wątpliwości, to można schować to również w divie, ale linijkę:

    var szablonWiersza = jQuery.format($(„#szablon”).val());

    trzeba zamienić na:

    var szablonWiersza = jQuery.format($(„#szablon”).html());

  21. remove() jest zrobione troche bez sensu, wlasnie skorzystalem z pomyslu ale przerabiam 😉

    zrobilbym clone() zamiast jakiegos nadmiarowego i chowanego textarea… (pozniej mozesz pojechac jQ i usunac value czy numer itd), odpada dodatkowa biblioteka validate czy jakos tak ktora sam musialem zaciagnac a juz i tak mam ich duzo za duzo.

    remove – $(this).parent().parent().remove() usunie „this” wiersz i to jest uniwersalne (przy tym html) bo sie nie babrzesz pozniej z licznikami w szablonie do edycji tylu nazwisk, dodajesz latwo ale pozniej chce miec mozliwosc edycji tego!
    Do tego hmm moze i samo bedzie dbac (przegladarka, oby wszystkie!) o odpowiednia numeracje (usuwajac 5-ty i 7-y z 10 elementow mamy „kaszane” w numeracji jak juz wyzej zauwazyli) ale z lenistwa nie chce mi sie tego sprawdzic 😉

    Tak wiec cos ala clone, lepszy remove ale to juz do doszlifowania wg potrzeb.

    pozdrawiam.

  22. Czemu to nie działa? Ani w przykładzie pokazanym ani w plikach które pobrałem?

  23. Wykorzystałem ten kod i wszystko działa doskonale. Jestem pod wrażeniem, jest to coś czego szukałem i będę wykorzystywał. Mam tylko mały problem, mianowicie pozycje formularza się tworzą, ale gdy dodałem do formularza listę rozwijana(dane są pobierane z bazy danych z tabeli produkty), to produkty są wyświetlane tylko w pierwszej linii, a potem po dodaniu kolejnych już nie. Nie wiem jak sobie z tym poradzić bardzo proszę o pomoc.
    Oto fragment kodu:

    {0}.
    <?php
    $zapytanie = mysql_query ("SELECT nazwa,id_services FROM mw_services ORDER BY nazwa DESC");
    echo '’;
    while($option = mysql_fetch_assoc($zapytanie)) {
    echo ”.$option[’nazwa’].”;
    }
    echo ”;
    ?>

  24. Przepraszam poprzednio źle skopiowało kod

    Zgłoszenie Niezgodności/Deviation Notice

    a
    {
    color: #0000FF;
    text-decoration: underline;
    }
    a:visited
    {
    color: #800080;
    }
    a:active
    {
    color: #FF0000;
    }
    a:hover
    {
    color: #0000FF;
    text-decoration: underline;
    }

    #wb_Form1
    {
    background-color: #FAFAFA;
    border: 0px #000000 solid;
    }
    #wb_Text1
    {
    background-color: transparent;
    border: 0px #000000 solid;
    padding: 0;
    text-align:center;
    }
    #wb_Text1 div
    {
    text-align: left;
    }
    #Combobox1
    {
    border: 2px #A9A9A9 solid;
    background-color: #FFFFFF;
    color: #000000;
    font-family: Arial;
    font-size: 13px;
    }
    #jQueryDatePicker1
    {
    border: 1px #A9A9A9 solid;
    background-color: #FFFFFF;
    color :#000000;
    font-family: Arial;
    font-size: 13px;
    text-align: left;
    vertical-align: middle;
    }
    .ui-datepicker
    {
    font-family: Arial;
    font-size: 13px;
    z-index: 1003 !important;
    display: none;
    }
    #wb_Text2
    {
    background-color: #DCDCDC;
    border: 1px #000000 solid;
    padding: 0;
    text-align: left;
    }
    #wb_Text2 div
    {
    text-align: left;
    }
    #wb_Text3
    {
    background-color: #DCDCDC;
    border: 1px #000000 solid;
    padding: 0;
    text-align: left;
    }
    #wb_Text3 div
    {
    text-align: left;
    }
    #Editbox1
    {
    border: 2px #A9A9A9 solid;
    background-color: #FFFFFF;
    color :#000000;
    font-family: Arial;
    font-size: 13px;
    text-align: left;
    vertical-align: middle;
    }
    #wb_Text4
    {
    background-color: #DCDCDC;
    border: 1px #000000 solid;
    padding: 0;
    text-align: left;
    }
    #wb_Text4 div
    {
    text-align: left;
    }
    #Editbox2
    {
    border: 2px #A9A9A9 solid;
    background-color: #FFFFFF;
    color :#000000;
    font-family: Arial;
    font-size: 13px;
    text-align: left;
    vertical-align: middle;
    }
    #wb_Text5
    {
    background-color: #DCDCDC;
    border: 1px #000000 solid;
    padding: 0;
    text-align: left;
    }
    #wb_Text5 div
    {
    text-align: left;
    }
    #Editbox3
    {
    border: 2px #A9A9A9 solid;
    background-color: #FFFFFF;
    color :#000000;
    font-family: Arial;
    font-size: 13px;
    text-align: left;
    vertical-align: middle;
    }
    #wb_Text6
    {
    background-color: #DCDCDC;
    border: 1px #000000 solid;
    padding: 0;
    text-align: left;
    }
    #wb_Text6 div
    {
    text-align: left;
    }
    #Editbox4
    {
    border: 2px #A9A9A9 solid;
    background-color: #FFFFFF;
    color :#000000;
    font-family: Arial;
    font-size: 13px;
    text-align: left;
    vertical-align: middle;
    }

    function ValidateForm1(theForm)
    {
    var regexp;
    if (theForm.Combobox1.selectedIndex < 0)
    {
    alert("Proszę wybrać/ Please choose");
    theForm.Combobox1.focus();
    return false;
    }
    regexp = /^[-+]?\d*\.?\d*$/;
    if (!regexp.test(theForm.Editbox3.value))
    {
    alert("PO number must be 10 signs long");
    theForm.Editbox3.focus();
    return false;
    }
    if (theForm.Editbox3.value == "")
    {
    alert("PO number must be 10 signs long");
    theForm.Editbox3.focus();
    return false;
    }
    if (theForm.Editbox3.value.length = && theForm.Editbox3.value <= ))
    {
    alert("PO number must be 10 signs long");
    theForm.Editbox3.focus();
    return false;
    }
    return true;
    }

    $(document).ready(function()
    {
    var jQueryDatePicker1Opts =
    {
    dateFormat: 'mm/dd/yy’,
    changeMonth: false,
    changeYear: false,
    showButtonPanel: false,
    showAnim: 'show’
    };
    $(„#jQueryDatePicker1”).datepicker(jQueryDatePicker1Opts);
    $(„#jQueryDatePicker1”).datepicker(„setDate”, „new Date()”);
    $(„#jQueryDatePicker1”).datepicker(„option”, $.datepicker.regional[’pl’]);
    });

    $(document).ready(function(){
    // tworzymy zmienną i, wykorzystamy ją do identyfikowania wierszy
    // ustawiamy jej wartość 2, ponieważ jeden wiersz jest już w tabeli
    var i = 2;
    // tworzymy zmienną szablonWiersza i wczytujemy do niej szablon z textarea #szablon
    var szablonWiersza = jQuery.format($(„#szablon”).val());
    // tworzymy funkcję, która dodaje szablon do właściwej tabeli
    function dodajWiersz() {
    var ii = i++;
    // dodajemy wiersz do właściwej tabeli
    $(„#listaOsob tbody”).append(szablonWiersza(ii));
    // Nowy wiersz jest już widoczny.
    // Każdy element nowego wiersza posiada swoje id
    // tr na id: wiersz_2, ikona minus ma id: usunOsobe_2
    // Do nowo dodanej ikony – dodajmy zdarzenie.
    // W momencie kliknięcia minusa, wiersz zostanie usunięty.
    $(„#usunOsobe_” + ii).click(function(){
    $(„#wiersz_” + ii).remove();
    });
    }
    // do przycisku #dodajOsobe dodajemy zdarzenie,
    // kliknięcie na przycisk plus wywoła fukcję dodajWiersz
    $(„#dodajOsobe”).click(dodajWiersz);
    });

    Rodzaj zamówienia:PO type

    Eksperyment/Experimental
    Produkcyjne/Production
    Nowe Uruchomienie/New Development

    Zgłoszenie niezgodnościDeviation Notification

    Nazwa dostawcy:Vendor Name

    Kod Dostawcy/Nr Kolejny/Rok:Vendor Code/Sequence Number/Year

    Zamówienie numer:PO number

    Kupiec:Buyer Name

    {0}/{0}

    <button id="usunOsobe_{0}" alt="usun">

    Item
    Położenie na rysunkuB/P Location
    Ilość sztuk niezgodnychQuantity
    +/-

    1/{0}

    +

    Wydrukuj/Print

  25. czesc, pytanie banalne, jak poprawic numeracje po usunięciu wiersza? dziekuje z góry

  26. Czy w kodzie jest jakiś błąd ? nie potrafię zmusić go do działania 🙁

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *