Ajax: powiązane pola select

Autor: Arkadiusz Tobiasz 7 września 2008

Nieraz w serwisach internetowych widziałeś dynamiczne pola select, które uaktywniały się po wyborze opcji z pierwszego selecta. Jeśli chcesz mieć coś takiego na swojej stronie, to ten wpis jest dla Ciebie. Postaram się tutaj krok po kroku pokazać jak to zrobić.

Zacznijmy od prostego formularza w html-u:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<form>
      <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
     <html xmlns="http://www.w3.org/1999/xhtml">
      <head>
      <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-2" />
      <title>Prosta strona HTML</title>
      </head>
      <body>
      <form>
        <table width="300" border="0">
          <tr>
            <td width="200">
          <select name="mid" id="mid">
              <option value="">Wybierz markę</option>
              <option value="1">Audi</option>
              <option value="2">BMW</option>
            </select>
          </td>
            <td width="100">
          <select name="model" id="model">
              <option value="">Wybierz model</option>
              <option value="1">A3</option>
              <option value="2">A4</option>
          <option value="2">323</option>
            </select>
          </td>
          </tr>
        </table>
      </form>
      </body>
      </html>

Jak widzimy w powyższym kodzie kiedy wybierzemy daną markę samochodu, dajmy na to BMW, to w modelach mamy do wyboru także modele Audi. W tym tutorialu po wyborze marki będą dostępne modele tylko wybranej marki.

Swój skrypt napiszemy w PHP wykorzystując MySQL. W tym celu musimy utworzyć dwie tabele, pierwszą, w której będziemy trzymać dane dotyczące marek samochodu oraz drugą z odpowiednimi modelami samochodów.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
CREATE TABLE `marki` (
`id` INT(9) NOT NULL AUTO_INCREMENT,
`marka` VARCHAR(255) NOT NULL DEFAULT '',
PRIMARY KEY  (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin2 ;

INSERT INTO `marki` VALUES(1, 'Audi');
INSERT INTO `marki` VALUES(2, 'BMW');

CREATE TABLE `modele` (
`id` INT(9) NOT NULL AUTO_INCREMENT,
`mid` INT(9) NOT NULL DEFAULT '0',
`model` VARCHAR(255) NOT NULL DEFAULT '',
PRIMARY KEY  (`id`),
KEY `mid` (`mid`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin2 ;

INSERT INTO `modele` VALUES(1, 1, 'A3');
INSERT INTO `modele` VALUES(2, 1, 'A4');
INSERT INTO `modele` VALUES(3, 2, '320');
INSERT INTO `modele` VALUES(4, 2, '323');
INSERT INTO `modele` VALUES(5, 2, '324');

Krótki komentarz, tworzymy powyżej dwie tabele marki i modele, tabela modele jest powiązana z tabelą marki za pomocą klucza mid.

Przyszedł czas na modyfikację kodu HTML i przerobienie go na skrypt php, który nazwiemy samochody.php. Musimy linijki 12-16 przerobić w taki sposób, aby dane były pobierane z bazy danych. A więc fragment:

1
2
3
4
5
<select id="mid" name="mid">
<option>Wybierz markę</option>
 <option value="1">Audi</option>
<option value="2">BMW</option>
 </select>

zamieniamy na:

1
2
3
4
5
6
7
8
9
10
11
12
      <?php
        include("config.php");
        echo "<select name=\"mid\" onchange=\"ajaxFunction()\" id=\"mid\" width=\"25\">"
        ."<option value=\"\">--wybierz--</option>";
        $result2 = mysql_query("SELECT id, marka FROM marki ORDER BY marka");
        while ($row = mysql_fetch_array($result2)) {
          $mid = intval($row['id']);
          $marka = $row['marka'];
          echo"<option value=\"".$mid."\">".$marka."</option>";
        }
        echo"</select><br>";
      ?>

W powyższym kodzie dołączyliśmy jeszcze jeden plik, który odpowiada za połączenie z bazą danych. Plik config.php wygląda następująco:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
      <?php
       
      $sql_host = "localhost";        // host bazy danych, najczesciej localhost
      $sql_user = "tobiasz_arek";  // uzytkownik bazy danych
      $sql_password = "haselko";  // haslo do bazy danych
      $sql_baza = "tobiasz_auto"; // nazwa bazy danych, z ktorej bedzie korzystal portal
      $prefix = "toar";    // prefix uzywany dla tabel korzystajacych z systemu ToAr

      /* Polaczenie z MySQL'em */
      $baza = mysql_connect($sql_host, $sql_user, $sql_password);
        if ($baza) {
      /* Jezeli polaczenie zostalo nawiazane wybieramy baze danych, z ktorej korzysta strona */
          $wynik = mysql_select_db($sql_baza);
        if($wynik) {
        /* narzucamy odpowiednie kodowanie dla clienta i polaczenia */
         mysql_query('SET character_set_connection=latin2');
         mysql_query('SET character_set_client=latin2');
         mysql_query('SET character_set_results=latin2');
        }
      }
      ?>

Po zapisaniu tych dwóch plików i wrzuceniu na serwer nasze marki samochodów powinni się wyświetlać, więc teraz czas na zrobienie dynamicznego generowania modeli. W kodzie selecta jest odwołanie do funkcji ajaxFunction(), dlatego teraz zajmiemy się nią. Na początku w sekcji musimy dodać plik Javascriptu:

1
2
3
4
5
      <head>
      <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-2" />
      <title>Prosta strona HTML</title>
      <script language="javascript" type="text/javascript" src="ajax.js"></script>
      </head>

Plik, który musimy stworzyć to ajax.js, jego struktura prezentuje się następująco:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
 function ajaxFunction(){
 var ajaxRequest;

try{
 ajaxRequest = new XMLHttpRequest();
 } catch (e){
 try{
 ajaxRequest = new ActiveXObject("Msxml2.XMLHTTP");
 } catch (e) {
 try{
 ajaxRequest = new ActiveXObject("Microsoft.XMLHTTP");
 } catch (e){
 alert("Your browser broke!");
 return false;
 }
 }
 }

ajaxRequest.onreadystatechange = function(){
 if(ajaxRequest.readyState == 4){
 var ajaxDisplay = document.getElementById('ajaxDiv');
 ajaxDisplay.innerHTML = ajaxRequest.responseText;
 }
 }
 var mid = document.getElementById('mid').value;
 var queryString = "?mid=" + mid;
 ajaxRequest.open("GET", "modele.php" + queryString, true);
 ajaxRequest.send(null);
 }

W linijkach 4-17 jest tzw. przechwytywanie wyjątków, które wyświetli odpowiedni komunikat jeżeli przeglądarka nie będzie w stanie wykonać skryptu. Kolejne linijki odpowiadają za przechwycenie wartości wybranego pierwszego selecta i wygenerowanie nowego, który znajduje się w pliku modele.php. Skrypt ten pobiera id wybranej marki samochodu za pomocą parametru $_GET[‚mid’]. W związku z tym najpierw w naszym pliku samochodu.php zmienimy kod drugiego selecta na:

1
2
3
      <td width="100">
      <div id='ajaxDiv'></div>
      </td>

W sekcji ajaxDiv po wybraniu marki samochodu pojawią się do wyboru jej modele, ale najpierw musimy stworzyć odpowiedni plik modele.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
      <?php
      $mid = $_GET['mid'];
      if(!empty($mid)) {
        include("config.php");
        $dropdown = "<select name=\"model\"  id=\"model\" width=\"25\">";
        $dropdown .= "<option value=\"\">--wybierz--</option>";

        $result2 = mysql_query("SELECT id, model FROM modele WHERE mid=".$mid." ORDER BY model");

        while ($row = mysql_fetch_array($result2)) {
          $id = intval($row['id']);
          $model = $row['model'];
          $dropdown .= "<option value=\"".$id."\">".$model."</option>";
        }
        $dropdown .= "</select><br>";
      echo $dropdown;
}
      ?>

Jeszcze na wszelki wypadek kod pliku samochody.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
      <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
      <html xmlns="http://www.w3.org/1999/xhtml">
      <head>
      <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-2" />
      <title>Prosta strona HTML</title>
      <script language="javascript" type="text/javascript" src="ajax.js"></script>
      </head>
      <body>
      <form>
        <table width="300" border="0">
          <tr>
            <td width="200">
        <?php
        include("config.php");
        echo "<select name=\"mid\" onchange=\"ajaxFunction()\" id=\"mid\" width=\"25\">"
        ."<option value=\"\">--wybierz--</option>";
        $result2 = mysql_query("SELECT id, marka FROM marki ORDER BY marka");
        while ($row = mysql_fetch_array($result2)) {
          $mid = intval($row['id']);
          $marka = $row['marka'];
          echo"<option value=\"".$mid."\">".$marka."</option>";
        }
        echo"</select><br>";
      ?>
          </td>
            <td width="100">
          <div id='ajaxDiv'></div>
      </td>
          </tr>
        </table>
      </form>
      </body>
      </html>

W taki oto sposób udało się wykonać nam dynamicznego selecta 😉 Mam nadzieję, że nigdzie nie popełniłem błędu, bo miejscami pisałem z głowy.

komentarzy 38

  1. Tomasz Olszewski napisał(a):

    podany skrypt nie działa

  2. Tomasz Olszewski napisał(a):

    poprawki do skryptu:

    HTML / linia 8

    >>

    AJAX:
    var bid = document.getElementById(‚mid’).value;
    >>
    var mid = document.getElementById(‚mid’).value;

    Pozdrawiam

  3. Arkadiusz Tobiasz napisał(a):

    Tak, mój błąd, literówka. Dokonałem kilka poprawek i już powinno wszystko śmigać.

  4. Mori napisał(a):

    Wybacz, bo tylko rzuciłem okiem… Nigdzie nie sprawdzasz stringów od usera? :/ Wstyd! I zła praktyka, bardzo zła.

    Używaj mysql_real_escape_string() !

  5. Arkadiusz Tobiasz napisał(a):

    ale to są pola select i user nie może użyć dowolnego ciągu znaków, jest ograniczony tylko i wyłącznie do listy rozwijanej.

  6. Mori napisał(a):

    Wybacz, Arkadiuszu, ale bzdura. Każdy może dowolnie zmodyfikować sobie zawartość POSTa w momencie przesyłania – musi mieć tylko odpowiednie narzędzia. Nie mówiąc o tym, że ja mogę sobie w Operze wyedytować kod Twojej strony, wczytać do lokalnie i mieć w SELECT’cie dowolne rzeczy.

    Hej, wrzuć to gdzieś live – takie demo. I np. zapisuj dokładnie string, jaki zostanie przesłany przez SELECT’a. Nie ingeruj w niego, nic… I zobaczymy, czy da się przesłać tylko to, co jest na liście.

  7. Arkadiusz Tobiasz napisał(a):

    Tylko na moim serwerze to nie zadziała, bowiem moje ustawienia PHP mają odblokowaną opcję Magic Quotes, która automatycznie dodaje znacznik prawego ukośnika do apostrofów i cudzysłowów.

  8. Mori napisał(a):

    Tak czy tak – zostało udowodnione, że magic quotes nie wystarcza. Do „zabicia” zapytania wystarczą i inne znaki.

    Zresztą… Zaiwanione z opisu funckji na PHP.net:

    mysql_real_escape_string() calls MySQL’s library function mysql_real_escape_string, which prepends backslashes to the following characters: \x00, \n, \r, \, ‚, ” and \x1a.

    Twój magic_quotes niczym poza ‚ czy ” się nie zajmie… Naprawdę Arku – testowałem, wiem.

  9. lukas napisał(a):

    A widzisz Mori ja właśnie tego szukałem, jeszcze nie zrobiłęm pod siebie ale zaraz próbuje. I nie interesuje mnie zabezpieczenie skryptu. Dopiero się ucze i wole aby był on czytelny na tyle abym mógł zrozumieć zasade działania.
    A w momencie kiedy dojde do wprawy zaczne zabezpieczać skrypt.
    W końcy w szkole też cię uczyli dodawać od 1+1 a nie od razu jakieś wyrażenia z całkami itd.
    Jak dla mnie przydatne info.
    A nigdzie nie jest napisane uwaga super bezpieczny skrypt itd.
    Pozdr

  10. Tadek napisał(a):

    Bardzo poprosze o wersje live. Tez sie dopiero ucze i nie chce smigac ;/ albo bez wielkiego czarowania co sie po kolei robi tylko gotowe pliki. Pozdrawiam

  11. lukas napisał(a):

    no prykro mi bardzo ale to u mnie nie działa, także szkoda czasu

  12. Arkadiusz Tobiasz napisał(a):

    już poprawiłem, zła nazwa id w kodzie było. Działający skrypt można podejrzeć pod adresem poniżej
    http://tobiasz.org/skrypty/ajax/samochody.php

  13. lukas napisał(a):

    teraz śmiga ładnie 😉

  14. Sisko napisał(a):

    A wiecie moze jak zrobic aby wyswietlone w ten sposob dane mozna bylo zapisac do bazy danych za pomoca php?Bo jakos nie moge sobie z tym poradzic. Z gory dzieki za info. Skrypt ogolnie super;)

  15. Arkadiusz Tobiasz napisał(a):

    jak w zwykłym formularzu, czyli po wysłaniu tego za pomocą metody POST
    powinniśmy mieć zmienną $_POST[‚model’], która będzie zawierać id danego modelu

  16. Sisko napisał(a):

    Robie to tak i niestety wyskakuje mi ze nie sa wypelnione wszystkie pola czyli tak jakby nie czyta mi tej zmniennej z formularza:/

    <?php

    //utworzenie nazw zmiennych
    $model=$_POST[‚model’];
    $marka=$_POST[‚marka’];
    $tytul=$_POST[‚tytul’];
    $opis=$_POST[‚opis’];
    $budzet=$_POST[‚budzet’];
    $platnosc=$_POST[‚platnosc’];

    if(!$model || !$marka || !$tytul || !$opis || !$budzet || !$platnosc)
    {
    echo ‚Nie podano wszystkich danych.’
    .’Wróć i spróbuj ponowanie.’;
    exit;
    }

    if (!get_magic_quotes_gpc())
    {
    $model = addslashes($model);
    $marka = addslashes($marka);
    $tytul = addslashes($tytul);
    $opis = addslashes($opis);
    $budzet = addslashes($budzet);
    $platnosc = addslashes($platnosc);
    }
    @$db = new mysqli(‚localhost’ , ‚root’ , ‚vertrigo’ , ‚ogloszenia’);

    if (mysqli_connect_errno())
    {
    echo ‚Błąd: : Połączenie z bazą danych nie powiodło się.’;
    exit;
    }

    $zapytanie = insert into ogloszenia (kategoriaid, tytul , opis , budzet , platnosc) values (‚.$model.’ , ‚.$marka.’ , ‚.$tytul.’ , ‚.$opis.’ , ‚.$budzet.’ , ‚.$platnosc.’);
    $wynik = $db->query($zapytanie);
    if ($wynik)
    echo $db->affected_rows.’ogłoszenie dodano do bazy.’;
    ?>

  17. Arkadiusz Tobiasz napisał(a):

    a formularz, ja korzystałem z tego przy robieniu komercyjnego portalu i wszystko śmigało jak trzeba 😉

  18. Piter napisał(a):

    Jak później pobrać dane value z drugiego selecta?
    $_POST zwraca mi wypisane wartości, a nie value.

    Proszę o odpowiedź na maila.

  19. Arkadiusz Tobiasz napisał(a):

    zmienna $_POST[‚model’] zwraca wartość drugiego selecta

  20. robbys napisał(a):

    Niestety nie działa 🙁 Pojawia się takie ostrzerzenie po poworcie do „wybierz” w pierwszym polu wyboru. Warning: mysql_fetch_array(): supplied argument is not a valid MySQL result resource in /home/tobiasz/domains/tobiasz.org/public_html/skrypty/ajax/modele.php on line 10

  21. Arkadiusz Tobiasz napisał(a):

    wystarczy dodać warunek

    if(!empty($mid)) {

    poprawiłem kod pliku modele.php

  22. widaw napisał(a):

    A jak zrobić to samo tylko z trzema polami?

  23. […] pola select, a nawet więcej. Tutorial ten będzie kontynuacją opublikowanego ponad rok temu wpisu Ajax: powiązane pola select, więc zanim przystąpisz do czytania dalszej części wpisu wykonaj wszystkie rzeczy w podanym […]

  24. robbys napisał(a):

    Jak zauważyłem brak ponumerowanych linków z kontynuacją tematu. W poprzedniej wersji bloga występowały. Szkoda 🙁

  25. Arkadiusz Tobiasz napisał(a):

    dzięki, już poprawiłem 😉

  26. […] czas temu napisałem jak można powiązać ze sobą dwa pola select, tak aby drugi generował się dynamicznie na podstawie wyboru dokonanego w pierwszym polu select. […]

  27. radek napisał(a):

    Witam,

    Jak zrobić żeby formularz pamiętał wybór z pierwszego selecta ? Po zatwierdzeniu tego formularza drugi select znika a w pierwszym należy ponownie wybrać opcję. Jak zrobić żeby pierwszy select pamiętał wybrana opcję ?

    • Arkadiusz Tobiasz napisał(a):

      zatwierdzeniu, czyli wysłaniu? Trzeba odczytać wartość wysłanego pola formularza i porównać z wartościami kolejnych opcji selecta, w przypadku, gdy się pokrywają dodać atrybut selected. Fragment przykładowego kodu:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      .'<select name="deliverer_id" onchange="this.form.submit();">'
          .'<option value="0">--wybierz--</option>';
          $delivererdata = Deliverer::find_all();
          $count = Deliverer::count_all();
          if($count > 0) {
              for($i=0; $i<$count; $i++) {
                  if(isset($_POST['deliverer_id']) && $_POST['deliverer_id'] == $delivererdata[$i]->deliverer_id) {
                      echo '<option value="'.$delivererdata[$i]->deliverer_id.'" selected>'.$delivererdata[$i]->name.'</option>';
                  } else {
                      echo '<option value="'.$delivererdata[$i]->deliverer_id.'">'.$delivererdata[$i]->name.'</option>';
                  }
              }
          }
      echo '</select>'
  28. Luka napisał(a):

    Nie działają mi polskie znaki. Czy wam też nie?

  29. Artur napisał(a):

    Witam mam Problem spodobal mi sie ten projek wiec postanowilem wykozystac tylko niestety mam taki problem, select dziala w IE zas w FF i Opra Google Chrom Mozilla — slelekt dziala, tylko w formularzu po wybraniu np marki nastpenie modelu auta wyslajac formularz nie wysla modelu. model jest pusty. i wbazie nie ma zapisanego modelu

    co moze byc powodem, nie wiem czy jasno opisalem ale bardzo prosze o pomoc co mam zrobic by w tych przegladarkach wykozystujac ten skrypt dalo sie wyslac formularzem ten wybor

  30. Ardo napisał(a):

    Mam pytanie czy ten skrypt dziala pod FF, Opera Google Chrome, bo u mnie nie, nie mam na mysli dzialnie wstylu wyciagnie z bazy danych, a za pomoca tego skryptu i umieszczeniu go w formularzu, czy dziala przesylanie zapytania, sprwadzalem na prztkladowym takim prostym formularzu dodajac ten wybor co tu jest napisany i wynika tak z tego doswiadczenia, pod IE dziala zas pod FF, Mozilla Opera Chrome juz nie, Obiawy sa takie widac dane pobrane z bazy ale wysylanie ich z formularza nie dziala, dane nie sa wyslane np jako model = IBIZA to formularz wysla model=”” czyli pusty

    czy tak jest a moze cos nalezy zminic by to dzialalo prosze o opinie na ten temat.

    • Arkadiusz Tobiasz napisał(a):

      dane są dobrze wysyłane, zastosowałem to rozwiązanie w kilkunastu serwisach i niezależnie od przeglądarki dane są wysyłane, oczywiście nalezy je odczytać jako $_POST[‚mid’] czy $_POST[‚model’]

  31. Miako napisał(a):

    Czy mozna prosić o pełny skrypt wraz z tym:
    „.”
    .’–wybierz–‚;
    $delivererdata = Deliverer::find_all();
    $count = Deliverer::count_all();
    if($count > 0) {
    for($i=0; $i<$count
    …."

    jestem poczatkujacy i nie bardzo wiem jak to mam polaczyc do Pana skryptu co jest wyzej by działalo prosze o pomoc w tej sprawie, co musi zawierac plik modele.php bo rozumie ze ten plik ma byc zmodyfikowany. Prosze o pomoc w tej sprawie.

    zgory dziekuje

    • Arkadiusz Tobiasz napisał(a):

      Plik odwołuje się do klasy Deliverer i to przykład z jednego skryptu jaki wykonywałem na zlecenie. Nie widzę potrzeby jego zamieszczania. Odpowiada on za wyświetlenie w pętli po kolei wszystkich dostępnych dostawców jako kolejne opcje selecta. W pliku modele.php należy umieścić w pętli kolejne opcje selecta, najczęściej robi się to poprzez wyciągnięcie ich w bazy i prezentację za pomocą pętli for() lub while()

  32. Marko napisał(a):

    Czy mozna liczyc na pomoc ?

    –> Jak zrobić żeby formularz pamiętał wybór z pierwszego selecta ?

    czy mozna prosić o przykladowy skrypt jak to wyglada w praktyce ?
    odpowiedz jest ale nie wiem jak to ma być połączone z skryptem głównym

Odpowiedz

 

Arkadiusz Tobiasz student Akademii Ekonomicznej im. Karola Adamieckiego w Katowicach na specjalnościach informatyka ekonomiczna oraz rachunkowość. Więcej...

jQuery Validation i funkcja remote

Jakiś czas temu zwrócił się do mnie użytkownik z problemem. Chodzi o to, że korzysta on z pluginu walidacji jQuery, […]

Zend Framework: integracja z Uploadify

W tym wpisie postaram się przedstawić Wam w jaki sposób zintegrować skrypt Uploadify z Zend Frameworkiem. Dzięki temu będziemy mogli […]

Javascript: Czasowe wyświetlanie reklamy

Czasami chcemy, aby na pewnym elemencie naszej strony wyświetlała się reklama przez jakiś czas, a następnie zniknęła. W tym wpisie […]

Linux: backup wszystkich baz danych MySQL

Swego czasu pisałem o tym jak z poziomu konsoli można szybko i przyjemnie zrobić backup bazy MySQL. Wszystko jest ładnie […]