CMB2
CMB2 copied to clipboard
Allow groups of fields to be broken into tabs
If you have a lot of fields for a page, instead of having one long list we could display them in tabs like Advanced Custom Fields offers. Ex: http://cl.ly/image/1x1x3r0Y0l3U
definitely :+1:
Right now grouped fields are strictly for repeatable groups, but we ought to make it more generic, making repeatable an option as well as tabs being an option: https://github.com/WebDevStudios/CMB2/blob/master/example-functions.php#L310-L314
I know @pmgarman already had it in the works to make field groups work w/o having to be repeatable.
@scottopolis used this approach for tabs. @billerickson is that what you had in mind? http://jsfiddle.net/syahrasi/Us8uc/
Yep, looks good to me!
+1 for tab implementation. Also, IMO tabs should only be a UI construct. Grouping elements into tabs shouldn't necessarily change the way the data is saved. There's already jQuery UI tabs built into core so the JS part is pretty straight forward.
I was able to hack this into place, but it required making modifications to CMB, which I'd really like to avoid if possible.
On the old CMB, I was able to make tabbed admin option pages, though I had to hack my way around a bit. Essentially, I made every individual meta box a wordpress admin tab. Ran into some issues with repeatable fields....but as my only use of CMB on that project was a single options page with tabs, I hacked stuff around a bit to make it work.
My rather crude approach, don't know if this is helpful, again, this was using the old CMB:
/**
* Adds a wrapper div for the WP tabs to work with
*
* @param $meta_box
*/
public function before_cmb_table( $meta_box ) {
// check to see if there is an active tab, or set it to the default
$active_tab = isset( $_GET[ 'tab' ] ) ? $_GET[ 'tab' ] : self::$default_tab;
// add a class to show the tab if it's active
$active_class = ($active_tab == 'cmb-tab-' . $meta_box['id'] ) ? 'cmb-tab-active' : '';
// div wraps the table
echo '<div id="cmb-tab-' . $meta_box['id'] . '" class="cmb-tab ' . $active_class . '">';
}
/**
* Closes the wrapper div
*/
public function after_cmb_table() {
echo '</div>';
}
/**
* Tabbed options page using CMB boxes
*
* @param $meta_boxes
* @param $object_id
* @param bool $echo
*
* @return string
*/
public function cmb_tabbed_admin_form( $meta_boxes, $object_id, $echo = true ) {
// discover if there is an active tab...
$active_tab = isset( $_GET[ 'tab' ] ) ? $_GET[ 'tab' ] : self::$default_tab;
$save_flag = false;
foreach( $meta_boxes as $key => $mb ) {
// set the defaults
$mb = cmb_Meta_Box::set_mb_defaults( $mb );
// Make sure this meta box should be shown
if ( ! apply_filters( 'cmb_show_on', true, $mb ) ) {
unset( $meta_boxes[$key] );
continue;
}
// if the default tab has not been set, make it this box (ie, the first box)
if ( self::$default_tab === null ) {
self::$default_tab = 'cmb-tab-' . $mb['id'];
if ( $active_tab === null ) $active_tab = self::$default_tab;
}
// Make sure that our object type is explicitly set by the metabox config
cmb_Meta_Box::set_object_type( cmb_Meta_Box::set_mb_type( $mb ) );
// this is a little inelegant for multiple boxes all in the same form, but there you have it...
if (
// check nonce
isset( $_POST['submit-cmb'], $_POST['object_id'], $_POST['wp_meta_box_nonce'] )
&& wp_verify_nonce( $_POST['wp_meta_box_nonce'], cmb_Meta_Box::nonce() )
&& $_POST['object_id'] == $object_id
) {
cmb_save_metabox_fields( $mb, $object_id );
$save_flag = true;
}
}
if ( $save_flag === true ) {
self::$myfunction->set_options( stripslashes_deep( get_option( self::$key ) ) );
echo '<div class="updated"><p>Your settings were updated.</p></div>';
}
// make sure the above checking did not empty the metaboxes array
if ( ! empty ( $meta_boxes ) ) {
// start tab navigation from scratch
$tabs = '';
// output the metaboxes as a form
ob_start();
foreach ( (array) $meta_boxes as $meta_box ) {
// the content tab's ID
$this_tab = 'cmb-tab-' . $meta_box['id'];
// check to see if the current tab is active
$active_flag = ( $active_tab == $this_tab ) ? 'nav-tab-active' : '';
// build tab navigation; we use the "data-tab" attribute as a shortcut for JS
$tabs .= '<a href="?page=' . $_GET[ 'page' ] . '&tab=' . $this_tab .
'" class="nav-tab ' . $active_flag . '" data-tab="' . $this_tab . '">' . $meta_box['title'] . '</a>';
cmb_print_metabox( $meta_box, $object_id );
}
$form = ob_get_contents();
ob_end_clean();
// removed the filter, if you add it back, be sure to work-around its individual metabox id!
$form_format = '<h2 class="nav-tab-wrapper">%s</h2>' .
'<form class="cmb-form" method="post" id="%s" enctype="multipart/form-data" encoding="multipart/form-data">' .
'<input type="hidden" name="object_id" value="%s">%s<hr>' .
'<p><input type="submit" name="submit-cmb" value="%s" class="button-primary"></p></form>';
$form = sprintf( $form_format, $tabs, $object_id, $object_id, $form, __( 'Save' ) );
if ( $echo )
echo $form;
return $form;
}
// if the meta_box array was empty
return '';
}
As I recall, I had to change some of the sanitization functions which were not expecting fields as strings or as arrays, or some such. Been awhile!
@marcusbattle is working on #93 which will eventually directly benefit this (future) feature. Hopefully we'll get this in in the next few weeks.
Has any progress been made with this? Preferably something like this:

@jtsternberg If you guys have implemented the tabbed version of metaboxes ?
We have not, though I don't think it would be too difficult. As @robneu said, it would just mean outputting multiple CMB2 instances in a single metabox and using jquery tabs.
@ashawkat , I am including bootstrap tabs.js separately and that's how I am creating my markup.
function my_page_metabox() {
$prefix = 'myprefix_';
$cmb_demo = new_cmb2_box( array(
'id' => $prefix . 'metabox',
'title' => __( 'Page Options', 'cmb2' ),
'object_types' => array( 'page', ), // Post type
'context' => 'normal',
'priority' => 'high',
'show_names' => true,
));
$cmb_demo->add_field( array(
'name' => __( '', 'cmb2' ),
'id' => $prefix . 'opts',
'type' => 'page'
));
}
add_action('cmb2_init', 'my_page_metabox');
function jt_cmb2_render_page_field_callback( $field, $value, $object_id, $object_type, $field_type_object ) {
$value = wp_parse_args($value, array(
'header_layout' => '',
'footer_layout' => ''
));
?>
<div class="my-tabs nav">
<ul class="nav-tabs">
<li class="active">
<a href="#cmb-tab-header" data-toggle="tab"><?php _e('Header', 'myprefix'); ?></a>
</li>
<li>
<a href="#cmb-tab-footer" data-toggle="tab"><?php _e('Footer', 'myprefix'); ?></a>
</li>
</ul>
<div class="tab-content">
<div id="cmb-tab-header" class="tab-pane fade in active">
<div class="label">
<label for="<?php echo $field_type_object->_id( '-header_layout' ); ?>">
<?php _e('Header Layout', 'myprefix'); ?>
</label>
<div class="desc">Enter Header Layout</div>
</div>
<div class="field">
<?php
echo $field_type_object->input( array(
'name' => $field_type_object->_name( '[header_layout]' ),
'id' => $field_type_object->_id( '-header_layout' ),
'value' => $value['header_layout'],
'desc' => ''
));
?>
</div>
</div>
<div id="cmb-tab-footer" class="tab-pane fade">
<div class="label">
<label for="<?php echo $field_type_object->_id( '-footer_layout' ); ?>">
<?php _e('Footer Layout', 'myprefix'); ?>
</label>
<div class="desc">Enter Header Layout</div>
</div>
<div class="field">
<?php
echo $field_type_object->input( array(
'name' => $field_type_object->_name( '[footer_layout]' ),
'id' => $field_type_object->_id( '-footer_layout' ),
'value' => $value['footer_layout'],
));
?>
</div>
</div>
</div>
</div>
<?php
}
add_filter( 'cmb2_render_page', 'jt_cmb2_render_page_field_callback', 10, 5 );
@jtsternberg , Is there any side effect with this approach. Although, I've to write a lot of my own, but it's very flexible. Do I need to escape / sanitize the basic fields ( like textbox, select etc ), with this approach ?
This feature would be VERY helpful for us, as we currently have to make several metaboxes that would make so much more sense if they were grouped together. Having the option to group all these settings together using tabs, will greatly enhance the user experience IMHO. I'm definitely keeping my eye on this thread :+1:
I vote for this too. Very important.
@jtsternberg , may you please look into my solution ( given above ) and answer the question regarding escaping / sanitization ?
Another +1 here! That would make settings pages WAY more user friendly...
@billerickson and everyone else here. Wanted to let you know that this is still on our radar and in progress. We made great progress today at WDS to get this going. So listen out for updates for this functionality in the very near future.

@marcusbattle , can you look at my solution above and confirm that I can use it, till your work is live ?
Hi everybody, any news on this? What I basically need is to replace the Options Framework that I currently use with CMB2 options page. So, just being able to put fields in a metabox under separate tabs would work great for me!!!
It requires some shenanigans but you can make tabs out of a single metabox. I did it using title fields.
- Make sure jQuery UI is enqueued (or roll your own... tabs are pretty simple). This needs to be hooked to
cmb2_after_formto work.
add_action( 'cmb2_after_form', 'your_prefix_admin_scripts' , 10, 4 );
function your_prefix_admin_scripts () {
wp_enqueue_script('jquery-ui-tabs');
// and the jQuery bit in 2) below either inline or called
}
- Some JS to build the tabs from
titlefields
jQuery(document).ready(function($){
'use strict';
function setUpOptionsTabs () {
var $container = $('#your-outer-box-div');
$container.prepend('<ul id="tab-nav"></ul>');
// create the tabs from title fields
$('.cmb2-metabox-title').each(function(i, item){
var ret = '<li><a class="nav-tab" href="#tab-'+(i+1)+'">'+ $(this).text() +'</a></li>';
$('#tab-nav').append(ret);
});
$container.tabs();
}
setUpOptionsTabs();
});
- Within your CMB fields, make use of
before_rowto insert the markup you'll need for the tab containers. The first title field only get's an opening div:
$cmb->add_field( array(
'name' => 'my name',
'id' => 'my-id',
'type' => 'title',
'before_row' => '<div id="tab-1">'
) );
Subsequent title fields need to close that one and start a new one.
$cmb->add_field( array(
'name' => 'my name',
'id' => 'my-id',
'type' => 'title',
'before_row' => '</div><div id="tab-2">'
) );
... and so on.
You're last field on the page needs the after_row to close it all up.
$cmb->add_field( array(
'name' => 'The Last Field',
'id' => 'last-id',
'type' => 'text',
'after_row' => '</div>', //close final tab
) );
Sidebar: Would be great if new_cmb2_box supported the full set of before and after field methods so that we could skip the first/last thing here. Indeed, with a known set of tabs anyway, we could skip the JS as well by stuffing the entire <ul class="tab-nav"> into the before_row on the meta-box itself.
- The tabs themselves will be stacked text links unless you also load the jQuery UI CSS. And then it will still be ugly because... Jquery UI. :) WordPress does have default tab styles but they don't come with the JS for free. Here's some CSS based on the wysiwyg tabs (Visual/Text) that should at least get you started.
.nav-tab {
background: #ebebeb none repeat scroll 0 0;
border: 1px solid #e5e5e5;
box-sizing: content-box;
color: #777777;
cursor: pointer;
float: left;
font: 13px/19px "Open Sans",sans-serif;
height: 20px;
margin: 5px 0 0 5px;
padding: 3px 8px 4px;
position: relative;
top: 1px;
}
.ui-tabs-active .nav-tab {
background-color: #333;
color: #fff;
}
Hope this helps until tabs land in CMB2 core.
Any update on this? @marcusbattle
https://github.com/rogerlos/cmb2-metatabs-options
I wrote the above class, which allows you to have:
- "Settings" pages with multiple meta boxes
- Optionally have tabs on that page; individual tabs can have multiple meta boxes, too
Fairly configurable and not too heavyweight. It's setup as a wordpress plugin, but wp.org rejected it "we don't want any more developer only plugins". (Shrug)
You can use it as a class within your project. Has a wiki...
Nice work @rogerlos
Yeah nice work @rogerlos
Hey @rogerlos thank you very much, your code is what in Italian is called "fat that runs" :)
Just out of curiosity, do you have any idea about what wp.org meant by saying "we don't want any more developer only plugins"?? I have 3 plugins there and I'm writing the forth, so I'm quite interested :-\
Hi @darkoromanov, they meant plugins which can only be used by the developers, like frameworks, framework extensions etc. I've uploaded one recently and got the same message :)
Oh I see! I was reading it like it was: "we don't want any more developers, we only want plugins" :)
Ha ha ha
@rogerlos @themarcusbattle Maybe a PR?
@rogerlos How it's possible to display pages or posts ??
Quite recently, our team has created an extension that solves the problem https://github.com/LeadSoftInc/cmb2-tabs