Using a Data List With Contact Form 7

I love using the list attribute in combination with a data list.

It just came up in a client project again, where the basic requirement was to only allow a certain combination of post codes and cities in an application form.

My first instinct was to use JS to validate the input of two fields, post code and city. But then I remembered the data list element and the list attribute.

The HTML is pretty straightforward:

<input type="text" list="city-list" />
<datalist id="city-list">
    <option>69181 Leimen</option>
    <option>69226 Nußloch</option>
    <option>69207 Sandhausen</option>
</datalist>

This will basically give you autofill for free!

You can test it by typing in the input field below. It works whether you start typing the post code or the city name. Neat!

See the Pen Untitled by Florian (@haptiq) on CodePen.


Implementing it in Contact Form 7

In this particular project we were using Contact Form 7, a widely used WordPress form plugin.

The list attribute can be added like this:

<p><label for="city">Post Code / City</label> [text city id:city list:city-list]</p>

Because I didn’t want to curate the city list in two different places and use the same set of values for the data list element and the server side validation, I started by creating a function, which returns the allowed cities as an array:

function get_city_list() {
    return [
        '69181 Leimen',
        '69226 Nußloch',
        '69207 Sandhausen'
    ];
}

I added two more functions.

The first one to create the data list element:

function generate_city_datalist() {
    $cities = get_city_list();

    $output = '<datalist id="city-list">';
    foreach ( $cities as $city ) {
        $output .= '<option value="' . esc_attr( $city ) . '">';
    }

    $output .= '</datalist>';

    return $output;
}

The second one to inject the data list into the form via Contact Form 7’s wpcf7_form_elements filter:

function inject_city_datalist( $form ) {
    // Get the form ID
    $wpcf7 = WPCF7_ContactForm::get_current();
    $form_id = $wpcf7->id();

    // Check the form ID and whether the "city" field is part of this form
    if ( $form_id == 123 && str_contains( $form, 'name="city"' ) ) {
        // Append the datalist to the end of the form
        $datalist = generate_city_datalist();
        $form = $form . $datalist;
    }

    return $form;
}  

add_filter( 'wpcf7_form_elements', 'inject_city_datalist' );

Finally, I added some custom validation to prevent any invalid city submissions, server-side:

function custom_field_validation( $result, $tags ) {
    // Make sure to only run this for a specific form
    $form = WPCF7_ContactForm::get_current();
    if ( $form->id() != 123 ) {
        return $result;
    }

    // Get the form submission data
    $submission = WPCF7_Submission::get_instance();
    $data = $submission->get_posted_data();

    // Iterate through the fields
    foreach ( $tags as $tag ) {
        $field_name = $tag->name;
        $field_value = $data[ $field_name ] ?? null;

        if ( $field_name == 'city' && ! empty( $field_value ) ) {
            $cities = get_city_list();

            if ( ! in_array( $field_value, $cities ) ) {
                $result->invalidate( $tag, 'Your place of residence is outside our area. Start entering your postal code to see available locations.' );
            }
        }
    }

    return $result;
}

add_filter( 'wpcf7_validate', 'custom_field_validation', 10, 2 );

And there you have it.

The data list element in combination with the list attribute on an input field provides autocomplete functionality without a single line of JavaScript. It also works great on mobile devices where typing is mostly a huge pain (at least for me).

Using the same source for the data list and the server-side validation ensures a single point of truth for the city data.

So, if you’re working with Contact Form 7 and need to restrict user input to predefined values while still offering a friendly autocomplete experience, give data lists a try! 🙂


Comment via Email or Mastodon