Skip to content

Instantly share code, notes, and snippets.

@ckchaudhary
Last active July 29, 2024 06:25
Show Gist options
  • Save ckchaudhary/d8b99b527d501ebb81f3b8ca167fdc2f to your computer and use it in GitHub Desktop.
Save ckchaudhary/d8b99b527d501ebb81f3b8ca167fdc2f to your computer and use it in GitHub Desktop.
Turn plugin/theme settings screen sections into tabs. Read more at https://blogs.recycleb.in/2024/07/wordpress-plugin-theme-settings-screen-turn-sections-into-tabs/
<?php
/**
* Plugin Name: RB Tabify Settings Sections
* Plugin URI: https://blogs.recycleb.in/2024/07/wordpress-plugin-theme-settings-screen-turn-sections-into-tabs/
* Description: Turn plugin/theme settings screen sections into tabs.
* Version: 1.0.0
* Author: ckchaudhary
* Author URI: https://www.recycleb.in/u/chandan/
* Licence: GPLv2
*
* @package RB Tabify Settings
*/
// Exit if accessed directly.
defined( 'ABSPATH' ) ? '' : exit();
/**
* Register a settings screen whose sections should be turned into tabs.
*
* @param string $settings_id Unique id of your settings. It is upto you to ensure this is unique.
* @param string $style Whether to style the tabs as main nav or subnav. Default 'nav'. Other accepted value is 'subnav'.
*
* @return void
*/
function rb_tabify_me( $settings_id, $style = 'nav' ) {
global $rb_tabify_settings_screens;
if ( empty( $rb_tabify_settings_screens ) ) {
$rb_tabify_settings_screens = array();
}
if ( empty( $rb_tabify_settings_screens ) || ! in_array( $settings_id, $rb_tabify_settings_screens, true ) ) {
$settings_id = sanitize_key( $settings_id );
$rb_tabify_settings_screens[] = $settings_id;
printf(
'<span style="display:none; visibility:hidden;" class="rb_tabify_marker" data-id="%s" data-style="%s"></span>',
esc_attr( $settings_id ),
esc_attr( $style )
);
}
}
/**
* Calls the script to turn sections into tabs, if required.
*
* @return bool
*/
function rb_tabify_maybe_load_script() {
global $rb_tabify_settings_screens;
if ( empty( $rb_tabify_settings_screens ) ) {
return false;
}
rb_tabify_print_inline_script();
rb_tabify_print_inline_style();
?>
<script>
jQuery( ( $ ) => {
$('.rb_tabify_marker').each(function(){
let $marker = $(this);
let $form = $marker.closest('form');
if ( $form.length > 0 ) {
let screen_id = $marker.data('id');
let style = $marker.data('style');
new RBSettingsTabify( screen_id, $form, style );
}
});
});
</script>
<?php
}
\add_action( 'admin_footer', 'rb_tabify_maybe_load_script' );
/**
* Prints the entire script inline!
* This plugin is intended to be used as a must use plugin( https://developer.wordpress.org/advanced-administration/plugins/mu-plugins/ ).
* Just drop this file inside wp-contents/mu-plugins folder.
* That's why the scripts and styles are not in separate .js and .css files.
*
* @return void
*/
function rb_tabify_print_inline_script() {
?>
<script>
class RBSettingsTabify {
#id = '';
#container = false;
#style = 'nav';
get id() { return this.#id; }
constructor ( screen_id, $container, style ) {
this.#id = screen_id;
this.#container = $container;
style = 'subnav' === style ? 'subnav' : 'nav';
this.#style = style;
this.init();
};
get_cookie_name () {
return 'rb_s_tabify_' + this.#id;
};
get_current_tab () {
return docCookies.getItem( this.get_cookie_name() );
};
set_current_tab ( tab ) {
// valid for 24 hours
docCookies.setItem( this.get_cookie_name(), tab, 24 * 60 * 60, '/' );
};
init () {
const _class = this;
// Convert settings sections as tabs
if ( _class.#container.find( '>h2' ).length > 1 ) {
// pick all section titles and convert those into subnav links
let links = [];
_class.#container.find( '>h2' ).each(function(){
let $section_title = jQuery(this);
let $table = $section_title.find( ' ~ table.form-table:first' );
if ( ! $table.length ) {
return 'continue';
}
let target_id = 'section_tab_' + links.length;
// From this h2 to first table.form-table, wrap everything in a div, excluding h2 but including table.form-table
let elms = [];
$section_title.find( ' ~ *' ).each( function(){
let $this = jQuery(this);
elms.push( this );
if ( $this.is('table.form-table') ) {
return false;// break;
}
});
links.push( [ $section_title.text(), target_id ] );
$section_title.hide();
jQuery( elms ).wrapAll( '<div class="psuedo_subnav_target" id="'+ target_id +'">' );
});
if ( links.length > 0 ) {
let current_tab = _class.get_current_tab();
let is_current_tab_valid = false;
for ( let i = 0; i < links.length; i++ ) {
if ( links[i][1] == current_tab ) {
is_current_tab_valid = true;
break;
}
}
if ( !is_current_tab_valid ) {
current_tab = links[0][1];
}
let nav_html = '';
if ( 'subnav' === _class.#style ) {
nav_html = '<ul class="subsubsub psuedo_subnav_links">';
for ( let i = 0; i < links.length; i++ ) {
nav_html += '<li>';
nav_html += `<a class="${current_tab === links[i][1] ? 'current' : ''}" href="#${links[i][1]}" >${links[i][0]}</a>`;
if ( current_tab !== links[i][1] ) {
jQuery( '#'+links[i][1] ).hide();
}
if ( i < ( links.length -1 ) ) {
nav_html += ' | ';
}
nav_html += '</li>';
}
nav_html += '</ul>';
} else {
nav_html = `<h2 id='nav-${_class.#id}' class='nav-tab-wrapper psuedo_subnav_links'>`;
for ( let i = 0; i < links.length; i++ ) {
nav_html += `<a class="nav-tab ${current_tab === links[i][1] ? 'nav-tab-active' : ''}" href="#${links[i][1]}" >${links[i][0]}</a>`;
if ( current_tab !== links[i][1] ) {
jQuery( '#'+links[i][1] ).hide();
}
}
nav_html += "</h2>";
}
_class.#container.prepend( nav_html );
jQuery('.psuedo_subnav_links a').click(function(e){
e.preventDefault();
jQuery('.psuedo_subnav_links a').removeClass('current').removeClass('nav-tab-active');
jQuery(this).addClass('current').addClass('nav-tab-active');
jQuery('.psuedo_subnav_target').hide();
jQuery( jQuery(this).attr('href') ).show();
_class.set_current_tab( jQuery(this).attr('href').replace( '#', '' ) );
});
}
}
}
}
/*\
|*|
|*| :: cookies.js ::
|*|
|*| A complete cookies reader/writer framework with full unicode support.
|*|
|*| Revision #3 - July 13th, 2017
|*|
|*| https://developer.mozilla.org/en-US/docs/Web/API/document.cookie
|*| https://developer.mozilla.org/User:fusionchess
|*| https://github.com/madmurphy/cookies.js
|*|
|*| This framework is released under the GNU Public License, version 3 or later.
|*| http://www.gnu.org/licenses/gpl-3.0-standalone.html
|*|
|*| Syntaxes:
|*|
|*| * docCookies.setItem(name, value[, end[, path[, domain[, secure]]]])
|*| * docCookies.getItem(name)
|*| * docCookies.removeItem(name[, path[, domain]])
|*| * docCookies.hasItem(name)
|*| * docCookies.keys()
|*|
\*/
var docCookies = docCookies || {
getItem: function (sKey) {
if (!sKey) { return null; }
return decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) || null;
},
setItem: function (sKey, sValue, vEnd, sPath, sDomain, bSecure) {
if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) { return false; }
var sExpires = "";
if (vEnd) {
switch (vEnd.constructor) {
case Number:
sExpires = vEnd === Infinity ? "; expires=Fri, 31 Dec 9999 23:59:59 GMT" : "; max-age=" + vEnd;
/*
Note: Despite officially defined in RFC 6265, the use of `max-age` is not compatible with any
version of Internet Explorer, Edge and some mobile browsers. Therefore passing a number to
the end parameter might not work as expected. A possible solution might be to convert the the
relative time to an absolute time. For instance, replacing the previous line with:
*/
/*
sExpires = vEnd === Infinity ? "; expires=Fri, 31 Dec 9999 23:59:59 GMT" : "; expires=" + (new Date(vEnd * 1e3 + Date.now())).toUTCString();
*/
break;
case String:
sExpires = "; expires=" + vEnd;
break;
case Date:
sExpires = "; expires=" + vEnd.toUTCString();
break;
}
}
document.cookie = encodeURIComponent(sKey) + "=" + encodeURIComponent(sValue) + sExpires + (sDomain ? "; domain=" + sDomain : "") + (sPath ? "; path=" + sPath : "") + (bSecure ? "; secure" : "");
return true;
},
removeItem: function (sKey, sPath, sDomain) {
if (!this.hasItem(sKey)) { return false; }
document.cookie = encodeURIComponent(sKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT" + (sDomain ? "; domain=" + sDomain : "") + (sPath ? "; path=" + sPath : "");
return true;
},
hasItem: function (sKey) {
if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) { return false; }
return (new RegExp("(?:^|;\\s*)" + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=")).test(document.cookie);
},
keys: function () {
var aKeys = document.cookie.replace(/((?:^|\s*;)[^\=]+)(?=;|$)|^\s*|\s*(?:\=[^;]*)?(?:\1|$)/g, "").split(/\s*(?:\=[^;]*)?;\s*/);
for (var nLen = aKeys.length, nIdx = 0; nIdx < nLen; nIdx++) { aKeys[nIdx] = decodeURIComponent(aKeys[nIdx]); }
return aKeys;
}
};
</script>
<?php
}
/**
* Prints some inline css for those psuedo tabs.
*
* @return void
*/
function rb_tabify_print_inline_style() {
?>
<style type="text/css">
.psuedo_subnav_links a{
outline: none;
}
div.psuedo_subnav_target{
clear: both;
padding-top: 30px;
}
</style>
<?php
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment