A common problem in WordPress is removing a hooked action (via remove_action
) too soon, i.e. before the original action is added via add_action
. The Beans framework solves this problem through its Actions API. It’s a major enhancement to the WordPress Plugin API. Let me show you.
Spoiler Alert
In this insight, I’ll demonstrate the awesomeness of Beans by relocating the primary navigation menu from the right of the site header to after the header…in one line of code.
Seriously, haven’t you ever wanted to just modify a hooked action instead of having to remove and the add it back in? Sure you have. We do it all the time. Beans makes that possible, saving you so much time, frustration, and project costs.
Have you ever been frustrated trying to figure out why the thing you are trying to remove is not being removed? You check your code and you have the right remove_action
with the right event name, callback, and priority. But still, it’s not being removed.
You’re not alone, as this drives me crazy all the time. Seriously, I’ve spent far too much time searching to figure out when to remove an action and what pre-conditions I need in order to do it. Argh!
But then I wrote the unit and integration tests for the Beans framework and found that it solves this problem.
A Practical Example – Relocate the Primary Menu Using the Traditional WordPress Way
It’s a common practice to remove or reposition some component in a theme or plugin. For example, let’s say you wanted to move the primary navigation menu. In a Beans-powered project, that menu is to the site’s header and floats to the right on a non-mobile device. In Genesis, we call this sidebar area “Header Right”.
How would you do this? Think about the steps you would go through in order to relocate the menu. You’d:
- Find where the menu is being built and rendered in the code.
- Figure out when that happens.
- Remove the original hooked action with
remove_action
. - Figure out the action event’s name where you want to relocate the menu.
- Add the new location by via
add_action
.
This is the traditional methodology to relocating the menu. Let’s walk through these steps together.
Step 1: Find it
In Beans, the menu is added in the menu fragment (click here to view it on GitHub) with this line of code:
beans_add_smart_action( 'beans_header', 'beans_primary_menu', 15 );
From this line of code, what do we know?
- The menu is being built and rendered in Beans.
- The callback is
beans_primary_menu()
. - The action event’s name is
'beans_header'
. - Its priority is 15.
What would you do now? Let’s try to remove it.
Showing the Problem: Let’s Try to Remove It
In WordPress, we use remove_action
to remove a hooked callback from an action event. Hmm, I wonder if that will work for us.
To demonstrate this, let’s just work in the child theme’s functions.php
file. I’m using the Beans starter child theme, which you can download for free by clicking here. Put this line of code at the bottom of the line:
remove_action( 'beans_header', 'beans_primary_menu', 15 );
Did it work? Refresh your browser. Nope, it didn’t work. Why?
To discover why, let’s see which file loads first, i.e. functions.php
or the menu fragment’s file. To do this, if you have Kint, you can use ddd()
; else, use var_dump()
and then die()
.
Aha, the child theme’s functions.php
loads first…before Beans loads the menu fragment file.
Why? WordPress calls your theme’s functions.php
first, as it’s purpose is to bootstrap your theme before WordPress loads the template.
That means we have a timing issue. It happens when we remove the hooked action before the original was ever added. Why? It has not been added yet. So we are trying to remove it too early. I explain this timing issue in this article.
Bummer, let’s keep going to fix it.
Traditional Step 2: Find the When
Without using Bean’s Action API, before we remove, we have to:
- find when the original action is added.
- then build code that waits until after the original action is added before running our removal code (oh yippee, more code).
So where do we start? Let’s figure out when the menu fragment is loaded.
I’ll skip ahead and tell you that it happens when WordPress does a template redirect (click here to see the code). Therefore, our “when” pre-condition is the 'template_redirect'
action event in WordPress. Let’s try that.
Replace the code above in your child theme with this new code:
add_action( 'template_redirect', 'beans_child_relocate_primary_menu' ); /** * Relocate the primary menu to after the site header. * * @since 1.0.0 * * @return void */ function beans_child_relocate_primary_menu() { remove_action( 'beans_header', 'beans_primary_menu', 15 ); }
Refresh your browser. Did it remove the menu? Yes. That worked! WooHoo!
Therefore, the code snippet above waits until WordPress fires the 'template_redirect'
action event. Then we remove the menu from the header.
I bet at this point, you’re wishing there was a way to just modify the action’s event name to save all of these steps. Am I right? Well, hold on, there is. Beans has it baked right in and it’s amazing!
Step 3: Find Where to Relocate
The next step is to figure out the action event’s name where you want to relocate the menu. We want to relocate the menu to be after the site’s header. The action event you want is 'beans_header_after_markup'
, where the '_after_markup'
occurs after the closing </header>
tag.
Step 4: Add the New Location’s Action Hook
Next, we need to add the menu’s callback to this new location. Add this line of code after the remove above:
add_action( 'template_redirect', 'beans_child_relocate_primary_menu' ); /** * Relocate the primary menu to after the site header. * * @since 1.0.0 * * @return void */ function beans_child_relocate_primary_menu() { remove_action( 'beans_header', 'beans_primary_menu', 15 ); add_action( 'beans_header_after_markup', 'beans_primary_menu' ); }
When refreshing your browser, you’ll find that it does not appear, but it is in the HTML markup after the header.
It’s not visually appearing because there is a UIKit class attribute that we want to remove.
To remove this attribute, put this code after the above code:
add_filter( 'beans_primary_menu_attributes', 'beans_child_change_primary_menu_class' ); /** * Modify the primary menu's class attributes. * * @since 1.0.0 * * @param array $attributes Array of markup attributes. * * @return array */ function beans_child_change_primary_menu_class( array $attributes ) { $attributes['class'] = 'tm-primary-menu uk-navbar'; return $attributes; }
Success! But there’s an easier way. And it will blow your mind.
The Beans Way to Relocate the Primary Menu
Ready to see how awesome Beans’ Actions API is? You’ll wonder how you ever lived with it. Just saying….
The problem with the code above is you had to create a new function which was hooked to when the menu’s fragment was loaded in order to relocate the menu. That’s too much code. Beans gets rid of that timing issue so that you don’t need that function.
You can use beans_modify_action()
like this:
beans_modify_action( 'beans_primary_menu', 'beans_header_after_markup', null, 10 );
This one line of code replaces hooking into 'template_redirect'
. One line of code and Beans handles:
- Removing the original action when it gets added.
- Adding the new action.
It literally modifies any of the parameters. In this code, we are modifying the action event’s name and the priority. Bam, so much easier for you! Less code. Less frustrations.
Refactor the Attributes
Let’s do one more refactor to make this code even more simple. As Didou pointed out in the comments below (way to go, Didou!), we can replace the the beans_child_change_primary_menu_class
callback and add_filter
code with one single line of code:
beans_remove_attribute( 'beans_primary_menu', 'class', 'uk-float-right' );
Wrap it Up
The Beans Actions API is so awesome that in Beans 2, we will be expanding it to include the filters. It just makes your code so much easier. And in my humble opinion, it is the missing functionality for hooks in WordPress.
-
Click here to get the final code
Resources for you:
beans_modify_action()
– Beans Official Documentation- Beans Theme Developer Roadmap
badabingbreda says
Ok, so now I’m curious to why you use a filter to output the classes you want to keep, instead of removing the class you want to remove. Wouldn’t you use
beans_remove_attribute( ‘beans_primary_menu’ , ‘class’ , ‘uk-float-right’);
to do that on one line of code?
Tonya Mork says
Bravo, Didou!
I was hoping someone would catch that and find a better way to remove the class attribute. You did it! Bravo!
I updated the article with a Refactor section.
Cheers,
Tonya