Creating Theme Options using object oriented programming (Part 2)

An image with a big and a little block on the side

Disclamer: I noticed a small bug in the code so I updated the code, and all should be working fine. It turns out that wp_ajax hook is picky when it comes to multiple instances of the class :)

Notice: This article was written in 2016. WordPress has changed a lot since then, so take this article with a grain of salt…

Last time we made a foundation for what would be our Theme Options page. We set it up, and created few example settings. Now we want to dig deeper. We want to add multiple submenu pages that have the same structure. As I’ve said before, we could do that by hand, separating each subpage into it’s own file, but since we sort of want the ‘uniformity’, the better solution is to use object oriented approach.

As the name suggest the main interest in oop are the objects. Objects have certain properties that define them, and they can preform certain actions using functions (another word for functions in oop is methods) that are innate to them. You can have many different objects that came from the same class, or that came from different class. So what is a class? Well it is sort of a blueprint of the object. The class will contain all the properties and methods, that will define the object once you’ve instantiated it. Every object is just an instance of the class.
In our ‘house’ model, you can define that house class will have doors, windows, walls and a roof, and you can instantiate many different house objects through it: one house will have 3 windows and a red roof, another will have 5 windows and blue roof.

The real life blueprint example can sometimes be confusing. The programmers aren’t building houses (well, not yet, but with the rise of the 3d printing who knows :D), we’re building digital objects – posts, products, web APIs, or theme settings. These are often not tangible objects, and it can be difficult to grasp their oop side. But the core principles still apply. We’re giving our objects a mean to interact with the real world – to show us input fields and drop downs, and to save or post some data we’ve inputted.

So we’ve said that objects are instances of classes. One great thing in oop is that you can even extend the functionality of the existing class. In our house model, that means that we can extend our house class, and call it HouseWithGarden and add a garden property. In a more real world example, that would be extending the Menu Walker class, and adding sidebars or megamenu to it.

The benefit to oop in my opinion is the fact that all our code (for a certain object) is neatly contained within a class, and you can use, or inherit, certain properties from the existing classes. We are breaking our code into many logically connected pieces and we’re then interconnecting those pieces.

For ore in depth explanation about OOP in WordPress I’d recommend reading great blog from Tom McFarlin on code.tutsplus.

Creating Theme Options subpages

When ever you want to create your digital object, you need to figure out what your object must contain, and how will it behave. I personally like to do this on paper, because I’m used to doing things with pen and paper, you do what ever works for you the best.

For our Theme Options sub pages, we’d like to have the ability to change names of course, and a certain default settings that will appear on our pages. These are kinda basic necessary options that we want, the backbone on which we will build our sub pages. We can figure out what kind of options do we want to show: input fields, textareas, select drop downs, menu pages, color picker, image uploads etc. We can add small descriptions under every field, so that the user knows what the certain option is for. You can literally build anything that you think can be important for Theme Options to have.

So when creating our sub pages, we’ll instantiate a class called SettingsSubageBuilder which will contain a name of our sub page, and an array of settings that will appear on the subpage. The way we create our array is also important. In this case I’ve added two major keyes to the array: description, and settings_control. The description will have a general description of the sub page which will appear under the title, and the settings_control will be another array which will have controls (another array), consisting of name, control_type and description. Of course depending on control type you’ll customize the control array. For instance the header settings array will look like this:

$header_settings_array = array(
	'description'      => esc_html__( 'Set up settings for different headers', 'twentysixteen' ),
	'settings_control' => array(
		'control_1' => array(
			'name'         => esc_html__( 'Fixed Header', 'twentysixteen' ),
			'description'  => esc_html__( 'Make the menu always fixed on top.', 'twentysixteen' ),
			'control_type' => 'checkbox',
		),
		'control_2' => array(
			'name'         => esc_html__( 'Logo', 'twentysixteen' ),
			'control_type' => 'image',
		),
		'control_3' => array(
			'name'         => esc_html__( 'Retina Logo', 'twentysixteen' ),
			'description'  => esc_html__( 'This logo is used only if the retina screens are detected.', 'twentysixteen' ),
			'control_type' => 'image',
		),
		'control_4' => array(
			'name'         => esc_html__( 'Retina Logo Width', 'twentysixteen' ),
			'description'  => esc_html__( 'Set the width of the original logo.', 'twentysixteen' ),
			'control_type' => 'input',
		),
		'control_5' => array(
			'name'         => esc_html__( 'Retina Logo Height', 'twentysixteen' ),
			'description'  => esc_html__( 'Set the height of the original logo.', 'twentysixteen' ),
			'control_type' => 'input',
		),
		'control_6' => array(
			'name'         => esc_html__( 'Background Image', 'twentysixteen' ),
			'control_type' => 'image',
		),
		'control_7' => array(
			'name'         => esc_html__( 'Background Color', 'twentysixteen' ),
			'description'  => esc_html__( 'Set the menu background color.', 'twentysixteen' ),
			'control_type' => 'colorpicker',
		),
		'control_8' => array(
			'name'         => esc_html__( 'Text Color', 'twentysixteen' ),
			'description'  => esc_html__( 'Set the menu items color.', 'twentysixteen' ),
			'control_type' => 'colorpicker',
		),
		'control_9' => array(
			'name'         => esc_html__( 'Text Hover Color', 'twentysixteen' ),
			'description'  => esc_html__( 'Set the menu items hover color.', 'twentysixteen' ),
			'control_type' => 'colorpicker',
		),
		'control_10' => array(
			'name'         => esc_html__( 'Options Select', 'twentysixteen' ),
			'description'  => esc_html__( 'Select the header options.', 'twentysixteen' ),
			'control_type' => 'select',
			'options' => array(
				'option_1' => esc_html__( 'Option 1', 'twentysixteen' ),
				'option_2' => esc_html__( 'Option 2', 'twentysixteen' ),
				'option_3' => esc_html__( 'Option 3', 'twentysixteen' ),
				'option_4' => esc_html__( 'Option 4', 'twentysixteen' ),
			),
		),
	),
);Code language: PHP (php)

We need to have all this in mind, so that we know how to handle this in our class. As I’ve mentioned, the object will receive the name and this array as arguments, and from it it will create our page. Let’s start building our class. in php creating a class is easy, just add class and name in CamelCase (Pascal Caps) and add curly brackets, and that’s it. A class is created. Empty one, but a class nonetheless. Since we want to input some stuff into our class initialization, we need to use magic method called __construct(). What it does is that it will automatically be called when ever an object is first created. In it we can add our actions as well. Magic methods will allow your class to ‘react’ to certain events, like construction, destruction, sleep etc. And they always begin with two underscores in front of their names. So when we create our sub page, we can pass in the necessary parameters that will be injected to __construct() method and with that will make those parameters available for us to use later on in our methods. Continuing to work in our theme-settings.php file we’ll have

/**
 * Builds Settings subpage
 *
 * @package WordPress
 * @subpackage twentysixteen/inc/theme-settings
 * @version 1.0.0
 * @author  Made by Denis <https://madebydenis.com/>
 * @license https://www.gnu.org/licenses/gpl-3.0.txt GNU/GPLv3
 * @link https://twentysixteen.io
 * @since  1.0.0
 */
class SettingsSubageBuilder {
	/**
	 * Name of the subpage menu
	 *
	 * @var string $subpage_name The name of the subpage.
	 */
	private $subpage_name;

	/**
	 * Array of elements to be used in the HTML render of the settings page
	 *
	 * @var string $subpage_name The name of the subpage.
	 */
	private $html_array;

	/**
	 * Initialization of the class
	 *
	 * @param  string $subpage_name The name of the settings page, e.g. Header Settings.
	 * @param  array  $html_array The HTML array details for building the settings page.
	 */
	public function __construct( $subpage_name, $html_array ) {
		$this->subpage_name = $subpage_name;
		$this->html_array   = $html_array;
		$ajax_hook = 'wp_ajax_twentysixteen_' . strtolower( str_replace( ' ', '_', $this->subpage_name ) ) . '_save_action';
		add_action( 'admin_menu', array( $this, 'create_submenu_pages' ) );
		add_action( $ajax_hook, array( $this, 'twentysixteen_all_settings_save' ) );
	}
}Code language: PHP (php)

We’we added two properties – sub page name and the array that will create our html. Notice the private and public keywords. They will describe the visibility of a property or a method. As described in the manual class members declared public can be accessed everywhere. Members declared protected can be accessed only within the class itself and by inherited classes. Members declared as private may only be accessed by the class that defines the member. So be careful when declaring the visibility of your properties and methods. We’ve declared the properties as private, and the magic method as public. This is because the properties that we declare within the class, should be only used within this class, and the construct method can be used even if we extend the SettingsSubpageBuilder class.

In our __construct() method, we referenced to our name and settings, and we also added two actions – one will be the action to add the sub menu page, and other will be for the ajax save. The construction array( $this, ‘function_name’ ) is just the way to callback the method defined in the class that you’re in. You’ll see the methods create_submenu_pages and twentysixteen_all_settings_save in a little while.

Note: Upon inspection it turned out that instantiating many classes at once and defining wp_ajax hook doesn’t work too well. For some reason if you put the same name for the ajax action, the ajax callback function will only recognize the first one that got instantiated, and thus the saved options will only be saved in the first instance of your class (there will only be one option settings in your wp_options table). In other words, when you’d save other options from the sub pages, they would overwrite the first settings over and over, and you’d have a problem. We’ll need to modify the JavaScript a bit, but that will come later on. This is why we need to define $ajax_hook variable that will change with every option name.

Next thing that we want is to add a method that will add our subpage

/**
 * Create submenu page for each settings
 *
 * @return void
 */
public function create_submenu_pages() {
	$page_hook = 'twentysixteen_theme_' . strtolower( str_replace( ' ', '_', $this->subpage_name ) );

	add_submenu_page( 'twentysixteen_theme_settings', $this->subpage_name, $this->subpage_name, 'edit_theme_options', $page_hook, array( $this, 'twentysixteen_settings_render_subpage' ) );
}Code language: PHP (php)

We want to have page hook that will be unique for each instance of our class, so we’ll convert our sub page name to lower case, and we’ll replace empty spaces with underscores. Then we’ll use WordPress built in function add_submenu_page(). This will take care of our subpage. Notice that we’re self referencing to sub page name using $this keyword. Next we want to create a method that will create our html elements. We want our method to take two arguments – default value defined in the array, and saved values from the database. The method looks like this

/**
 * Settings control render function
 *
 * This function takes the single control value, and based on what that control is
 * renders the appropriate control.
 *
 * @param  array $default_value   Array of the associated default controls with all the details of the controls.
 * @param  array $saved_values    Array of the values from the database.
 * @return string 				  HTML of controls.
 */
public function twentysixteen_theme_settings_control_render( $default_value, $saved_values ) {
	$name             = ( isset( $default_value['name'] ) && '' !== $default_value['name'] ) ? $default_value['name'] : '';
	$control_type     = ( isset( $default_value['control_type'] ) && '' !== $default_value['control_type'] ) ? $default_value['control_type'] : '';
	$description      = ( isset( $default_value['description'] ) && '' !== $default_value['description'] ) ? '<p class="description">' . $default_value['description'] . '</p>' : '';
	$control_slug     = 'twentysixteen_' . str_replace( ' ', '_', strtolower( $name ) );
	$control_settings = ( isset( $saved_values ) && '' !== $saved_values ) ? $saved_values : '';
	$value            = ( isset( $control_settings[$control_slug] ) && '' !== $control_settings[$control_slug] ) ? $control_settings[$control_slug] : '';

	$out = '';

	switch ( $control_type ) {
		case 'input':
			$out .= '<div class="twentysixteen_options_wrapper">
						<div class="twentysixteen_options_left">
							<h4>' . esc_html( $name ) . '</h4>
						</div>
						<div class="twentysixteen_options_right">
							<input type="text" name="' . esc_attr( $control_slug ) . '" value="' . esc_attr( $value ) . '" class="twentysixteen_setting">
							' . wp_kses_post( $description ) . '
						</div>
					</div>';
			break;

		case 'image':
			$out .= '<div class="twentysixteen_options_wrapper">
						<div class="twentysixteen_options_left">
							<h4>' . esc_html( $name ) . '</h4>
						</div>
						<div class="twentysixteen_options_right">
							<div class="twentysixteen_uploaded_image">
								<img src="' . esc_url( $value ) . '" />
							</div>
							<input type="text" name="' . esc_attr( $control_slug ) . '" value="' . esc_url( $value ) . '" class="twentysixteen_setting twentysixteen_image_upload">
							<input type="button" name="image_upload" value="' . esc_html__( 'Upload Image', 'twentysixteen' ) . '" class="button upload_image_button">
							<input type="button" name="remove_image_upload" value="' . esc_html__( 'Remove Image', 'twentysixteen' ) . '" class="button remove_image_button">
							' . wp_kses_post( $description ) . '
						</div>
					</div>';
			break;

		case 'checkbox':
			$checked = '';
			if ( isset( $value ) && '1' === $value ) {
				$checked = 'checked';
			}
			$out .= '<div class="twentysixteen_options_wrapper">
						<div class="twentysixteen_options_left">
							<h4>' . esc_html( $name ) . '</h4>
						</div>
						<div class="twentysixteen_options_right">
							<input type="checkbox" name="' . esc_attr( $control_slug ) . '" value="' . esc_attr( $value ) . '" ' . checked( $value, 1, false ) . ' class="twentysixteen_setting">
							<label class="checkbox_label"><span class="twentysixteen_setting checkbox ' . $checked . '"></span>' . esc_html( $name ) . '</label>
							' . wp_kses_post( $description ) . '
						</div>
					</div>';
			break;

		case 'colorpicker':
			$out .= '<div class="twentysixteen_options_wrapper">
						<div class="twentysixteen_options_left">
							<h4>' . esc_html( $name ) . '</h4>
						</div>
						<div class="twentysixteen_options_right">
							<input type="text" name="' . esc_html( $control_slug ) . '" value="' . esc_attr( $value ) . '" class="twentysixteen_setting twentysixteen_color_picker"/>
							' . wp_kses_post( $description ) . '
						</div>
					</div>';
			break;

		case 'select':
			$out .= '<div class="twentysixteen_options_wrapper">
						<div class="twentysixteen_options_left">
							<h4>' . esc_html( $name ) . '</h4>
						</div>
						<div class="twentysixteen_options_right">
							<select class="twentysixteen_setting" name="' . esc_html( $control_slug ) . '">
								<option value=""> -- </option>';
			if ( isset( $default_value['options'] ) && '' !== $default_value['options'] ) {
				foreach ( $default_value['options'] as $option_name => $option_value ) {
					$selected = ( isset( $value ) && '' !== $value && $option_name === $value ) ? 'selected="selected"' : '';
					$out .= '<option value="' . esc_attr( $option_name ) . '" ' . esc_attr( $selected ) . ' >' . esc_html( $option_value ) . '</option>';
				}
			}
					$out .= '</select>
							' . wp_kses_post( $description ) . '
						</div>
					</div>';
			break;

		default:
			break;
	}

	return $out;

}Code language: PHP (php)

First we define our names, control types, descriptions all from the default values, and we take the actual saved value from the $saved_values array. Then, based on the control type we’ll output the desired html of the control. The idea behind this is that we will loop through our control array we defined at the top, through the controls and for each one we’ll output the desired field. Here I’ve added the controls that appear in the array.

The next method is the method which will render our sub page. We want our sub page to look like the Theme Options page, so it will have page title, description, save button, and our settings which will be rendered according to the fact that we may or may not have something saved in our database.

/**
 * Rendering function for settings subpage
 *
 * @return void HTML rendered content.
 */
public function twentysixteen_settings_render_subpage() {
	$settings_array = $this->html_array;

	if ( isset( $settings_array ) && ! empty( $settings_array ) ) {

		$set_options = json_decode( json_decode( get_option( 'twentysixteen_' . str_replace( ' ', '_', strtolower( $this->subpage_name ) ), '' ) ) ); // Get options from the database.

		?>
		<div class="wrap">
			<h2 class="page_title"><?php echo esc_html( $this->subpage_name ); ?></h2>
			<p class="description"><?php echo esc_html( $settings_array['description'] ); ?></p>
			<div class="settings_above_meta">
				<a href="#" class="save_all" id="twentysixteen_settings_save_subpage"><?php esc_html_e( 'Save All', 'twentysixteen' ) ?></a>
				<span class="spinner"></span><span class="saved_options"></span>
			</div>
			<ul class="theme_settings_inner_content">
				<?php
				if ( isset( $set_options ) && '' !== $set_options ) {
					foreach ( (array) $set_options as $options_key => $options_value ) {
						?>
						<li class="settings_container <?php echo ( '0' === $options_key ) ? 'show' : '' // WPCS: XSS ok. ?>">
						<?php
						foreach ( $settings_array['settings_control'] as $control_name => $default_value ) {
							echo $this->twentysixteen_theme_settings_control_render( $default_value, (array) $options_value ); // WPCS: XSS ok.
						}
						wp_nonce_field( 'twentysixteen_settings_nonce_action', 'twentysixteen_settings_nonce' );
						?>
						</li>
						<?php
					}
				} else {
					?>
					<li class="settings_container show">
					<?php
					foreach ( $settings_array['settings_control'] as $control_name => $default_value ) {
						echo $this->twentysixteen_theme_settings_control_render( $default_value, null ); // WPCS: XSS ok.
					}
					wp_nonce_field( 'twentysixteen_settings_nonce_action', 'twentysixteen_settings_nonce' );
					?>
					</li>
					<?php
				} ?>
			</ul>
		</div>
		<?php
	} else {
		?>
		<div class="wrap">
			<h2 class="page_title"><?php echo esc_html( $this->subpage_name ); ?></h2>
			<p class="description"><?php esc_html_e( 'The options array seems to be empty. Please fill it up with settings to render the page.', 'twentysixteen' ); ?></p>
		</div>
		<?php
	}
}Code language: JavaScript (javascript)

The last method that we need is the ajax callback function

/**
 * Settings save AJAX callback function
 *
 * @return void
 */
public function twentysixteen_all_settings_save() {
	if ( isset( $_POST['data'], $_POST['twentysixteen_settings_nonce'] ) && wp_verify_nonce( sanitize_key( $_POST['twentysixteen_settings_nonce'] ), 'twentysixteen_settings_nonce_action' ) && '' !== $_POST['data'] ) { // Input var okay.
		$twentysixteen_settings = wp_json_encode( sanitize_text_field( wp_unslash( $_POST['data'] ) ) ); // Input var okay.
		$settings_name = 'twentysixteen_' . str_replace( ' ', '_', strtolower( $this->subpage_name ) );
		update_option( $settings_name, $twentysixteen_settings );
	}
	wp_die();
}Code language: PHP (php)

Be sure that you validate your saved fields, verify the nonce, and sanitize text before putting it into the database. This is very important security aspect, because you never know when you could get a security breach. To our settings-style.css we can add

.twentysixteen_options_left {
	width: 30%;
	margin-left: 30px;
	display: inline-block;
	vertical-align: top;
	margin-bottom: 20px;
}

.twentysixteen_options_right {
	width: 60%;
	display: inline-block;
	vertical-align: top;
	margin-bottom: 20px;
}Code language: CSS (css)

and for our image

/*****---------- 1.3 Image Field ----------*****/

.twentysixteen_uploaded_image img {
	max-width: 30%;
	margin-bottom: 20px;
}Code language: CSS (css)

To separate titles from the options a bit. You can style it further if you want to, but for the sake of the demonstration this will do.

Now to add your subpage we need to create a new instance of the class

$header_settings = new SettingsSubageBuilder( esc_html__( 'Header Settings', 'twentysixteen' ), $header_settings_array );Code language: PHP (php)

You’ll end up with something that looks like this:

Theme Settings Header Options screen
Theme Settings Header Options screen

Now we need a way to save our settings, add colorpicker and way to upload images. We’ll need to modify our enqueued scripts a bit

add_action( 'admin_enqueue_scripts', 'twentysixteen_theme_settings_admin_scripts' );
/**
 * Enqueue style and scripts for Theme Settings page
 *
 * @since  1.0.0
 * @param  string $hook Page hook suffix where the scripts and styles should be loaded.
 * @return void
 */
function twentysixteen_theme_settings_admin_scripts( $hook ) {
	$screen = get_current_screen();
	$screen_id = $screen->id;
	if ( strpos( $screen_id, 'page_twentysixteen_theme' ) !== false ) {
		wp_enqueue_style( 'twentysixteen_theme_settings_css', get_template_directory_uri() . '/inc/theme-settings/css/settings-style.css', '', '1.0.0' );

		wp_enqueue_style( 'wp-color-picker' );
		wp_enqueue_media();
		wp_enqueue_script( 'twentysixteen_theme_settings_js', get_template_directory_uri() . '/inc/theme-settings/js/settings-custom.js', array( 'jquery', 'wp-color-picker' ), '1.0.0', true );
		wp_localize_script( 'twentysixteen_theme_settings_js', 'twentysixteen_settings_object', array(
			'saved'	 => esc_html__( 'Settings Saved', 'twentysixteen' ),
			'delete' => esc_html__( 'Delete', 'twentysixteen' ),
			'save'	 => esc_html__( 'Save', 'twentysixteen' ),
		) );
	}
}Code language: PHP (php)

We’ve added color picker, and media upload. In our settings-custom.js file we’ll add few controls:

jQuery( document ).ready(function( $) {
	"use strict";

	$( document ).on('click', '#twentysixteen_settings_save', settings_save )
				 .on('click', '#twentysixteen_settings_save_subpage', settings_save_subpage )
				 .on('click', '.upload_image_button', upload_image_button )
				 .on('click', '.remove_image_button', remove_image_button )
				 .on('click', '.checkbox_label', checkbox_switch );

	color_picker();

	function checkbox_switch(e) {
		e.preventDefault();
		var $this = $(this);
		var hide_layout = $this.data('settings');
		var $hidden_input = $this.prev('input');
		var $checkbox = $this.find('.checkbox');

		if ( $checkbox.hasClass('checked') ) {
			$checkbox.removeClass('checked');
			$hidden_input.val('0');
			$( hide_layout ).hide();
		} else {
			$checkbox.addClass('checked');
			$hidden_input.val('1');
			$( hide_layout ).show();
		}
	}

	function settings_save(e) {
		e.preventDefault();
		var $saved = $('.settings_above_meta .saved_options');
		var $spinner = $('.settings_above_meta .spinner');
		var settings_out = {};
		settings_out['settings'] = {};
		settings_out['settings']['google_api_key'] = $('.twentysixteen_options_wrapper .google_api_key').val();
		settings_out['settings']['google_maps_enable'] = $('.twentysixteen_options_wrapper .google_maps_enable').val();
		$.ajax({
			type: 'POST',
			url: ajaxurl,
			data: {
				'action' : 'twentysixteen_settings_save',
				'data' : JSON.stringify( settings_out, null, 2 ),
				'twentysixteen_settings_nonce' : $('#twentysixteen_settings_nonce').val()
			},
			success: function(data) {
				$saved.text( twentysixteen_settings_object.saved ).delay( 2000 ).fadeOut();
			},
			beforeSend : function () {
				$saved.text('').show();
				$spinner.css('visibility', 'visible');
			},
			error : function (jqXHR, textStatus, errorThrown) {
				$loader.html( jqXHR + ' :: ' + textStatus + ' :: ' + errorThrown );
			},
			complete : function () {
				$spinner.css('visibility', 'hidden');
			}
		});
	}

	function settings_save_subpage(e) {
		e.preventDefault();
		var $saved = $('.settings_above_meta .saved_options');
		var $spinner = $('.settings_above_meta .spinner');
		var page_name = $('.page_title')[0].innerHTML; // For the ajax hook issue we had earlier
		var hook_name = 'twentysixteen_'+page_name.toLowerCase().replace(/ /g, '_' )+'_save_action';
		var settings_out = {};
		settings_out['settings'] = {};
		$('.twentysixteen_setting').each(function(){
			var $setting = $(this);
			var settings_name = $setting.attr('name');
			var settings_value;
			if ('undefined' !== typeof settings_name ) {
				if ( settings_name.indexOf('_layout') !== -1 ) {
					if ( $setting.is(':checked') ) {
						settings_value = $setting.val();
						settings_out['settings'][settings_name] = settings_value;
					}
				} else {
					settings_value = $setting.val();
					settings_out['settings'][settings_name] = settings_value;
				}
			}
		});
		$.ajax({
			type: 'POST',
			url: ajaxurl,
			data: {
				'action' : hook_name,
				'data' : JSON.stringify( settings_out, null, 2 ),
				'twentysixteen_settings_nonce' : $('#twentysixteen_settings_nonce').val()
			},
			success: function(data) {
				$saved.text( twentysixteen_settings_object.saved ).delay( 2000 ).fadeOut();
			},
			beforeSend : function () {
				$saved.text('').show();
				$spinner.css('visibility', 'visible');
			},
			error : function (jqXHR, textStatus, errorThrown) {
				$saved.html( jqXHR + ' :: ' + textStatus + ' :: ' + errorThrown );
			},
			complete : function () {
				$spinner.css('visibility', 'hidden');
				$('.save.settings_changed').removeClass('settings_changed');

			}
		});
	}

	function color_picker(e) {
		$('.twentysixteen_color_picker').each( function(){
			$(this).wpColorPicker();
		});
	}

	function upload_image_button(e) {
		e.preventDefault();
		var $this = $(e.currentTarget);
		var $input_field = $this.prev();
		var $image = $this.parent().find('.twentysixteen_uploaded_image');
		var custom_uploader = wp.media.frames.file_frame = wp.media({
			title: 'Add Image',
			button: {
				text: 'Add Image'
			},
			multiple: false
		});
		custom_uploader.on('select', function() {
			var attachment = custom_uploader.state().get('selection').first().toJSON();
			$input_field.val( attachment.url );
			$image.html('<img src="' + attachment.url + '" />');
		});
		custom_uploader.open();
	}

	function remove_image_button(e) {
		e.preventDefault();
		var $this = $(e.currentTarget);
		var $input_field = $this.parent().find('.twentysixteen_image_upload');
		var $image = $this.parent().find('.twentysixteen_uploaded_image');

		$input_field.val('');
		$image.html('');
	}

});Code language: PHP (php)

We’ve added ajax save function, color picker and a way to add image by calling the WordPress media uploader. With that enabled, our settings page will look like this

Theme Settings Header Options screen finished
Theme Settings Header Options screen finished

So now that we’ve finished this, what can we do with it? Well the rest is simple – you want footer options? Specify the footer settings aray and instantiate a new class:

$footer_settings_array = array(
	'description'      => esc_html__( 'Set up settings your footer', 'twentysixteen' ),
	'settings_control' => array(
		'control_1' => array(
			'name'         => esc_html__( 'Logo', 'twentysixteen' ),
			'control_type' => 'image',
		),
		'control_2' => array(
			'name'         => esc_html__( 'Copyright', 'twentysixteen' ),
			'description'  => esc_html__( 'Set the copyright text.', 'twentysixteen' ),
			'control_type' => 'input',
		),
	),
);

$footer_settings = new SettingsSubageBuilder( esc_html__( 'Footer Settings', 'twentysixteen' ), $footer_settings_array );Code language: PHP (php)
Theme Settings Footer Options screen
Footer options were created by creating a new object.

We’ve only specified two things and we got a whole new page. Much simpler than writing everything from scratch, no? Here’s the finished file in full

<?php
/**
 * Theme Settings
 *
 * @package WordPress
 * @subpackage twentysixteen/inc/theme-settings
 * @version 1.0.0
 * @author  Made by Denis <https://madebydenis.com/>
 * @license https://www.gnu.org/licenses/gpl-3.0.txt GNU/GPLv3
 * @link https://twentysixteen.io
 * @since  1.0.0
 */

/**
 * Theme Settings
 *
 * @package WordPress
 * @subpackage twentysixteen/inc/theme-settings
 * @version 1.0.0
 * @author  Made by Denis <https://madebydenis.com/>
 * @license https://www.gnu.org/licenses/gpl-3.0.txt GNU/GPLv3
 * @link https://twentysixteen.io
 * @since  1.0.0
 */

add_filter( 'admin_init', 'twentysixteen_theme_settings_capability' );
/**
 * Restrict access to the theme settings page to admins
 *
 * @since  1.0.0
 * @return void If user is not administrator they won't see Theme Settings
 */
function twentysixteen_theme_settings_capability() {
	if ( ! current_user_can( 'manage_options' ) ) {
		return;
	}
}

add_action( 'admin_menu', 'twentysixteen_theme_settings_add_page' );
/**
 * Theme Settings add menu page
 *
 * @since  1.0.0
 * @return void The resulting page's hook_suffix
 */
function twentysixteen_theme_settings_add_page() {
	add_menu_page( esc_html__( 'Theme Settings', 'twentysixteen' ), esc_html__( 'Theme Settings', 'twentysixteen' ), 'edit_theme_options', 'twentysixteen_theme_settings', 'twentysixteen_settings_render_page' );
}

/**
 * Theme Settings page builder function
 *
 * @param  string $title  The title of the page.
 * @param  array  $values Array of the setting values from the database.
 * @return string         Returned compiled HTML of the page
 */
function twentysixteen_build_settings_html( $title, $values = null ) {
	$out = '';

	$value = 0;
	$checked = '';
	$maps_enabled = ( isset( $values ) && '' !== $values ) ? $values->google_maps_enable : '';

	if ( isset( $maps_enabled ) && '1' === $maps_enabled ) {
		$value = 1;
		$checked = 'checked';
	}

	$out .= '<h3>' . esc_html__( 'Settings', 'twentysixteen' ) . '</h3>
			<div class="twentysixteen_options_wrapper">
				<h4>' . esc_html__( 'Enable Google Maps', 'twentysixteen' ) . '</h4>
				<input type="hidden" class="twentysixteen_setting google_maps_enable" name="google_maps_enable" value="' . $value . '">
				<label class="checkbox_label" for="google_maps_enable" data-settings=".twentysixteen_google_maps_api_key"><span class="twentysixteen_setting checkbox ' . $checked . '"></span>' . esc_html__( 'Enable google map', 'twentysixteen' ) . '</label>
				<p class="info">' . esc_html__( 'Use this if you are not using external plugin to power the Google Maps . ', 'twentysixteen' ) . '</p>
			</div>';

	if ( isset( $maps_enabled ) && '1' === $maps_enabled ) {
		$out .= '<div class="twentysixteen_options_wrapper twentysixteen_google_maps_api_key show">
					<h4>' . esc_html__( 'Add Google Maps API key', 'twentysixteen' ) . '</h4>
					<input type="text" class="twentysixteen_setting google_api_key" name="google_api_key" value="' . esc_attr( $values->google_api_key ) . '" />
					<p class="info">' . esc_html__( 'You need to find your API key? Just click on this link: ', 'twentysixteen' ) . '<a href="https://console.developers.google.com/">' . esc_html__( 'Google API keys', 'twentysixteen' ) . '</a>.</p>
				</div>';
	} else {
		$out .= '<div class="twentysixteen_options_wrapper twentysixteen_google_maps_api_key" style="display:none;">
					<h4>' . esc_html__( 'Add Google Maps API key', 'twentysixteen' ) . '</h4>
					<input type="text" class="twentysixteen_setting google_api_key" name="google_api_key" value="" />
					<p class="info">' . esc_html__( 'You need to find your API key? Just click on this link: ', 'twentysixteen' ) . '<a href="https://console.developers.google.com/">' . esc_html__( 'Google API keys', 'twentysixteen' ) . '</a>.</p>
				</div>';
	}

	return $out;
}

/**
 * Theme Settings page render function
 *
 * @return void
 */
function twentysixteen_settings_render_page() {
	?>
	<div class="wrap">
		<h2 class="page_title"><?php esc_html_e( 'Theme Settings', 'twentysixteen' ) ?></h2>
		<p class="description"><?php esc_html_e( 'General theme settings', 'twentysixteen' ) ?></p>
		<div class="settings_above_meta">
			<a href="#" class="save_all" id="twentysixteen_settings_save"><?php esc_html_e( 'Save All', 'twentysixteen' ) ?></a>
			<span class="spinner"></span><span class="saved_options"></span>
		</div>
		<div class="theme_settings_inner_content">
			<?php
			$twentysixteen_settings = get_option( 'twentysixteen_settings', '' );
			if ( isset( $twentysixteen_settings ) && '' !== $twentysixteen_settings ) {
				foreach ( $twentysixteen_settings as $key => $values ) {
					echo twentysixteen_build_settings_html( 'Settings', $values ); // WPCS: XSS ok.
				}
			} else {
				echo twentysixteen_build_settings_html( 'Settings' ); // WPCS: XSS ok.
			}
			wp_nonce_field( 'twentysixteen_settings_nonce_action', 'twentysixteen_settings_nonce' ); ?>
		</div>
	</div><?php
}

add_action( 'wp_ajax_twentysixteen_settings_save', 'twentysixteen_settings_save_callback' );
/**
 * Settings save AJAX callback function
 *
 * @return void
 */
function twentysixteen_settings_save_callback() {
	if ( isset( $_POST['data'], $_POST['twentysixteen_settings_nonce'] ) && wp_verify_nonce( sanitize_key( $_POST['twentysixteen_settings_nonce'] ), 'twentysixteen_settings_nonce_action' ) && '' !== $_POST['data'] ) { // Input var okay.
		$twentysixteen_settings = json_decode( sanitize_text_field( wp_unslash( $_POST['data'] ) ) ); // Input var okay.
		update_option( 'twentysixteen_settings', $twentysixteen_settings );
	}
	wp_die();
}

add_action( 'admin_enqueue_scripts', 'twentysixteen_theme_settings_admin_scripts' );
/**
 * Enqueue style and scripts for Theme Settings page
 *
 * @since  1.0.0
 * @param  string $hook Page hook suffix where the scripts and styles should be loaded.
 * @return void
 */
function twentysixteen_theme_settings_admin_scripts( $hook ) {
	$screen = get_current_screen();
	$screen_id = $screen->id;
	if ( strpos( $screen_id, 'page_twentysixteen_theme' ) !== false ) {
		wp_enqueue_style( 'twentysixteen_theme_settings_css', get_template_directory_uri() . '/inc/theme-settings/css/settings-style.css', '', '1.0.0' );

		wp_enqueue_style( 'wp-color-picker' );
		wp_enqueue_media();
		wp_enqueue_script( 'twentysixteen_theme_settings_js', get_template_directory_uri() . '/inc/theme-settings/js/settings-custom.js', array( 'jquery', 'wp-color-picker' ), '1.0.0', true );
		wp_localize_script( 'twentysixteen_theme_settings_js', 'twentysixteen_settings_object', array(
			'saved'	 => esc_html__( 'Settings Saved', 'twentysixteen' ),
			'delete' => esc_html__( 'Delete', 'twentysixteen' ),
			'save'	 => esc_html__( 'Save', 'twentysixteen' ),
		) );
	}
}


/**
 * Builds Settings subpage
 *
 * @package WordPress
 * @subpackage twentysixteen/inc/theme-settings
 * @version 1.0.0
 * @author  Made by Denis <https://madebydenis.com/>
 * @license https://www.gnu.org/licenses/gpl-3.0.txt GNU/GPLv3
 * @link https://twentysixteen.io
 * @since  1.0.0
 */
class SettingsSubageBuilder {
	/**
	 * Name of the subpage menu
	 *
	 * @var string $subpage_name The name of the subpage.
	 */
	private $subpage_name;

	/**
	 * Array of elements to be used in the HTML render of the settings page
	 *
	 * @var string $subpage_name The name of the subpage.
	 */
	private $html_array;

	/**
	 * Initialization of the class
	 *
	 * @param  string $subpage_name The name of the settings page, e.g. Header Settings.
	 * @param  array  $html_array The HTML array details for building the settings page.
	 */
	public function __construct( $subpage_name, $html_array ) {
		$this->subpage_name = $subpage_name;
		$this->html_array   = $html_array;
		add_action( 'admin_menu', array( $this, 'create_submenu_pages' ) );
		add_action( 'wp_ajax_twentysixteen_settings_save_action', array( $this, 'twentysixteen_all_settings_save' ) );
	}

	/**
	 * Create submenu page for each settings
	 *
	 * @return void
	 */
	public function create_submenu_pages() {
		$page_hook = 'twentysixteen_theme_' . strtolower( str_replace( ' ', '_', $this->subpage_name ) );

		add_submenu_page( 'twentysixteen_theme_settings', $this->subpage_name, $this->subpage_name, 'edit_theme_options', $page_hook, array( $this, 'twentysixteen_settings_render_subpage' ) );
	}

	/**
	 * Settings control render function
	 *
	 * This function takes the single control value, and based on what that control is
	 * renders the appropriate control.
	 *
	 * @param  array $default_value   Array of the associated default controls with all the details of the controls.
	 * @param  array $saved_values    Array of the values from the database.
	 * @return string 				  HTML of controls.
	 */
	public function twentysixteen_theme_settings_control_render( $default_value, $saved_values ) {
		$name             = ( isset( $default_value['name'] ) && '' !== $default_value['name'] ) ? $default_value['name'] : '';
		$control_type     = ( isset( $default_value['control_type'] ) && '' !== $default_value['control_type'] ) ? $default_value['control_type'] : '';
		$description      = ( isset( $default_value['description'] ) && '' !== $default_value['description'] ) ? '<p class="description">' . $default_value['description'] . '</p>' : '';
		$control_slug     = 'twentysixteen_' . str_replace( ' ', '_', strtolower( $name ) );
		$control_settings = ( isset( $saved_values ) && '' !== $saved_values ) ? $saved_values : '';
		$value            = ( isset( $control_settings[$control_slug] ) && '' !== $control_settings[$control_slug] ) ? $control_settings[$control_slug] : '';

		$out = '';

		switch ( $control_type ) {
			case 'input':
				$out .= '<div class="twentysixteen_options_wrapper">
							<div class="twentysixteen_options_left">
								<h4>' . esc_html( $name ) . '</h4>
							</div>
							<div class="twentysixteen_options_right">
								<input type="text" name="' . esc_attr( $control_slug ) . '" value="' . esc_attr( $value ) . '" class="twentysixteen_setting">
								' . wp_kses_post( $description ) . '
							</div>
						</div>';
				break;

			case 'image':
				$out .= '<div class="twentysixteen_options_wrapper">
							<div class="twentysixteen_options_left">
								<h4>' . esc_html( $name ) . '</h4>
							</div>
							<div class="twentysixteen_options_right">
								<div class="twentysixteen_uploaded_image">
									<img src="' . esc_url( $value ) . '" />
								</div>
								<input type="text" name="' . esc_attr( $control_slug ) . '" value="' . esc_url( $value ) . '" class="twentysixteen_setting twentysixteen_image_upload">
								<input type="button" name="image_upload" value="' . esc_html__( 'Upload Image', 'twentysixteen' ) . '" class="button upload_image_button">
								<input type="button" name="remove_image_upload" value="' . esc_html__( 'Remove Image', 'twentysixteen' ) . '" class="button remove_image_button">
								' . wp_kses_post( $description ) . '
							</div>
						</div>';
				break;

			case 'checkbox':
				$checked = '';
				if ( isset( $value ) && '1' === $value ) {
					$checked = 'checked';
				}
				$out .= '<div class="twentysixteen_options_wrapper">
							<div class="twentysixteen_options_left">
								<h4>' . esc_html( $name ) . '</h4>
							</div>
							<div class="twentysixteen_options_right">
								<input type="checkbox" name="' . esc_attr( $control_slug ) . '" value="' . esc_attr( $value ) . '" ' . checked( $value, 1, false ) . ' class="twentysixteen_setting">
								<label class="checkbox_label"><span class="twentysixteen_setting checkbox ' . $checked . '"></span>' . esc_html( $name ) . '</label>
								' . wp_kses_post( $description ) . '
							</div>
						</div>';
				break;

			case 'colorpicker':
				$out .= '<div class="twentysixteen_options_wrapper">
							<div class="twentysixteen_options_left">
								<h4>' . esc_html( $name ) . '</h4>
							</div>
							<div class="twentysixteen_options_right">
								<input type="text" name="' . esc_html( $control_slug ) . '" value="' . esc_attr( $value ) . '" class="twentysixteen_setting twentysixteen_color_picker"/>
								' . wp_kses_post( $description ) . '
							</div>
						</div>';
				break;

			case 'select':
				$out .= '<div class="twentysixteen_options_wrapper">
							<div class="twentysixteen_options_left">
								<h4>' . esc_html( $name ) . '</h4>
							</div>
							<div class="twentysixteen_options_right">
								<select class="twentysixteen_setting" name="' . esc_html( $control_slug ) . '">
									<option value=""> -- </option>';
				if ( isset( $default_value['options'] ) && '' !== $default_value['options'] ) {
					foreach ( $default_value['options'] as $option_name => $option_value ) {
						$selected = ( isset( $value ) && '' !== $value && $option_name === $value ) ? 'selected="selected"' : '';
						$out .= '<option value="' . esc_attr( $option_name ) . '" ' . esc_attr( $selected ) . ' >' . esc_html( $option_value ) . '</option>';
					}
				}
						$out .= '</select>
								' . wp_kses_post( $description ) . '
							</div>
						</div>';
				break;

			default:
				break;
		}

		return $out;

	}

	/**
	 * Rendering function for settings subpage
	 *
	 * @return void HTML rendered content.
	 */
	public function twentysixteen_settings_render_subpage() {
		$settings_array = $this->html_array;

		if ( isset( $settings_array ) && ! empty( $settings_array ) ) {

			$set_options = json_decode( json_decode( get_option( 'twentysixteen_' . str_replace( ' ', '_', strtolower( $this->subpage_name ) ), '' ) ) ); // Get options from the database.

			?>
			<div class="wrap">
				<h2 class="page_title"><?php echo esc_html( $this->subpage_name ); ?></h2>
				<p class="description"><?php echo esc_html( $settings_array['description'] ); ?></p>
				<div class="settings_above_meta">
					<a href="#" class="save_all" id="twentysixteen_settings_save_subpage"><?php esc_html_e( 'Save All', 'twentysixteen' ) ?></a>
					<span class="spinner"></span><span class="saved_options"></span>
				</div>
				<ul class="theme_settings_inner_content">
					<?php
					if ( isset( $set_options ) && '' !== $set_options ) {
						foreach ( (array) $set_options as $options_key => $options_value ) {
							?>
							<li class="settings_container <?php echo ( '0' === $options_key ) ? 'show' : '' // WPCS: XSS ok. ?>">
							<?php
							foreach ( $settings_array['settings_control'] as $control_name => $default_value ) {
								echo $this->twentysixteen_theme_settings_control_render( $default_value, (array) $options_value ); // WPCS: XSS ok.
							}
							wp_nonce_field( 'twentysixteen_settings_nonce_action', 'twentysixteen_settings_nonce' );
							?>
							</li>
							<?php
						}
					} else {
						?>
						<li class="settings_container show">
						<?php
						foreach ( $settings_array['settings_control'] as $control_name => $default_value ) {
							echo $this->twentysixteen_theme_settings_control_render( $default_value, null ); // WPCS: XSS ok.
						}
						wp_nonce_field( 'twentysixteen_settings_nonce_action', 'twentysixteen_settings_nonce' );
						?>
						</li>
						<?php
					} ?>
				</ul>
			</div>
			<?php
		} else {
			?>
			<div class="wrap">
				<h2 class="page_title"><?php echo esc_html( $this->subpage_name ); ?></h2>
				<p class="description"><?php esc_html_e( 'The options array seems to be empty. Please fill it up with settings to render the page.', 'twentysixteen' ); ?></p>
			</div>
			<?php
		}
	}

	/**
	 * Settings save AJAX callback function
	 *
	 * @return void
	 */
	public function twentysixteen_all_settings_save() {
		if ( isset( $_POST['data'], $_POST['twentysixteen_settings_nonce'] ) && wp_verify_nonce( sanitize_key( $_POST['twentysixteen_settings_nonce'] ), 'twentysixteen_settings_nonce_action' ) && '' !== $_POST['data'] ) { // Input var okay.
			$twentysixteen_settings = wp_json_encode( sanitize_text_field( wp_unslash( $_POST['data'] ) ) ); // Input var okay.
			$settings_name = 'twentysixteen_' . str_replace( ' ', '_', strtolower( $this->subpage_name ) );
			update_option( $settings_name, $twentysixteen_settings );
		}
		wp_die();
	}
}

$header_settings_array = array(
	'description'      => esc_html__( 'Set up settings for different headers', 'twentysixteen' ),
	'settings_control' => array(
		'control_1' => array(
			'name'         => esc_html__( 'Fixed Header', 'twentysixteen' ),
			'description'  => esc_html__( 'Make the menu always fixed on top.', 'twentysixteen' ),
			'control_type' => 'checkbox',
		),
		'control_2' => array(
			'name'         => esc_html__( 'Logo', 'twentysixteen' ),
			'control_type' => 'image',
		),
		'control_3' => array(
			'name'         => esc_html__( 'Retina Logo', 'twentysixteen' ),
			'description'  => esc_html__( 'This logo is used only if the retina screens are detected.', 'twentysixteen' ),
			'control_type' => 'image',
		),
		'control_4' => array(
			'name'         => esc_html__( 'Retina Logo Width', 'twentysixteen' ),
			'description'  => esc_html__( 'Set the width of the original logo.', 'twentysixteen' ),
			'control_type' => 'input',
		),
		'control_5' => array(
			'name'         => esc_html__( 'Retina Logo Height', 'twentysixteen' ),
			'description'  => esc_html__( 'Set the height of the original logo.', 'twentysixteen' ),
			'control_type' => 'input',
		),
		'control_6' => array(
			'name'         => esc_html__( 'Background Image', 'twentysixteen' ),
			'control_type' => 'image',
		),
		'control_7' => array(
			'name'         => esc_html__( 'Background Color', 'twentysixteen' ),
			'description'  => esc_html__( 'Set the menu background color.', 'twentysixteen' ),
			'control_type' => 'colorpicker',
		),
		'control_8' => array(
			'name'         => esc_html__( 'Text Color', 'twentysixteen' ),
			'description'  => esc_html__( 'Set the menu items color.', 'twentysixteen' ),
			'control_type' => 'colorpicker',
		),
		'control_9' => array(
			'name'         => esc_html__( 'Text Hover Color', 'twentysixteen' ),
			'description'  => esc_html__( 'Set the menu items hover color.', 'twentysixteen' ),
			'control_type' => 'colorpicker',
		),
		'control_10' => array(
			'name'         => esc_html__( 'Options Select', 'twentysixteen' ),
			'description'  => esc_html__( 'Select the header options.', 'twentysixteen' ),
			'control_type' => 'select',
			'options' => array(
				'option_1' => esc_html__( 'Option 1', 'twentysixteen' ),
				'option_2' => esc_html__( 'Option 2', 'twentysixteen' ),
				'option_3' => esc_html__( 'Option 3', 'twentysixteen' ),
				'option_4' => esc_html__( 'Option 4', 'twentysixteen' ),
			),
		),
	),
);

$footer_settings_array = array(
	'description'      => esc_html__( 'Set up settings your footer', 'twentysixteen' ),
	'settings_control' => array(
		'control_1' => array(
			'name'         => esc_html__( 'Logo', 'twentysixteen' ),
			'control_type' => 'image',
		),
		'control_2' => array(
			'name'         => esc_html__( 'Copyright', 'twentysixteen' ),
			'description'  => esc_html__( 'Set the copyright text.', 'twentysixteen' ),
			'control_type' => 'input',
		),
	),
);

$header_settings = new SettingsSubageBuilder( esc_html__( 'Header Settings', 'twentysixteen' ), $header_settings_array );
$footer_settings = new SettingsSubageBuilder( esc_html__( 'Footer Settings', 'twentysixteen' ), $footer_settings_array );
Code language: HTML, XML (xml)

And the css (JavaScript file is given above)

/*

[Table of Contents]

1. Theme Settings General
	1.1 Checkbox
	1.2 Input Field
	1.3 Image Field

*/

/*************** 1. Theme Settings General ***************/
.theme_settings_inner_content {
	padding: 30px;
	background: #fff;
	margin-top: 20px;
}

.settings_above_meta .save_all {
	text-decoration: none;
	background: #ddd;
	border-radius: 3px;
	margin-top: 15px;
	text-transform: uppercase;
	padding: 8px 10px;
	display: inline-block;
	color: #4e4a4a;
	-webkit-transition: all 200ms ease-in;
	transition: all 200ms ease-in;
}

.settings_above_meta .save_all:hover {
	background: #ccc;
}

.settings_above_meta .save_all:active,
.settings_above_meta .save_all:focus {
	border: 0;
	box-shadow: none;
	outline: 0;
}

.settings_above_meta .spinner {
	background: url(spinner.gif) no-repeat;
	-webkit-background-size: 20px 20px;
	background-size: 20px 20px;
	display: inline-block;
	visibility: hidden;
	float:none;
	vertical-align: middle;
	opacity: .7;
	filter: alpha(opacity=70);
	width: 20px;
	height: 20px;
	margin: -2px 10px 0;
}

.settings_above_meta .saved_options {
	color: #3eb72f;
	font-weight: 600;
	text-transform: uppercase;
}

.twentysixteen_options_left {
	width: 30%;
	margin-left: 30px;
	display: inline-block;
	vertical-align: top;
	margin-bottom: 20px;
}

.twentysixteen_options_right {
	width: 60%;
	display: inline-block;
	vertical-align: top;
	margin-bottom: 20px;
}

/*****---------- 1.1 Checkbox ----------*****/
input[type="checkbox"].twentysixteen_setting {
	display: none;
}

.checkbox_label{
	-webkit-touch-callout: none;
	-webkit-user-select: none;
	-khtml-user-select: none;
	-moz-user-select: none;
	-ms-user-select: none;
	user-select: none;
}

.twentysixteen_setting.checkbox {
	width: 25px;
	height: 25px;
	background: #fff;
	border: 1px solid #ddd;
	border-radius: 3px;
	margin-right: 20px;
	display: inline-block;
	vertical-align: middle;
	position: relative;
}

.twentysixteen_setting.checkbox:before {
	content: '✔';
	color: transparent;
	position: absolute;
	top: 5px;
	left: 6px;
	font-size: 19px;
	line-height: 0.8;
	-webkit-transition: all 150ms ease-in;
	transition: all 150ms ease-in;
}

.twentysixteen_setting.checkbox.checked:before {
	content: '✔';
	color: #2bb1ef;
}

/*****---------- 1.2 Input Field ----------*****/
.twentysixteen_options_wrapper input[type="text"] {
	background: #fff;
	border: 1px solid #ddd;
	padding: 3px 10px;
	border-radius: 3px;
	box-shadow: none;
}

.twentysixteen_options_wrapper input[type="text"]:active,
.twentysixteen_options_wrapper input[type="text"]:focus {
	border: 1px solid #ddd;
	box-shadow: none;
	outline: 0;
}

/*****---------- 1.3 Image Field ----------*****/

.twentysixteen_uploaded_image img {
	max-width: 30%;
	margin-bottom: 20px;
}Code language: CSS (css)

Now all you need to do, when calling the options from the database is to pull it with

$header_options = json_decode( json_decode( get_option( 'twentysixteen_header_settings', '' ) ), true );Code language: PHP (php)

Remember that you’ve saved your option in the ajax callback function that used your theme slug, and the name of the option sub page (lower caps and underscore instead of space).

So this is it, I hope you’ve learned something new, and that object oriented programming doesn’t seem so scary now. It’s a great way to solve certain problems. Of course this is just a small part of what oop is capable of, but it’s a great start for someone who is new to it. If you have any questions, leave them below, and as always: happy coding :)

6 responses

  1. How i get the value?

    get_option(“google_api_key”);

    1. I think all the options are stored in the `get_option( ‘twentysixteen_settings’, ” );` array, so you’d first use that and from that array get the `google_api_key` value.

  2. It doesn’t work. I try to get the value but not display the settings value.

    1. Hi! Which part is not working for you? I haven’t tested this code on newer WP versions (the original was written in 2016) so there’s a lot that could be wrong (I mean, I can certainly say I’d write this totally different today xD)

  3. I tried to run it not working properly when saving header and footer data, data will not be saved when setting one of header and footer.

    1. Hi!

      Are you getting any kind of errors when saving? I haven’t check this code in a long time, and to be honest, I would do things a looot different than described here 😅

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.