How to Display Product Variations - P2 - Using Meta Box and Oxygen
Tags: metabox, tutorial, variations
Published: 2022-03-15 03:15:02

Having variations for products is a common thing in e-commerce websites. If your website is built with Oxygen, let’s dig in this tutorial to know <strong>how to have product variations using custom fields and display them on a single product page beautifully</strong>.

<!--more-->

This is an example:

<img class="aligncenter" src="https://i.imgur.com/TbbFLpa.gif" alt="The example of displaying product variations on the single product page." width="1200" height="675" />

Please be noticed that we’ll use a lot of code in this practice since this is a kind of advanced thing. You can follow this one or use <strong>MB Views</strong> as the <a href="https://metabox.io/display-product-variations-p1-meta-box-gutenberg/">previous tutorial</a> (even when you are using Oxygen instead of Gutenberg).
<h2>Video Version</h2>
https://youtu.be/tDgPWecAVEw
<h2>Before Getting Started</h2>
For custom fields, we need the <strong>Meta Box plugin</strong> which is free and available on <a href="https://wordpress.org/plugins/meta-box/">wordpress.org</a>. For advanced features, I use some extensions of Meta Box as follows:
<ul>
 	<li><a href="https://metabox.io/plugins/custom-post-type/">MB Custom Post Types &amp; Custom Taxonomies</a>: the name said it all, it helps to create a custom post type that we need for products.</li>
 	<li><a href="https://metabox.io/plugins/meta-box-builder/">Meta Box Builder</a>: to have a UI to create custom fields right in the back end.</li>
 	<li><a href="https://metabox.io/plugins/meta-box-conditional-logic/">Meta Box Conditional Logic</a>: to show/hide fields precisely as any rules you want.</li>
 	<li><a href="https://metabox.io/plugins/meta-box-group/">Meta Box Group</a>: to organize custom fields into repeatable/cloneable and collapsible groups.</li>
</ul>
And the last one, you should use the <a href="https://oxygenbuilder.com/">Oxygen</a> versions 3.9+ that has native integration with Meta Box already.
<h2>Step 1: Create a New Custom Post Type</h2>
We’ll need a post type for products, so go to <strong>Meta Box</strong> &gt; <strong>Post Types</strong> &gt; <strong>New Post Type</strong> to create one.

<img class="aligncenter" src="https://i.imgur.com/sA6AR2s.png" alt="Go to Meta Box &gt; Post Types &gt; New Post Type to create a new post type.." width="1200" height="428" />

After publishing the new post type, a new menu will appear on the admin menu. In this case, my post type is <strong>Online Shops</strong>.

<strong><img class="aligncenter" src="https://i.imgur.com/AnZuzlD.png" alt="A new menu appears after creating a new post type." width="1200" height="411" /></strong>
<h2>Step 2: Create Custom Fields for Products</h2>
The product normally has a lot of information that we need to save in custom fields. For example product images, price, color, size, dimension, technical information, etc.

In this tutorial, just take a typical example of a fashion product and look at the below structure for fields:
<table>
<tbody>
<tr>
<td><b>Field</b></td>
<td><b>Types of Field</b></td>
<td><b>ID</b></td>
</tr>
<tr>
<td><span style="font-weight: 400;">Does This Product Have Variations?</span></td>
<td><span style="font-weight: 400;">Switch</span></td>
<td><span style="font-weight: 400;">Does_this_product_have_variaitons</span></td>
</tr>
<tr>
<td><span style="font-weight: 400;">Simple Product</span></td>
<td><span style="font-weight: 400;">Group</span></td>
<td><span style="font-weight: 400;">simple_product</span></td>
</tr>
<tr>
<td><span style="font-weight: 400;">          Product Image</span></td>
<td><span style="font-weight: 400;">Image Advanced</span></td>
<td><span style="font-weight: 400;">product_image</span></td>
</tr>
<tr>
<td><span style="font-weight: 400;">          Size</span></td>
<td><span style="font-weight: 400;">Checkbox List</span></td>
<td><span style="font-weight: 400;">size</span></td>
</tr>
<tr>
<td><span style="font-weight: 400;">          Original Price</span></td>
<td><span style="font-weight: 400;">Text</span></td>
<td><span style="font-weight: 400;">original_price</span></td>
</tr>
<tr>
<td><span style="font-weight: 400;">          Promotional Price</span></td>
<td><span style="font-weight: 400;">Text</span></td>
<td><span style="font-weight: 400;">promotional_price</span></td>
</tr>
<tr>
<td><span style="font-weight: 400;">          Status</span></td>
<td><span style="font-weight: 400;">Select</span></td>
<td><span style="font-weight: 400;">status</span></td>
</tr>
<tr>
<td><span style="font-weight: 400;">Variations of Product</span></td>
<td><span style="font-weight: 400;">Group</span></td>
<td><span style="font-weight: 400;">variations_of_product</span></td>
</tr>
<tr>
<td><span style="font-weight: 400;">          Product Image</span></td>
<td><span style="font-weight: 400;">Image Advanced</span></td>
<td><span style="font-weight: 400;">product_image</span></td>
</tr>
<tr>
<td><span style="font-weight: 400;">          Size</span></td>
<td><span style="font-weight: 400;">Checkbox List</span></td>
<td><span style="font-weight: 400;">size</span></td>
</tr>
<tr>
<td><span style="font-weight: 400;">          Original Price</span></td>
<td><span style="font-weight: 400;">Text</span></td>
<td><span style="font-weight: 400;">original_price</span></td>
</tr>
<tr>
<td><span style="font-weight: 400;">          Promotional Price</span></td>
<td><span style="font-weight: 400;">Text</span></td>
<td><span style="font-weight: 400;">promotional_price</span></td>
</tr>
<tr>
<td><span style="font-weight: 400;">          Status</span></td>
<td><span style="font-weight: 400;">Select</span></td>
<td><span style="font-weight: 400;">status</span></td>
</tr>
<tr>
<td><span style="font-weight: 400;">          Color Name</span></td>
<td><span style="font-weight: 400;">Select</span></td>
<td><span style="font-weight: 400;">color_name</span></td>
</tr>
</tbody>
</table>
I have a <strong>Switch</strong> field at first to define if the product has variations or not. Depending on the chosen option, we will display the corresponding group of fields: Simple Product or Variations of Product.

<strong>Note</strong>: <em>Logically, there is no need to have the Simple Product group in reality, we just need to use the Variations of Product only. However, I create both of them in this practice to show the differences of display fields from a cloneable group to the normal one</em>.

To set when each group should be displayed, there is <a href="https://metabox.io/plugins/meta-box-conditional-logic/">Meta Box Conditional Logic</a> extension. Go to the <strong>Advanced</strong> tab in the group settings, add a rule as below.

<img class="aligncenter" src="https://i.imgur.com/6ZXbzXb.png" alt="Set the conditional rule." width="1200" height="675" />

<strong>0</strong> stands for the value of the option <strong>No</strong> in the <strong>Switch</strong> field, and <strong>1</strong> stands for the <strong>Yes</strong> option.

In both groups, I have the same subfields, except the Color which is just used for variations. And there’s also a difference between those groups is that the Variations of Product group is cloneable while the Simple Product is not.

<img class="aligncenter" src="https://i.imgur.com/bTOZOMH.png" alt="Set the Variations of Product group as cloneable to have more spaces to add variations." width="1200" height="675" />

This setting allows you to add information for more than one variation for a product.

Now, back to the post editor of <strong>Product</strong>, you will see the fields. Change the <strong>Switch</strong> field status, you will see different groups of fields display.

<img class="aligncenter" src="https://i.imgur.com/5z6XKsV.gif" alt="The created custom fields are displayed in post editor." width="1200" height="675" />

Enter information for the product then move to the next step.
<h2>Step 3: Display the Product Variations Information on the Product Page</h2>
Go to the <strong>Oxygen</strong> menu and choose to create a new template.

In the settings of the template, choose a design that you want the template to inherit from, then choose your post type in the Singular section to apply the template to it.

<img class="aligncenter" src="https://i.imgur.com/Lnkfcnq.png" alt="Choose the design and post type for the template." width="1200" height="650" />

Back to the post editor of your post type, you will see the section with Edit with Oxygen button. Click there to edit the template.

<img class="aligncenter" src="https://i.imgur.com/vTyDODo.png" alt="Click Edit with Oxygen to edit the template." width="1200" height="675" />

Now, add a <strong>Section</strong> component for having a container.

<img class="aligncenter" src="https://i.imgur.com/kcgbyHK.png" alt="Add a Section component" width="1200" height="675" />

Then, add the <strong>Code Block</strong> inside and choose the <strong>PHP &amp; HTML</strong> section to add code.

<img class="aligncenter" src="https://i.imgur.com/FW8fQ9F.png" alt=" Go to PHP &amp; HTML in Code Block to add code." width="1200" height="675" />

[caption id="" align="aligncenter" width="1200"]<img src="https://i.imgur.com/uEMcRNl.png" alt="Add code to the Code Block" width="1200" height="675" /> Add code to the Code Block[/caption]

There are many lines of code so I uploaded it to <a href="https://github.com/wpmetabox/tutorials/blob/master/display-product-variations-with-Oxygen/template.php">Github</a>, you can refer to it for more details.

There are some important things that I want to point out here.

We’ll use the <code>rwmb_meta()</code> function to get value from the custom fields created by Meta Box. It will be used throughout this practice.

```
<?php 
	$product_types  = rwmb_meta( 'does_this_product_have_variations' ); 
?>
```
I created a variable here to get the value from the <strong>Switch</strong> field. <code>does_this_product_have_variations</code> is the ID of this field, you should change it to the ID of your field when applying.

The <strong>Switch</strong> field has two default values which are <strong>1</strong> and <strong>0</strong>. As I said before, they stand for the option Yes and No.

If the product has no variation, this field will be set No. It means that the <code>$product_types</code> variable will obtain the <strong>0</strong> value. In this case, the value of sub-fields from the Simple Product group, which has the ID as <code>simple_product</code>, will be loaded and displayed through this lines of code:

```
<?php if ( $product_types == "0" ): ?>
	<?php $simple_product = rwmb_meta( 'simple_product' ); ?>
……………………………
			?>
<?php endif; ?>
```
Since the <strong>Simple Product</strong> is a group of fields, this function will return values in an array with all the values from the sub-fields. To get the value of any subfield inside a group, we use this syntax:

```
$simple_product['ID_of_the_subfield']
```
To get product images, I use this code:

```
<?php $simple_image_ids = $simple_product['product_images'];?>
<?php foreach ( $simple_image_ids as $large_img ) : ?>
	<?php $large_size = RWMB_Image_Field::file_info( $large_img, ['size' => 'large'] ); ?>
	<img src="<?php echo $large_size['full_url'] ?>">
<?php endforeach; ?>
<?php foreach ( $simple_image_ids as $thumb_img ) : ?>
	<?php $thumb_size = RWMB_Image_Field::file_info( $thumb_img, ['size' => 'thumbnail'] ); ?>
	<img src="<?php echo $thumb_size['full_url'] ?>">
<?php endforeach; ?>
```
<strong>In there</strong>:

<ul>
 	<li><code>&lt;?php $simple_image_ids = $simple_product['product_images'];?&gt;</code> is to get value from the field that has ID <code>product_images</code>.</li>
 	<li>I created a loop to display all the images saved in the field thanks to this code: <code>&lt;?php foreach ( $simple_image_ids as $large_img ) : ?&gt;</code></li>
 	<li><code>RWMB_Image_Field::file_info()</code>: is to get images url.</li>
 	<li>I displayed all the images twice, one in large size, and one in thumbnail size. They’ll be used to set in the slider later.</li>
</ul>
For the price, there are Promotional Price and Original Price, but the promotional price is not always available. So, I set a rule to display them as below:

```
<?php if ($simple_product['promotional_price']): ?>
	<?php echo $simple_product['promotional_price'] ?>
	<?php echo $simple_product['original_price'] ?>
<?php else: ?>
	<?php echo $simple_product['original_price'] ?>
<?php endif; ?>
```
For the Size, it is a checkbox list field with several options and allows choosing more than one option. So that we need a loop to get all the saved options. In addition, each option always has both the value and label. We use this syntax to get the values of options:

```
<?php $values = $simple_product['size']; ?>
<?php foreach ( $values as $value ) : ?>
<?php echo $value; ?>
<?php endforeach;?>
```
The Status field is a select field (which has options as well as the checkbox list), but instead of getting the value of options as the Size, we should get the label. Then, the code will be much different from the Size’s one.

```
<?php 
	$status = isset( $simple_product['status'] ) ? $simple_product['status'] : '';
	$group  = rwmb_get_field_settings( 'simple_product' );
	foreach ( $group['fields'] as $field ) {
		if ( empty( $field['options'] ) ) {
			continue;
		}
?>
<?php if($field['options'][$status]): ?>
	<?= $field['options'][$status]; ?>
	<?php endif; ?>
	<?php
		}
	?>
```
In the event that the product has variation, we will display subfields of the <strong>Variations of Product</strong> group which are cloneable. Since it’s cloneable, to get value from each subfield, we must create a loop for each one. For example, to get the product images, there is an extra line of code at first as below:

```
<?php foreach ( $variations_of_product as $gallery ) : ?>
	<?php $variation_image_ids = $gallery['product_images'];?>
	<?php foreach ( $variation_image_ids as $large_image ) : ?>
		<?php $large = RWMB_Image_Field::file_info( $large_image, ['size' => 'large'] ); ?>
		<img src="<?php echo $large['full_url'] ?>">
	<?php endforeach; ?>
	<?php foreach ( $variation_image_ids as $thumbnail_image ) : ?>
		<?php $thumbnail = RWMB_Image_Field::file_info( $thumbnail_image, ['size' => 'thumbnail'] ); ?>
		<img src="<?php echo $thumbnail['full_url'] ?>">
	<?php endforeach; ?>
<?php endforeach; ?>

Or, for the Size:

<?php foreach ( $variations_of_product as $size_group ) : ?>
	<?php $values = $size_group['size']; ?>
	<?php foreach ( $values as $value ) : ?>
		<?php echo $value; ?>
	<?php endforeach;?>
<?php endforeach; ?>
```
Furthermore, variations are differentiated by color, so we have this code to get colors:

```
<?php foreach ( $variations_of_product as $color ) : ?>
	<?php $variation_color = $color['color_name']; ?>
			<a href="#<?php echo $variation_color ?>" class="color <?php echo $variation_color ?>" title="<?php echo $variation_color ?>"><?php echo $variation_color ?></a>
<?php endforeach; ?>
```
In there, I added an <code>&lt;a&gt;</code> tag<a> and a dynamic class. There will be different classes for each color and each one will be the name of the color. This class plays an important role in helping us to define which variations are showing.</a>

Then, I added some <code>div</code> tags to the code to set elements in a reasonable layout. I also added an attribute named <code>data-id</code> in the gallery, price, size, and status sections.

```
data-id="<?php echo $price['color_name'] ?>
```
This attribute will obtain the name of the corresponding color, then we’ll know which images, prices, status, or sizes are of which variation. So that we can easily choose which information should be shown to fit the chosen color.

Apply the code then back to a single product page, you will see all the information of the product display.

<img class="aligncenter" src="https://i.imgur.com/fOaCeZD.png" alt="All the information of the product is displayed." width="1200" height="675" />

Let’s set rules to display the information and style them for a better look.
<h2>Step 4: Set Rules to Display the Variations Information</h2>
<h3>Download the JS and CSS Library</h3>
As the example at the beginning of this post, the images of the product variations are in a slider, and all the information of each variation appears only when you choose the matching color.

To have it, I use some JS and CSS. Instead of adding them directly to the theme, I’m using the My Custom Functionality plugin. You can download it from <a href="https://github.com/wpmetabox/tutorials/tree/master/display-product-variations/my-custom-functionality-master">GitHub</a> or choose to use an alternative one for the same feature such as the <a href="https://wordpress.org/plugins/code-snippets/">Code Snippets</a> plugin.

For the slider, I use the Slick library. It is also available on <a href="https://github.com/kenwheeler/slick">Github</a>. Just download these three files and upload them to the <strong>js</strong> and <strong>css</strong> folders in the My Custom Functionality plugin’s folder.

<img class="aligncenter" src="https://i.imgur.com/fkSE9Mv.png" alt="JS and CSS files in the Slick library." width="1200" height="675" />
<h3>Create Custom JS for Slider and Rules</h3>
Next, create a new <code>custom.js</code> file in the <code>js</code> folder and add code to it.

<img class="aligncenter" src="https://i.imgur.com/qpLNLT0.png" alt="Add code to custom.js file." width="1902" height="1052" />

```
jQuery(document).ready(function ($) {
    $('.slider-single').slick({
        slidesToShow: 1,
        slidesToScroll: 1,
        arrows: false,
        adaptiveHeight: false,
        infinite: false,
        useTransform: true,
        speed: 400,
        cssEase: 'cubic-bezier(0.77, 0, 0.18, 1)',

    });

    $('.slider-nav')
        .on('init', function (event, slick) {
            $('.slider-nav .slick-slide.slick-current').addClass('is-active');
        })
        .slick({
            slidesToShow: 4,
            slidesToScroll: 4,
            dots: false,
            focusOnSelect: false,
            infinite: false,
            responsive: [{
                breakpoint: 1024,
                settings: {
                    slidesToShow: 5,
                    slidesToScroll: 5,
                }
            }, {
                breakpoint: 640,
                settings: {
                    slidesToShow: 4,
                    slidesToScroll: 4,
                }
            }, {
                breakpoint: 420,
                settings: {
                    slidesToShow: 3,
                    slidesToScroll: 3,
                }
            }]
        });

    $('.slider-single').on('afterChange', function (event, slick, currentSlide) {
        $('.slider-nav').slick('slickGoTo', currentSlide);
        var currrentNavSlideElem = '.slider-nav .slick-slide[data-slick-index="' + currentSlide + '"]';
        $('.slider-nav .slick-slide.is-active').removeClass('is-active');
        $(currrentNavSlideElem).addClass('is-active');
    });

    $('.slider-nav').on('click', '.slick-slide', function (event) {
        event.preventDefault();
        var goToSingleSlide = $(this).data('slick-index');

        $('.slider-single').slick('slickGoTo', goToSingleSlide);
    });
    jQuery(".grouped-product .color-contain-group .color-group .color-name a").click(function (e) {
jQuery(".color-contain-group .color-group .color-name").removeClass("active");
jQuery(this).show();
jQuery(this).parent().addClass("active");
jQuery("div[data-id]").removeClass("active");
jQuery("div[data-id='" + jQuery(this).attr("href").replace("#", "") + "']").addClass("active");
jQuery('.slider-single').slick('refresh');
jQuery('.slider-nav').slick('refresh');
e.preventDefault();
    });

    jQuery(".size-contain-group .size-group .info .list-size a").click(function (e) {
        e.preventDefault();
    });

    jQuery('.size-contain-group .size-group .info .list-size .size-name').click(function(){
        jQuery(this).addClass('active');
        jQuery('.size-contain-group .size-group .info .list-size .size-name').not(this).removeClass('active')
    })
})
```
<strong>Explaination</strong>:
<ul>
 	<li><code>$('.slider-single').slick({ })</code> to create a slider for the elements that have the <code>.slider-single</code> class. They are product images that I set to display in the large size.</li>
 	<li><code>$('.slider-nav')</code>: to create a slider as well. The elements which have the <code>.slider-nav</code> class are product images that I set to display in the thumbnail size.</li>
 	<li><code>.on('init', function (event, slick) { })</code>: to identify which thumbnail is in the current slide. And, that thumbnail will be added a class as <code>“is active”</code>.</li>
 	<li><code>$('.slider-single').on('afterChange', function (event, slick, currentSlide) { })</code>: to trigger the event that someone clicks on the large image to move to the other one, then the thumbnail slider will be changed to the corresponding thumbnail.</li>
 	<li><code>$('.slider-nav').on('click', '.slick-slide', function (event) { })</code>: to trigger that event when someone clicks on the thumbnail slider. Then, it also displays the corresponding large image in the large slider.</li>
 	<li><code>jQuery(".grouped-product .color-contain-group .color-group .color-name a").click(function (e) { })</code>: to trigger when someone clicks on a product color using the A tag we added in the view.</li>
 	<li><code>jQuery(".color-contain-group .color-group .color-name").removeClass("active"); jQuery(this).show(); jQuery(this).parent().addClass("active")</code>: to remove the active class from the unselected color and add it to the selected one.</li>
 	<li><code>jQuery("div[data-id]").removeClass("active"); jQuery("div[data-id='" + jQuery(this).attr("href").replace("#", "") + "']").addClass("active")</code>: to remove and add the active class to all the elements that have the value of the <code>data-id</code> attribute as the name of the color. It means that when you click on a color, all the corresponding information of that variation such as price, size, status, and image gallery will be displayed.</li>
 	<li><code>jQuery('.slider-single').slick('refresh') and jQuery('.slider-nav').slick('refresh')</code>: to refresh both sliders to load new images.</li>
</ul>
<h3>Declare the JS and CSS Files</h3>
Add code inside the function <code>custom_enqueue_files()</code> in the <code>plugin.php</code> file to declare all the above js and css files.

```
 wp_enqueue_style('slick', plugin_dir_url( __FILE__ ).'/assets/css/slick.css');
	wp_enqueue_style('slick-theme', plugin_dir_url( __FILE__ ).'/assets/css/slick-theme.css');

	wp_enqueue_script('custom', plugin_dir_url( __FILE__ ).'/assets/js/custom.js', ['jquery']);
	wp_enqueue_script('slick-min', plugin_dir_url( __FILE__ ).'/assets/js/slick.min.js', ['jquery']);
	wp_enqueue_script('script', plugin_dir_url( __FILE__ ).'/assets/js/script.js', ['jquery']);
```
Now, go back to a single product page, there is a slider and some differences. Let’s move to the next step to style this page.

<img class="aligncenter" src="https://i.imgur.com/QR1FF2Z.gif" alt="The product images have already turned into a slider." width="1200" height="675" />
<h2>Step 5: Style the Product Page Using CSS</h2>
Back to page editor by Oxygen, go to <strong>Manage</strong> &gt; <strong>Stylesheets</strong> &gt; <strong>Add Stylesheet</strong> to have space to add CSS.

<img class="aligncenter" src="https://i.imgur.com/bXom5pT.png" alt="Go to Manage &gt; Stylesheets &gt; Add Stylesheet to have space to add CSS" width="1200" height="269" />

Then add code into the box. All the code is uploaded into <a href="https://github.com/wpmetabox/tutorials/blob/master/display-product-variations-with-Oxygen/custom.css">Github</a>, so you can refer to it.

<img class="aligncenter" src="https://i.imgur.com/fTmhDu0.png" alt="Add CSS to the box" width="1200" height="675" />

Finally, go to the single product page once again and you will see a new look. When you select a color, the photo gallery will change to that color automatically. At the same time, the sizes and prices also change correspondingly. All the things will be exactly the same with the example that I show at the beginning of this post. If not, check the ID of the fields and the name of variables carefully.

<img class="aligncenter" src="https://i.imgur.com/TbbFLpa.gif" alt="The product variations turned into a new look." width="1200" height="675" />
<h2>Last Words</h2>
Displaying product variations is not a simple task, it is quite advanced and must use coding. In the real case, you can abate the advanced things by creating only the cloneable groups for variations. The goal of this practice is to show you a comprehensive way to display fields with Oxygen using code, so I added additional things for more instances. You should pick up a part only to apply to your own cases.

If you are confused or have any ideas, let us know by leaving a comment. See you next blog!