Metabox repeating fields – radio buttons not saving correctly

I have multiple radio buttons set up inside of a metabox in a custom post type. I have created the metabox like so:

    add_action('admin_init', 'add_meta_boxes', 1);
    function add_meta_boxes() {
        add_meta_box( 'repeatable_fields', 'Top 10 Movie List', 'repeatable_meta_box_display', 'cpt_top_ten_list', 'normal', 'default');
    }

    function repeatable_meta_box_display() {
        global $post;
        $repeatable_fields = get_post_meta($post->ID, 'repeatable_fields', true);
        wp_nonce_field( 'repeatable_meta_box_nonce', 'repeatable_meta_box_nonce' );

     if ( $repeatable_fields ) :

    // set a variable so we can append it to each row
    $num = 0;
    $second_num = 0;

    foreach ( $repeatable_fields as $field ) {
    $num++;

<div class=" playbackformat-holder-<?php echo $num; ?> playbackformat-holder">
    <form id="playback-form-<?php echo $num; ?>">
        <label for="dvd-<?php echo $num; ?>">
        <input type="radio" class="playbackformat-holder-radiobutton" value="dvd" name="playback_format[<?php echo $second_num; ?>]" id="dvd-<?php echo $num; ?>" <?php if($field['playback_format'] == 'dvd') { echo 'checked'; } ?>>DVD
        </label>

        <label for="bluray-<?php echo $num; ?>">
        <input type="radio" class="playbackformat-holder-radiobutton" value="bluray" name="playback_format[<?php echo $second_num; ?>]" id="bluray-<?php echo $num; ?>" <?php if($field['playback_format'] == 'bluray') { echo 'checked'; } ?>>Bluray
        </label><br>    

        <label for="3d-<?php echo $num; ?>">
        <input type="radio" class="playbackformat-holder-radiobutton" value="3d" name="playback_format[<?php echo $second_num; ?>]" id="3d-<?php echo $num; ?>" <?php if($field['playback_format'] == '3d') { echo 'checked'; } ?>>3d
        </label><br />
    </form>     
</div>
second_num++;
}

And here is my save function to store the data into an array.

Read More
add_action('save_post', 'repeatable_meta_box_save', 10, 2);
function repeatable_meta_box_save($post_id) {

    if ( ! isset( $_POST['repeatable_meta_box_nonce'] ) ||
    !wp_verify_nonce( $_POST['repeatable_meta_box_nonce'], 'repeatable_meta_box_nonce' ) )
    return;

    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE)
    return;

    if (!current_user_can('edit_post', $post_id))
    return;

    $old = get_post_meta($post_id, 'repeatable_fields', true);
    $new = array();

    $playbackFormats = $_POST['playback_format'];


    $count = count( $names ) - 1;

    for ( $i = 0; $i < $count; $i++ ) {

    // currently only storing the last stored radio value
    $new[$i]['playback_format'] = $playbackFormats[$i];

    // save movie description
     // and however you want to sanitize
    if ( !empty( $new ) && $new != $old ) {
        update_post_meta( $post_id, 'repeatable_fields', $new );
    } elseif ( empty($new) ) {
        delete_post_meta( $post_id, 'repeatable_fields', $old );
    }

}

The problem I am facing is that , lets say I have three rows. If I set the first to DVD, the second to bluray and the third to 3D only the last value stores as 3D. All other ['playback_format'] store as ‘null’.

The way I have things set up it looks like things should be saving properly. This is a trimmed down version. I have input fields that are storing correctly, but the radio buttons have been a major issue.

It looks like my save function should be storing each ['playback_format'] into its appropriate place in the array. But only one stores. I’ve gotten it to store the same value for all three rows, but can’t get a unique value for each row and not sure why.

Any help would be appreciated, as this is the last piece of the puzzle!

Thanks! (Screenshot below for a visual aid)

repeatable rows - visual aid

Here is the value of var_dump($field); after storing the data in the screenshot above.

array(4) { 
 ["name"]=> string(116) "http://onecinephile.staging.wpengine.com/wp-content/uploads/2014/01/lon-chaney-phantom-hunchback-penalty-300x280.jpg" 
 ["select"]=> string(14) "Test #2" 
 ["url"]=> string(35) "Testing movie description #2" 
 ["playback_format"]=> string(3) "dvd" 
} 

array(4) { 
 ["name"]=> string(103) "http://onecinephile.staging.wpengine.com/wp-content/uploads/2014/01/spencer-tracy-boys-town-300x225.jpg" 
 ["select"]=> string(14) "Test #1" 
 ["url"]=> string(35) "Testing movie description #1" 
 ["playback_format"]=> NULL 
} 

array(4) { 
 ["name"]=> string(100) "http://onecinephile.staging.wpengine.com/wp-content/uploads/2014/01/Ronald-and-Madeleine-225x300.jpg" ["select"]=> string(14) "Test #3
 ["url"]=> string(35) "Testing movie description #3" 
 ["playback_format"]=> NULL 
}

Related posts

1 comment

  1. So I came back to this after practice and implemented some of the Alchemy setup, meaning all the inputs have names like name="_movies[3][title]" where 3 is the iteration we’re on in the loop.

    Create the Metabox

    // metaboxes should be registered on the add_meta_boxes hook
    add_action('add_meta_boxes', 'add_meta_boxes' );
    function add_meta_boxes() {
        add_meta_box( 'repeatable_fields', 'Top 10 Movie List', 'repeatable_meta_box_display', 'cpt_top_ten_list', 'normal', 'default');
    }
    
    
    function repeatable_meta_box_display( $post ) { 
    
    $repeatable_fields = get_post_meta($post->ID, 'repeatable_fields', true); 
    
    if ( empty( $repeatable_fields ) ){
        $repeatable_fields[] = array ( 
                                    'image' => '',
                                    'title' => '',
                                    'playback_format' => 'dvd',
                                    'description' => '' );
    }
    
    wp_nonce_field( 'hhs_repeatable_meta_box_nonce', 'hhs_repeatable_meta_box_nonce' ); 
    ?> 
    
    <table id="repeatable-fieldset-one" class="widefat fixed" cellspacing="0" style="width:100%;"> 
    <thead> 
    <tr> 
    <th style="width:25px;" scope="col">Rank</th> 
    <th style="width:170px;" scope="col">Image</th> 
    <th width="145px" scope="col">Movie Title</th> 
    <th width="300px" scope="col">Movie Description</th> 
    <th width="8%" scope="col">Re-Order</th> 
    </tr> 
    </thead> 
    <tbody> 
    
    
    <?php 
    
    // set a variable so we can append it to each row 
    $i = 1; 
    
    foreach ( $repeatable_fields as $field ) { ?>
    
    <tr class="single-movie-row ui-state-default"> 
    
        <td> 
        <label for="_movies[<?php echo $i;?>][rank]"> 
        <input name="_movies[<?php echo $i;?>][rank]" id="_movies[<?php echo $i;?>][rank]" class="movie_rank_number" disabled="disabled" type="text" value="# <?php echo $i;?>" /> 
        </label>    
        </td> 
    
        <td> 
        <label for="_movies[<?php echo $i;?>][image]"> 
        <input name="_movies[<?php echo $i;?>][image]" class="upload_image" id="_movies[<?php echo $i;?>][image]" type="text" size="36" value="<?php echo esc_attr( $field['image'] );?>" /> 
        <input class="upload_image_button" id="_movies[<?php echo $i;?>][upload_image_button]" type="button" value="Upload Image" /> 
        </label> 
        </td> 
    
        <td> 
        <!-- title field --> 
        <textarea name="_movies[<?php echo $i;?>][title]" id="_movies[<?php echo $i;?>][title]" class="title_tinymce_editor"><?php echo esc_html( $field['title'] );?></textarea> 
    
        <div class="playbackformat-holder"> 
    
        <label for="_movies[<?php echo $i;?>][playback_format][dvd]"> 
        <input type="radio" id="_movies[<?php echo $i;?>][playback_format][dvd]" name="_movies[<?php echo $i;?>][playback_format]" value="dvd" <?php checked( $field['playback_format'], 'dvd' ); ?> />DVD 
        </label> 
        <label for="_movies[<?php echo $i;?>][playback_format][bluray]"> 
        <input type="radio" id="_movies[<?php echo $i;?>][playback_format][bluray]" name="_movies[<?php echo $i;?>][playback_format]" value="bluray" <?php checked( $field['playback_format'], 'bluray' ); ?> />Bluray 
        </label><br>    
        <label for="_movies[<?php echo $i;?>][playback_format][3d]"> 
        <input type="radio" id="_movies[<?php echo $i;?>][playback_format][3d]" name="_movies[<?php echo $i;?>][playback_format]" value="3d" <?php checked( $field['playback_format'], '3d' ); ?> />3d 
        </label><br />  
    
        </div> 
    
        </td> 
    
        <td>
        <textarea id="_movies[<?php echo $i;?>][description]" name="_movies[<?php echo $i;?>][description]" class="movie_description_editor_hidden"><?php echo esc_html( $field['description'] );?></textarea>
        </td> 
    
        <td>
        <a class="button remove-row" href="#">Remove Row</a><img src="<?php echo get_template_directory_uri() ?>/images/draggable-icon.png" alt="sortable icon" class="jQuerySortableIcon">
        </td> 
    
    </tr> 
    
    <?php $i++; } ?>
    
    
    <!-- empty hidden one for jQuery --> 
    <tr class="empty-row screen-reader-text single-movie-row"> 
    
        <td> 
        <label for="_movies[%s][rank]"> 
        <input name="_movies[%s][rank]" id="_movies[%s][rank]" class="movie_rank_number" disabled="disabled" type="text" value="" /> 
        </label>    
        </td> 
    
        <td> 
        <label for="_movies[%s][image]"> 
        <input name="_movies[%s][image]" class="upload_image" id="_movies[%s][image]" type="text" size="36" value="" /> 
        <input class="upload_image_button" id="_movies[<?php echo $i;?>][upload_image_button]" type="button" value="Upload Image" /> 
        </label> 
        </td> 
    
        <td> 
        <!-- title field --> 
        <textarea name="_movies[%s][title]" id="_movies[%s][title]" class="title_tinymce_editor"></textarea> 
    
        <div class="playbackformat-holder"> 
    
        <label for="_movies[%s][playback_format][dvd]"> 
        <input type="radio" id="_movies[%s][playback_format][dvd]" name="_movies[%s][playback_format]" value="dvd" <?php checked( 'dvd', 'dvd' ); ?> />DVD 
        </label> 
        <label for="_movies[%s][playback_format][bluray]"> 
        <input type="radio" id="_movies[%s][playback_format][bluray]" name="_movies[%s][playback_format]" value="bluray" />Bluray 
        </label><br>    
        <label for="_movies[%s][playback_format][3d]"> 
        <input type="radio" id="_movies[%s][playback_format][3d]" name="_movies[%s][playback_format]" value="3d" />3d 
        </label><br />  
    
        </div> 
    
        <!-- drop down or checkbox's with release formats --> 
        </td> 
    
        <td>
        <textarea id="_movies[%s][description]" name="_movies[%s][description]" class="movie_description_editor_hidden"></textarea>
        </td> 
    
    
        <td>
        <a class="button remove-row" href="#">Remove Row</a><img src="<?php echo get_template_directory_uri() ?>/images/draggable-icon.png" alt="sortable icon" class="jQuerySortableIcon">
        </td> 
    
    </tr> 
    
    </tbody> 
    </table> 
    
    <p id="add-row-p-holder"><a id="add-row" class="btn btn-small btn-success" href="#">Insert Another Row</a></p> 
    <?php 
    }
    

    Save the Meta

    Saving is now relatively easy because the arrays in the names sets it up for us:

    add_action('save_post', 'hhs_repeatable_meta_box_save', 10, 2); 
    function hhs_repeatable_meta_box_save($post_id) { 
    
    if ( ! isset( $_POST['hhs_repeatable_meta_box_nonce'] ) || 
    !wp_verify_nonce( $_POST['hhs_repeatable_meta_box_nonce'], 'hhs_repeatable_meta_box_nonce' ) ) 
    return; 
    
    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) 
    return; 
    
    if (!current_user_can('edit_post', $post_id)) 
    return; 
    
    $clean = array(); 
    
    if  ( isset ( $_POST['_movies'] ) && is_array( $_POST['_movies'] ) ) :
    
        foreach ( $_POST['_movies'] as $i => $movie ){
    
        // skip the hidden "to copy" div
        if( $i == '%s' ){ 
            continue;
        }
    
    $playback_formats = array ( 'dvd', 'bluray', '3d' );
    
        $clean[] = array( 
            'image' => isset( $movie['image'] ) ? sanitize_text_field( $movie['image'] ) : null,
            'title' => isset( $movie['title'] ) ? sanitize_text_field( $movie['title'] ) : null,
            'playback_format' => isset( $movie['playback_format'] ) && in_array( $movie['playback_format'], $playback_formats ) ? $movie['playback_format'] : null,
            'description' => isset( $movie['description'] ) ? sanitize_text_field( $movie['description'] ) : null,
            );
    
    }
    
    endif;
    
    // save movie data 
    if ( ! empty( $clean ) ) { 
        update_post_meta( $post_id, 'repeatable_fields', $clean ); 
    } else
        delete_post_meta( $post_id, 'repeatable_fields' ); 
    }
    

    I will say that I ran into the same issue that we talked about in the chat…. I about flipped when after all this I was still running into the second and third ‘playback_format’ radios weren’t evening posting. But finally, I noticed that you are wrapping the radios in a <form> element. I’m convinced this is the problem because as soon as I got rid of it, it worked.

    I would suggest you try dropping that extra <Form> from your code first to see if that solves it. It might be the rest of your code works. If not, then I’m pretty sure mine does now… however, I’ve changed some names and some IDs so that will probably effect the rest of the work that relies on this.

    And finally, b/c of how I changed everything, I needed to modify your add-row JS. It is simplified here as I didn’t want to deal with the tinyMCE editors, so I just left the textareas for my testing purposes.

    Duplicate Row with Javascript

    /*********************** Add Row Button Click *****************************/ 
    
    $( '#add-row' ).click(function() { 
    var rowCount = $('#repeatable-fieldset-one').find('.single-movie-row').not(':last-child').size(); 
    var newRowCount = rowCount + 1;
    
    var row = $( '.empty-row.screen-reader-text' ).clone(true); 
    
    // Loop through all inputs
    row.find('input, textarea, label').each(function(){ 
    
        if ( !! $(this).attr('id') )
            $(this).attr('id',  $(this).attr('id').replace('[%s]', '[' + newRowCount + ']') );  // Replace for
    
        if ( !! $(this).attr('name') )
            $(this).attr('name',  $(this).attr('name').replace('[%s]', '[' + newRowCount + ']') );  // Replace for
    
        if ( !! $(this).attr('for') )
            $(this).attr('for',  $(this).attr('for').replace('[%s]', '[' + newRowCount + ']') );  // Replace for
    
    });
    
    row.removeClass( 'empty-row screen-reader-text' ).find('.movie_rank_number').val('# '+newRowCount);
    row.insertBefore( '.empty-row' ); 
    
    // if row count hits 10, hide the add row button 
    if ( newRowCount == 10 ) { 
    jQuery('#add-row').fadeOut(); 
    } 
    
    return false; 
    });
    

Comments are closed.