Drupal

AJAX Example: Textfields driven by checkboxes

In this example, we just use checkboxes to determine whether textboxes are displayed.

One of the fundamental ideas of having a form change based on selections within the form is that the form is reconfiguring itself based on $form_state. So here, the generation of the form is driven by $form_state['values']. If the checkbox for last name is checked, then we generate a textfield for last name.

(Experiment with this one at http://d7.drupalexamples.info/examples/ajax_example/autotextfields or see the current code at http://api.drupal.org/api/function/ajax_example_autotextfields/7.)

<?php
/**
* Show/hide textfields based on AJAX-enabled checkbox clicks.
*/
function ajax_example_autotextfields($form, &$form_state) {

 
$form['ask_first_name'] = array(
   
'#type' => 'checkbox',
   
'#title' => t('Ask me my first name'),
   
'#ajax' => array(
     
'callback' => 'ajax_example_autotextfields_callback',
     
'wrapper' => 'textfields',
     
'effect' => 'fade',
    )
  );
 
$form['ask_last_name'] = array(
  
'#type' => 'checkbox',
  
'#title' => t('Ask me my last name'),
   
'#ajax' => array(
     
'callback' => 'ajax_example_autotextfields_callback',
     
'wrapper' => 'textfields',
     
'effect' => 'fade',

    ),
  );

 
$form['textfields'] = array(
   
'#title' => t("Generated text fields for first and last name"),
   
'#prefix' => '<div id="textfields">',
   
'#suffix' => '</div>',
   
'#type' => 'fieldset',
   
'#description' => t('This is where we put automatically generated textfields'),
  );

   if (!empty(
$form_state['values']['ask_first_name']) && $form_state['values']['ask_first_name']) {
   
$form['textfields']['first_name'] = array(
     
'#type' => 'textfield',
     
'#title' => t('First Name'),
    );
  }
  if (!empty(
$form_state['values']['ask_last_name']) && $form_state['values']['ask_last_name']) {
   
$form['textfields']['last_name'] = array(
     
'#type' => 'textfield',
     
'#title' => t('Last Name'),
    );
  }


 
$form['submit'] = array(
   
'#type' => 'submit',
   
'#value' => t('Click Me'),
  );


  return
$form;
}


/**
* Selects the piece of the form we want to use as replacement text and returns
* it as a form (renderable array).
*
* @return renderable array (the textfields element)
*/
function ajax_example_autotextfields_callback($form, $form_state) {
  return
$form['textfields'];
}
?>

Form API Changes for Drupal 7, Part 1: $form_state changes

You may know that lots of delicious things have happened to Drupal's Form API in Drupal 7. (Only a geek can say "delicious" and "Form API" in the same sentence. Try it!) The finest minds in the business have been working on it, I can assure you. Give effulgentsia, fago, frando, and chx a big hug when you see them, because Form API is much improved. (Sorry to those of you I forgot to name, but THANKS!)

I'm going to do a series covering Form API changes, starting with this one. I won't attempt to cover the deep details, just the things that ordinary developers might use:

  1. $form_state changes and form builder function signature changes
  2. AJAX Forms changes
  3. New properties (#attached and many friends)

Let me know if you have other topics to suggest.

OK, to business. This article is mostly parroted from the api.drupal.org topic: Form Generation. Thanks to Alex Bronstein (effulgentsia) for his reviews and contributions to that doc.

Don't forget that the form builder function signature changed!

In Drupal 6 the form builder function looked like this:

function my_module_funky_form(&$form_state) { ... }

but in Drupal 7 it's
function my_module_funky_form($form, &$form_state, ... ) { ... }

$form_state in Drupal 7

Mostly the members of the $form_state array are the same ones you know and love from Drupal 6:

  • $form_state['values']: An associative array of values that have been submitted to the form. The validation and submit functions use this array for nearly all their decisionmaking. (Note that #tree determines whether the values are a flat array or an array whose structure parallels the $form array.) This is nearly the same as it was in D6.
  • $form_state['rebuild']: If the submit handler sets $form_state['rebuild'] to TRUE, submission is not completed and instead the form is rebuilt using any information that the submit function has made available to the form builder function via $form_state. This is commonly used for wizard-style multi-step forms, add-more buttons, and the like. For further information see drupal_build_form(). This is the same as D6.
  • $form_state['redirect']: a URL that will be used to redirect the form on submission. See drupal_redirect_form() for complete information. This should always be used instead of drupal_goto() in a forms context. Note that $form['#redirect'] went away in Drupal 7 and no longer has any effect.
  • $form_state['storage']: $form_state['storage'] is no more! It used to be the place for application-specific values, but now it has no specific meaning. Now nearly all $form_state keys persist in a multi-step form, so the recommended approach is to use $form_state['your_module']['whatever']. ($form_state['storage'] still works for persistent storage, just like $form_state['timbuktu'] works.)
  • $form_state['triggering_element': (read-only) The form element that triggered submission. This is the same as the deprecated $form_state['clicked_button']. It is the element that caused submission, which may or may not be a button (in the case of AJAX forms.) This is often used to distinguish between various buttons in a submit handler, and is also used in AJAX handlers.
  • $form_state['cache']: The typical form workflow involves two page requests. During the first page request, a form is built and returned for the user to fill in. Then the user fills the form in and submits it, triggering a second page request in which the form must be built and processed. By default, $form and $form_state are built from scratch during each of these page requests. In some special use-cases, it is necessary or desired to persist the $form and $form_state variables from the initial page request to the one that processes the submission. A form builder function can set 'cache' to TRUE to do this. One example where this is needed is to handle AJAX submissions, so ajax_process_form() sets this for all forms that include an element with a #ajax property. (In AJAX, the handler has no way to build the form itself, so must rely on the cached version created on each page load, so it's a classic example of this use case.) Note that the persistence of $form and $form_state across successive submissions of a multi-step form happens automatically regardless of the value for 'cache'. You probably won't need to use $form_state['cache']. And note that $form['#cache'] is gone in D7 and now has no effect on anything.
  • $form_state['input']: The array of values as they were submitted by the user. These are raw and unvalidated, so should not be used without a thorough understanding of security implications. In almost all cases, code should use the data in the 'values' array exclusively. The most common use of this key is for multi-step forms that need to clear some of the user input when setting 'rebuild'.

Drupal 7 FAPI Resources:

Next time: Part 2: AJAX forms changes. See AJAX Forms in Drupal 7 if you're in a hurry.

Translatable Regions Module: For user-contributed content in many languages

Drupal is *great* at handling multilingual situations, but how do you make user-contributed content in multiple languages accessible to those who can't read them?

The Translatable Regions module tackles this by using the automated Google AJAX Language API via the jquery-translate plugin.

What do you have to do?

  • Install the module.
  • Decide what selectors you want to have translation buttons.
  • Configure the selectors at admin/settings/translatableregions.

Whenever those selectors appear on the page, a button is added offering to translate to many languages, the default being the browser language.

Here's an example of the use on user profiles on http://warmshowers.org.

To see live user-generated content translation, to go The Portuguese-language forums on Warmshowers.org.

New Queue and Menu Examples for Examples Project

The Examples for Developers Project aims to provide high-quality, well-documented API examples for a broad range of Drupal core functionality. You can download the code from the project page, view it on api.drupal.org, or experience it (for several of them) at drupalexamples.info.

Recently, we've added two new example suites:

Examples for Developers is a community project. Contributions are encouraged (just use the issue queue). If you find a problem, please submit an issue. If you want to fix a problem you find, please submit a patch. It will never be perfect, but if we tend it as a community resource, it should get better all the time.

PHP 5.2 on Ubuntu 10.04 Lucid Lynx

Lots of us are starting to run Drupal on Ubuntu 10.04 Lucid Lynx, and of course, Lucid comes with PHP 5.3, for which Drupal 6 is not yet ready.

I've seen a couple of fine recipes, but here's mine, with the files you need attached.

Update: You'll probably want to look at Khalid's more extensive discussion of various ways to do this.

Essentially, all you have to do is tell the apt system where to find Karmic packages and then tell it that you want it to use Karmic's PHP packages.

So put the attached karmic.list in /etc/apt/sources.list.d and the attached "php" into /etc/apt/preferences.d.

Then:

sudo apt-get update
sudo apt-get remove php5 libapache2-mod-php5 php5-xsl php5-gd php-pear php5-mysql php5-curl php5-memcache
sudo apt-get install php5 libapache2-mod-php5 php5-xsl php5-gd php-pear php5-mysql php5-curl php5-memcache

If you had not already installed apache, php, and mysql, you can do that with the above files in place and get the right versions without removing anything:

sudo apt-get install apache2 mysql-server php5 libapache2-mod-php5 php5-xsl php5-gd php-pear libapache2-mod-auth-mysql php5-mysql php5-curl php5-memcache

DrupalCon: Drupal 7 AJAX and Javascript

Rob Loach, Kat Bailey and I will be presenting Drupal 7 AJAX and Javascript Monday, 3-4pm, in room 307.

Here are the basics:

  • The new AJAX Framework
  • AJAX forms, with a complete how-to and sample, easy to understand code.
  • The fantastic new #states feature for dynamic forms.
  • D7 Javascript changes from D6.
  • Using Javascript Libraries.

We've made great strides in Drupal 7. Come and hear about it.

If you used AHAH in D6, you're probably afraid of AJAX forms. But if you never used #ahah, you'll walk out of the room feeling competent to try out #ajax. You can't believe how easy it is now.

D8 Software Process Improvement Discussions at Core Dev Summit

We can substantially improve our core software development process with some simple steps in Drupal 8.

  1. Improve our issue queue setup on drupal.org.
    Our issue queue works the way it does because Drupal worked that way a long time ago. We can do better than that.
    • Each core issue should have an issue summary, and the issue summary should be correct and complete before commit.
    • An issue should have links to related and dependent issues.
    • There should be checkoffs for docs, usability, security, etc.

    Of course we know how to do this with Drupal. Let's do it. Related issues: Improve the workflow for documentation of core API changes and Provide a mechanism for issue meta discussions

  2. Full functional in-browser testing, including javascript and AJAX. I'll be presenting a brainstorm on this at the Core Dev Summit and there will be a BoF on this topic at 4pm on Monday in room 200. We have to solve our lack of test coverage in this area. AJAX and javascript get essentially no real test coverage, so it's like the old days... somebody has to report that something is broken. Even with things as important as install.php.
  3. Every feature should have a full, working example in the tests module. We create "mock modules" to write tests against... but sometimes features actually go in that nobody knows how to use. A simple remedy: Before commit, there needs to be a comprehensive example in the related mock module in the tests. The comprehensive example can be used as fodder for tests, but it's also there for people to see how to use something. Enable the mock module, try it out. Recently the fantastic #states feature went in with no documentation and essentially no usage examples. And it wasn't testable because we don't have Javascript testing. But if we had the requirement of a comprehensive example, we'd have been fine (and several bugs would have been avoided.)

    What are your suggestions for software process improvements in D8? Let's talk about this at the Core Dev Summit on Saturday!

Drupal 7 FAPI's #states: A Great New UI Improvement For Forms

The little-known #states feature has gone into Drupal 7, and it rocks.

Before you read on, try this dynamic form live at d7.drupalexamples.info. It's developed without using a line of javascript, just plain Form API.

Essentially, you can provide dynamic behavior in a form based on changes to other elements in the form. An easy example: Often you only need to collect information if a particular element is selected. If they select type=student, you don't have to require them to fill in a further "Employer" field.

The new #states example in the Examples module's Form Example shows how a dynamic form can work. You can try it out live as well at d7.drupalexamples.info.

The idea of #states is that you add a #states property to a form element that is supposed to change when some other form element changes. So if a form element is supposed to be shown or hidden, the #states property will be added on that element, not on the element that caused the change.

The #states property is a structured array of action => condition arrays. The action is 'visible' or 'checked' or 'required' or several other options. The condition is an associative array of 'jquery_selector' => array(value_statement). But mostly you can do it by copying and pasting examples. Much of the time the jquery selector can be ":input[name=field_name]" and the rest of the array can be cookbook from example code.

In the example, the 'tests_taken' field is only to be visible if the form-filler is a high school student:

  $form['tests_taken'] = array(
    '#type' => 'checkboxes',
    '#options' => drupal_map_assoc(array(t('SAT'), t('ACT'))),
    '#title' => t('What standardized tests did you take?'),
    '#states' => array(
      'visible' => array(   // action to take.
        ':input[name=student_type]' => array('value' => t('High School')),
      ),
    ),
  );

So we set the action to 'visible' when the condition (our select called student_type is set to 'High School') is true.

That's basically all there is to it.

So when would you use #states as opposed to AJAX forms or a sprinkling of jquery?

  • If your form actually changes under the hood based on user input, then you need to use AJAX forms. #states doesn't fundamentally change any of the elements, it just changes their presentation.
  • If you don't change the form, but need a transformation that simple conditional logic can't do, you'll have to roll some jQuery.

#states will be covered in detail at Drupalcon:

For more detailed comments and possibilities, read the #states example.

Examples module now on api.drupal.org

Thanks to excellent work by api module maintainer drumm, all of the examples in Examples module are now available on api.drupal.org.

If you're not familiar with Examples module, it's an attempt to provide easy-to-understand examples of key Drupal APIs, so that developers have a known source of a working example they can understand. Back in the day, there were a few examples in the Documentation branch of contrib in CVS, but they were poorly maintained and nobody knew how to improve them. All of those have been moved to Examples (for D6 and D7) and updated.

So with Examples now on api.drupal.org, you have more than one way to access the various examples:

Currently on Drupal 7 we have these examples: AJAX, Batch, Block, DBTNG, E-mail, Field, , Form, Image, Javascript, Node, Page, Simpletest, Token, Trigger, Vertical Tabs, and XML-RPC.

Community contribution is encouraged. If you find errors, can suggest improvements, provide patches, provide new examples, please do it in the Examples issue queue. This is a big and important project and it will take the whole community to get it right. Thanks!

Drupalcon: Best Practices in Contrib Development

Mark your calendar for Wednesday the 21st at 3pm: Greg Knaddison, Dave Reid, Derek Wright, Jennifer Hodgdon and I are doing a panel presentation on how to maintain and support a contrib module or theme. It will cover:

Community management: Enlisting help, finding maintainers and co-maintainers, handling abandoned projects, dealing with duplication, handling the issue queue.

Drupal project management: What's expected of a maintainer, code and release management.

Coding issues: Coding standards, documentation, namespacing, simpletest, internationalization.

Resources for maintainers.

If you're a maintainer or might become one, or want to start helping overburdened module maintainers by helping out with these responsibilities, come and hear about how to contribute to the community more effectively and efficiently.

Here's the link to the DCSF page. The session is Wednesday at 3pm in 304, which is "Phase2 Technology".

Materials and resources will be posted on the Contrib Development Best Practices Groups.drupal.org page.

Syndicate content