Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow groups of fields to be broken into tabs #5

Open
billerickson opened this issue Aug 20, 2014 · 38 comments
Open

Allow groups of fields to be broken into tabs #5

billerickson opened this issue Aug 20, 2014 · 38 comments

Comments

@billerickson
Copy link
Collaborator

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

@jtsternberg
Copy link
Member

definitely 👍

@jtsternberg
Copy link
Member

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
Copy link
Member

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

@billerickson
Copy link
Collaborator Author

Yep, looks good to me!

@robneu
Copy link

robneu commented Sep 15, 2014

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

@rogerlos
Copy link

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!

@jtsternberg
Copy link
Member

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

@teamcrisis
Copy link

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

@ashawkat
Copy link

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

@jtsternberg
Copy link
Member

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.

@jashwant
Copy link

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

@ghost
Copy link

ghost commented May 26, 2015

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 👍

@StaggerLeee
Copy link

I vote for this too. Very important.

@jashwant
Copy link

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

@vaclavgreif
Copy link

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

@themarcusbattle
Copy link

@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 themarcusbattle self-assigned this Jul 20, 2015
@jashwant
Copy link

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

@vaclavgreif
Copy link

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

@willthemoor
Copy link

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.

@hsleonis
Copy link

hsleonis commented Mar 1, 2016

Any update on this?
@marcusbattle

@rogerlos
Copy link

rogerlos commented Mar 1, 2016

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

@hsleonis
Copy link

hsleonis commented Mar 1, 2016

Nice work @rogerlos

@DevinWalker
Copy link
Contributor

Yeah nice work @rogerlos

@darkoromanov
Copy link

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

@hsleonis
Copy link

hsleonis commented Sep 7, 2016

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

@darkoromanov
Copy link

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

@hsleonis
Copy link

hsleonis commented Sep 7, 2016

Ha ha ha

@andreasupftw
Copy link

@rogerlos @themarcusbattle
Maybe a PR?

@websumon
Copy link

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

@kuzmenko1256
Copy link

kuzmenko1256 commented Nov 9, 2016

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

@WhereBeTheDan
Copy link

I altered @willthemoor method to be a pure javascript solution. This assumes you want to create tabs within a single metabox:

  1. Enqueue jQueryUI:
    add_action( 'cmb2_after_form', 'your_prefix_admin_scripts' , 10, 4 );
    function your_prefix_admin_scripts () {
         wp_enqueue_script('jquery-ui-tabs');
    }
  1. Add this to your admin scripts:
$('.cmb2-metabox').each( function(index, el) {
    var tabs = $(this).find('.cmb-type-title');

    if (tabs.length > 1) {
        var metabox = $(this),
        nav = $('<ul class="tab-nav" />');
                
        tabs.each( function(index, el) {
            nav.append('<li><a class="nav-tab" href="#' + metabox.attr('id') + '-tab-' + index + '">' + $(this).find('.cmb2-metabox-title').text() + '</a></li>');

            $(this).nextUntil('.cmb-type-title').addBack().wrapAll('<div id="' + metabox.attr('id') + '-tab-' + index + '" class="tab" />');
       });

       $(this).prepend(nav);

       $(this).tabs();
    }
});
  1. Add any number of the default cmb2 title field type to your metabox. All other fields until the next title field will be grouped as a tab.

@stabilimenta
Copy link

Here is some code that will create tabs for dynamically created repeating sections, in case it might be useful to someone. It doesn't rely on ID like jquery-ui tabs, and instead works with class.

This is my CMB2 code. My code is using the before_row, after_row attributes to get most of the html in place:

$content_sections->add_group_field( $group_field_id, array(
    'id'      => 'section-content',
    'type'    => 'wysiwyg',
    'options' => array( 'textarea_rows' => 5, ),
    'before_row'   => '
        <div class="cmb2-tabs">
            <ul class="tabs-nav">
                <li class="current"><a href="#tab-content-1">Content</a></li>
                <li><a href="#tab-content-2">Content Styling</a></li>
            </ul>
            <div class="tab-content tab-content-1 current">
    ',
    'after_row'    => '</div>',
) );

$content_sections->add_group_field( $group_field_id, array(
    'name' => esc_html__( 'Inner CSS classes', 'cmb2' ),
    'id'   => 'section-css-classes',
    'type' => 'text_medium',
    'default' => 'page-width',
    'row_classes'    => 'half',
    'before_row'   => '<div class="tab-content tab-content-2"><div class="row">',
) );

$content_sections->add_group_field( $group_field_id, array(
    'name' => esc_html__( 'Section ID', 'cmb2' ),
    'id'   => 'section-ID',
    'type' => 'text_medium',
    'row_classes'    => 'half',
    'after_row'   => '</div>',
) );

$content_sections->add_group_field( $group_field_id, array(
    'name' => esc_html__( 'Outer CSS classes', 'cmb2' ),
    'id'   => 'section-css-classes-2',
    'type' => 'text_medium',
    'row_classes'    => 'half',
    'before_row'   => '<div class="row">',
) );

$content_sections->add_group_field( $group_field_id, array(
    'name'    => esc_html__( 'Background Color', 'cmb2' ),
    'id'   => 'section-background-color',
    'type' => 'colorpicker',
    'default'  => '',
    'desc'    => esc_html__( 'Background color for section', 'cmb2' ),
    'row_classes'    => 'half',
    'after_row'   => '</div>',
) );

$content_sections->add_group_field( $group_field_id, array(
    'name' => esc_html__( 'Background Image', 'cmb2' ),
    'id'   => 'section-image',
    'type' => 'file',
    'options' => array(
        'url' => false, // Hide the text input for the url
    ),
    'desc'    => esc_html__( '&#8776;1400px x 915px', 'cmb2' ),
) );

$content_sections->add_group_field( $group_field_id, array(
    'name'             => esc_html__( 'Parallax Background', 'cmb2' ),
    'desc'             => esc_html__( 'Does not work on mobiles.', 'cmb2' ),
    'id'               => 'section-parallax',
    'type'             => 'radio_inline',
    'default' => 'true',
    'options'          => array(
        'false' => esc_html__( 'Off', 'cmb2' ),
        'true'   => esc_html__( 'On', 'cmb2' ),
    ),
    'after_row'    => '
            </div><!-- /.tab-content -->
        </div><!-- /.cmb2-tabs -->
    ',
) );

Then I included this javascript file in the admin:

jQuery(document).ready(function($) {
    $('.tab-content:not(.current)').css('display', 'none');
    $('body').on('click', '.tabs-nav a', function(event) {
        event.preventDefault();
        $(this).parent().addClass("current");
        $(this).parent().siblings().removeClass("current");
        var tab = $(this).attr("href");
        tab = tab.replace("#", "."); // change anchor tag # (id ref) to . (class ref) so it works with repeating sections
        // alert(tab);
        $(this).closest('.cmb2-tabs').children('.tab-content').not(tab).css('display', 'none');
        $(this).closest('.cmb2-tabs').children(tab).fadeIn();
    });
});

And included a css file in the admin:

.no-js .cmb2-tabs { display: none; }

.cmb2-tabs .tabs-nav
{
    margin: 6px 0 0 10px;
    clear: both;
    overflow: hidden;
}

.cmb2-tabs .tabs-nav li {
    list-style: none;
    float: left;
    position: relative;
    top: 0;
    margin: 1px .2em 0 0;
    padding: 0;
    white-space: nowrap;
    border: 1px #CCC solid;
    background-color: #EEE;
}

.cmb2-tabs .tabs-nav li a {
    display: block;
    text-decoration: none;
    font-size: 14px;
    line-height: 18px;
    padding: 10px 20px;
}

.cmb2-tabs .tabs-nav li.current {
     background-color: #FFF;
     border-bottom: 1px #FFF solid;
 }

.cmb2-tabs ul li a:focus {
    -webkit-box-shadow: none;
    box-shadow: none
}

.cmb2-tabs .tab-content
{
    padding: 10px 20px;
    border: 1px #CCC solid;
    border-bottom: 1px #CCC solid;
    margin-top: -1px;
    background-color: #FFF;
}

@polupraneeth
Copy link

polupraneeth commented Jul 11, 2017

I have made a simple CMB2 Tabs extension that solves the problem.
CMB2 Tabs
https://github.com/stackadroit/cmb2-extensions

@manzoorwanijk
Copy link
Contributor

Do I still need my own tweaking/extension or there is something in the core to create tabs?

@tw2113
Copy link
Contributor

tw2113 commented Nov 30, 2017

Still need to do your own thing last I knew. I haven't seen any changes made/released with CMB2 core that would indicate otherwise.

@JiveDig
Copy link

JiveDig commented May 29, 2018

This was rolled into 2.4.x right? I think this issue can be closed.

Example: https://github.com/CMB2/CMB2-Snippet-Library/blob/master/options-and-settings-pages/options-pages-with-tabs-and-submenus.php

@sharmashivanand
Copy link

The update in #5 (comment) seems tobe only available for standalone options page. Guess we are still looking for a solution for tabs inside metabox.

@dskurth
Copy link

dskurth commented Jul 15, 2019

@jtsternberg,
Similar question, but instead of breaking up fields into tab groups, is there a way to break up repeatable records into separate groups? Such as posts do? For example: https://snag.gy/njsJpF.jpg

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests