drupal lightning fapi jumpstart

31
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

Upload: guestfd47e4c7

Post on 24-Jan-2015

6.125 views

Category:

Technology


1 download

DESCRIPTION

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

TRANSCRIPT

Page 1: Drupal Lightning FAPI Jumpstart

Lightning (It's gonna be fast paced)

FAPI (Expect lots of code)

Jumpstart (Feel the power!)

Doug Stumbergerdrupal.org = twitter = facebook = ‘dougstum’

DND Communicationswww.raceonedesign.com

dstumberger @ hotmail.com

Page 2: Drupal Lightning FAPI Jumpstart

It's a Drupal talk, so…

*

* No cats were harmed in the creation of these slides.

Page 3: Drupal Lightning FAPI Jumpstart

Don't recognize this? Uh-oh.

Page 4: Drupal Lightning FAPI Jumpstart

Something simple to begin withfunction 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; }

Page 5: Drupal Lightning FAPI Jumpstart

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());

Page 6: Drupal Lightning FAPI Jumpstart

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 handlerfunction 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'];

Page 7: Drupal Lightning FAPI Jumpstart

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 = FALSEfunction 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'];

Page 8: Drupal Lightning FAPI Jumpstart

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) ); } }

Page 9: Drupal Lightning FAPI Jumpstart

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

Page 10: Drupal Lightning FAPI Jumpstart

Custom elements Define a custom element with fields to be merged function fcklite_elements() { return array('fcklite_textarea' => array( '#input' => TRUE, '#prefix' => "<div class='fcklite_textarea'>", '#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)

Page 11: Drupal Lightning FAPI Jumpstart

A custom fckeditor element

Page 12: Drupal Lightning FAPI Jumpstart

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 itemsfunction newsletter_menu() { $items['newsletter/subscribe'] = array( 'page callback' => 'drupal_get_form', 'page arguments' => array('newsletter_subscribe_form'), 'access arguments' => array('manage subscriptions'), ); return $items;}

Page 13: Drupal Lightning FAPI Jumpstart

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, ));};

Page 14: Drupal Lightning FAPI Jumpstart

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

Page 15: Drupal Lightning FAPI Jumpstart

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

Page 16: Drupal Lightning FAPI Jumpstart

Modifying forms: hook_form_alterfunction 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

Page 17: Drupal Lightning FAPI Jumpstart

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="inline_title">',   '#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!

Page 18: Drupal Lightning FAPI Jumpstart

#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, );

Page 19: Drupal Lightning FAPI Jumpstart
Page 20: Drupal Lightning FAPI Jumpstart

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!)

Page 21: Drupal Lightning FAPI Jumpstart

Multi-step forms Multi-page surveys, wizards, notifications, etc.

First, conditionalize UI elements, especially for form navigation

function 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; }

Page 22: Drupal Lightning FAPI Jumpstart

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

Page 23: Drupal Lightning FAPI Jumpstart

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

Page 24: Drupal Lightning FAPI Jumpstart

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

Page 25: Drupal Lightning FAPI Jumpstart

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']

Page 26: Drupal Lightning FAPI Jumpstart

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("Hello World.");' => array('type' => 'inline'),

);

return $form;}

Page 27: Drupal Lightning FAPI Jumpstart

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

Page 28: Drupal Lightning FAPI Jumpstart

Appendix

Page 29: Drupal Lightning FAPI Jumpstart

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."),    '#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

Page 30: Drupal Lightning FAPI Jumpstart

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'] = '[email protected]';

$form_state['values']['pass'] = 'password';

$form_state['values']['op'] = t('Create new account');

drupal_execute('user_register', $form_state);

Page 31: Drupal Lightning FAPI Jumpstart

http://drupal.org/node/165104