Conditionally Loading Javascript & CSS with Fancybox for WordPress

The Problem

I’ve been working on improving the performance of a client’s WordPress-based website recently and it’s become very clear to me just how much CSS & Javascript plugins load, even when it’s not needed.

On this particular website 47% of the Javascript and 57% of the CSS loaded on the site’s homepage is not required on the homepage, but is required elsewhere, so it should be loaded conditionally. Sometimes this is easy, but sometimes it’s not.

Fancybox for WordPress is not an easy plugin to make load client-side resources, after all, it’s made to show a lightbox on any image, and especially on any gallery. When the wp_enqueue_scripts action happens, where it is recommended that we set the scripts and styles that will be used on a page, we don’t yet know what will be on the page. Fortunately, we can call the wp_enqueue_script() function after that, during the body of the page, and those scripts will be printed in the wp_footer() function. The same is true for wp_enqueue_style(). So, here’s what we do:

  1. Stop Fancybox for WordPress from including its scripts & styles by default.
  2. If a gallery or image that we want to use Fancybox on is displayed, we tell WordPress to display the Fancybox scripts & styles in the footer.
  3. Enjoy faster pageloads.

Sounds simple, but there are a few interesting bits. Now for the code.

The Code

I use three functions to check if the Fancybox scripts & styles are needed. They can be in a theme’s functions.php, (where I have them at the moment), or they could even be rolled into their own plugin.

First tell Fancybox not to include its code by default. Instead of manually dequeuing every style & script, remove the Fancybox functions that do the enqueuing from the wp_enqueue_scripts and wp_head action hooks:

// don't enqueue Fancybox scripts by default.
function jb_dequeue_fancybox_css_js() {
remove_action( 'wp_enqueue_scripts', 'mfbfw_styles' );
remove_action( 'wp_enqueue_scripts', 'mfbfw_scripts' );
remove_action( 'wp_head', 'mfbfw_init' );
}
add_action( 'wp', 'jb_dequeue_fancybox_css_js' );

Next create a function that re-enables Fancybox, and remembers that it did so. Here there are two static variables, which will hold their value between function calls, (kind of like hidden globals), which store state. The $fancybox_is_used variable is returned so this function can be called with no arguments to find out if Fancybox has been used on page. Note the priority of 25 when hooking mfbfw_init() to the wp_footer action. This is needed because mfbfw_init() prints some Javascript to the page that relies on jQuery, and WordPress prints the enqueued scripts in the wp_footer action with a priority of 20, so mfbfw_init() needs to execute with a priority higher than 20.

function jb_fancybox_is_used( $used = false ) {

// this is returned so we can call this function with no arguments to learn
// if Fancybox has been used on a particular page.
static $fancybox_is_used = false;

// remember if Fancybox has been re-enabled already, so we don't enqueue the
// scripts multiple times
static $fancybox_is_setup = false;

if( $used === true ) {
$fancybox_is_used = true;
}

if( $fancybox_is_used && ! $fancybox_is_setup ) {
if( function_exists( 'mfbfw_styles' ) ) {
mfbfw_styles(); // enqueue fancybox styles
mfbfw_scripts(); // enqueue fancybox scripts
// the 25 is important. WordPress prints footer scripts in the
// wp_footer action with a priority of 20, and mfbfw_init() has to
// be
called after the footer scripts are already on the page.
add_action( 'wp_footer', 'mfbfw_init', 25 );
}
$fancybox_is_setup = true;
}

return $fancybox_is_used;
}

Finally, make a function that looks for places where Fancybox is used on the page. On the site I’m working on the CSS class fancybox is used on images and in galleries that I want to be Fancyboxed, so I look for the string “fancybox” in the_content filter, and when it’s found I call jb_fancybox_is_used( true ) to re-enable Fancybox on the page. I added this to the_content with priority 11 because shortcodes, including gallery shortcodes, are executed at priority 10, and I want to be able to look through the output of short codes for the fancybox CSS class:


function jb_hunt_for_fancybox( $content ) {

if( false !== stripos( $content, 'fancybox') || false !== stripos( $content, 'thickbox' ) ) {
jb_fancybox_is_used( true );
}
return $content;
}
add_filter( 'the_content', 'jb_hunt_for_fancybox', 11 );

If you include a something you want to fancybox in a template you can call jb_fancybox_is_used( true ) manually from the template file to include the CSS & Javascript.

Other Ways

This isn’t the only way to conditionally include Fancybox’s Javascript & CSS. Instead of using jb_hunt_for_fancybox() to filter the_content there’s probably an action or filter in the gallery shortcode that jb_fancybox_is_used() could be hooked onto. It may even be possible to use the $wp_query object in an action hook just before wp_enqueue_scripts to determine if there is content on the page that needs to be Fancyboxed, let that decide whether or not to run jb_dequeue_fancybox_css_js(), and forget about the other two functions.

Let’s Do Better

Plugin authors should be working hard to only add what is needed to each page load. Who is doing a great job? How can we hack our themes to bend other plugins to our will? Comment or tweet @johnbeales to let me know.

Generating Form Elements with Javascript in IE 10

There’s a DOM manipulation gotcha in IE10 that just got me, and Google didn’t help much, so I’m giving Google something to show anyone who has this problem in the future.

When you dynamically generate a form input in Internet Explorer 10 the you must set the type attribute before doing anything else to the element, otherwise any values you set will be ignored when submitting the form, and in some cases will not be displayed. When inspecting elements using IE’s developer tools, however, the correct value appears in the generated document tree.

So this works as expected:

var sub = document.createElement('input');
sub.setAttribute('type', 'submit');
sub.setAttribute('value', 'Submit Generated Button');

But this doesn’t:

var sub = document.createElement('input');
sub.setAttribute('value', 'Submit Generated Button');
sub.setAttribute('type', 'submit');

This is particularly hard to catch with radio buttons and checkboxes, (this is what got me). Their default value is “on” which doesn’t tell me much, especially if you’re submitting an array of them.

Here’s a demo. The first button shows its value, (and submits its value), and the second shows, (and submits) the default of “Submit Query.”

The only documentation I found on this behaviour is a passing sentence in the createElement documentation at MSDN. When they say “then set the type property to the appropriate value in the next line of code” they’re serious about the next line of code.

Also, who chooses “Submit Query” as a default value for a submit button in 2013 anyway? Are they trying to confuse people? Shouldn’t it just be “Submit”?

Does anyone use PayPal integration in Appointment Booking WordPress Plugins?

I’m working on my own branch of an appointment booking plugin for WordPress for a client site. It works great, but there’s more to do.

The original plugin has an option to require payment via PayPal when someone books an appointment. I want to know if people use this feature.

For those of you who use a plugin to accept appointment bookings through your WordPress site, (or one you develop), do you use PayPal integration to accept payment for those appointments?

Bonus Questions: Do you use some other payment provider to accept payments for appointments? Which appointment booking plugin do you use?

Please leave a comment below, or E-mail me via my contact form.

Thanks!

Boil Water Advisory: A Lack of Timely Communication in Montreal

TL;DR

My water was brown this morning. It took over an hour and a half for me to find out if it was safe to drink, wash, or anything else with it. We can do better. When something goes wrong that affects your customers, let them know what to expect!

The Long Version

Around 8:30 this morning I turned on the tap to find the water was the colour of weak coffee. Gross. My thoughts went something like this: Is it safe to drink? How about wash my baby’s face, which at the time was covered in banana & oatmeal? Who knows. Is it just my apartment? Can I shower? Do I want to shower in brown water even if it is safe?

Twitter confirmed that it wasn’t just my apartment:

The Montreal website, Montreal Twitter, and Montreal Twitter Account all had no indication of what was going on, and when I called 311 I got a “We can’t answer your call, please call back later” message! Total communication breakdown. Was it safe wash my baby’s face?

Around 9 311 actually answered and they told me that there is a problem at the water treatment plant affecting my whole borough, (later I discovered it’s both my borough and the neighbouring one, for a combined population of 135,682 people). Update (11 AM): As I was writing this the boil water advisory was expanded to cover most of the city. I was told to boil water for 10 minutes. Then, at 9:04 Montreal finally tweeted:

Ok, what about washing my baby’s face? (Actually, he’d been washed at this point using some water from the Brita).

20 minutes later the phone rang and a recording told me a boil water advisory was in effect, and I should boil water for 1 minute. So, is it 1 minute or 10? At this point I want a page on the city website that tells me what I should do with my water when there’s a boil water advisory. It turns out there is one, but I found out about it because it was posted to Facebook, not from any official source, (it looks like they think 1 minute of boiling is sufficient).

Finally, around 10, Montreal tweeted a link to it as well:

It took me way too long to find out what was going on this morning. It should have been different.

Make it right next time

It is not hard get information to people who are looking for it. Next time there’s a boil water advisory, (or any emergency), I recommend:

  • Posting to social media immediately. This is super easy, there’s no code to write on a website, (don’t wait until the social media person comes in at 9).
  • Updating the city and/or borough websites ASAP. Yes, this may require someone to write HTML, that’s why you just posted to your social media accounts.
  • Linking to the “What to do in a Boil Water Advisory” document in your initial social media post, or in another post 2 seconds later. Don’t wait an hour to link to that document.

Conclusion

This was a preventative boil water advisory. Hopefully the city will make a better effort in the case of a more severe emergency.

Announcing DeadTrees

Today I’m releasing DeadTrees, a WordPress plugin to share the books you read. Get it from wordpress.org or search for DeadTrees in the Plugins > Add section of your WordPress admin.

Features

DeadTrees lets you post the books you read, with or without writing about them, (really, does the internet need to know what you thought of the last mystery you read?). It generates Amazon affiliate links to those books so you, (or I), can make a little money if your readers buy the books, and it auto-fetches the books’ cover art from Amazon so things look cool.

Why?

I have been posting about books that I read for a while now, but ground to a halt when I got lazy & didn’t want to write a whole post about each book, and realized often it doesn’t matter what I think about a book. However, I did want to keep posting at least the te title & author of each book I read, (and so my sister can check to see what I’ve read before giving me a book).

Why write a plugin when there are other plugins to share the books I read? Because the other plugins didn’t do it how I wanted them to. I couldn’t find another plugin that uses WordPress’s Custom Post Types to store books I’ve read, and books are such a perfect use of CPTs that they’re even used as the example in the WordPress documentation!

Support & All That

I’ve put DeadTrees up at GitHub, if you have issues try to submit them there. My contact page is also always available to reach me.

See It Live

DeadTrees is up & running here. Take a look at the books I’ve read.