2007-04-26 Tutorial on Drupal form API, Drupal 表單使用教學

原本打算先寫一個cck 的教學
但因為工作上的需要, 急需對Drupal form API 有深一點的認知
所以就埋頭苦幹, 翻讀又翻讀Drupal 主站的手冊
定要打好form 處理的根基

本文既翻譯官方手冊關於form API的一些部分
再加上自己的一點研發而成

表單乃任何應用系統的中心
令使用者和系統之間的交流具體化
確實請求, 遞交的介面, 公用接口等
如果沒有表單, 用戶只可被動的接受系統的一切
用戶的要求並不能傳到系統中,
像啞巴, 不能表達自己的訴求
用了表單, 用戶可以在系統允許的情況之下要求
如排序, 提交等等
所以一個小型的模組很可能已經需要使用表單,
作了解用戶的設定, 請求之用
可惜, Drupal 的 form API 使用概念上和html form 有頗大差異
但的確簡化了傳統上 顯示, 處理, 儲存 三個部分的處理

用form API 生成表單, 大概有三個地方可放代碼
*.module 文件, template 文件, block boxes.(hook_form_alter() 只用作修改, 而非建設表單)

*.module 文件中放代碼這方法為主流, 也正統, 又萬能
官網的教學全都集中在這個地方.
主要目的多是開發一個新的模組, 要設定模組的各參數而使用表格
學習上, 因為有很多現成模組的例子, 算是簡單
生成方法要從hook_menu 說起
user.module 為例:

<?php
$items
[] = array(
 
'path' => 'user',
 
'title' => t('User account'),
 
'callback' => 'drupal_get_form',
 
'callback arguments' => array('user_login'),
 
'access' => !$user->uid,
 
'type' => MENU_CALLBACK
);
?>

設定:
路徑(path) /%DRUPAL%/user
頁面標題(title) t('User account') (t()為可以翻譯)
存取時轉到函數(callback) drupal_get_form()
函數參數(callback arguments) array('user_login')
權限(access) !$user->uid (指已經登入者)

實際上呼叫函數drupal_get_form('user_login')
drupal_get_form('user_login') 呼叫 user_login(),

<?php
user_login
(){
...
 
$form['name'] = array(
   
'#type' => 'textfield',
   
'#title' => t('Username'),
   
'#size' => 60,
   
'#maxlength' => USERNAME_MAX_LENGTH,
   
'#required' => TRUE,
   
'#attributes' => array('tabindex' => '1'),
  );
...
  return
$form;
}
?>

例子中定義了一個textfield (#type)
field 的題目(類似說明)為t('Username') (t()為可以翻譯)
等等, 詳細參數表請參考API http://api.drupal.org/api/5/file/developer/topics/forms_api_reference.html
user_login() 最後返回變數$form, 給drupal_get_form()
表單顯示輸出部份到此完成
只要設定好 user_login, hook_menu,
其餘的部分(action, method, form_id, form tags)由Drupal 負責

接著是確認表單有效的步驟: ( 按了submit, button type 後 )
續用user_login 為例,
定義一個函數: 函數名_validate()

<?php
function user_login_validate($form_id, $form_values) {
  if (
$form_values['name']) {
 
//略
?>

$form_id 為 函數名
$form_values 為array, 像一般的$_POST 般使用
_validate() 認證錯誤的話, 使用form_set_error('login' , $message);
Drupal 自動跳回表單
否則不用返回, Drupal 跳到儲存函數

儲存函數: 函數名_submit
(按下type 為 submit 的元素後)

<?php
function user_login_submit($form_id, $form_values) {
/**
*  數據庫處理等等
*/
 
$message = 'submit complete';
  return
"/user".$user->uid;
}
?>

$form_id 為 函數名
$form_values 為array, 像一般的$_POST 般使用
返回值為路徑, Drupal 會跳到該路徑
並顯示$message

到此, 基本, 主要, 一般使用form API 的方法完成

Template 文件上使用form API 實際上違反了MVC 的概念
但卻能快速的實現表單, 添加和修改頁面隨意
如 "加一個表單到關於我們, 填電話吧" 之類的請求
用一個模組加上hook_form_alter, hook_node_api 會令模組管理混亂
系統效能下降的問題
而更麻煩的是開發時間長
使用template 能減輕一定程度的工作量,
特別當改動少, 但又不得不改代碼, CSS 不能代勞時.

使用概念跟使用druapl_get_form() 大致相約
template 中一樣要定義三個函數, 如

<?php
function user_login2(){
 
$form[]=array(
    ..........................
  );
  return
$form
}

function
user_login2_submit($form_id, $form_values){
  .............
}

function
user_login2_validate($form_id, $form_values){
  .............
}

print_r (drupal_get_form('user_login2'));

?>

最後用一句 print_r 就可以了,
Drupal 會顯示表單.
也使用 user_login2_validate() 確認
user_login2_submit() 提交

但留意, 切忌過份使用, 否則只會做成維護的困難
又,
如果只改動現有模組, 可先作處理, 再呼叫原模組:

<?php
function user_login2(){
 
$form[] = user_login();
 
$form[] = array(
    ....
  );
}
function
user_login2_submit($form_id, $form_values){
 
//more operation here
 
user_login($form_id,$form_values);
}
?>

至於block boxes,
block 可以自己定制, 可以使用php code內建到block
在site build->block 內有add block 選項
code 的使用和template 使用form API 的方法一樣
不再重覆, 主要使用print_r(drupal_get_form())
但值得一提的是block 的擺放位置, 如加上panels
幾乎可以放到任何位置, block 又可以設定何時顯示, 何時隐藏
又開發快速
唯一缺點, 如果php 代碼有錯, 將會做成修改不能的錯誤
要手動到資料庫修改, 故只適用於對php 非常熟悉的人使用

Originally want to write a tutorial about cck first.
However, because of my job, there is an emergency that i need to have a deeper knowledge about Drupal's form API.
So here comes hours ad hours study, rushing through drupal's handbook,
build up so solid base about form.

form system is a concrete base of any application.
it makes the communication between user and system become practical, applicatable,
include confirm requests, submit interface, public sockets etc etc.
if there is no forms at all, users can only accept the information from server passively.
user's request cannot be sent to server,
like mute, who cannot present themselves.
under forms, users can submit request upon their needs,
for example sorting or submit etc etc.
as a result, there may be already a need for forms even inside a small module.
but unluckily, the concept between Drupal's form API and HTML form is very different,
in terms of three core part of form: presentation, process, save.

To use from API to generate forms, there are three places that your code may place
*.module file, templates, boxes block. ( hook_form_alter() can only edit a form, but not create one )

form API inside *.module is the most popular method, Drupal style, and also powerful.
the tutorial inside drupal.org is mainly focus in this scoop.
This method is mainly used to configure the parameters of the module, during development a new module.
This method ie relatively easy to learn as there is many existing modules that you can refer to.
the start point is hook_menu():
use user.module as an example,

<?php
$items
[] = array(
 
'path' => 'user',
 
'title' => t('User account'),
 
'callback' => 'drupal_get_form',
 
'callback arguments' => array('user_login'),
 
'access' => !$user->uid,
 
'type' => MENU_CALLBACK
);
?>

details:
path /%DRUPAL%/user
title t('User account') (t()means translatable)
callback drupal_get_form()
callback arguments array('user_login')
access !$user->uid (means logged-in user)

in practical, this will have call a function drupal_get_form('user_login')
drupal_get_form calls user_login():

<?php
user_login
(){
//...some code before...
 
$form['name'] = array(
   
'#type' => 'textfield',
   
'#title' => t('Username'),
   
'#size' => 60,
   
'#maxlength' => USERNAME_MAX_LENGTH,
   
'#required' => TRUE,
   
'#attributes' => array('tabindex' => '1'),
  );
//...some more code...
 
return $form;
}
?>

In this example, it defines a textfield by #type,
the title of this field is Username (t()means translatable)
etc etc. and the parameters reference: http://api.drupal.org/api/5/file/developer/topics/forms_api_reference.html
finally, user_login() return the $form variable, back to drupal_get_form()
the output part of form finished.
you only have to setup user_login, hook_menu, the elements inside the form,
the rest part( action, method, form_id, form tags ) will left to Drupal.

next is the validation of form( by pressing any button, submit type ):
user_login() as example again,
define a new function inside *.module, named function _name_validate()

<?php
function user_login_validate($form_id, $form_values) {
  if (
$form_values['name']) {
 
// some code
?>

$form_id is the name of the form function,
$form_values is an array, like normal $_POST,
if validation fails, you may use form_set_error('login' , $message);
Drupal will jump back to the form
otherwise, drupal will jump to save process.

save function: function_name_submit()
(when press element that is 'submit' type)

<?php
function user_login_submit($form_id, $form_values) {
/**
*  database process
*/
 
$message = 'submit complete';
  return
"/user".$user->uid;
}
?>

$form_id is the name of the form function,
$form_values is an array, like normal $_POST,
return the path that will be redirected,
and display $message

up to this point, the basic use of form API finish.

The use of form API inside template file actually contradict the MVC concept,
but this method can build up form quickly, edit them quickly.
for example, a need of "add a form to about us, user may enter their phone number"
this kind of need is not worth to open a new module because of performance, long develop time
using template can reduce some workload,
especially when changes is small, but CSS cannot help.

the basic concept is the same, also use drupal_get_form(),
define three function inside the template file:

<?php
function user_login2(){
 
$form[]=array(
    ..........................
  );
  return
$form
}

function
user_login2_submit($form_id, $form_values){
  .............
}

function
user_login2_validate($form_id, $form_values){
  .............
}

print_r (drupal_get_form('user_login2'));

?>

finally use print_r, drupal will display the form.
user_login2_validate to process validation the form
user_login2_submit to save to database,

but keep in mind, do not use it frequently, which will increase the difficulties to maintain

finally, about from API inside block boxes,
as blocks can embed php code inside,
form can also embedded inside blocks.
the use of code is just like the use inside template,
use print_r(drupal_get_form()) again,
define three function....
one more to note, in combination with panels modules,
blocks can be placed everywhere, in specific page, specific place,
with fast development
the only disadvantage is, if there is php code error inside block,
you may face down site, which can only be repair from database.
so it is only advice to advance php users

Google