jQuery Accordion: add hash to URL, send Analytics event on open

Recently I had an intense project launching a buddy’s site in Hubspot on a short timeline. Along the way, we created an FAQ page, the Custom Module for this accordion uses jQuery UI Accordion (https://jqueryui.com/accordion/), and so the tricks in general apply far and wide, I think.

The goal:

  • add a URL hash (https://thesitename/faq#thisistheahash) when a question is clicked open, and remove it when closed. Primarily so someone can copy a link to the page with that hash in it
  • make the accordion open if someone comes to the page with the hash in the URL, like if it were emailed to you that way
  • send Google Analytics event data when the question is opened so we can get a sense what questions people are really concerned with.

Just so you know the HTML that gets written out for each accordion item is more or less like this (I simplified it from the actual output in Hubspot):

<div class="accordion" data-url-hash="how-long-does-it-take-to-get-started" role="tablist">
<h4 class="ui-accordion-header" role="tab" id="ui-id-1" >How long does it take to get started?</h4>
<div class="ui-accordion-content" id="ui-id-2"> ... expanded content in here ... </div>

These refer to the code below…

Line 2 is just to keep this action happening only on the FAQ page. Unlike WordPress, in Hubspot I can’t just ask the server what page I’m on (I think).

jQuery UI has an Accordion function,  what was already in use on this Custom Module. It’d be easy to wire up yourself if your theme or site is not already using it. In this case the wrapper <div> has a class ‘accordion’ that’s what activates the expanding/contracting. So I’m just checking for click event on that same div.

In the Custom Module, which is where I define the simple HTML of the accordions, I assigned a value to the data-attribute: ‘data-url-hash’ and have it as lower case, no spaces (dashes instead). When the FAQ accordion is expanded it gets a class ‘ui-accordion-header-active’ (by jQuery UI already) so I just check for that and then using plain javascript write the hash into the URL.

Also, while I’m at it, I defined a Google Analytics event trigger to send information about this FAQ Question. We should see those events in the coming days and I’ll update with a note about it. But I checked that it was firing using a google analytics debugger for Chrome.

Then, I wrote a simple function to clear the hash when the accordion is closed again.

Next,  I had to deal with the case when someone comes to the page and has the hash in the URL already I want to trigger it opening the appropriate accordion. Since I have the data-attribute to work with I run a jQuery filter to find the div.accordion that has that attribute match, and then activate the accordion. In order to get the hash there’s a hand javascript function, but I have to strip out the ‘#’ on the way or it won’t match with my data-attribute.

This is the page where it’s in action: https://www.bigsis.com/faq/ Here is the whole shebang. It’s late so I don’t have time to write the instructions along with the lines themeselves like a better blogger, but maybe I will come back to it if I have time. This all, by the way, is wrapped in a document.ready, and is in a customJS file I have saved in my coded files in Hubspot. It’s key to load the script in the footer, or at least AFTER jQuery, and jQuery UI, AND the accordion instantiation happens.


//get URL and hash for FAQ page
if(window.location.href.indexOf('faq') &gt; -1){

//get clicked or opened FAQ then add a hash to the URL
  $('.accordion').click(function(){
  var dataUrlHash = $(this).data('url-hash');
  var header = $(this).find('h4');
  if(header.hasClass('ui-accordion-header-active')){
    window.location.hash = dataUrlHash;
    ga('send', 'event', 'FAQ', 'Open', dataUrlHash);
  } else{
    removeHash();
  }
});

//check if url has a hash, open accordion...
if(window.location.hash) {
  var urlHash = $(location).attr('hash').replace('#', '');
  $('.accordion').filter(function(){
    return $(this).data('url-hash') === urlHash;
  }).accordion("option", "active", 0);
}

//toggle off the hash when an open accordion is clicked
function removeHash () {
  history.pushState("", document.title, window.location.pathname+ window.location.search);
}

Launch a lightbox pop up from a menu link

I had a few clients who needed to launch some pop up lightbox content from a link on the site. In this case it was a menu item. I was using a child theme of Enfold in both cases, which I know already has Magnific Pop up in it so I wanted to simply leverage that. Here’s how it went. If you don’t have Enfold as your theme, you can get magnific (or another lightbox system) and do a similar thing.

This is one of the sites: bluboxrooms.com. Notice the Contact link in the menu, it launches a popup lightbox with their MailChimp signup in it.

In the functions.php file, put some code to tell the site to use the rel tag to activate the magnific (the first snippet) and then write a function to create the content that will pop up.


//Pop up handler
add_action('wp_head','enfold_customization_add_magnific_handler');
function enfold_customization_add_magnific_handler() {
	?>
	<script type="text/javascript">
	jQuery(document).ready(function() {
	    jQuery('a[rel=magnific]').magnificPopup({
	        type:'inline',
	        preloader:false,
	    });
	});
	</script>
	<?php
}

function add_popup_content(){ ?>
     <div id="popup" class="white-popup mfp-hide lightbox-added">
          <!-- HTML of Pop up Here --!>
     </div>
<?php }
add_action('ava_after_main_menu', 'add_popup_content');

Inside the #popup div, I pasted the HTML of the MailChimp embed code, which I tweaked and styled to suit the site. On another site, that was simply an image with a link to one of their other pages (where they were doing a giveaway/signup). You could also show an iframe in the pop up of course. In the case of Enfold+Magnific, it’s important to have the classes on the #popup div to work properly and that’d be true of prettyPhoto or another lightbox too.

Child Theme – it’s easy! and necessary

If you are building a serious WordPress site, it’s likely you have bought a good theme, either because it looks good, or is easy to use or both. I’m a fan of Enfold, I like the visual page builder than the others I’ve seen, and I know that handing it off to clients who don’t spend their days on Stack Overflow evaluating code snippets.

Your theme, if it’s good, will be getting updated from time to time, and you want to protect whatever customization you make to the theme. Therefore, a child theme is essential. Luckily, it’s so friggin easy.

Part One, super easy:

Steps (I do this in my sleep now for new projects):

  • get set up with FTP access to your site
  • make sure your parent theme is there, and note the name of the folder it is in. The folder name is important here not the name in the heading of the stylesheet.
  • in the wp-content > themes folder, create a new folder for your child theme. I usually name it for the client and the year, so we can track how old it gets, so something like “clientname2016”
  • In that folder create two files: “styles.css” and “functions.php”

Put a header in the style.css file that looks like this. It needs a minimum of 2 things: the child theme name, and the template (parent) theme name, but if can have more lines in it (see below).

 /*
Theme Name: Client Name 2016
Template: twentyfifteen
*/ 

Put this in the functions.php file:

&lt;?php
add_action( 'wp_enqueue_scripts', 'theme_enqueue_styles' );
function theme_enqueue_styles() {
    wp_enqueue_style( 'parent-style', get_template_directory_uri() . '/style.css' );

} 

Now you can go back to your WP Admin dashboard and activate the child theme you made. You can, of course, but in a .png as a preview for your themes page, and of course take off and running with your customizations.

Part Two, get off and running…

The child theme allows you to make modifications to your theme without being affected when you update the parent theme. Adding CSS to that styles.css file will override parent theme styles, and functions added to that functions theme will be added to the parent functions used in the site.

Furthermore, files you add to the child theme folder will replace the ones in the parent. For example, if you want to customize the header.php you can copy the one in the parent, and paste it in the child theme in the same relative position. Feel free to edit the file, changing php and html code as you wish. More complicated themes often have various template parts or files in subfolders, and once you identify one you need to modify, set up the same structure in your child. Say, in a folder called “includes” there is one called “helper-main-menu.php”. Simply create a folder in your child theme called includes, and paste that file in it, then hack away!

Adding page name to the body classes in WordPress

I seem to always have to remind myself of this one… whether it is someone else’s theme I’m dealing with, or an Underscores starter, or for whatever reason the body classes seem insufficient, I always like having the page name in the body classes. This makes it easy to target something that may need to happen on a particular page, and safeguards against the client deleting and re-establishing that page. In any event, if you need the same this is a simple fix. The body_class() function can be filtered and elements added to the array there. You can of course use the page slug or any other detail from the $post object.

//Page Slug Body Class
function add_body_class( $classes ) {
global $post;
if ( isset( $post ) ) {
$classes[] = $post->post_name;
}
return $classes;
}
add_filter('body_class','add_body_class');

PHP copyright and current year

Often in the footer of your site you want to write something like: “© 2015 Mick Follari”. But you also don’t want to have to remember to go change it every year, or to teach your clients how to do it. Another thing I found this useful for is in one case I had a client I needed to have an image header with the year built in to it, like “2015 Adventure Film Festival”. I pre-made 5 years of headers and gave them names with the year in them and used the PHP year in the img src filename so they would automatically update on Jan 1.

Many people just want this snippet:

&copy; <?php echo date("Y"); ?>
&copy; <?php echo date("Y").' -YourNameHere'; ?>
&copy; 2012-<?php echo date("Y").' -YourNameHere'; ?>

The first displays just the year, the second adds your name or company, the third shows a range (like “© 2012-2015 Mick Follari”).

There is a long list of arguments you can use to configure the date if you’d rather more than just the year. Here’s the index.

This is a pretty basic thing, but I find people are always asking or looking up how to do. Since most of my sites are in WordPress, I’ll mention first that you should create a Child Theme, then alter the footer.php from your Parent theme in there. If you need to learn more about Child themes there’s plenty out there (and I’ll make a new blog post about it), but start here.

Once you have your Child Theme, copy the footer.php from your Parent theme into the new folder, and start editing the place where it shows the copyright. If the Parent theme is using a filter or hook to write in the copyright, then you only need to unhook that function, and write your own to replace it. That’s another post…

 

Woocommerce Product Variation Description with dynamic loading

Recently I had a project where a client needed to offer a variable product with extra information about each variation displayed, and this needed to change as the selector changed the variation. The product page would be linked to from another page on their site, with the desired variation pre-loaded in the drop-down selector on arrival. However, once there you could of course play with the drop-down to select other variations.

Let’s break down what I did to get everything working.

Here’s a quick summary:

  • create a textarea that gets added to the variations editor in the Woocommerce product
  • hook it in properly so it responds to javascript and normal page loading
  • get the contents of the textareas and inject them into the product page on the front end
  • hide all of them except the one we want to show based on the drop-down selector

The variable product custom field text area:

First, I wanted to create a textarea for each product variation for the client to be able to enter a description. Originally, I simply grabbed a plugin that did just that. But after we had a bunch of nightmares with malware and site hacking, we wiped everything and started over on a new host where we wanted to stay as clean as possible, limit our plugins, and by then that plugin had been removed from the repository. I found great instruction on adding custom fields to variable products by Remi Corson which looked easy to implement. Using his post as a guide I added a function to create the textarea and hook in properly to Woocommerce. And, as he points out, we have to deal with the Ajax creation of new variations, so everything is done twice as you’ll see. This goes in the functions file.


//Product Variation Description

//Display Fields
add_action( 'woocommerce_product_after_variable_attributes', 'variable_fields', 10, 3 );
//JS to add fields for new variations
add_action( 'woocommerce_product_after_variable_attributes_js', 'variable_fields_js' );
//Save variation fields
add_action( 'woocommerce_process_product_meta_variable', 'save_variable_fields', 10, 1 );

//Create new fields for variations
function variable_fields( $loop, $variation_data, $variation ) {
?>
 <tr>
  <td>
   <?php
   // Textarea
   woocommerce_wp_textarea_input(
    array(
     'id'          => '_textarea['.$loop.']',
     'label'       => __( 'Description', 'woocommerce' ),
     'placeholder' => '',
     'value'       => get_post_meta( $variation->ID, '_textarea', true ),
    )
   );
?>
 </td>
  </tr>
   <?php
}

// Create new fields for new variations
function variable_fields_js() {
?>
 <tr>
  <td>
   <?php
   // Textarea
   woocommerce_wp_textarea_input(
    array(
     'id'          => '_textarea[ + loop + ]',
     'label'       => __( 'Description', 'woocommerce' ),
     'placeholder' => '',
     'value'       => '',
    )
   );
?>
  </td>
 </tr>
<?php
}

//Save new fields for variations
function save_variable_fields( $post_id ) {
 if (isset( $_POST['variable_sku'] ) ) :
  $variable_sku          = $_POST['variable_sku'];
  $variable_post_id      = $_POST['variable_post_id'];
  // Textarea
  $_textarea = $_POST['_textarea'];
  for ( $i = 0; $i < sizeof( $variable_sku ); $i++ ) :
   $variation_id = (int) $variable_post_id[$i];
    if ( isset( $_textarea[$i] ) ) {
     update_post_meta( $variation_id, '_textarea', stripslashes( $_textarea[$i] ) );
    }
  endfor;
 endif;
}

The code gave me this in my variable products:

variable-descriptions

Get the Description to use on the Product Page:

To get the description I had created for them and add it to the product page, I’m using the all-powerful get_post_meta, and also grabbing the price, since they are all different. I’m doing this from the functions file and hooking in to the ‘before add to cart’ in Woocommerce. As you’ll notice this is going to write them all out on the page, which is not what we want, but I added some javascript to hide them all, then turn on each one depending on what’s selected in the drop-down. This also goes in the functions file.


function my_theme_function_woocommerce_before_add_to_cart_form() {
 // get the product
 global $product;
 // Get the post IDs of all the product variations
 $variation_ids = $product->children;
 // check if we have variations
 if ( empty( $variation_ids ) )
  return;
 // walk the variations
 foreach( $variation_ids as $variation_id ) {
  $variable_product= new WC_Product_Variation( $variation_id );
  $description = get_post_meta($variation_id, '_textarea');
  $price = $variable_product ->regular_price;
  echo '<div id="variation-' . $variation_id . '" style="display: none;" class="camp-description">';
  echo '<p>'.$description[0].'</p>';
  echo '<b> $'.$price.'</b>';
  echo '</div>';
 }
}
add_action( 'woocommerce_before_add_to_cart_form', 'my_theme_function_woocommerce_before_add_to_cart_form', 5 );

jQuery to show the one we want:

I needed this solution fast, so I resorted to some jQuery trickery to display the one description we want out of all the descriptions hidden on the page, based on what option was selected in the drop-down. This goes in a custom.js file that adds various extra jQuery I need on the site. It should be wrapped in the usual ‘jQuery(document).ready(function ($) {}’.


//Summer Camp products get variation ID to show/hide descriptions
setTimeout(function(){
 variationid= '';
 if(typeof $('input[name=variation_id]').val()  != "undefined" && $('input[name=variation_id]').val() !== null) {
  variationid = $('input[name=variation_id]').val();
  $('div#variation-'+variationid).show();
 }
}, 100);

setTimeout(function(){
 $('body').on('change', '#camps',function(){
  $('div#variation-'+variationid).hide();
  setTimeout(function(){
  variationid = $('input[name="variation_id"]').val();
  $('div#variation-'+variationid).show();
  }, 100)
 });
}, 200);

SIDE NOTE: to land on the page in the first place the user is clicking through from another page. The link they click is set up to leverage Woocommerce pre-filling the drop-down with a certain variation. The href in the link looks like this:

/summer-camp-registration/?attribute_camps=early-childhood-session-1-latin-celebrations

 Wrap up:

I’m sure there are some more elegant ways to do things, but this got me what I needed in a hurry, and seems to be working fine. I’m not crazy about having the timeouts in the js to allow for all the other Woocommerce js to work, but again it’s not too much of a drag on the user experience I think. If I get time I’ll go through more explanation of how things work on here. Here is the product page where you can see this working: https://www.waldorfsandiego.org/summer-camp-registration/?attribute_camps=early-childhood-session-1-latin-celebrations