Czym jest OPF?
Krótko o projekcie - Open Power Forms to biblioteka służąca do przetwarzania formularzy HTML. Jej główną zaletą ma być pełna integracja z systemem szablonów Open Power Template, co uczyni projektowanie wyglądu formularzy dużo łatwiejszym, niż w przypadku czystego PHP, szczególnie przy zaawansowanych układach. Projekt jest niemal równie stary, jak OPT (pomysł i pierwsze kawałki kodu powstały już w 2005 roku) i choć wykorzystałem go z powodzeniem na paru stronach, nie doczekał się nigdy stabilnego, a nawet przyzwoitego publicznego wydania. Wraz z ukazaniem się OPT 2.0, było tylko kwestią czasu rozpoczęcie prac nad OPF 2.0, dużo lepiej zaprojektowanym i zintegrowanym z nową wersją.
Pierwsze uruchomienie
Najtrudniejszą rzeczą na początku prac jest uruchomienie wszystkich podstawowych mechanizmów tak, aby uzyskać jakiś punkt startowy dla całej reszty zadań. Celem przetestowania, stworzyłem prosty formularz:
<?php /** * The development test file used in the implementation process. * * @author Tomasz Jęrzejewski <http://www.zyxist.com/> */ require('./init.php'); class My_Form extends Opf_Form { // An event public function onInit() { $item = $this->itemFactory('title'); $item->setRequired(true); $item->addValidator(new Opf_Validator_Length(5), 'The length is invalid'); $item->setWidget(new Opf_Widget_Input); $item = $this->itemFactory('countries'); $item->setRequired(true); $item->addValidator(new Opf_Validator_Type(Opf_Validator_Type::INTEGER), 'The field type is invalid.'); $item->setWidget(new Opf_Widget_Select); } // end onInit(); // An event public function onRender() { $view = $this->getView(); $view->setFormat('default', 'Form/Form'); $item = $this->itemFactory('countries'); $item->getWidget()->setOptions(array(0 => 'China', 'France', 'Germany', 'Great Britain', 'Poland', 'Russia', 'Spain', 'United States' )); } // end onRender(); // An event public function onAccept() { $view = $this->getView(); $view->setTemplate('results.tpl'); $results = array(); foreach($this->getValues() as $name => $value) { $results[] = array('name' => $name, 'value' => $value); } $view->results = $results; } // end onAccept(); } // end MyForm; try { $tpl = new Opt_Class; $opf = new Opf_Class; $tpl->sourceDir = './templates/'; $tpl->compileDir = './templates_c/'; $tpl->compileMode = Opt_Class::CM_REBUILD; $tpl->setup(); $view = new Opt_View('situation_1.tpl'); $view->devFile = 'situation_1.php'; $form = new My_Form('form1'); $form->setView($view); $form->execute(); $output = new Opt_Output_Http; $output->render($view); } catch(Opf_Exception $exception) { $handler = new Opf_ErrorHandler; $handler->display($exception); } catch(Opt_Exception $exception) { $handler = new Opt_ErrorHandler; $handler->display($exception); } catch(Opl_Exception $exception) { $handler = new Opl_ErrorHandler; $handler->display($exception); }
W OPF, formularz będzie można zaprojektować na kilka sposobów. Jednym z nich, prezentowanym na przykładzie, jest rozszerzenie klasy Opf_Form i nadpisanie kilku zdarzeń w stylu onInit() czy onAccept(). Jeśli nie chcemy tworzyć dziesiątek klas, zawsze możemy przechwycić wynik metody execute() i odpowiednio nań zareagować, konfigurując cały formularz w używającym go kodzie.
Formularz składa się z tzw. elementów (ang. items). Element to nazwany zbiór filtrów i sprawdzarek, któremu możemy przypisać widget. Widgety odpowiadają za wyświetlenie kontrolki formularza, za pomocą której użytkownik będzie wprowadzał dane do elementu i od strony technicznej są zwyczajnymi komponentami OPT. W tym przypadku wybrałem budowanie formularza w całości po stronie skryptu. Szablon dostaje już gotowe widgety, które musi tylko wyświetlić przy pomocy sekcji. Tu ujawnia się pierwsza rzecz, która ma wyróżniać OPF spośród innych systemów. W skrypcie PHP nie będziemy musieli wywoływać setWidget() na elemencie, aby zdefiniować, jak będzie mógł się wyświetlić. Zadanie to będzie mogło być przerzucone w całości na szablon, gdzie użyjemy zwykłego znacznika XHTML odpowiadającego danemu widgetowi. System automatycznie skojarzy go z odpowiednim elementem, zaś my będziemy mogli go ustawić dokładnie tak, jak chcemy, bez bawienia się skomplikowaną obiektówką.
Oczywiście na tym zalety wykorzystania OPT się nie kończą. Nawet definiując widgety po stronie skryptu, wciąż możemy w bardzo intuicyjny sposób manipulować otoczeniem każdego pola dzięki dostępnym w OPT snippetom. Oto fragment szablonu dla powyższego przykładu:
<opf:form name="form1"> <p opt:if="not $system.form.valid">The form is invalid.</p> <!-- display the items assigned to "default" placeholder --> <opt:section name="default"> <opt:component from="$default.component"> <!-- a single field look --> <com:div> <p>{$system.component.title}</p> <opt:display /> <opt:onEvent name="error"> <p class="error">{$system.component.error}</p> </opt:onEvent> </com:div> </opt:component> </opt:section> <input type="submit" value="Submit" /> </opf:form>
Otoczenie pola formularza zdefiniowane zostało prosto, łatwo i przyjemnie, bez żonglowania dziesiątkami plików i klas, często łamiących ideę separacji logiki od prezentacji. Jeśli widok nam się spodoba tak bardzo, że zechcemy go wykorzystać w innych formularzach, pakujemy go w snippet i ładujemy wszędzie, gdzie tego pragniemy:
<opt:snippet name="widget"> <!-- a single field look --> <com:div> <p>{$system.component.title}</p> <opt:display /> <opt:onEvent name="error"> <p class="error">{$system.component.error}</p> </opt:onEvent> </com:div> </opt:snippet> <opf:form name="form1"> <p opt:if="not $system.form.valid">The form is invalid.</p> <!-- display the items assigned to "default" placeholder --> <opt:section name="default"> <opt:component from="$default.component" template="widget"> </opt:component> </opt:section> <input type="submit" value="Submit" /> </opf:form>
Poniżej przedstawiony jest z kolei szablon formularza, gdzie zdecydowaliśmy się wszystko robić ręcznie (z tym samym snippetem):
<opf:form name="form1"> <p opt:if="not $system.form.valid">The form is invalid.</p> <!-- display the items assigned to "default" placeholder --> <opf:input name="title" template="widget"> </opf:input> <opf:select name="countries" datasource="$countries" template="widget"> </opf:select> <input type="submit" value="Submit" /> </opf:form>
W najbliższych dniach będę dalej implementować minimalną funkcjonalność, tj. wypełnianie pól domyślnymi danymi, przepisywanie do nich wartości w przypadku błędnego wypełnienia, obsługę błędów czy wsparcie dla CSS-a. Jednak przed biblioteką jeszcze długa droga. Planowo, narzędzie ma umożliwić tworzenie i bezproblemowe działanie nawet najbardziej skomplikowanym układom. Krok w tym kierunku już został poczyniony - klasa Opf_Form jest jednym z potomków Opf_Item reprezentującego element, co umożliwi tworzenie dużego formularza poprzez połączenie ze sobą kilku mniejszych. Niebawem zacznę także projektować obsługę zdarzeń zapewniających formularzom podobną funkcjonalność, jak zachowania (behaviours) w Doctrine, dla którego nawiasem mówiąc też jakieś wsparcie planuję. Będzie bardzo miło, jeśli formularz OPF będzie potrafił zintegrować się z odpowiednim modelem i automatycznie przepisywać do niego przychodzące dane...
Zakończenie
Nieodłączne pytanie brzmi: kiedy? Ciężko mi na nie odpowiedzieć w tej chwili. Podstawy stworzyć jest prosto, jednak później zostaje cała masa dłubaniny związanej z napisaniem n sprawdzarek, m filtrów i p widgetów. W tym ostatnim zaofiarował się trochę z pomocą eXtreme, więc można mieć nadzieję, że OPF będzie posiadać porządny zbiór gotowych do wykorzystania kontrolek.
Zarówno opisywany kod, jak i biblioteka, znajdują się już w repozytorium SVN Invenzzii i tam też można je sobie dokładniej obejrzeć.






Napisał AdvMDev w wtorek, 1 września 2009 o 14:45
Nice one, dude. But I'm worried about one thing. We have (thanks to you) OPL Exception, OPF Exception and OPT Exception. Is it possible to make a parrent class for them, and then access error handler as an object's method (ex. $Exception -> HandleException();)? It would save a lot of code. ;)