Custom Meta Boxes: Store two values in one repeatable field

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.

Read More

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!

Related posts

Leave a Reply

1 comment

  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++;
                    }
                }
            }