CMB2 icon indicating copy to clipboard operation
CMB2 copied to clipboard

Allow groups of fields to be broken into tabs

Open billerickson opened this issue 11 years ago • 38 comments

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

billerickson avatar Aug 20 '14 20:08 billerickson

definitely :+1:

jtsternberg avatar Aug 21 '14 12:08 jtsternberg

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.

jtsternberg avatar Aug 21 '14 13:08 jtsternberg

@scottopolis used this approach for tabs. @billerickson is that what you had in mind? http://jsfiddle.net/syahrasi/Us8uc/

jtsternberg avatar Sep 03 '14 21:09 jtsternberg

Yep, looks good to me!

billerickson avatar Sep 03 '14 21:09 billerickson

+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.

robneu avatar Sep 15 '14 22:09 robneu

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' ] . '&amp;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!

rogerlos avatar Oct 17 '14 08:10 rogerlos

@marcusbattle is working on #93 which will eventually directly benefit this (future) feature. Hopefully we'll get this in in the next few weeks.

jtsternberg avatar Nov 24 '14 15:11 jtsternberg

Has any progress been made with this? Preferably something like this: demo-metabox

teamcrisis avatar Mar 24 '15 10:03 teamcrisis

@jtsternberg If you guys have implemented the tabbed version of metaboxes ?

ashawkat avatar May 19 '15 16:05 ashawkat

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.

jtsternberg avatar May 19 '15 20:05 jtsternberg

@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 ?

jashwant avatar May 24 '15 10:05 jashwant

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:

ghost avatar May 26 '15 11:05 ghost

I vote for this too. Very important.

StaggerLeee avatar Jun 09 '15 09:06 StaggerLeee

@jtsternberg , may you please look into my solution ( given above ) and answer the question regarding escaping / sanitization ?

jashwant avatar Jun 27 '15 07:06 jashwant

Another +1 here! That would make settings pages WAY more user friendly...

vaclavgreif avatar Jul 14 '15 20:07 vaclavgreif

@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. screen shot 2015-07-20 at 4 49 53 pm

themarcusbattle avatar Jul 20 '15 20:07 themarcusbattle

@marcusbattle , can you look at my solution above and confirm that I can use it, till your work is live ?

jashwant avatar Jul 21 '15 06:07 jashwant

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!!!

vaclavgreif avatar Oct 02 '15 11:10 vaclavgreif

It requires some shenanigans but you can make tabs out of a single metabox. I did it using title fields.

  1. Make sure jQuery UI is enqueued (or roll your own... tabs are pretty simple). This needs to be hooked to cmb2_after_form to 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
    }
  1. Some JS to build the tabs from title fields
    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();
    });

  1. Within your CMB fields, make use of before_row to 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.

  1. 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.

willthemoor avatar Dec 08 '15 04:12 willthemoor

Any update on this? @marcusbattle

hsleonis avatar Mar 01 '16 04:03 hsleonis

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...

rogerlos avatar Mar 01 '16 06:03 rogerlos

Nice work @rogerlos

hsleonis avatar Mar 01 '16 06:03 hsleonis

Yeah nice work @rogerlos

DevinWalker avatar May 10 '16 22:05 DevinWalker

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 :-\

darkoromanov avatar Sep 06 '16 20:09 darkoromanov

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 :)

hsleonis avatar Sep 07 '16 09:09 hsleonis

Oh I see! I was reading it like it was: "we don't want any more developers, we only want plugins" :)

darkoromanov avatar Sep 07 '16 09:09 darkoromanov

Ha ha ha

hsleonis avatar Sep 07 '16 09:09 hsleonis

@rogerlos @themarcusbattle Maybe a PR?

andreasupftw avatar Sep 16 '16 07:09 andreasupftw

@rogerlos How it's possible to display pages or posts ??

websumon avatar Oct 22 '16 12:10 websumon

Quite recently, our team has created an extension that solves the problem https://github.com/LeadSoftInc/cmb2-tabs

kuzmenko1256 avatar Nov 09 '16 15:11 kuzmenko1256