Custom Meta Boxes: Store two values in one repeatable field

The question:

I am trying to create a custom meta box where the user can add fields as needed. I followed this tutorial:
http://wp.tutsplus.com/tutorials/reusable-custom-meta-boxes-part-1-intro-and-basic-fields/

I would like to expand on this tutorial and create a repeatable field that allows two inputs. For Example: Name and URL.

How could I achieve this?

I found this other question here: Create more Meta Boxes as needed

I was able to integrate the meta box example into my custom post type but I couldn’t figure out how to output the meta values into my theme. Here the code I am working with:

    /*---------------------------*/
    /* Define Metabox Fields
    /*---------------------------*/

    $prefix = 'tz_';

    $custom_downloads = array(
         'id' => 'custom-downloads',
         'title' => __('Custom Downloads Downloads (Beta)'),
         'page' => 'categories-supported',
         'context' => 'normal',
         'priority' => 'high',
         'fields' => array(

        array(
            'name' => __('Add Product Downloads', 'framework'),
            'desc' => __('This meta box is under development', 'framework'),
            'id' => $prefix . 'custom-downloads',
            'type' => 'text',
            'std' => ''
        ),                        
      )
    );

    /*-------------------------------------*/
    /* Add Meta Box to CPT screen 
    /*-------------------------------------*/

    function tz_add_box() {
        global $custom_downloads;

        add_meta_box($custom_downloads['id'], $custom_downloads['title'], 'tz_show_box_custom_dwnld', $custom_downloads['page'], $custom_downloads['context'], $custom_downloads['priority']);


    }

 }

 add_action('admin_menu', 'tz_add_box');

    /*------------------------------------------*/
    /* Callback function/show fields in meta box
    This is taken directly from: https://wordpress.stackexchange.com/questions/19838/create-more-meta-boxes-as-needed
    /*------------------------------------------*/


    function tz_show_box_custom_dwnld() {
        global $custom_downloads, $post;
        // Use nonce for verification
        echo '<input type="hidden" name="tz_meta_box_nonce" value="', wp_create_nonce(basename(__FILE__)), '" />';


        ?>
        <div id="meta_inner">
        <?php

        //get the saved meta as an arry
        $songs = get_post_meta($post->ID,'songs',true);

        $c = 0;
        if ( count( $songs ) > 0 ) {
            foreach( $songs as $track ) {
                if ( isset( $track['title'] ) || isset( $track['track'] ) ) {
                    printf( '<p>Song Title <input type="text" name="songs[%1$s][title]" value="%2$s" /> -- Track number : <input type="text" name="songs[%1$s][track]" value="%3$s" /><span class="remove">%4$s</span></p>', $c, $track['title'], $track['track'], __( 'Remove Track' ) );
                    $c = $c +1;
                }
            }
        }

        ?>
        <span id="here"></span>
        <span class="add"><?php _e('Add Tracks'); ?></span>
        <script>
        var $ =jQuery.noConflict();
        $(document).ready(function() {
        var count = <?php echo $c; ?>;
        $(".add").click(function() {
        count = count + 1;

        $('#here').append('<p> Song Title <input type="text" name="songs['+count+'][title]" value="" /> -- Track number : <input type="text" name="songs['+count+'][track]" value="" /><span class="remove">Remove Track</span></p>' );
        return false;
        });
        $(".remove").live('click', function() {
        $(this).parent().remove();
        });
        });
        </script>
        </div>
        <?php

    }

/*-------------------------------------*/
/* Save data when post is edited
/*-------------------------------------*/


function tz_save_data($post_id) {
    global $meta_box, $meta_box_video, $meta_box_video_page, $meta_box_product_tabs, $meta_deployments, $meta_features, $meta_downloads;
    // verify nonce
    if (!wp_verify_nonce($_POST['tz_meta_box_nonce'], basename(__FILE__))) {
        return $post_id;
    }
    // check autosave
    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
        return $post_id;
    }
    // check permissions
    if ('page' == $_POST['post_type']) {
        if (!current_user_can('edit_page', $post_id)) {
            return $post_id;
        }
    } elseif (!current_user_can('edit_post', $post_id)) {
        return $post_id;
    }


 $songs = $_POST['songs'];
    update_post_meta($post_id,'songs',$songs);


}

add_action('save_post', 'tz_save_data');

Using The above code I am able to generate the dynamic meta box on my CPT edit screen and am able to save data in the fields successfully.

I’m a little embarrassed to admit it, but I don’t know how to display the info from these fields in my theme. I have been able to successfully display other custom meta information stored in other fields by using

<?php $post_meta_data = get_post_custom($post->ID); ?>
<?php $custom_features = unserialize($post_meta_data['tz_features-repeat'][0]); ?>

<?php echo '<ul class="deployments">';
    foreach ($custom_deployments as $string) {
        echo '<li>'.$string.'</li>';
    }
    echo '</ul>';
?>

Any help would be greatly appreciated!

The Solutions:

Below are the methods you can try. The first solution is probably the best. Try others if the first one doesn’t work. Senior developers aren’t just copying/pasting – they read the methods carefully & apply them wisely to each case.

Method 1

The serialized array your meta boxes are saving is deformed. I made a few slight changes to your code to get it to work. I created a new post and here is what was saved after being unserialized.

Note: WordPress will automatically unserialize an an array stored in post_meta. Use an online unserializer if you ever want to check the contents.

Array
(
[1] => Array
(
[title] => Back in Black
[track] => 1
) [2] => Array
(
[title] => Hells Bells
[track] => 2
) [3] => Array
(
[title] => Hot for Teacher
[track] => 3
) [4] => Array
(
[title] => Rack City Bitch
[track] => 4
) 
)

To output this on the front end you just call get_post_meta() and loop through the array.

add_filter( 'the_content', 'get_your_track_meta' );
    function get_your_track_meta( $content ) {
        global $post;
        $metadata = get_post_meta( $post->ID, 'songs', true );
        $content .= '<ul>';
        foreach ( (array) $metadata as $meta) {
            $content .= '<li>' . $meta['title'] . ' -- ' . $meta['track'] . '</li>';
        }
        $content .= '</ul>';
        return $content;
    }

On the front end this gives us a nice unordered list with the track and title:

  • Back in Black — 1
  • Hells Bells — 2
  • Hot for Teacher — 3
  • Rack City Bitch — 4

Code Changes:

I had to change your page field to post in you meta box definitions to get it to load on a post and I also moved them inside the add_meta_box function so we wouldn’t have to deal with making them global. In your show box function I cast the get_post_meta variable to an array to get rid of the invalid argument supplied for foreach error I got in Xdebug and fixed your counter.

$songs = get_post_meta( $post->ID, 'songs', true );
        $c = 0;
        if ( count( $songs ) > 0 ) {
            foreach ( (array)$songs as $track ) {
                if ( isset( $track['title'] ) || isset( $track['track'] ) ) {
                    printf( '<p>Song Title <input type="text" name="songs[%1$s][title]" value="%2$s" /> -- Track number : <input type="text" name="songs[%1$s][track]" value="%3$s" /><span class="remove">%4$s</span></p>', $c, $track['title'], $track['track'], __( 'Remove Track' ) );
                    $c++;
                }
            }
        }


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Comment