I added custom code to generate custom checkout fields based on whether a custom has one or two products in their cart. For each product, 4 custom checkout fields are needed and added. They need to be displayed in two columns, side-by-side.
The desired end result is:
col1 | col2 |
---|---|
A1 | A2 |
B1 | B2 |
C1 | C2 |
D1 | D2 |
Currently, the script below generates this:
col1 | col2 |
---|---|
A1 | B1 |
C1 | D1 |
A2 | B2 |
C2 | D2 |
function add_custom_checkout_fields_for_product_1234() {
global $woocommerce;
// Get the current cart.
$cart = WC()->cart;
// Product ID to check for.
$product_id_to_check = 5027;
// Calculate the total quantity of the specified product in the cart.
$total_quantity = 0;
foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) {
if ( $cart_item['product_id'] == $product_id_to_check ) {
$total_quantity += $cart_item['quantity'];
}
}
// If the product with ID 1234 is in the cart, add the custom fields based on quantity.
if ( $total_quantity > 0 ) {
echo '<div id="custom_checkout_fields">
<h3>' . __('Custom Fields', 'woocommerce') . '</h3>
<p>' . __('Enter your custom information:', 'woocommerce') . '</p>
<div class="custom-fields-container">';
// Loop to display custom fields based on quantity.
$custom_field_labels = array('A1', 'B1', 'C1', 'D1');
$second_batch_labels = array('A2', 'B2', 'C2', 'D2');
for ( $i = 1; $i <= $total_quantity; $i++ ) {
// Determine which set of labels to use based on even or odd iteration.
$labels = ($i % 2 == 0) ? $second_batch_labels : $custom_field_labels;
foreach ($labels as $label) {
echo '<div class="custom-field">
<label for="custom_field_' . $i . $label . '">' . __('Custom Field', 'woocommerce') . ' ' . $label . ' ' . $i . '</label>
<input type="text" class="input-text" name="custom_field_' . $i . $label . '" id="custom_field_' . $i . $label . '" />
</div>';
}
}
echo '</div></div>';
}
}
add_action('woocommerce_before_checkout_form', 'add_custom_checkout_fields_for_product_1234');
// Validate and save the custom field data.
function validate_and_save_custom_checkout_fields($posted_data) {
// Loop through the posted data to validate custom fields.
for ( $i = 1; $i <= $total_quantity; $i++ ) {
foreach ($custom_field_labels as $label) {
$field_name = 'custom_field_' . $i . $label;
if ( empty( $posted_data[$field_name] ) ) {
wc_add_notice( __('Please fill in all custom fields.', 'woocommerce'), 'error' );
}
}
}
// Return the posted data.
return $posted_data;
}
add_filter('woocommerce_checkout_posted_data', 'validate_and_save_custom_checkout_fields');
The additional CSS used for this table:
.custom-fields-container {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
/* Style custom field columns */
.custom-field {
flex-basis: calc(50% - 10px); /* Adjust width as needed */
margin-bottom: 10px;
box-sizing: border-box;
}
First, you can't use woocommerce_before_checkout_form
hook to add custom checkout fields, as the fields will be outside the checkout form (the hook name is explicit).
Instead, you can use woocommerce_checkout_before_customer_details
hook to add checkout fields.
Note: The code, doesn't need any CSS (styling) as I use WooCommerce checkout form fields.
The fields will be displayed In 2 columns, just like in your desired result:
The fields will be validated and saved to the database on submit.
// Utility function: Get specific product total cart item quantity
function get_specific_cart_item_quantity() {
$targeted_id = 5027; // <== Product ID to check for.
$item_qty = 0; // Initializing
// Loop through cart items
foreach ( WC()->cart->get_cart() as $item ) {
if ( $item['product_id'] == $targeted_id ) {
$item_qty += $item['quantity'];
}
}
return $item_qty;
}
// Add custom checkout fields
add_action('woocommerce_checkout_before_customer_details', 'add_custom_checkout_fields');
function add_custom_checkout_fields() {
$item_qty = get_specific_cart_item_quantity();
if ( $item_qty ) {
$domain = 'woocommerce';
echo '<div id="custom_checkout_fields">
<h3>' . __('Custom Fields', $domain) . '</h3>
<p>' . __('Enter your custom information:', $domain) . '</p>
<div class="custom-fields-container">';
$qty1 = $item_qty % 2 == 0 ? $item_qty / 2 : ( $item_qty == 1 ? 1 : ($item_qty + 1) / 2 );
$qty2 = $item_qty == 1 ? 1 : 2;
// Multi-loop to display custom fields based on quantity in 2 columns
for ( $k = 1; $k <= $qty1; $k++ ) {
$qty2 = $item_qty % 2 != 0 && $k == $qty1 ? 1 : 2;
// 2nd Loop (letters)
foreach ( array('A', 'B', 'C', 'D') as $letter ) {
$key = strtolower($letter);
// 3rd Loop (Columns)
for ( $h = 1; $h <= $qty2; $h++ ) {
$class = $item_qty > 1 ? (($h % 2 == 0) ? 'last' : 'first' ) : 'wide';
$class = $item_qty % 2 != 0 && $k == $qty1 ? 'wide' : $class;
$index = $item_qty == 1 ? 1 : 2;
$index = in_array($class, ['first', 'wide']) ? ($k*2)-1 : $k*2;
$field = "custom_field_{$key}{$index}";
$label = sprintf('%s %s%d', __('Custom Field', $domain), $letter, $index);
woocommerce_form_field( $field, array(
'type' => 'text',
'label' => $label,
'placeholder' => '',
'class' => array('custom-field form-row-'.$class),
'required' => true, // or false
), WC()->checkout->get_value( $field ) );
}
}
}
echo '</div></div>';
}
}
// Validate custom fields
add_action( 'woocommerce_after_checkout_validation', 'validate_custom_checkout_fields', 10, 2 );
function validate_custom_checkout_fields( $data, $errors ) {
if ( did_action('woocommerce_checkout_process') >= 2 ) return;
$item_qty = get_specific_cart_item_quantity();
if ( $item_qty ) {
$domain = 'woocommerce';
$break = false;
// 1st Loop (Letters)
foreach ( array('A', 'B', 'C', 'D') as $letter ) {
$key = strtolower($letter);
// 2nd Loop (Numbers)
for ( $i = 1; $i <= $item_qty; $i++ ) {
$field = "custom_field_{$key}{$i}";
if ( isset($_POST[$field]) && empty($_POST[$field]) ) {
$errors->add( 'validation', __('Please fill in all custom fields.', $domain), 'error' );
$break = true;
break;
}
}
if ( $break ) break;
}
}
}
// Save custom field data as custom order meta data
add_action( 'woocommerce_checkout_create_order', 'save_custom_checkout_fields', 10, 2 );
function save_custom_checkout_fields( $order, $data ) {
$item_qty = get_specific_cart_item_quantity();
if ( $item_qty ) {
// 1st Loop (Letters)
foreach ( array('A', 'B', 'C', 'D') as $letter ) {
$key = strtolower($letter);
// 2nd Loop (Numbers)
for ( $i = 1; $i <= $item_qty; $i++ ) {
$field = "custom_field_{$key}{$i}";
if ( isset($_POST[$field]) && ! empty($_POST[$field]) ) {
$order->update_meta_data( $field, sanitize_text_field($_POST[$field]) );
}
}
}
}
}
Code goes in functions.php file of your child theme (or in a plugin). Tested and works.