Pracując ostatnio nad pewnym projektem przyszło mi zaprogramować niezliczoną ilość formularzy. No taka niestety jest specyfika paneli administracyjnych. Postanowiłam więc przy tej okazji pokazać jak można tworzyć formularze za pomocą modułu formo, jak przekazać formularz do widoku oraz jak w widoku wykorzystać jego elementy.
Instalacja modułu formo
Moduł formo to dość wygodne narzędzie choć ma pewne niedoróbki. Prawdopodobnie dlatego wśród użytkowników Kohana można spotkać zarówno zwolenników jak i przeciwników tego narzędzia. Ja, mimo pewnych wad tego modułu lubię go używać do konstruowania formularzy. Oczywiście, żeby wykorzystać moduł formo moduł, trzeba go pobrać, rozpakować do katalogu modułów i dołączyć do aplikacji dokonując w pliku config.php następującego wpisu:
$config['modules'] = array ( MODPATH.'formo', }
Warto również zajrzeć na stronę, która zawiera manual formo. Nie jest on może dostatecznie obszerny i szczegółowy, ale na początek będzie musiał wystarczyć. Ja przyznam, że do wielu rzeczy musiałam dochodzić drogą analizowania kodu, co nie jest łatwe, gdyż moduł ten bazuje głównie na funkcji __call().
Projekt formularza
Praktycznie w każdym serwisie internetowym, w którym dopuszczamy możliwość logowania się użytkowników, w panelu administracyjnym musimy udostępnić odpowiednie narzędzia do zarządzania kontami użytkowników. Jednym z podstawowych jest formularz służący do dodawania nowego użytkownika lub edycji danych już istniejącego konta. Taki właśnie formularz postanowiłam omówić w tym artykule. Zaplanowałam, że będzie on wyglądał następująco:
Tworzenie formularza
Nie będę omawiać szczegółowo budowy konstruktora w którym zamieściłam metodę zawierającą implementację formularza, bo zakładam, że takie podstawy czytelnik już ma. Jeśli nie to zapraszam do poprzednich wpisów na temat Kohana 2. Ograniczę się do fragmentów niezbędnych do pokazania jak się tworzy formularz i przesyła dane do widoku.
Każda z metod modułu formo zwraca obiekt klasy Formo, dlatego metody można wywoływać łańcuchowo, tak jak to widać w poniższym kodzie:
$objForm = Formo::factory('user') ->add('username')->label('Nazwa użytkownika') ->add('email')->label('E-mail') ->add('password')->label('Hasło')->type('password') ->add('password2')->label('Powtórz hasło')->type('password') ->add_rule('password2', 'matches[password]') ->add_select('role',$arrRoles)->label('Uprawienia:') ->add_group('active', $arrActive)->label(' ')->check('active', 'nieaktywny') ->add_group('active_to[]', array(1 => ' tak, do: ')) ->label('Ograniczenie ważności konta: ') ->add_select('year', $arrYear) ->add_select('month',$arrMonth)->value($strMoth) ->add_select('day',$arrDay)->value($strDay) ->add('submit')->value('Dodaj');
Omówię teraz pokrótce kod. W pierwszej linijce wywołujemy statyczną metodę factory (choć to samo można zrobić za pomocą new i konstruktora). Do metody tej przekazuję parametr definiujący unikalną nazwę formularza.
W linijkach 2-5 mamy wykorzystanie metody add(), która powoduje dodanie do formularza zwykłych pól tekstowych. Za każdym razem, po dodaniu pola tekstowego wykorzystuję metodę label() za pomocą której definiuję etykietę tego pola. Warto poszperać w manualu i zobaczyć, że można to realizować na różne sposoby. W przypadku pól służących do wpisywania hasła wykorzystałam jeszcze metodę type(), która powoduje, że hasło jest ukryte. Ponad to w przypadku powtórzenia hasła wykorzystałam metodę add_rule(), która definiuje mi regułę niezbędną przy walidacji formularza. Reguła ta automatycznie, podczas walidacji sprawdza, czy hasła wpisane w pierwszym i w drugim polu są zgodne.
W liniach 7, 11-13 mamy metodę add_select(). Jest to metoda, która tworzy listę rozwijaną. Pierwszy z parametrów to unikalny identyfikator elementu, drugi parametr do tablica zawierająca elementy, które mają si na liście rozwijanej znaleźć. Później opiszę jak konstruowałam te tablice. W niektórych przypadkach zastosowałam metodę value() aby nadać ustawić listę rozwijaną w pozycji sugerowanej wartości.
W liniach 8 i 9 mamy metodę add_group() (najsłabszy moim zdaniem element modułu formo), która służy do zdefiniowanie pól opcji (radio button) w linii 8 i pól opcji (checkbox) w linii 9. Ich wywołanie różni się tylko tym, że w przypadku checkboxów identyfikator podany jako parametr pierwszy zawiera nawiasy prostokątne ‘[]’ i po tym moduł rozpoznaje, że to ma być checkbox a nie radio. Oczywiście można podać wprost odpowiedni parametr, ale jak widać nie jest to konieczne. Poza identyfikatorem elementu przyjmuje tablicę elementów, które mają być wyświetlone jako lista radio albo checkboxów. Konstrukcję tych tablic tez omówię później.
No i na końcu za pomocą metody add() dodajemy pole typu submit, czyli przycisk powodujący przesłanie danych z formularza.
Warto dodać, że kolejność dodawania elementów nie jest bardzo istotna. Oczywiście jeśli chcemy wyświetlać cały formularz automatycznie, to wtedy jego elementy zostaną wyświetlone w takiej kolejności w jakiej zostały do formularza dodane. Jednak etykiety i wartości poszczególnych elementów mogą być zmienione w dowolnym momencie (oczywiście nim przekażemy formularz do widoku) i na różne sposoby.
Tablice dla list rozwijanych checkboxów i radio
Konstrukcja tablic wykorzystywanych do tworzenia elementów takich jak lista rozwijana (select), pole opcji (radio button) czy pole wyboru (checkbox) jest bardzo prosta. Indeksy tablicy są wartościami przekazywanymi przez formularz a wartości tablicy są etykietami opisującymi poszczególne pola. I tak dla listy wyboru, za pomocą której zdefiniujemy poziom dostępu dla użytkownika (linia 7), wystarczy tablica o następującej zawartości:
$arrRoles = array(1 => 'admin', 2 => 'editor', 3 => 'subscribe');
Oczywiście to tylko przykład i w poważniejszych serwisach te dane najprawdopodobniej będą pobierane z odpowiedniej tabeli w bazie.
Podobnie prosta konstrukcję ma tablica dla elementu ‘active’ (linia 8):
$arrActive = array(0 => 'Nieaktywny',1 => 'Aktywny');
W przypadku jedynego checkboxa odpowiednia tablica została wpisana bezpośrednio w metodzie add_group():
add_group('active_to[]', array(1 => ' tak, do: '))
Na ten checkbox, jak również na omówione w poprzednim akapicie pola opcji (radio) warto zwrócić uwagę, bo będą one nas interesowały kiedy przejdziemy do omawianie skryptu jQuery.
No i na koniec kod definiujący trzy tablice używane w listach rozwijanych służących do ustalenia daty, po przekroczeniu której konto użytkownika przestaje być aktywne.
$arrYear = $arrMonth = $arrDay = array(); $arrYear[0] = $intYear = date('Y'); $strMoth = date('m'); $indMothDays = date('t'); $strDay = date('d'); for($i=1;$i<4;$i++){ $arrYear[$i] = (string)($arrYear[0]+$i); } for($i=1;$i<=12;$i++){ if($i<10) $arrMonth[$i] = '0'.$i; else $arrMonth[$i]=(string)$i; } for($i=1;$i<=$indMothDays;$i++){ if($i<10) $arrDay[$i] = '0'.$i; else $arrDay[$i] = (string)$i; } [/code] Jak widać daję możliwość wyboru roku z zakresu od bieżącego do czterech kolejnych lat. Poza tym ustalam bieżącą datę i wartości zmiennych $strMoth i $strDay wykorzystuję do ustawienia wartości pól selectów na bieżącej dacie. Zaś wartość zmiennej $indMothDays, to po prostu liczba dni w bieżącym miesiącu, która ogranicza wielkość tablicy arrDay. Wnikliwy czytelnik od razu zauważy, że jest to bez sensu, bo jeśli bieżącym miesiącem będzie luty, to będziemy mieć do wyboru tylko 28 dni i wybranie daty 13 maja będzie w takim wypadku nie możliwe. Oczywiście można by ustalić długość tablicy $arrDay na 31 pozycji ale w takim układzie można by wybrać datę 31 luty co również jest bez sensu. Najlepiej więc byłoby element ten obsłużyć za pomocą jQuery i w zależności od wartości wybranej z listy 'month' modyfikować dynamicznie długość listy 'day'. Temat ten zostawię sobie na następny artykuł. <h3>Przekazanie danych do widoku</h3> Dane do widoku można przekazać na dwa sposoby. Za pomocą metody get() można wygenerować string zawierający pełen kod naszego formularza, przekazać go do widoku i tam po prostu go wyświetlić. Metoda get() wywołana bez parametru przyjmuje domyślnie 'false'. W kontrolerze wystarczy wtedy wywołać kod $strFormularz = $objForm->get(); $this->teplate = new View('nasz_widok'); $this->template->set('formularz',$strFormularz);
W widoku wystarczy wtedy kod:
echo $formularz;
Jak widać jest to banalnie proste i bardzo fajne jeśli nie szczególnie zależy nam na wyglądzie formularza. Mnie osobiście nie podoba się wygenerowany w ten sposób kod HTML. Oczywiście można co nieco zmienić, grzebiąc choćby w pliku konfiguracyjnym modułu formo, albo wręcz przerabiając drivery poszczególnych elementów formularza. Ja jednak wolę przekazać do widoku tablicę zawierającą elementy formularza i w widoku obudować je odpowiednim kodem HTML. W takim przypadku trzeba wygenerować nie string a tablicę. Robimy to tak samo za pomocą metody get() ale z parametrem ‘true’. W kontrolerze wystarczy zatem kod:
$arrFormularz = $objForm->get(true); $this->teplate = new View('nasz_widok',arrFormularz );
W widoku trzeba się jednak bardziej namęczyć>. Można na przykład użyć tabeli, jak w przykładzie poniżej, albo np. listy definicyjnej terminów (dt) i opisów (dd).
<?php echo $open?> <table> <tr> <th><?php echo $username->label;?></th> <td><?php echo $username;?></td> </tr> <tr> <th><?php echo $email->label;?></th> <td><?php echo $email;?></td> </tr> <tr> <th><?php echo $password->label;?></th> <td><?php echo $password;?></td> </tr> <tr> <th><?php echo $password2->label;?></th> <td><?php echo $password2;?></td> </tr> <tr> <th><?php echo $role->label;?></th> <td><?php echo $role;?></td> </tr> <tr> <th></th> <td><?php echo $active;?></td> </tr> <tr id="to"> <th><?php echo $active_to->label;?></th> <td><?php echo $active_to.' '.$year.' - '.$month.' - '.$day;?></td> </tr> </table> <?php echo $close;?>
Jak widać w pierwszej i ostatniej linii trzeba wyświetlić elementy otwierające i zamykające, czyli generujące znacznik otwierający i zamykający formularza. Poza tym ręcznie wyświetlam etykietę każdego elementu oraz sam element. Jeśli do elementów zdefiniujemy reguły walidacji, jak to było w przypadku elementu ‘password2’, to możemy jeszcze wyświetlić, informacje o błędzie walidacji:
<?php echo $password2->error(); ?>
Uruchamiając demo 1 i demo 2 można zobaczyć jak różne efekty można uzyskać stosując obie opisane metody wyświetlania formularza. Czytelnik sam będzie mógł zdecydować, który sposób wybrać.
Walidacja formularza
Na koniec pozostało coś, co na ogół jest najmniej przyjemną częścią tworzenia formularza. Walidacja. Na szczęście formo załatwia całą robotę za nas. Wystarczy dobrze zdefiniować reguły dla każdego pola a wtedy obsługa formularza będzie wyglądać tak:
if($objForm>validate()){ //poprawna walidacja, zrób z danymi z formularza to co Ci się podoba }else{ //niepoprawna walidacja, wyświetl ponownie formularz albo informacje o błędzie }
Ciekawe wprowadzenie – nie korzystam z Kohany, ale z tego co wiem, modułu Formo da się użyć też w swoich projektach, także przyda się jako referencja.
Ponawiam swoją uwagę co do strony głównej – nie sposób jest eksplorować blog bez listy wpisów, a nie każdy wie, że boks “ostatnie wpisy” znajduje się na samym dole. Ja wiem, Webmasterko, że takie Twoje widzimisię, ale Internauta się nieco krzywi. ;]
Fajny wpis.
Od siebie powiem, że tych wynalazków jak formo nie lubię. Produkują dość syfiasty kod, a jakoś poprawiać mi się nie chce. Ja po prostu lubię jak formularz jest schludnie napisany, jak reszta kodu.
A.
Do Tomka: Co do eksploracji bloga. Po prawej strony jest drzewo ‘Działy’ z podziałem tematycznym wpisów :)
Co do sugestii, to jeszcze trochę się zastanowię :)
Do Andrzeja: Ja również nie lubię kodu HTML, który formo generuję automatycznie. Z resztą podkreśliłam to przy okazji. Podałam tez rozwiązanie tego problemu. :) Formo lubią za to, że dobrze zdefiniowane reguły załatwiają validację automatycznie.
Bardzo fajny opis. Co prawda o Formo dowiedziałem się po tym jak napisaliśmy klasę do obsługi formularzy w firmie, ale widzę, że idea jest podobna. Prosto i przejrzyście. Jedno pytanie czy Formo ma wsparcie dla AJAXowej walidacji danych?
Ja polecam formo 2 (Kohana 3). Moim zdaniem zdecydowanie lepiej zaprojektowane i łatwiej się rozszerza (napisanie wtyki dla ckeditora to 10min pracy).
Mało tego ma wsparcie dla subformów i prefixowania nazw w fomularzach. Przyzwyczaił mnie do tego symfony i bywa mega przydatne.
Do Andrzeja: W formo 2 wszystko co “renderuje” formo to tak na prawdę widoki kohanowe. Jeśli nałożyć na to to że ustalasz typ “folder w view/formo/” i kaskadowość kohany możliwości są niemal nieograniczone.
Do Krzysztofa: Coś tam ma:
http://avanthill.com/formo_manual/doku.php?id=plugins:ajaxval
ale ja nie próbowałam i nie wgryzałam się w temat.
Do ZuluSa: Jeszcze się nie przekonałam do Kohana 3, pewne rzeczy mi się tam nie podobają i póki co nie zamierzam się przesiadać :)
Joanna: Pracuje na obu (stare/nowe projekty). Na pewno jest szybszy, zwłaszcza na php 5.3 (wsparcie dla APC, hierarchia klas zgodna SPL, ograniczenie “magii” php jak się tylko dało dzięki czemu netbeans wreszcie ładnie podpowiada, lepsza kaskadowość konfiguracji i najważniejsze dla mnie: nielabelowy i18n, który stosunkowo łatwo spiąć z gettextem ). Niestety ie gra dobrze z Postgresem, ale da się to obejść ;)
Wady to jak z przejściem z Symfony 1.4 na Symfony 2. Praktycznie trza się wszystkiego nauczyć od nowa ;)
No i właśnie ta wymieniona przez Ciebie wada nieco mnie stopuje. Póki co nie mam czasu na naukę nowego frameworka i przepisywanie wszystkich zaczętych projektów :)
Poza tym nie przekonuje mnie zastosowany tam wzorzec projektowy (wiem wiem, nie jest obowiązkowy) i parę innych rzeczy. No ale ja to konserwa jestem. Jak się w czymś zakochuje to prawie na całe zycie i trzeba naprawdę czegoś ekstra, żebym zmieniła obiekt westchnień.
Świetny artykuł.
Napisany z sensem, objaśniony każdy szczegół.
Zaliczył bym go do grona 1% tych sensownych w sieci.
Super stronka
Bardzo przydatny wpis, ale prosiłbym pamiętać, że słowo “puki” pisze się przez “ó”, czyli “póki”; już kolejny raz ten sam błąd widzę :)
Pozdrawiam
Rzeczywiście, aż dziwne, że podpowiadacz mi tego nie wyłapuje :)