- •Contents at a Glance
- •Contents
- •Foreword
- •About the Authors
- •About the Technical Reviewers
- •Acknowledgments
- •Introduction
- •How Drupal Works
- •What Is Drupal?
- •Technology Stack
- •Core
- •Administrative Interface
- •Modules
- •Hooks
- •Themes
- •Nodes
- •Fields
- •Blocks
- •File Layout
- •Serving a Request
- •The Web Server’s Role
- •The Bootstrap Process
- •Processing a Request
- •Theming the Data
- •Summary
- •Writing a Module
- •Creating the Files
- •Implementing a Hook
- •Adding Module-Specific Settings
- •Defining Your Own Administration Section
- •Presenting a Settings Form to the User
- •Validating User-Submitted Settings
- •Storing Settings
- •Using Drupal’s variables Table
- •Retrieving Stored Values with variable_get()
- •Further Steps
- •Summary
- •Hooks, Actions, and Triggers
- •Understanding Events and Triggers
- •Understanding Actions
- •The Trigger User Interface
- •Your First Action
- •Assigning the Action
- •Changing Which Triggers an Action Supports
- •Actions That Support Any Trigger
- •Advanced Actions
- •Using the Context in Actions
- •How the Trigger Module Prepares the Context
- •Changing Existing Actions with action_info_alter()
- •Establishing the Context
- •How Actions Are Stored
- •The actions Table
- •Action IDs
- •Calling an Action Directly with actions_do()
- •Defining Your Own Triggers with hook_trigger_info()
- •Adding Triggers to Existing Hooks
- •Summary
- •The Menu System
- •Callback Mapping
- •Mapping URLs to Functions
- •Creating a Menu Item
- •Page Callback Arguments
- •Page Callbacks in Other Files
- •Adding a Link to the Navigation Block
- •Menu Nesting
- •Access Control
- •Title Localization and Customization
- •Defining a Title Callback
- •Wildcards in Menu Items
- •Basic Wildcards
- •Wildcards and Page Callback Parameters
- •Using the Value of a Wildcard
- •Wildcards and Parameter Replacement
- •Passing Additional Arguments to the Load Function
- •Special, Predefined Load Arguments: %map and %index
- •Building Paths from Wildcards Using to_arg() Functions
- •Special Cases for Wildcards and to_arg() Functions
- •Altering Menu Items from Other Modules
- •Altering Menu Links from Other Modules
- •Kinds of Menu Items
- •Common Tasks
- •Assigning Callbacks Without Adding a Link to the Menu
- •Displaying Menu Items As Tabs
- •Hiding Existing Menu Items
- •Using menu.module
- •Common Mistakes
- •Summary
- •Working with Databases
- •Defining Database Parameters
- •Understanding the Database Abstraction Layer
- •Connecting to the Database
- •Performing Simple Queries
- •Retrieving Query Results
- •Getting a Single Value
- •Getting Multiple Rows
- •Using the Query Builder and Query Objects
- •Getting a Limited Range of Results
- •Getting Results for Paged Display
- •Other Common Queries
- •Inserts and Updates with drupal_write_record()
- •The Schema API
- •Using Module .install Files
- •Creating Tables
- •Using the Schema Module
- •Field Type Mapping from Schema to Database
- •Textual
- •Varchar
- •Char
- •Text
- •Numerical
- •Integer
- •Serial
- •Float
- •Numeric
- •Date and Time: Datetime
- •Binary: Blob
- •Declaring a Specific Column Type with mysql_type
- •Maintaining Tables
- •Deleting Tables on Uninstall
- •Changing Existing Schemas with hook_schema_alter()
- •Modifying Other Modules’ Queries with hook_query_alter()
- •Connecting to Multiple Databases Within Drupal
- •Using a Temporary Table
- •Writing Your Own Database Driver
- •Summary
- •Working with Users
- •The $user Object
- •Testing If a User Is Logged In
- •Introduction to user hooks
- •Understanding hook_user_view($account, $view_mode)
- •The User Registration Process
- •Using profile.module to Collect User Information
- •The Login Process
- •Adding Data to the $user Object at Load Time
- •Providing User Information Categories
- •External Login
- •Summary
- •Working with Nodes
- •So What Exactly Is a Node?
- •Not Everything Is a Node
- •Creating a Node Module
- •Creating the .install File
- •Creating the .info File
- •Creating the .module File
- •Providing Information About Our Node Type
- •Modifying the Menu Callback
- •Defining Node-Type–Specific Permissions with hook_permission()
- •Limiting Access to a Node Type with hook__node_access()
- •Customizing the Node Form for Our Node Type
- •Validating Fields with hook_validate()
- •Saving Our Data with hook_insert()
- •Keeping Data Current with hook_update()
- •Cleaning Up with hook_delete()
- •Modifying Nodes of Our Type with hook_load()
- •Using hook_view()
- •Manipulating Nodes That Are Not Our Type with hook_node_xxxxx()
- •How Nodes Are Stored
- •Creating a Node Type with Custom Content Types
- •Restricting Access to Nodes
- •Defining Node Grants
- •What Is a Realm?
- •What Is a Grant ID?
- •The Node Access Process
- •Summary
- •Working with Fields
- •Creating Content Types
- •Adding Fields to a Content Type
- •Creating a Custom Field
- •Adding Fields Programmatically
- •Summary
- •The Theme System
- •Themes
- •Installing an Off-the-Shelf Theme
- •Building a Theme
- •The .info File
- •Adding Regions to Your Theme
- •Adding CSS Files to Your Theme
- •Adding JavaScript Files
- •Adding Settings to Your Theme
- •Understanding Template Files
- •The Big Picture
- •The html.php.tpl File
- •The page.tpl.php File
- •The region.tpl.php File
- •The node.tpl.php File
- •The field.tpl.php File
- •The block.tpl.php File
- •Overriding Template Files
- •Other Template Files
- •Introducing the theme() Function
- •An Overview of How theme() Works
- •Overriding Themable Items
- •Overriding with Template Files
- •Adding and Manipulating Template Variables
- •Using the Theme Developer Module
- •Summary
- •Working with Blocks
- •What Is a Block?
- •Block Configuration Options
- •Block Placement
- •Defining a Block
- •Using the Block Hooks
- •Building a Block
- •Enabling a Block When a Module Is Installed
- •Block Visibility Examples
- •Displaying a Block to Logged-In Users Only
- •Displaying a Block to Anonymous Users Only
- •Summary
- •The Form API
- •Understanding Form Processing
- •Initializing the Process
- •Setting a Token
- •Setting an ID
- •Collecting All Possible Form Element Definitions
- •Looking for a Validation Function
- •Looking for a Submit Function
- •Allowing Modules to Alter the Form Before It’s Built
- •Building the Form
- •Allowing Functions to Alter the Form After It’s Built
- •Checking If the Form Has Been Submitted
- •Finding a Theme Function for the Form
- •Allowing Modules to Modify the Form Before It’s Rendered
- •Rendering the Form
- •Validating the Form
- •Token Validation
- •Built-In Validation
- •Element-Specific Validation
- •Validation Callbacks
- •Submitting the Form
- •Redirecting the User
- •Creating Basic Forms
- •Form Properties
- •Form IDs
- •Fieldsets
- •Theming Forms
- •Using #prefix, #suffix, and #markup
- •Using a Theme Function
- •Telling Drupal Which Theme Function to Use
- •Specifying Validation and Submission Functions with hook_forms()
- •Call Order of Theme, Validation, and Submission Functions
- •Writing a Validation Function
- •Form Rebuilding
- •Writing a Submit Function
- •Changing Forms with hook_form_alter()
- •Altering Any Form
- •Altering a Specific Form
- •Submitting Forms Programmatically with drupal_form_submit()
- •Dynamic Forms
- •Form API Properties
- •Properties for the Root of the Form
- •#action
- •#built
- •#method
- •Properties Added to All Elements
- •#description
- •#attributes
- •#required
- •#tree
- •Properties Allowed in All Elements
- •#type
- •#access
- •#after_build
- •#array_parents
- •#attached
- •#default_value
- •#disabled
- •#element_validate
- •#parents
- •#post_render
- •#prefix
- •#pre_render
- •#process
- •#states
- •#suffix
- •#theme
- •#theme_wrappers
- •#title
- •#tree
- •#weight
- •Form Elements
- •Text Field
- •Password
- •Password with Confirmation
- •Textarea
- •Select
- •Radio Buttons
- •Check Boxes
- •Value
- •Hidden
- •Date
- •Weight
- •File Upload
- •Fieldset
- •Submit
- •Button
- •Image Button
- •Markup
- •Item
- •#ajax Property
- •Summary
- •Filters
- •Filters and Text formats
- •Installing a Filter
- •Knowing When to Use Filters
- •Creating a Custom Filter
- •Implementing hook_filter_info()
- •The Process Function
- •Helper Function
- •Summary
- •Searching and Indexing Content
- •Building a Custom Search Page
- •The Default Search Form
- •The Advanced Search Form
- •Adding to the Search Form
- •Introducing the Search Hooks
- •Formatting Search Results with hook_search_page()
- •Making Path Aliases Searchable
- •Using the Search HTML Indexer
- •When to Use the Indexer
- •How the Indexer Works
- •Adding Metadata to Nodes: hook_node_update_index()
- •Indexing Content That Isn’t a Node: hook_update_index()
- •Summary
- •Working with Files
- •How Drupal Serves Files
- •Managed and Unmanaged Drupal APIs
- •Public Files
- •Private Files
- •PHP Settings
- •Media Handling
- •Upload Field
- •Video and Audio
- •File API
- •Database Schema
- •Common Tasks and Functions
- •Finding the Default Files URI
- •Copying and Moving Files
- •Checking Directories
- •Uploading Files
- •Getting the URL for a File
- •Finding Files in a Directory
- •Finding the Temp Directory
- •Neutralizing Dangerous Files
- •Checking Disk Space
- •Authentication Hooks for Downloading
- •Summary
- •Working with Taxonomy
- •The Structure of Taxonomy
- •Creating a Vocabulary
- •Creating Terms
- •Assigning a Vocabulary to a Content Type
- •Kinds of Taxonomy
- •Flat
- •Hierarchical
- •Multiple Hierarchical
- •Viewing Content by Term
- •Using AND and OR in URLs
- •Specifying Depth for Hierarchical Vocabularies
- •Automatic RSS Feeds
- •Storing Taxonomies
- •Module-Based Vocabularies
- •Creating a Module-Based Vocabulary
- •Keeping Informed of Vocabulary Changes with Taxonomy Hooks
- •Common Tasks
- •Displaying Taxonomy Terms Associated with a Node
- •Building Your Own Taxonomy Queries
- •Using taxonomy_select_nodes()
- •Taxonomy Functions
- •Retrieving Information About Vocabularies
- •taxonomy_vocabulary_load($vid)
- •taxonomy_get_vocabularies()
- •Adding, Modifying, and Deleting Vocabularies
- •taxonomy_vocabulary_save($vocabulary)
- •taxonomy_vocabulary_delete($vid)
- •Retrieving Information About Terms
- •taxonomy_load_term($tid)
- •taxonomy_get_term_by_name($name)
- •Adding, Modifying, and Deleting Terms
- •taxonomy_term_save($term)
- •taxonomy_term_delete($tid)
- •Retrieving Information About Term Hierarchy
- •taxonomy_get_parents($tid, $key)
- •taxonomy_get_parents_all($tid)
- •taxonomy_get_children($tid, $vid, $key)
- •taxonomy_get_tree($vid, $parent, $max_depth, $load_entities = FALSE)
- •Finding Nodes with Certain Terms
- •Additional Resources
- •Summary
- •Caching
- •Knowing When to Cache
- •How Caching Works
- •How Caching Is Used Within Drupal Core
- •Menu System
- •Caching Filtered Text
- •Administration Variables and Module Settings
- •Disabling Caching
- •Page Caching
- •Static Page Caching
- •Blocks
- •Using the Cache API
- •Caching Data with cache_set()
- •Retrieving Cached Data with cache_get() and cache_get_multiple()
- •Checking to See If Cache Is Empty with cache_is_empty()
- •Clearing Cache with cache_clear_all()
- •Summary
- •Sessions
- •What Are Sessions?
- •Usage
- •Session-Related Settings
- •In .htaccess
- •In settings.php
- •In bootstrap.inc
- •Requiring Cookies
- •Storage
- •Session Life Cycle
- •Session Conversations
- •First Visit
- •Second Visit
- •User with an Account
- •Common Tasks
- •Changing the Length of Time Before a Cookie Expires
- •Changing the Name of the Session
- •Storing Data in the Session
- •Summary
- •Using jQuery
- •What Is jQuery?
- •How jQuery Works
- •Using a CSS ID Selector
- •Using a CSS Class Selector
- •jQuery Within Drupal
- •Your First jQuery Code
- •Targeting an Element by ID
- •Method Chaining
- •Adding or Removing a Class
- •Wrapping Existing Elements
- •Changing Values of CSS Elements
- •Where to Put JavaScript
- •Adding JavaScript via a Theme .info File
- •A Module That Uses jQuery
- •Overridable JavaScript
- •Building a jQuery Voting Widget
- •Building the Module
- •Using Drupal.behaviors
- •Ways to Extend This Module
- •Compatibility
- •Next Steps
- •Summary
- •Localization and Translation
- •Enabling the Locale Module
- •User Interface Translation
- •Strings
- •Translating Strings with t()
- •Replacing Built-In Strings with Custom Strings
- •String Overrides in settings.php
- •Replacing Strings with the Locale Module
- •Exporting Your Translation
- •Starting a New Translation
- •Generating .pot Files with Translation Template Extractor
- •Creating a .pot File for Your Module
- •Using the Command Line
- •Using the Web-Based Extractor
- •Creating .pot Files for an Entire Site
- •Installing a Language Translation
- •Setting Up a Translation at Install Time
- •Installing a Translation on an Existing Site
- •Right-to-Left Language Support
- •Language Negotiation
- •Default
- •User-Preferred Language
- •The Global $language Object
- •Path Prefix Only
- •Path Prefix with Language Fallback
- •URL Only
- •Content Translation
- •Introducing the Content Translation Module
- •Multilingual Support
- •Multilingual Support with Translation
- •Localizationand Translation-Related Files
- •Additional Resources
- •Summary
- •What Is XML-RPC?
- •Prerequisites for XML-RPC
- •XML-RPC Clients
- •XML-RPC Client Example: Getting the Time
- •XML-RPC Client Example: Getting the Name of a State
- •Handling XML-RPC Client Errors
- •Network Errors
- •HTTP Errors
- •Call Syntax Errors
- •A Simple XML-RPC Server
- •Mapping Your Method with hook_xmlrpc()
- •Automatic Parameter Type Validation with hook_xmlrpc()
- •Built-In XML-RPC Methods
- •system.listMethods
- •system.methodSignature
- •system.methodHelp
- •system.getCapabilities
- •system.multiCall
- •Summary
- •Writing Secure Code
- •Handling User Input
- •Thinking About Data Types
- •Plain Text
- •HTML Text
- •Rich Text
- •Using check_plain() and t() to Sanitize Output
- •Using filter_xss() to Prevent Cross-Site Scripting Attacks
- •Using filter_xss_admin()
- •Handling URLs Securely
- •Making Queries Secure with db_query()
- •Keeping Private Data Private with hook_query_alter()
- •Dynamic Queries
- •Permissions and Page Callbacks
- •Cross-Site Request Forgeries (CSRF)
- •File Security
- •File Permissions
- •Protected Files
- •File Uploads
- •Filenames and Paths
- •Encoding Mail Headers
- •Files for Production Environments
- •SSL Support
- •Stand-Alone PHP
- •AJAX Security, a.k.a. Request Replay Attack
- •Form API Security
- •Protecting the Superuser Account
- •Summary
- •Development Best Practices
- •Coding Standards
- •Line Indention and Whitespace
- •Operators
- •Casting
- •Control Structures
- •Function Calls
- •Function Declarations
- •Function Names
- •Class Constructor Calls
- •Arrays
- •Quotes
- •String Concatenators
- •Comments
- •Documentation Examples
- •Documenting Constants
- •Documenting Functions
- •Documenting Hook Implementations
- •Including Code
- •PHP Code Tags
- •Semicolons
- •Example URLs
- •Naming Conventions
- •Checking Your Coding Style with Coder Module
- •Finding Your Way Around Code with grep
- •Summary
- •Optimizing Drupal
- •Caching Is the Key to Drupal Performance
- •Optimizing PHP
- •Setting PHP Opcode Cache File to /dev/zero
- •PHP Process Pool Settings
- •Tuning Apache
- •mod_expires
- •Moving Directives from .htaccess to httpd.conf
- •MPM Prefork vs. Apache MPM Worker
- •Balancing the Apache Pool Size
- •Decreasing Apache Timeout
- •Disabling Unused Apache Modules
- •Using Nginx Instead of Apache
- •Using Pressflow
- •Varnish
- •Normalizing incoming requests for better Varnish hits
- •Varnish: finding extraneous cookies
- •Boost
- •Boost vs. Varnish
- •Linux System Tuning for High Traffic Servers
- •Using Fast File Systems
- •Dedicated Servers vs. Virtual Servers
- •Avoiding Calling External Web Services
- •Decreasing Server Timeouts
- •Database Optimization
- •Enabling MySQL’s Query Cache
- •MySQL InnoDB Performance on Windows
- •Drupal Performance
- •Eliminating 404 Errors
- •Disabling Modules You’re Not Using
- •Drupal-Specific Optimizations
- •Page Caching
- •Bandwidth Optimization
- •Pruning the Sessions Table
- •Managing the Traffic of Authenticated Users
- •Logging to the Database
- •Logging to Syslog
- •Running cron
- •Architectures
- •Single Server
- •Separate Database Server
- •Separate Database Server and a Web Server Cluster
- •Load Balancing
- •File Uploads and Synchronization
- •Multiple Database Servers
- •Database Replication
- •Database Partitioning
- •Finding the Bottleneck
- •Web Server Running Out of CPU
- •Web Server Running Out of RAM
- •Identifying Expensive Database Queries
- •Identifying Expensive Pages
- •Identifying Expensive Code
- •Optimizing Tables
- •Caching Queries Manually
- •Changing the Table Type from MyISAM to InnoDB
- •Summary
- •Installation Profiles
- •Creating a New Installation Profile
- •The enhanced.info File
- •The enhanced.profile File
- •The enhanced.install File
- •Using hook_install_tasks and hook_install_tasks_alter
- •Summary
- •Testing
- •Setting Up the Test Environment
- •How Tests Are Defined
- •Test Functions
- •Test Assertions
- •Summary
- •Database Table Reference
- •Resources
- •Code
- •The Drupal Source Code Repository on GIT
- •Examples
- •Drupal API Reference
- •Security Advisories
- •Updating Modules
- •Updating Themes
- •Handbooks
- •Forums
- •Mailing Lists
- •Development
- •Themes
- •Translations
- •User Groups and Interest Groups
- •Internet Relay Chat
- •North America
- •Europe
- •Asia
- •Latin America / Caribbean
- •Oceania
- •Africa
- •Videocasts
- •Weblogs
- •Conferences
- •Contribute
- •Index
- •Numbers
CHAPTER 11 ■ THE FORM API
// Store the allowed choices in the form definition. $allowed_flavors = array(t('spicy'), t('sweet')); $form['flavor'] = array(
'#type' => 'textfield', '#title' => 'flavor',
'#allowed_flavors' => $allowed_flavors, '#element_validate' => array('formexample_flavor_validate')
);
Then your element validation function would look like this:
function formexample_flavor_validate($element, $form_state) {
if (!in_array($form_state['values']['flavor'], $element['#allowed_flavors'])) { form_error($element, t('You must enter spicy or sweet.'));
}
}
The validation function for the form will still be called after all element validation functions have been called.
■ Tip Use form_set_error() when you have the name of the form element you wish to file an error against and form_error() when you have the element itself. The latter is simply a wrapper for the former.
Form Rebuilding
During validation, you may decide that you do not have enough information from the user. For example, you might run the form values through a textual analysis engine and determine that there is a high probability that this content is spam. As a result, you want to display the form again (complete with the values the user entered) but add a CAPTCHA to disprove your suspicion that this user is a robot. You can signal to Drupal that a rebuild is needed by setting $form_state['rebuild'] inside your validation function, like so:
$spam_score = spamservice($form_state['values']['my_textarea']; if ($spam_score > 70) {
$form_state['rebuild'] = TRUE; $form_state['formexample']['spam_score'] = $spam_score;
}
In your form definition function, you would have something like this:
262
CHAPTER 11 ■ THE FORM API
function formexample_nameform($form_state) { // Normal form definition happens.
...
if (isset($form_state['formexample']['spam_score']) {
//If this is set, we are rebuilding the form;
//add the captcha form element to the form.
...
}
...
}
Writing a Submit Function
The submit function is the function that takes care of actual form processing after the form has been validated. It executes only if form validation passed completely and the form has not been flagged for rebuilding. The submit function is expected to modify $form_state['redirect'].
function formexample_form_submit($form, &$form_state) {
//Do some stuff.
...
//Now send user to node number 3. $form_state['redirect'] = 'node/3';
}
If you have multiple functions handling form submittal (see the “Submitting the Form” section earlier in this chapter), the last function to set $form_state['redirect'] will have the last word.
■ Tip The $form_state['rebuild'] flag can be set in submit functions too, just like in validation functions. If set, all submit functions will run but any redirect value will be ignored, and the form will be rebuilt using the submitted values. This can be useful for adding optional fields to a form.
Changing Forms with hook_form_alter()
Using hook_form_alter(), you can change any form. All you need to know is the form’s ID. There are two approaches to altering forms.
Altering Any Form
Let’s change the login form that is shown on the user login block and the user login page.
263
CHAPTER 11 ■ THE FORM API
function formexample_form_alter(&$form, &$form_state, $form_id) {
//This code gets called for every form Drupal builds; use an if statement
//to respond only to the user login block and user login forms.
if ($form_id == 'user_login_block' || $form_id == 'user_login') { // Add a dire warning to the top of the login form. $form['warning'] = array(
'#markup' => t('We log all login attempts!'), '#weight' => -5
);
// Change 'Log in' to 'Sign in'. $form['submit']['#value'] = t('Sign in');
}
}
Since $form is passed by reference, we have complete access to the form definition here and can make any changes we want. In the example, we added some text using the default form element (see “Markup” later in this chapter) and then reached in and changed the value of the Submit button.
Altering a Specific Form
The previous approach works, but if lots of modules are altering forms and every form is passed to every hook_form_alter() implementation, alarm bells may be going off in your head. “This is wasteful,” you’re probably thinking. “Why not just construct a function from the form ID and call that?” You are on the right track. Drupal does exactly that. So the following function will change the user login form too:
function formexample_form_user_login_alter(&$form, &$form_state) { $form['warning'] = array(
'#value' => t('We log all login attempts!'), '#weight' => -5
);
// Change 'Log in' to 'Sign in'. $form['submit']['#value'] = t('Sign in');
}
The function name is constructed from this:
modulename + 'form' + form ID + 'alter'
For example,
'formexample' + 'form' + 'user_login' + 'alter'
results in the following:
formexample_form_user_login_alter
In this particular case, the first form of hook_form_alter() is preferred, because two form IDs are involved (user_login for the form at http://example.com/?q=user and user_login_block for the form that appears in the user block).
264
CHAPTER 11 ■ THE FORM API
Submitting Forms Programmatically with drupal_form_submit()
Any form that is displayed in a web browser can also be filled out programmatically. Let’s fill out our name and favorite color programmatically:
$form_id = 'formexample_nameform'; $form_state['values'] = array(
'user_name' => t('Marvin'), 'favorite_color' => t('green')
);
// Submit the form using these values. drupal_form_submit($form_id, $form_state);
That’s all there is to it! Simply supply the form ID and the values for the form, and call drupal_form_submit().
■ Caution Many submit functions assume that the user making the request is the user submitting the form. When submitting forms programmatically, you will need to be very aware of this, as the users are not necessarily the same.
Dynamic Forms
We’ve been looking at simple one-page forms. But you may need to have users fill out a form that dynamically displays elements on the form based on selections the user made as he or she filled out the form. The following example demonstrates how to display form elements dynamically as the user picks various options while filling out the form.
Start by creating a directory in your site/all/modules/custom folder named form_example_dynamic. In that directory, create a form_example_dynamic.info file with the following information.
name = Form Example – Creating a Dynamic Form description = An example of a dynamic form. package = Pro Drupal Development
core = 7.x files[]=form_example_dynamic.module
Next create the form_example_dynamic.module file, and begin by placing the following header information in the file.
265
CHAPTER 11 ■ THE FORM API
<?php
/**
*@file
*An example of how to use the new #states Form API element, allowing
*dynamic form behavior with very simple setup.
*/
With the header information in place, the next step is to create a menu item that a visitor can use to access the new form. The module provides a single menu entry that can be accessed via www.example.com/form_example_dynamic.
/**
* Implements hook_menu(). */
function form_example_dynamic_menu() { $items['form_example_dynamic'] = array(
'title' => t('Form Example Dynamic Form'), 'page callback' => 'drupal_get_form',
'page arguments' => array('form_example_dynamic_form'), 'access callback' => TRUE,
'type' => MENU_NORMAL_ITEM
);
return $items;
}
With the menu complete, I’m now ready to create the form. The first item displayed on the form is a series of three radio buttons that allow a site visitor to select a room type to reserve.
function form_example_dynamic_form($form, &$form_state) { $form['room_type'] = array(
'#type' => 'radios',
'#options' => drupal_map_assoc(array(t('Study Room'), t('Small Conference Room'), t('Board Room'))),
'#title' => t('What type of room do you require?')
);
The next form item is a fieldset that contains details about the study room and uses the #states attribute to determine whether this item should be displayed on the page. The #states attribute sets whether the fieldset will be visible by examining the room_type radio buttons to see whether the Study Room option was selected. If the Study Room option was selected, then the value is set to true and the form will render the fieldset using jQuery. The syntax of the visibility test follows the syntax of using selectors in jQuery. In this case, we’re looking at an input element (the radio buttons) named room_type. We’re examining whether the value of the input is Study Room.
266
CHAPTER 11 ■ THE FORM API
$form['study_room'] = array( '#type' => 'fieldset',
'#title' => t('Study Room Details'), '#states' => array(
'visible' => array(
':input[name="room_type"]' => array('value' => t('Study Room')),
),
),
);
The next item shown on the form is two check boxes that allow a visitor to provide details about the types of equipment to be set up in the study room. In the example, I’ve limited those choices to chairs and a PC. I use the same #states approach as the preceding fieldset. I want the check boxes displayed only if the visitor has selected Study Room from the list of available rooms.
$form['study_room']['equipment'] = array( '#type' => 'checkboxes',
'#options' => drupal_map_assoc(array(t('Chairs'), t('PC'))), '#title' => t('What equipment do you need?'),
'#states' => array(
'visible' => array( // action to take. ':input[name="room_type"]' => array('value' => t('Study Room')),
),
),
);
If the user checked the Chairs check box, I’ll display a text field that allows the visitor to enter the number of chairs to be set up in the room prior to his or her arrival. I’m using #action to control visibility of this text field, displaying the field only if the user checked the Chairs check box.
$form['study_room']['chairs'] = array( '#type' => 'textfield',
'#title' => t('How Many Chairs Do You Need?:'), '#size' => 4,
'#states' => array(
'visible' => array( // action to take. ':input[name="equipment[Chairs]"]' => array('checked' => TRUE),
),
),
);
The next element on the form is another text box that allows a visitor to enter details about the type of PC to be set up in the study room. Like the foregoing chairs item, I’m using #action to control visibility by checking to see whether the visitor checked the PC check box.
$form['study_room']['pc'] = array( '#type' => 'textfield',
'#title' => t('What Type of PC do you need?:'), '#size' => 15,
267
CHAPTER 11 ■ THE FORM API
'#states' => array(
'visible' => array( // action to take. ':input[name="equipment[PC]"]' => array('checked' => TRUE),
),
),
);
The next set of form elements is displayed only if the visitor clicked the “Small Conference Room” radio button. It follows the same pattern of using the #actions attribute to determine whether form items should be visible based on a condition or action taken by the visitor.
$form['small_conference_room'] = array( '#type' => 'fieldset',
'#title' => t('small_conference_room Information'), '#states' => array(
'visible' => array(
':input[name="room_type"]' => array('value' => t('Small Conference Room')),
),
),
);
$form['small_conference_room']['how_many_pcs'] = array( '#type' => 'select',
'#title' => t('How many PCs do you need set up in the small conference room?'), '#options' => array(
1 => t('One'),
2 => t('Two'),
3 => t('Three'),
4 => t('Four'),
5 => t('Lots'),
),
);
$form['small_conference_room']['comment'] = array( '#type' => 'item',
'#description' => t("Wow, that's a long time."), '#states' => array(
'visible' => array(
':input[name="how_many_pcs"]' => array('value' => '5'),
),
),
);
$form['small_conference_room']['room_name'] = array( '#type' => 'textfield',
'#title' => t('Which room do you want to use?:'),
);
268
CHAPTER 11 ■ THE FORM API
$form['small_conference_room']['hours'] = array( '#type' => 'select',
'#options' => drupal_map_assoc(array(t('Free'), t('Paid'))),
'#title' => t('Do you want to reserve the room when it is free (no fees) or paid (prime time)?'),
);
The following form element utilizes two conditional checks to determine whether the text field should be displayed. With #action you can simply list out any number of conditions that must be met before the form item will be displayed. In this case, I check to see whether the visitor selected either Free or Paid from the preceding hours field.
$form['small_conference_room']['hours_writein'] = array( '#type' => 'textfield',
'#size' =>50,
'#title' => t('Please enter the date and time you would like to reserve the room and the duration.'),
'#states' => array(
'visible' => array( // Action to take: Make visible. ':input[name="hours"]' => array('value' => t('Free')), ':input[name="hours"]' => array('value' => t('Paid')),
),
),
);
The reminder form item here introduces a new visibility check by verifying that the visitor seleted either Free or Paid and that he or she entered something in the hours_writein field.
$form['small_conference_room']['reminder'] = array( '#type' => 'item',
'#description' => t('Remember to enter the date, start time, and end time.'), '#states' => array(
'visible' => array(
'input[name="hours"]' => array('value' => t('Free')), 'input[name="hours"]' => array('value' => t('Paid')), 'input[name="hours_writein"]' => array('filled' => TRUE),
),
),
);
$form['board_room'] = array( '#type' => 'fieldset',
'#title' => t('Board Room Information'), '#states' => array(
'visible' => array(
':input[name="room_type"]' => array('value' => t('Board Room')),
),
),
);
269
Download from Wow! eBook <www.wowebook.com>
CHAPTER 11 ■ THE FORM API
$form['board_room']['more_info'] = array( '#type' => 'textarea',
'#title' => t('Please enter the date and time of when you would like to reserve the board room'),
);
$form['board_room']['info_provide'] = array( '#type' => 'checkbox',
'#title' => t('Check here if you have provided information above'), '#disabled' => TRUE,
'#states' => array(
'checked' => array( // Action to take: check the checkbox. ':input[name="more_info"]' => array('filled' => TRUE),
),
),
);
$form['expand_more_info'] = array( '#type' => 'checkbox',
'#title' => t('Check here if you want to add special instructions.'),
);
$form['more_info'] = array( '#type' => 'fieldset',
'#title' => t('Special Instructions'), '#collapsible' => TRUE,
'#collapsed' => TRUE, '#states' => array(
'expanded' => array(
':input[name="expand_more_info"]' => array('checked' => TRUE),
),
),
);
$form['more_info']['feedback'] = array( '#type' => 'textarea',
'#title' => t('Please provide any additional details that will help us better serve you.'),
);
$form['submit'] = array( '#type' => 'submit',
'#value' => t('Submit your information'),
);
return $form;
}
function form_example_dynamic_form_submit($form, &$form_state) { drupal_set_message(t('Submitting values: @values', array('@values' =>
var_export($form_state['values'], TRUE))));
}
270
CHAPTER 11 ■ THE FORM API
With the module complete, I’ll enable the module and visit the form at www.example.com/form_example_dynamic. The first page of the form should look like Figure 11-7.
Figure 11-7. The initial state of the form
Selecting Study Room from the list of options reveals the next part of the form (see figure 11-8), which asks the visitor about the type of equipment to be set up in the room before he or she arrives.
Figure 11-8. Study Room Details fieldset is displayed based on the previous option selected.
271
CHAPTER 11 ■ THE FORM API
Selecting the Small Conference Room option instead of Study Room displays the form elements related to the Small Conference Room (see figure 11-9).
Figure 11-9. The Small Conference room form elements are displayed after selecting Small Conference room from the room types.
If the visitor selects the Board Room from the list of room types, the details shown in figure 11-10 are displayed.
272
