Drupal Lightning FAPI Jumpstart
Upcoming SlideShare
Loading in...5
×
 

Drupal Lightning FAPI Jumpstart

on

  • 6,358 views

Session presented 10/24 at Pacific Northwest Drupal Summit discussing Drupal form API

Session presented 10/24 at Pacific Northwest Drupal Summit discussing Drupal form API

Statistics

Views

Total Views
6,358
Views on SlideShare
6,342
Embed Views
16

Actions

Likes
4
Downloads
95
Comments
1

1 Embed 16

http://www.slideshare.net 16

Accessibility

Categories

Upload Details

Uploaded via as Microsoft PowerPoint

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment
  • If this doesn't look familiar to you, then you're in for a rough hour. Use t() for titles, descriptions, and values Weight specifies relative vertical location of element on the form canvas D6; use “Save” instead of “Submit” (#287986) Don't forget to return your form!
  • drupal_get_form Primary way of invoking forms Manages all phases of form lifecycle Returns rendered html Can be parameterized to pass default values D7: returns form array, not HTML drupal_execute Programmatic counterpart to drupal_get_form Pass in form_id and $form_state['values'] Returns validation errors D7: becomes drupal_form_submit
  • Easiest way to alter a form Can alter your own or core/contrib forms Called on every form invocation Called after form creation but before rendering Also hook_form_FORM_ID_alter() for specific Remains in Drupal 7 Also exp
  • Goal: match a form's appearance to its context

Drupal Lightning FAPI Jumpstart Drupal Lightning FAPI Jumpstart Presentation Transcript

  • Lightning (It's gonna be fast paced) FAPI (Expect lots of code) Jumpstart (Feel the power!) Doug Stumberger drupal.org = twitter = facebook = ‘dougstum’ DND Communications www.raceonedesign.com dstumberger @ hotmail.com
  • It's a Drupal talk, so… * * No cats were harmed in the creation of these slides.
  • Don't recognize this? Uh-oh.
  • Something simple to begin with
    • function newsletter_subscribe_form ( $form_state ) {
    • $form = array();
    • $form['email'] = array(
    • '#type' => 'textfield',
    • '#title' => t('E-mail address') , // use t() for localization
    • '#size' => 64,
    • '#maxlength' => 64,
    • '#required' => TRUE,
    • '#weight' => -10,
    • );
    • $form['subscribe'] = array(
    • '#type' => 'radios',
    • '#title' => t('Subscribe'),
    • '#default_value' => 0 ,
    • '#options' => array(t('Yes'), t('No')) ,
    • '#weight' => 5,
    • );
    • $form['submit'] = array(
    • '#type' => 'submit',
    • '#value' => t(' Save '), // D6; use “Save” instead of “Submit” (#287986)
    • '#weight' => 20,
    • );
    • return $form;
    • }
  • Validate & submit
    • Validate
      • Default handler: FORMID_validate ($form, &$form_state)
      • Modify $form['#validate'] array in form definition or hook_form_alter
      • Use form_set_error to signal validation errors associated with the form
      • Also useful: form_get_error($element) and form_get_errors()
      • Required fields are handled automatically, no need to check in validate handler
    • Submit
      • Default: FORMID_submit ($form, &$form_state)
      • Modify $form['#submit'] array in form definition or hook_form_alter
      • Particularly useful for redirecting the user (default is to return to form)
      • drupal_goto ($path = '', $query = NULL, $fragment = NULL, $http_response_code = 302)
      • // Block redirection (returns to form)
      • $form_state['redirect'] = FALSE ;
      • // Redirect to site homepage
      • $form_state['redirect'] = '<front>' ;
      • // Redirect to relative path from base_path
      • $form_state['redirect'] = 'node/' . $node->nid ;
      • // Specify arguments to drupal_goto()
      • $form_state['redirect'] = array($node->path, drupal_get_destination()) ;
  • Passing data to validate & submit
    • Pass values from form definition to validate and submit handlers
      • $form['node'] = array(
      • '#type' => 'value' ,
      • '#value' => $node ,
      • );
    • Once form is submitted, retrieve data from $form_state['values']
      • function subscribe_form_validate($form, & $form_state) {
      • $node = $form_state['values']['node'] ;
      • };
    • Pass values (or modify passed values) from validate to submit handler
      • function subscribe_form_validate($form, & $form_state) {
      • $form_state['values']['node'] ->title = t('Hello World');
      • };
      • -- OR –-
      • function subscribe_form_validate($form, & $form_state) {
      • $node = $form_state['values']['node'];
      • $node->title = t('Hello World');
      • form_set_value ($form['node'], $node, $form_state);
      • };
    • Once form is submitted, retrieve data from $form_state['values']
    • $node = $form_state['values']['node'] ;
  • Fieldsets and trees (and access!)
    • Collapsible groupings of form fields
      • $form['options'] = array(
      • '#type' => ' fieldset ',
      • ' #access ' => user_access('administer nodes') ,
      • '#title' => t('Publishing options'),
      • ' #collapsible ' => TRUE, // Default is False.
      • ' #collapsed ' => TRUE, // Default is FALSE.
      • '#tree' => FALSE,
      • );
      • $form['options']['status'] = array(
      • '#type' => 'checkbox',
      • '#title' => t('Published'),
      • '#default_value' => $node->status,
      • );
    • Default: values are “collapsed” when #tree = FALSE
      • function mymodule_form_submit($form, &$form_state) {
      • $status = $form_state['values']['status'] ;
      • };
    • But when '#tree' = TRUE, you must specify complete path to value
      • $status = $form_state['values']['options']['status'] ;
  • Tabular forms
    • Widely used for admin forms
    • function request_form($form_state){
    • $form = array();
    • foreach ($requests as $request_id => $request) {
    • $form['request'][$request_id]['#tree'] = TRUE;
    • $form['request'][$request_id]['delete'] = array(
    • '#type' => 'checkbox',
    • '#default_value' => 0,
    • );
    • $form['request'][$request_id]['user'] = array(
    • '#value' => theme('username', user_load(array('uid' => $request->uid))),
    • );
    • $form['request'][$request_id]['time'] = array(
    • '#value' => date('j M H:I', $request->timestamp),
    • );
    • $form['request'][$request_id]['operations'] = array(
    • '#value' => l('Add article', 'newsletter/add_article', 'id=' . $request_id)
    • );
    • }
    • }
  • Tabular forms: theme
    • Use form theming to convert into table markup
    • function theme_request_form($form) {
    • $headers = array(
    • '',
    • t('User'),
    • t('Time'),
    • t('Operations')
    • );
    • $rows = array();
    • foreach ($form['add'] as $request_id => $request) {
    • $rows[] = array(
    • drupal_render ($form['request'][$request_id]['delete']),
    • drupal_render($form['request'][$request_id]['user']),
    • drupal_render($form['request'][$request_id]['time']),
    • drupal_render($form['request'][$request_id]['operations']),
    • );
    • }
    • return '<h3>' . t('Requests') . '</h3>'
    • . theme('table', $headers, $addrows)
    • . drupal_render($form);
    • }
    • Remember, FAPI will automatically look for theme_FORMID
  • Custom elements
    • Define a custom element with fields to be merged
    • function fcklite_elements () {
    • return array('fcklite_textarea' => array(
    • ' #input ' => TRUE,
    • '#prefix' => &quot;<div class='fcklite_textarea'>&quot;,
    • '#suffix' => '</div>',
    • ' #rows ' => 20,
    • ));
    • }
    • Theme your element (remember to add to hook_theme)
    • function theme_fcklite_textarea ($element) {
    • return theme('form_element' , array(
    • '#title' => $element['#title'],
    • '#description' => $element['#description'],
    • '#id' => $element['#id'],
    • ),
    • FCKeditor_IsCompatibleBrowser()
    • ? fcklite_open_textarea($element['#id'], $val, $element['#rows'])
    • : t('Incompatible browser')
    • );
    • }
    • D7: hook_elements -> hook_element_info and hook_element_info_alter (#572932)
  • A custom fckeditor element
  • Invocation: drupal_get_form
    • Primary means of invoking Drupal forms programmatically
    • Manages building, theming, presentation, execution
    • Returns HTML (at least in Drupal 6!)
      • $html = drupal_get_form('newsletter_subscribe_form');
      • drupal_set_content(‘column_1’, $html);
    • Frequently used as a page callback for hook_menu items
      • function newsletter_menu () {
      • $items['newsletter/subscribe'] = array(
      • 'page callback' => 'drupal_get_form',
      • 'page arguments' => array(' newsletter_subscribe_form '),
      • 'access arguments' => array('manage subscriptions'),
      • );
      • return $items;
      • }
  • drupal_get_form: default values
    • Use your hook_menu wildcards and loaders!
      • function newsletter_menu () {
      • // Note: %user wildcard loader = user_load
      • $items[' newsletter/subscribe/%user '] = array(
      • 'page callback' => 'drupal_get_form ',
      • 'page arguments' => array('subscribe_form', 2) ,
      • 'access arguments' => array('manage subscriptions'),
      • );
      • return $items;
      • }
    • Remember to use $form_state as first parameter (almost always!)
      • function newsletter_form($form_state, $objUser ) {
      • $form = array(
      • 'email' => array(
      • '#type' => 'textfield',
      • '#title' => t('Email address'),
      • '#default_value' => $objUser->mail ,
      • ));
      • };
  • Add/edit forms for node types
    • Use hooks to change default behavior for two URLs
      • Create URL: www.example.com/node/add/TYPE
      • Edit URL: www.example.com/node/%node/edit
    • Replace standard node create/edit forms
      • Define function TYPE_node_form($form_state, $node)
      • Use node_form_validate & node_form_submit
      • OR customize by specifying $form['#submit][] or $form['#validate'][]
      • To theme, either use default: theme_node_form
      • OR implement theme_TYPE_node_form (don't forget to add to hook_theme)
    • Or add custom fields to standard node create/edit forms
      • Only works with node types defined by a module (not through admin)
      • Implement hook_form ($node, $form_state) // NOTE REVERSED ARGUMENTS
      • Uses node_form_validate & node_form_submit
      • Themed using theme_TYPE_node_form or theme_node_form
  • Form factories using hook_forms
    • Multiple modules can use the same form function
    • Each invocation can use a different form_id
    • Modules can alter submission, validation etc.
    • function node_forms() {
    • $forms = array();
    • if ($types = node_get_types ()) {
    • foreach (array_keys($types) as $type) {
    • $forms[$type .'_node_form']['callback'] = 'node_form';
    • }
    • }
    • return $forms;
    • }
    • Calls to TYPE_node_form will result in calls to function node_form(...) with form_id = TYPE_form
  • Modifying forms: hook_form_alter
    • function newsletter_form_alter( &$form , $form_state, $form_id) {
    • // Switch statement is also commonly used.
    • if ($form_id == 'newsletter_subscribe_form') {
    • // Add elements to the form.
    • $form['name'] = array(
    • '#type' => 'textfield',
    • '#title' => t('Name') ,
    • );
    • // Remove elements from the form.
    • unset($form['phone_number']);
    • // Add a new submit handler (or replace altogether).
    • $form['#submit'][] = 'mymodule_submit_handler';
    • // Replace existing validation with your code (or add validation).
    • $form['#validate'] = array('mymodule_validate_handler');
    • // Move form items around using weights.
    • $form['email']['#weight'] = -3;
    • // Fix tabindex problems, add prefix/suffix, etc.
    • $form['submit']['#attributes']['tabindex'] = '3';
    • }
    • }
    • See for example login_toboggan.module
    • Also explore: #after_build, #pre_render, #post_render
  • Theming forms & elements
    • Goal: match the look of a form to its design or layout context
    • Leave markup the same but modify the CSS ( Use FIREBUG!!! )
    • Use #prefix and #suffix fields on a form or on an individual element. Especially useful when set using hook_form_alter.
      • $form['access'] = array(   '#type' => 'textfield',   '#title' => t('Log'),   ' #prefix ' => '<div class=&quot;inline_title&quot;>',    '#suffix ' => '</div>', );
    • Override the default individual element theme functions
      • function MY_THEME_NAME_radio($element) { ... }
      • A default theme function exists for every visible element type
      • But, this overrides the theming for every element of that type on every form
      • Can also override theme_form and theme_form_element !
  • #theme…the hammer, er, scalpel
    • Specify theme function on whole form or individual element
      • $form = array(
      •    ' #theme ' => 'table_based_form',
      • );
    • Make sure the theme function is defined in hook_theme
    • Remember, you can add '#theme' during hook_form_alter
    • The usual algorithm for theming forms:
      • Loop through all elements in a form
        • Output the element's surrounding markup, description, etc.
        • Render the element with drupal_render($form[$element_id])
      • Render the remainder of the form with drupal_render($form)
    • You can pass parameters into the theming using custom form elements and custom '#' fields on individual elements
    $form['fb_fields'] = array( '#type' => 'value', '#value' => array('title', 'body'), ); $form['sender_email'] = array( '#type' => 'item', '#fb_divider' => TRUE, );
  •  
  • Here, a standard imagefield form becomes both more functional and more consistent in presentation with the rest of the site. Combining form_alter and theming gives you the power to dramatically modify contrib module forms, icluding CCK-based forms. (after_build helps!)
  • Multi-step forms
    • Multi-page surveys, wizards, notifications, etc.
    • First, conditionalize UI elements, especially for form navigation
    • f unction newsletter_subscribe_form($form_state) {
    • $form = array();
    • // Second time through the form builder.
    • if (isset($form_state['storage']['confirm'])) {
    • $form['confirm_message'] = array(
    • '#type' => 'item' ,
    • '#value' => t('Email address !email is subscribed.',
    • array('!email' => $form_state['storage']['email'])),
    • );
    • }
    • // First time through the form builder.
    • else {
    • $form['email'] = array(
    • '#type' => 'textfield',
    • '#title' => t('E-mail address'),
    • );
    • $form['submit'] = array(
    • '#type' => 'submit',
    • '#value' => t('Save'),
    • );
    • }
    • return $form;
    • }
  • Cycle data into next step: form_state
    • Pass data from one step to the next
      • Finally, a purpose for $form_state in form definition signature!
      • Use $form_state['storage'] in submit handler for signals, data
    • function newsletter_subscribe_form_submit($form_state) {
    • // signal that the next form building shows confirmation message.
    • $form_state['storage']['confirm'] = 1;
    • // Pass the email address (assume validated) back to form build.
    • $form_state['storage']['email'] = $form_state['values']['email'];
    • // trigger a form rebuild.
    • $form_state['rebuild'] = TRUE;
    • }
    • Signal rebuild of form or redirect in submit handler
      • $form_state['rebuild'] = TRUE;
      • Optional redirect on final step
  • Drupal 7 Changes
    • drupal_get_form returns structured array, not HTML
    • Specify text format in textarea and textfield elements
    • Attach CSS & JS to forms
    • Javascript element dependency (#557272)
      • $form['element']['#dependencies'] => array(   'enabled' => array(     '#edit-menu-has-menu' => array('checked' => TRUE)   ) ),
    • Use '#markup' not '#value' for elements markup & item (#252013)
    • drupal_execute => drupal_form_submit (#408024)
    • Easier to check for node form in hook_form_alter (#161301)
    • Changes to element definitions re: theming (#355236)
    • hook_elements -> hook_element_info (#572932) + hook_element_info_alter
  • D7: drupal_get_form (#353069)
    • drupal_get_form returns form definition array, NOT HTML
        • D6: $html = drupal_get_form('mymodule_form');
        • D7: $arrayForm = drupal_get_form('mymodule_form');
    • Array structure should be passed back to menu handler
      • All menu handlers should return arrays, not HTML
      • Arrays are passed through new page building stack
      • See hook_page_build for rendering
    • Do not do this!
      • $html = drupal_render(drupal_get_form('mymodule_form'));
    • Can now modify form structure in hook_page_alter but use hook_form_alter just as you do in D6
  • D7: Specify text format (#125315)
    • Specify text format for either textfield or textarea elements
    • $form['comment'] = array(
    • '#type' => 'textarea',
    • '#title' => t('Comment'),
    • // Value from {filter_format}.format or filter_fallback_format() etc.
    • ' #text_format ' => filter_default_format() ,
    • );
    • Form structure is split into value and format sub-arrays
    • // Stores the actual textarea element in subarray
    • $form['comment'][' value ']
    • // Create format picker using filter_form(...) with default
    • $form['comment'][' format ']
    • Submit handler retains split between value and format
    • // Unaltered form element value
    • $comment_body = $form_state['values'][' comment '];
    • // Text format (original value specified in #text_format field)
    • $comment_format = $form_state['values'][' comment_format ']
  • D7: Attach CSS & JS (#323112)
    • Attached CSS and Javascript directly in form definition
    • function mymodule_form() {
    • $form = array();
    • // Attach a css file.
    • $form['#attached']['css'] = array(
    • drupal_get_path('module', mymodule') . '/mymodule.css'
    • );
    • // Attach Javascript 'file', 'inline', 'external' or 'setting'
    • $form['#attached']['js'] = array(
    • // Inline Javascript.
    • 'alert(&quot;Hello World.&quot;);' => array('type' => 'inline'),
    • );
    • return $form;
    • }
  • Resources
    • API Reference
      • http://api.drupal.org/api/drupal/developer--topics--forms_api_reference.html/6
    • Quick start guide
      • http://api.drupal.org/api/drupal/developer--topics--forms_api.html/6
    • Tree and parents
      • Tree and parents: http://drupal.org/node/48643
    • Upgrading modules to D7
      • Upgrading to D7: http://drupal.org/update/modules/6/7
    • Maybe useful contributed modules
      • form_builder: Nate Haug's GUI-based form layout tool
      • Webform: one-off forms such as surveys created as nodes
      • Multistep: add multistep forms to CCK
      • Formblock: forms in blocks
      • Formfilter: form editing/filter including formfilter_filter_form API
      • MerlinofChaos ctools: multistep, AJAX (rumor moving into D7 core?)
      • skip_validation (may provide solution for Cancel problem)
      • Fieldset_helper: Saves the collapsed state of a collapsible fieldset
  • Appendix
  • AHAH
    • “ Asynchronous HTML and HTTP” : Javascript-based dynamic behavior similar to AJAX
    • “… the response from the request is used directly without parsing on the clientside”
    • See poll & upload modules for core samples
      • $form['choice_wrapper']['poll_more'] = array(     '#type' => 'submit',     '#value' => t('More choices'),     '#description' => t(“click here to add more choices.&quot;),     '#weight' => 1,     '#submit' => array('poll_more_choices_submit'), // If no javascript action.     ' #ahah ' => array(       'event' => 'click',       'path' => 'mymodule/js',       'wrapper' => 'myform-wrapper',       'method' => 'replace',       'effect' => 'fade',       'progress' => array(         'type' => 'bar',         'message' => t('Loading...')     )
    •  http://drupalsn.com/learn-drupal/drupal-tutorials/getting-going-ahah-and-drupal-6
  • drupal_execute
    • // See user.module, line 2387.
    • function user_register () {
    • ...
    • }
    • // http://api.drupal.org/api/function/drupal_execute/6
    • // register a new user
    • $form_state = array();
    • $form_state['values']['name'] = 'robo-user';
    • $form_state['values']['mail'] = 'robouser@example.com';
    • $form_state['values']['pass'] = 'password';
    • $form_state['values']['op'] = t('Create new account');
    • drupal_execute ('user_register', $form_state);
  • http://drupal.org/node/165104