Each field of the default Woocommerce form checkout has this markup:

<p class="form-row form-row-first validate-required" id="billing_first_name_field" data-priority="10">
     <label for="billing_first_name" class="">Name&nbsp;
           <abbr class="required" title="required">*</abbr>
     </label>
     <span class="woocommerce-input-wrapper">
          <input type="text" class="input-text " name="billing_first_name" id="billing_first_name" placeholder="" value="" autocomplete="given-name">
     </span>
</p>

Now, I want:

  1. Wrap each field in a wrapper like <div class="single-field-wrapper"><p [...] </div>
  2. Wrap the first two fields, ‘billing_first_name’ and ‘billing_last_name’ in a wrapper like <div class="first-and-second-field-wrapper"> [...] </div>

To achieve this, I’ve tried to use the filter hooks made available by function woocommerce_form_field( $key, $args, $value = null ) called in form-billing.php template in this way:

<?php
        $fields = $checkout->get_checkout_fields( 'billing' );

        foreach ( $fields as $key => $field ) {
            if ( isset( $field['country_field'], $fields[ $field['country_field'] ] ) ) {
                $field['country'] = $checkout->get_value( $field['country_field'] );
            }
            woocommerce_form_field( $key, $field, $checkout->get_value( $key ) );
        }
    ?>

So in functions.php I wrote:

function change_woocommerce_field_markup($field, $key, $args, $value){

  $field = '<div class="single-field-wrapper">'.$field.'</div>';

  if($key === 'billing_first_name') {

      $field = '<div class="first-and-second-field-wrapper">'.$field;
   }
  else if ($key === 'billing_last_name') {

      $field = $field.'</div>';
   }

   else {
      $field = $field;
   }

    return $field;
   } 

  add_filter("woocommerce_form_field","change_woocommerce_field_markup", 10, 4);

Unexpectedly, I get this markup:

<div class="first-and-second-field-wrapper">
       <div class="single-field-wrapper">
            <div class="single-field-wrapper">
            [here there are all the fields, one under the other ]
            </div>
       <div class="single-field-wrapper"></div> 
       <div class="single-field-wrapper"></div> 
       <div class="single-field-wrapper"></div> 
       <div class="single-field-wrapper"></div> 
       <div class="single-field-wrapper"></div>   
       <div class="single-field-wrapper"></div> 
       <div class="single-field-wrapper"></div> 
       <div class="single-field-wrapper"></div> 
 </div>

Anyone can explain why this unexpected behavior?

2 Answers
2

There is no hook woocommerce_form_field, there is hook woocommerce_form_field_{$args[type]} (doc).

$args[type] can be (look here for available options):

  • text,
  • checkbox,
  • country,

Code below will wrap ‘billing_first_name‘ and ‘billing_last_name‘ fields in a wrapper like <div class="first-and-second-field-wrapper"> [...] </div>.

function change_woocommerce_field_markup($field, $key, $args, $value) {

   $field = '<div class="single-field-wrapper">'.$field.'</div>';

   if($key === 'billing_first_name')
      $field = '<div class="first-and-second-field-wrapper">'.$field;
   else if ($key === 'billing_last_name')
      $field = $field.'</div>';

    return $field;
} 

add_filter("woocommerce_form_field_text","change_woocommerce_field_markup", 10, 4);

It will also wrap text type fields with <div class="single-field-wrapper">...</div>.
BUT
some text fields that have own type (like state or email) require additional hooks, for example:

add_filter("woocommerce_form_field_country","change_woocommerce_field_markup", 10, 4);
add_filter("woocommerce_form_field_email","change_woocommerce_field_markup", 10, 4);

UPDATE #1

Above code works in WC-v3.3.7.

In WC-v3.4.xx you get this:

<div class="first-and-second-field-wrapper">
    <div class="single-field-wrapper">
        <div class="single-field-wrapper">
            [here there are all the fields, one under the other ]
        </div>
    <div class="single-field-wrapper"></div> 
    <div class="single-field-wrapper"></div> 
    <div class="single-field-wrapper"></div> 
    ....
    <div class="single-field-wrapper"></div>
</div>

because javascript sorts form rows inside .woocommerce-billing-fields__field-wrapper. Look at the file woocommerce/assets/js/frontend/address-i18n.js, from line 99.
JS finds all HTML tags with “.form-row” class inside wrapper, takes parent of first item (tag with class .form-row), sort items by priority and insert them into previously selected parent element.

For the test, change in file address-i18.js, in line 99
var fieldsets = $('.woocommerce-billing-fields__field-wrapper, ...
to
var fieldsets = $('.woocommerce-billing-fields__field-wrapper2, ...
and upload as address-i18.min.js.

Removing JS sorting is not a solution, it is just a test. Whithout sorting by JS you will get this:

<div class="first-and-second-field-wrapper">
    <div class="single-field-wrapper">
        <p class="form-row ..." id="billing_first_name_field"> <label for="billing_first_name"> ... </p>
    </div>
    <div class="single-field-wrapper">
        <p class="form-row ..." id="billing_last_name_field"> <label for="billing_last_name">... </p>
    </div>
</div>
<div class="single-field-wrapper">
   <p class="form-row ..." id="billing_company_field"> <label for="billing_company">...  </p>
</div>

Leave a Reply

Your email address will not be published. Required fields are marked *