Menggunakan Form Builder
========================
Ketika kita membuat form HTML, kita sering harus menulis kode view yang berulang-ulang. Hal seperti
ini biasanya cukup sulit untuk dipakai ulang pada projek lain. Contohnya, untuk setiap input field,
kita harus menghubungkannya pada sebuah label dan menampilkan pesan error validasi yang mungkin ada.
Untuk meningkatkan kemungkinan pemakaian ulang pada kode-kode ini, kita dapat menggunakan fitur form builder.
Konsep Dasar
------------
Form builder di Yii menggunakan objek [CForm] untuk mewakili spesifikasi yang diperlukan untuk
membentuk sebuah form HTML, termasuk data model yang berhubungan dengan form, input field seperti
apa yang diperlukan di dalam form, dan bagaimana me-render form secara keseluruhan.
Developer umumnya perlu membuat dan mengatur objek [CForm] ini, dan memanggil fungsi render untuk
menampilkan form.
Spesifikasi input pada form diatur pada hirarki elemen form. Pada bagian root dari hirarki tersebut,
merupakan objek [CForm]. Objek form root ini mengatur turunannya dalam dua kumpulan: [CForm::buttons] dan [CForm::elements].
Yang pertama mengandung elemen-elemen tombol(seperti tombol submit, tombol reset), sedangkan yang kedua
berisi elemen-elemen input, teks statis, dan sub-form. Sebuah sub-form adalah objek [CForm] yang mengandung
kumpulan [CForm::elements] dari form lain. Sub-form bisa memiliki data model, [CForm::buttons] dan [CForm::elements] tersendiri.
Ketika pengguna men-submit sebuah form, maka data yang dimasukkan ke dalam input field dari keseluruhan hirarki form akan dikirim,
termasuk input field milik sub-form. [CForm] menyediakan metode mudah yang secara otomatis dapat memberikan nilai input data ke atribut model bersangkutan
dan melakukan validasi pada data.
Menciptakan Sebuah Form Sederhana
---------------------------------
Pada contoh berikut, kita akan melihat bagaimana membuat sebuah form login dengan form builder.
Pertama, kita menulis sebuah kode action login:
~~~
[php]
public function actionLogin()
{
$model = new LoginForm;
$form = new CForm('application.views.site.loginForm', $model);
if($form->submitted('login') && $form->validate())
$this->redirect(array('site/index'));
else
$this->render('login', array('form'=>$form));
}
~~~
Pada kode di atas, kita membuat sebuah objek [CForm] yang merujuk ke jalur alias `application.views.site.loginForm`
(akan dijelaskan beberapa saat lagi).
Objek [CForm] ini berkaitan dengan model `LoginForm` seperti yang dijelaskan pada [Pembuatan Model](/doc/guide/form.model).
Begitu kode membaca, jika form sudah di-submit dan semua input tidak ada error ketika divalidasi, maka
kita dapat me-redirect browser pengguna ke halaman `site/index`. Kalau tidak, maka kita menampilkan
sebuah view `login` dengan form.
Jalur alias `application.views.site.loginForm` sebenarnya merujuk ke file PHP `protected/views/site/loginForm.php`.
File ini akan mengembalikan larik(array) PHP yang mewakiliki konfigurasi [CForm] yang diperlukan, seperti di bawah ini:
~~~
[php]
return array(
'title'=>'Please provide your login credential',
'elements'=>array(
'username'=>array(
'type'=>'text',
'maxlength'=>32,
),
'password'=>array(
'type'=>'password',
'maxlength'=>32,
),
'rememberMe'=>array(
'type'=>'checkbox',
)
),
'buttons'=>array(
'login'=>array(
'type'=>'submit',
'label'=>'Login',
),
),
);
~~~
Konfigurasi tersebut merupakan sebuah larik yang terdiri dari pasangan nama-nilai yang digunakan
untuk menginisialisasi properti yang berhubungan pada [CForm]. Properti yang paling penting untuk diatur,
seperti yang telah disinggung, adalah [CForm::elements] dan [CForm::buttons]. Setiap dari elemen ini
memerlukan sebuah larik untuk menentukan daftar elemen form. Kita akan melihat lebih detail
bagaimana mengatur elemen form pada subbab berikutnya.
Akhirnya, kita menulis sebuah skrip view, yang dapat sesederhana berikut ini,
~~~
[php]
Login
~~~
> Tip: Kode di atas `echo $form;` adalah ekuivalen dengan `echo $form->render();`.
> Alasannya karena [CForm] mengimplementasi metode `__toString` yang akan memanggil
> `render()` dan mengembalikan hasilnya sebagai string yang berisi objek form.
Menspesifikasi Elemen Form
--------------------------
Ketika menggunakan sebuah form builder, tugas utama kita akan beralih dari menulis kode skrip view menjadi
menentukan elemen form. Pada subbab ini, kita akan mempelajari bagaimana menspesifikasi properti [CForm::elements].
Kita tidak akan menjelaskan [CForm::buttons] karena konfigurasinya hampir sama dengan [CForm::elements]
Properti [CForm::elements] menerima sebuah larik sebagai nilainya. Setiap elemen larik menspesifikasi sebuah
elemen form yang akan menjadi elemen input, sebuah teks statik atau sebuah sub-form.
### Menspesifikasi Elemen Input
Sebuah input elemen terdiri dari sebuah label, sebuah field input, dan sebuah teks petunjuk dan sebuah tampilan error.
Elemen input harus berasosiasi dengan atribut model. Spesifikasi untuk sebuah elemen input diwakilkan
sebagai sebuah instance dari [CFormInputElement]. Kode berikut akan menspesifikasi input tunggal dalam larik [CForm::elements]:
~~~
[php]
'username'=>array(
'type'=>'text',
'maxlength'=>32,
),
~~~
Di situ menjelaskan bahwa atribut model bernama `username`, dan input field berjenis `text` yang nilai atribut `maxlength` adalah 32
Setiap properti [CFormInputElement] yang dapat ditulis dapat dikonfigurasi dengan cara demikian. Misalnya, kita dapat
menspesifikasi opsi [hint|CFormInputElement::hint] untuk menampilkan teks petunjuk, atau kita dapat menspesifikasi
opsi [items|CFormInputElement::items] jika field input-nya berupa sebuah list box, drop-down list, sebuah list check-box
atau sebuah list radio-button. Jika sebuah nama opsi bukan properti dari [CFormInputElement], maka akan dianggap
sebagai atribut HTML dari elemen input bersangkutan. Misalnya, karena `maxlength` di contoh di atas bukanlah sebuah
properti dari [CFormInputElement], maka `maxlength` akan di-render sebagai `maxlength` dari atribut HTML field input teks.
Opsi [type|CFormInputElement::type] patut mendapat perhatian tambahan. Opsi ini menspesifikasi jenis field input
yang akan di-render. Misalnya, jenis `text` artinya adalah field input teks normal yang harus di-render;
jenis `password` artinya sebuah input field password yang harus di-render. [CFormInputElement] mampu mengenali jenis-jenis built-in seperti:
- text
- hidden
- password
- textarea
- file
- radio
- checkbox
- listbox
- dropdownlist
- checkboxlist
- radiolist
Di antara jenis-jenis built-in tersebut, kita akan mempelajari sedikit lebih dalam mengenai penggunaan jenis "list",
yakni `dropdownlist`, `checkboxlist` dan `radiolist`. Jenis-jenis ini memerlukan pengaturan properti [items|CFormInputElement::items]
pada elemen input yang bersangkutan. Kita dapat mengaturnya demikian:
~~~
[php]
'gender'=>array(
'type'=>'dropdownlist',
'items'=>User::model()->getGenderOptions(),
'prompt'=>'Please select:',
),
...
class User extends CActiveRecord
{
public function getGenderOptions()
{
return array(
0 => 'Male',
1 => 'Female',
);
}
}
~~~
Kode di atas akan menghasilkan sebuah selector berupa drop-down list dengan teks "please select:". Opsi selector
termasuk "Male" dan "Female", yang dikembalikan oleh metode `getGenderOptions` di dalam kelas model `User`.
Selain jenis-jenis built-in, opsi [type|CFormInputElement::type] juga dapat mengambil sebuah nama kelas widget
atau jalur alias ke sana. Kelas widget haruslah diturunkan dari [CInputWidget] atau [CJuiInputWidget]. Ketika me-render elemen input,
sebuah instance dari kelas widget yang dispesifikasi akan diciptakan dan di-render. Widget tersebut akan diatur supaya
menggunakan spesifikasi yang diberikan untuk elemen input.
### Menspesifikasi Teks Statik
Dalam banyak kasus, sebuah form bisa saja mengandung beberapa kode HTML yang bersifat dekorasi selain field input. Misalnya, sebuah
garis horizontal(horizontal line) mungkin diperlukan untuk memisahkan bagian pada form, sebuah gambar diperlukan di beberapa tempat
untuk meningkatkan sisi visual pada form. Kita dapat menspesifikasi kode HTML ini sebagai teks statik di kumpulan [CForm::elements].
Misalnya:
~~~
[php]
return array(
'elements'=>array(
......
'password'=>array(
'type'=>'password',
'maxlength'=>32,
),
'
',
'rememberMe'=>array(
'type'=>'checkbox',
)
),
......
);
~~~
Pada kode di atas, kita memasukkan sebuah garis horizontal di antara input `password` dan input `rememberMe`.
Teks statik paling cocok digunakan ketika konten berisi teks dan posisi mereka tidak menentu. Jika setiap
elemen input dalam sebuah form diperlukan untuk dekorasi sejenisnya, kita harus mengkustomisasi pendekatan
cara me-render form. Hal ini akan dijelaskan setelah bagian ini.
### Menspesifikasi Sub-form
Sub-form dipakai untuk memisahkan sebuah form panjang menjadi beberapa bagian yang secara logikal terkoneksi.
Misalnya, kita mungkin ingin memisahkan form registrasi user menjadi dua buah sub-form: informasi login
dan informasi pribadi. Setiap sub-form bisa saja berhubungan dengan sebuah data model ataupun tidak. Dalam contoh form registrasi user,
jika kita menyimpan informasi login user dan informasi pribadi ke dalam dua buah tabel database yang berbeda
(yang berarti dua buah model data), maka tiap sub-form akan dihubungkan dengan data model bersangkutan. Jika kita menyimpan
semuanya ke dalam sebuah tabel database, maka tidak ada satu sub-form pun yang memiliki data model karena mereka
memiliki model yang sama dari form root-nya.
Sebuah sub-form juga mewakili objek [CForm]. Untuk menspesifikasi sebuah sub-form, kita harus mengatur
properti [CForm::elements] dengan elemen yang tipenya `form`:
~~~
[php]
return array(
'elements'=>array(
......
'user'=>array(
'type'=>'form',
'title'=>'Login Credential',
'elements'=>array(
'username'=>array(
'type'=>'text',
),
'password'=>array(
'type'=>'password',
),
'email'=>array(
'type'=>'text',
),
),
),
'profile'=>array(
'type'=>'form',
......
),
......
),
......
);
~~~
Seperti mengkonfigurasi form root, kita mengatur properti [CForm::elements] untuk sebuah sub-form. Jika
sebuah sub-form perlu diasosiasikan dengan sebuah data model, maka kita mengkonfigurasi properti [CForm::model] juga.
Kadang-kadang, kita ingin mewakili sebuah form dengan menggunakan kelas yang bukan dari [CForm]. Misalnya,
seperti yang akan dipelajari sebentar lagi, kita ingin menurunkan [CForm] untuk mengatur logikal rendering dari form.
Dengan menspesifikasi jenis elemen input menjadi `form`, sebuah sub-form akan otomatis mewakili sebuah
ojbek yang kelasnya sama dengan form induknya. Jika kita menspesifikasi jenis elemen input menjadi sesuatu
seperti `XyzForm` (sebuah string yang diakhiri dengan `Form`), maka sub-form akan diwakilkan sebagai objek `XyzForm`.
Mengakses Elemen Form
---------------------
Mengakses elemen form segampang mengakses elemen larik(array). Properti [CForm::elements] mengembalikan
sebuah objek [CFormElementCollection], yang diturunkan dari [CMap] dan memungkinkan mengakses elemennya seperti
sebuah larik normal. Misalnya, untuk mengakses elemen `username` di contoh form login, kita dapat menggunakan
kode berikut:
~~~
[php]
$username = $form->elements['username'];
~~~
Dan untuk mengakses elemen `email` di contoh form registrasi user, kita dapat menggunakan
~~~
[php]
$email = $form->elements['user']->elements['email'];
~~~
Karena [CForm] mengimplementasi pengaksesan larik untuk properti [CForm::elements]-nya, kode di atas dapat
disederhanakan lagi menjadi:
~~~
[php]
$username = $form['username'];
$email = $form['user']['email'];
~~~
Membuat sebuah Nested Form(Form Bersarang)
------------------------------------------
Kita sudah mempelajari sub-form. Kita memanggil sebuah form dengan sub-form sebagai nested form. Pada
bagian ini kita akan menggunakan form registrasi user sebagai contoh untuk melihat bagaimana membuat
sebuah nested form yang berhubungan dengan beberapa model data. Kita mengasumsi bahwa informasi penting
di model `User` dan informasi pribadi user di dalam model `Profile`.
Pertama-tama kita membuat action `register`:
~~~
[php]
public function actionRegister()
{
$form = new CForm('application.views.user.registerForm');
$form['user']->model = new User;
$form['profile']->model = new Profile;
if($form->submitted('register') && $form->validate())
{
$user = $form['user']->model;
$profile = $form['profile']->model;
if($user->save(false))
{
$profile->userID = $user->id;
$profile->save(false);
$this->redirect(array('site/index'));
}
}
$this->render('register', array('form'=>$form));
}
~~~
Pada contoh di atas, kita membuat sebuah form dengan menggunakan konfigurasi yang dispesifikasi oleh `application.views.user.registerForm`.
Setelah form di-submit dan divalidasi dengan sukses, kita mencoba untuk menyimpan model user dan profile.
Kita mendapatkan model user dan profile dengan mengakses properti `model` dari objek sub-form bersangkutan.
Karena validasi input sudah dilakukan, kita memanggil `$user->save(false)` untuk melewati validasi. Kita melakukan
hal yang sama untuk model profile.
Berikutnya, kita menulis sebuah file konfigurasi form `protected/views/user/registerForm.php`:
~~~
[php]
return array(
'elements'=>array(
'user'=>array(
'type'=>'form',
'title'=>'Login information',
'elements'=>array(
'username'=>array(
'type'=>'text',
),
'password'=>array(
'type'=>'password',
),
'email'=>array(
'type'=>'text',
)
),
),
'profile'=>array(
'type'=>'form',
'title'=>'Profile information',
'elements'=>array(
'firstName'=>array(
'type'=>'text',
),
'lastName'=>array(
'type'=>'text',
),
),
),
),
'buttons'=>array(
'register'=>array(
'type'=>'submit',
'label'=>'Register',
),
),
);
~~~
Pada contoh di atas, ketika kita menspesifikasi setiap sub-form, kita juga menspesifikasi properti [CForm::title].
Logika render form yang default akan disertakan pada tiap sub-form dalam sebuah field-set yang menggunakan properti ini sebagai title-nya.
Terakhir, kita menulis sebuah skrip view `register`:
~~~
[php]
Register
~~~
Kustomisasi Tampilan Form
------------------------
Manfaat utama menggunakan form builder adalah pemisahan logika (konfigurasi form disimpan di file terpisah)
dan presentasi (metode [CForm::render]). Sebagai hasilnya, kita dapat mengkustomisasi tampilan form dengan cara meng-override
[CForm::render] atau menyediakan sebuah partial view untuk me-render form. Kedua pendekatan ini dapat memastikan
bahwa konfigurasi tetap utuh dan bisa digunakan ulang secara gampang.
Ketika meng-override [CForm::render], tujuan utamanya adalah melewati kumpulan [CForm::elements] dan [CForm::buttons]
dan memanggil metode [CFormElement::render] untuk setiap elemen. Misalnya,
~~~
[php]
class MyForm extends CForm
{
public function render()
{
$output = $this->renderBegin();
foreach($this->getElements() as $element)
$output .= $element->render();
$output .= $this->renderEnd();
return $output;
}
}
~~~
Kita juga bisa menulis sebuah skrip view `_form` untuk me-render form
~~~
[php]
renderBegin();
foreach($form->getElements() as $element)
echo $element->render();
echo $form->renderEnd();
~~~
Untuk menggunakan skrip view, kita cukup memanggil:
~~~
[php]
$this->renderPartial('_form', array('form'=>$form));
~~~
Jika sebuah form umum tidak bekerja pada form tertentu (misalnya, form-nya memerlukan
dekorasi yang tidak beraturan untuk elemen tertentu), kita dapat melakukan hal berikut pada skrip view:
~~~
[php]
some complex UI elements here
some complex UI elements here
some complex UI elements here
~~~
Pendekatan terakhir, form builder kelihatannya seperti tidak terlalu banyak membawa manfaat, karena
kita masih harus menulis sebagian besar kode form. Tetapi sebetulnya masih membawa faedah, karena kita
menspesifikasi form dengan menggunakan file konfigurasi yang terpisah sehingga membantu developer untuk
lebih fokus ke logika.
$Id: form.builder.txt 2890 2011-01-18 15:58:34Z qiang.xue $