03. Create an input form
We'd next like to create the input form. We've already defined what fields we'd like, so generating the form will be straight forward.
In particular, we'd like:
- Summary as a textfield (the node title)
- Description as a textarea (the node body)
- Due date as a date selector (custom field)
- Priority as a drop down list select (custom field)
- Current status as a drop down list select (custom field)
So, let's create the form, based on these fields. There are many pages that describe the Form API. Using the Forms API documentation, we'll add the form:
<?php
function todo_form(&$node) {
// summary / title
$form['title'] = array(
'#type' => 'textfield',
'#title' => t('Task'),
'#required' => TRUE,
'#default_value' => $node->title,
'#weight' => -5,
'#description' => t('Task summary')
);
// full description / body
$form['body_filter']['body'] = array(
'#type' => 'textarea',
'#title' => t('Task details'),
'#default_value' => $node->body,
'#rows' => 10,
'#required' => FALSE
);
$form['body_filter']['format'] = filter_form($node->format);
// due date
$form['duedate'] = array(
'#type' => 'textfield',
'#title' => t('Due date'),
'#required' => FALSE,
'#default_value' => $node->duedate,
'#weight' => 0,
'#description' => 'Format dd/mm/yyyy'
);
// priority
$priorities = _todo_priorities();
$form['priority'] = array(
'#type' => 'select',
'#title' => t('Priority'),
'#required' => FALSE,
'#default_value' => $node->priority,
'#options' => $priorities,
'#weight' => 1
);
// current status
$statuses = _todo_statuses();
$form['taskstatus'] = array(
'#type' => 'select',
'#title' => t('Current status'),
'#required' => TRUE,
'#default_value' => $node->taskstatus,
'#options' => $statuses,
'#weight' => 2
);
return $form;
}
/**
* Return an array of priorities
* @return array of priorities
*/
function _todo_priorities() {
return array('none', 'low', 'medium', 'high');
}
/**
* Return an array of task statuses
* @return array of statuses
*/
function _todo_statuses() {
return array(
'unknown',
'not started',
'in progress',
'dependency blocked',
'complete'
);
}
?>A few notes about these additions to our module:
The priority and status values are returned in separate functions so that we have the values in one place: we can use these arrays in other functions without declaring the arrays in each of those other functions. Later, we can make these arrays user-configurable, saving the array values in the database, and loading them from the database in these functions.
We use the underscore-prefix naming convention for the two functions to indicate these functions are internal functions and not for external module access. module_invoke will not work with these two functions, preventing other modules from calling these functions. We may not want this limitation later, and may rename the functions.
The order matters with the priority and status arrays. The order of the values in the array is the order they will display as the drop-down select options. If you want a default option, either put it first in the list, or programmatically determine the correct default value and set it with the #default_value field.
The priority and status values map to numbers so that we can search more quickly: SELECT * FROM {todo} WHERE taskstatus >= 2 will execute faster than SELECT * FROM {todo} WHERE taskstatus in ('medium', 'high'), for example. However, if these values are made configurable, each new option would need to be given a new number, so that nodes with old values don't have new values assigned to them. For example, if the priority values array is changed to array('none', 'low', 'medium low', 'medium', 'medium high', 'high', 'critical'), any current nodes with priority 3 will go from high priority to medium priority, which may not be the desired result.
Our task status variable cannot be named 'status', as the node already has a field named 'status'.
The value of each field is specified in the #default_value field, and not the #value field. Any input that can be edited by the user should go into the #default_value parameter. You can adjust the value during the module's validation or save functions, but using #value during the form creation will prevent the user from changing the value. Conversely, if the value should never change, use #value instead of #default_value.
More details about creating a form can be found in the Forms API documentation.
More details about the form hook can be found in the hook_form documentation.
Hook Prefix Caution
When I tried to create my own content type module following this wonderful tutorial I faced some issues. The problem was my module name and my new content type name's base (let's call it todo_list instead of todo) were different. So at the node/add/todo_list path my form was not displaying with a warning. Solution: make the fuction's name todo_list_form (since the hook is on the node's type not module name). Similarly at the node/add help page my new content was not listed. Again for a similar reason, so I had to change the fuction's name implementing hook_access to todo_list_access.
