Update: 26/06/2010
Post has been updated to fix a flaw. Huge thanks to Kathy for helping with finding & fixing it.
Original Post
I recently recieved an e-mail asking if I could help make an admin for a theme that was powered by AJAX & therefore did not need the page to be refreshed to save the data. Unfortunately I couldn’t help as although I have built admins for plugins & themes before, I didn’t have the knowledge to add the AJAX functionality to it. However I’ve spent the past day or so reading & trying to understand the theory behind AJAX usage within WordPress’ admin & I think I’ve managed to come up with something.
Good For You! How Does That Help Me?
Well I wanted to understand it so I could write a simple tutorial for those who are getting into theme development & want to add that little bit of ‘je ne sais quoi’ to the admin of a theme.
So How Do We Start?
We add our admin page link to WordPress’ menu. Since we are going to add code into the header we need to do a few things different to a normal admin page or the header code will be output to all of the pages in the WordPress admin. Exactly what we don’t want since it is useless for anything other than running our admin page.
Here is the code to add your page into the WordPress admin:
add_action('admin_menu', 'test_add_theme_page');
function test_add_theme_page() {
if ( isset( $_GET['page'] ) && $_GET['page'] == basename(__FILE__) ) {
add_action('admin_head', 'test_theme_page_head');
}
add_theme_page(__('Test Admin'), __('Test Admin'), 'edit_themes', basename(__FILE__), 'test_theme_page');
}
First we call add_action() this tells WP to run a function into the admin_menu hook. The function test_add_theme_page, change the name if you like (just make sure they match), checks that the admin page we are on is actually the current file. If it is, we can add the code into the header (code coming in a minute) using the admin_head hook. No matter what page we are on we still need to add the link for the admin page into the admin menu so we run the function add_theme_page(). I explained the useage in my other tutorial, but just in case here it is again.
- __(‘Test Admin’)
- This is the text shown as the browser title, that’s the text shown in the title bar of your browser.
- __(‘Test Admin’)
- This is the text shown as the link title in the WordPress menu. It will appear under the Appearance tab
- edit_theme
- This is the role required to access the page.
- basename(__FILE__)
- This is a unique identifier. I’ve used the current file’s filename.
- test_theme_page
- The function that will create the contents of the options page.
Adding the jQuery
As I mentioned earlier add_action('admin_head', 'test_theme_page_head'); adds code into the header of the page. This is what we will use to add our jQuery code. Here is the function I have used:
function test_theme_page_head() {
?>
<script type="text/javascript">
jQuery(document).ready(function($) {
jQuery('form#test_form').submit(function() {
var data = jQuery(this).serialize();
jQuery.post(ajaxurl, data, function(response) {
if(response == 1) {
show_message(1);
t = setTimeout('fade_message()', 2000);
} else {
show_message(2);
t = setTimeout('fade_message()', 2000);
}
});
return false;
});
});
function show_message(n) {
if(n == 1) {
jQuery('#saved').html('<div id="message" class="updated fade"><p><strong><?php _e('Options saved.'); ?></strong></p></div>').show();
} else {
jQuery('#saved').html('<div id="message" class="error fade"><p><strong><?php _e('Options could not be saved.'); ?></strong></p></div>').show();
}
}
function fade_message() {
jQuery('#saved').fadeOut(1000);
clearTimeout(t);
}
</script>
<?php
}
Ok, I’m going to attempt to explain everything written here. It might take a while so bare with me.
First we define the function we named in our add_action() call. We then break out of PHP so we can easily add our jQuery code. We open a script tag as normal, then we start our jQuery with a standard run on DOM load. I’ve passed the $ through the run on DOM function so you should be able to use the standard $ inside it, however I chose to continue using the jQuery that WordPress defines for conflict prevention with other JS libraries.
Next we target our form, which hasn’t been made yet, so pick an ID or something here for it, and attach a function to it’s submit event. We serialize the data from the form, this excludes the submit buttons value & encodes data in standard URL query format, storing it in the variable data. We can use jQuery(this) because we are inside the element that is holding the data, in this case the form. Next we create a standard AJAX call using jQuery’s jQuery.post function. ajaxurl is a javascript global variable defined by WordPress & should always be used for AJAX requests inside WordPress for security reasons. data is our serialized form data & the the function is to be ran on the completion (callback) of our AJAX request. response is the data returned from the AJAX request.
Inside the callback we check the response data to see if it contains 1. This will be sent back by our AJAX function we will create later, if it is 1 we take it as meaning true & show an options saved box, if it is anything else we show an options not saved box. The message boxes are simple jQuery that show a message in styling defined by WordPress & then use a simple setTimeout & clearTimeout to show & hide the boxes. I’m not going to add to the length of this tutorial by explaining them, if you want to know more about them leave a comment & I’ll explain more about the functions. We finally use return false in the submit event to stop the form from submitting and refreshing the page.
Creating The HTML Form
Next we are going to create the form needed for the data to be inserted by the user. My form isn’t pretty & does absolutely nothing, by that I mean although it saves data the data isn’t used in any way. Here is my code, remember that this function is used by the add_theme_page function we called earlier:
function test_theme_page() {
?>
<div class="wrap">
<h2><?php _e('Test Admin'); ?></h2>
<div id="saved"></div>
<?php $options = get_option('test_themes'); ?>
<form action="/" name="test_form" id="test_form">
<input type="text" name="test_text" value="<?php echo $options['test_text']; ?>" /><br />
<input type="checkbox" name="test_check" <?php echo ($options['test-check'] == 'on') ? 'checked' : ''; ?> /><br />
<input type="hidden" name="action" value="test_theme_data_save" />
<input type="hidden" name="security" value="<?php echo wp_create_nonce('test-theme-data'); ?>" />
<input type="submit" value="Submit" />
</form>
</div>
<?php
}
The is the function that creates the page you see when you visit the admin page. Although you can use the header section to add your own CSS code I have just used the default WordPress CSS. That is why the HTML starts with a div with the class of wrap.
The form is the important part. You must give it a name, and the ID must be the same as the one you used in the jQuery submit event function earlier. The interior of the form can include anything your theme requires, however you need 2 extra input fields. These fields are hidden ones that send required data for the AJAX to work correctly. First is the action hidden field, the value should be whatever you want WordPress to call it’s hook that will be used to hook the function we will make to get results from the AJAX call. The other hidden field security is the infamous nonce code used by WordPress for security. The value must be as shown however you can change the word inside wp_create_nonce() to anything you like (preferable something relevent to your theme), remember it though as we’ll need it later.
We have also added in some PHP to recall the options saved in the database. We simply store the options into a variable & then echo out the data into the value fields. In the case of checkboxes we check for the value on & the add ‘checked’ if it is detected. I’ve used a ternary as it is shorter when writing PHP inline with HTML.
Creating The PHP AJAX Function
Ok so we are nearly done. We just have to create the function called by jQuery, so here’s what I ended up with:
add_action('wp_ajax_test_theme_data_save', 'test_theme_save_ajax');
function test_theme_save_ajax() {
check_ajax_referer('test-theme-data', 'security');
$data = $_POST;
unset($data['security'], $data['action']);
if(!is_array(get_option('test_theme'))) {
$options = array();
} else {
$options = get_option('test_theme');
}
if(!empty($data)) {
$diff = array_diff($options, $data);
$diff2 = array_diff($data, $options);
$diff = array_merge($diff, $diff2);
} else {
$diff = array();
}
if(!empty($diff)) {
if(update_option('test_theme', $data)) {
die('1');
} else {
die('0');
}
} else {
die('1');
}
}
First is add_action the name of this hook was determined by the value of our action field in our form, you just prepend wp_ajax_ to the front of it. The function can be called anything you like again, but try to keep them all relevent to your theme.
Now onto the actual function. First we use a function defined by WordPress to check if the nonce key we received is active & valid. This is where you need to recall that string I told you to remember as it has to be written in the first parameter identically to the one you wrote in wp_create_nonce(). If like me you haven’t used the default post value $_POST['_ajax_nonce'] you will need to tell the function what the $_POST key for the nonce code is, in our case security is the code. This is gotten from the name parameter of our hidden form field. Next we transfer our data from the $_POST global array to a variable called $data, we unset both the nonce key & our action which are passed by the form & are no longer needed. One thing to note is that if check_ajax_referer() does not validate the code it will die outputting ‘-1′, so when debuging if you receive that value you know what is doing it.
Next it gets a little strange. I don’t know if it is a bug or not, but if the values sent from the form are the same as the current database values update_option() will return as if it failed, so we need to screen the options first. We check to see if the options in the database are the same as the once recieved from the form submission by checking for differences using array_diff(). To get the differences correctly we check both arrays against each other & then merge the results together. We also set a blank array if there is no data sent to trigger our jQuery error. If $diff is not an empty array we have differences and can safely save the options. If the update is successful stop PHP processing & echo out 1 to our jQuery function. If it fails then stop processing & echo 0 to our jQuery function. Else if the variable $diff is empty we do not need to update the database since the options are identical so just stop processing & echo out 1.
A quick note about update_option(). You can change the part in the parentheses to anything you like, but try to make it unique & relevent to your theme in some way. Also you may wonder how it is storing an array in a single database field, this is because update_option() auto serializes an array before saving it. In the same vein, get_option() unserializes a serialized array back into a normal array when it grabs data. These are important things to remember when using both these functions.
We’ll that is it, I think. I don’t think I’ve missed anything. I’ve been at this post so long I’ve lost feeling in my legs.
I hope this post helps you, despite it ending up a lot more complicated that I’d wanted it to. You may need to read the post a few times to get a proper understanding of what is going on. If you have any problems or notice something I’ve missed please, please, please email me or leave me a comment & I’ll sort it out as quickly as I can.
Thank you for reading, good luck, and I hope I haven’t made your brain explode.
Discussion: 77 Comments
the only thing i did was piece your snippets together and add the alert.
http://pastebin.com/dfmAjTiU
here is the info i am getting from teh alrt:
test_text=&test_check=on&action=test_theme_data&security=4e1882cb80
whick doesn’t look anything like a 0 or -1 like you mentioned. hmmm.
I apologise for being a complete idiot. I told you to alert the wrong thing. You need to alert
responsenotdata.Thanks for the pastebin I’ll have a check over it. Let me know what response comes out with & I’ll get back to you.
Here is what I get:
Warning: array_diff() [function.array-diff]:Argument #1 is not an array in D:\helga\xampp\htdocs\plagueround\wp-content\themes\thematic_options_me\functions\test-admin.php on line 111 1
this is line 111:
$diff = array_diff(get_option(‘test_theme’), $data);
Is that error appearing when you click the button or when you load the page? I’m not sure why changing the value that the alert gives back would cause an error?
that is what i get when i click on the submit button… so i presume that is what i am getting for my ‘response’ variable. then once i click ok on the popup i get the fading message that my options could not be saved. do you have a working sample you’d be willing to pastebin? that might be easier than trying to figure out where i’ve gone astray.
That’s very odd. Unfortunately I don’t, the only code I have is for a theme I built & it has theme specific stuff tied into it.
You could try changing the code slightly to cope with the error. Try this:
if(!is_array(get_option('test_theme'))) { $options = array(); } else { $options = get_option('test_theme'); } $diff = array_diff($options, $data);Let me know how that goes.
Hi again Paul! Well I tried your latest tweak. There seems to be some progress. the alert(response) coughs up a 1 and i get the faded message that my options were saved. however, i don’t see them in the database and i draw a blank when i try to echo them back out…. with get_option(‘test_text’)
doing a bit of a manual labor project at the moment and all the time in the sun is zapping my brain power. i appreciate all your help with this.
Yeah this heat is driving me mad. I don’t mind it during the day, it on a night time that bothers me.
Well that means we are getting past the security check & the options are being retrieved, but for some reason they aren’t being saved. Due to some strange problem with update_option you need to check if it’s changed from the original value or it fails to update.
I thought the if…else at the end there did that, but for some reason it doesn’t seem to be doing the job? I’ll have a play around with it and see if I can find out what’s going on.
hi again,
i “think” i have at least isolated the problem down to array_diff(). From what I understand, array_diff checks the first array against the second array and returns as a result an array of things from the first array that are NOT in array 2.
BUT, if array1 is null… such as when the theme is first activated as well as at other times, then array_diff returns a null array and so nothing is ever updated to the database. i tried flipping the 2 around like array_diff($data,$options) and that got some things into the database, except when i went to uncheck the checkbox and save that… it wouldn’t work b/c if the checkbox is unmarked then there is no test_check is the $data.
still working…
ps- i meant to ask if you have any sort of failsafe in case a user does not have javascript enabled?
Hmm! No I hadn’t thought about that. I would say set the forms action to the php page that AJAX contacts. In that case the data will be sent as if it was being sent by AJAX.
I think I remember having a problem like you mentioned and I thought I’d fixed it.
I’ll have a look at the version I mentioned before and see if I can find how I got around it.
Can you please try this bit of code. If it works I’ll update the post. I fixed this a little while ago & it looks like I didn’t update the post.
I apologize.
//Find this line $diff = array_diff(get_option('test_theme'), $data); //Replace with this if(!empty($data)) { $diff = array_diff(get_option('test_theme'), $data); $diff2 = array_diff($data, get_option('test_theme')); $diff = array_merge($diff, $diff2); } else { $diff = array('dummy' => 'dummy'); }The actual saving part would work it’s just the bit that checks to see if the options are different isn’t. Hopefully that will fix it. I have tested it & it seems to work.
Let me know how it goes.
we might have it!? i had to blend your last 2 suggestions together and now my whole function looks like:
http://pastebin.com/0WJsfsw7
this seems to work. i get options showing in the database. i can echo them back. i can delete the whole field from my database and it re-creates on the next save.
now… on to applying this to all my options! thank you so much for all the help.
i am sure that you know how to do this, but if you are going to update the post it might be nice to show up to get the data back out of the array for use in the theme, etc. for instance this would be how to show the current values in the form:
<?php $options = get_option('test_theme');?> <input type="text" name="test_text" value="<?php echo $options[test_text];?>"/><br /> <input type="checkbox" name="test_check" " <?php if($options[test_check]=='on'){echo 'checked';} ?>/><br />Ahhh yes. No worries. Also nice job on that addition to the code. I actually had that in & forgot to copy it over as part of the replacement. Doh!
I apologize for all the problems I’m assuming you’re in the UK (from your sun comment before), I think the heat has just killed my brain.
I’ll update the post tomorrow (well today, lol) and I’ll add in how to return the values back. Which I should have done in the first place.
nope… across the pond in ‘the colonies’, but we’ve (at least my area) been getting killed by the sun too.
yours is the only tutorial on ajaxifying theme options so it was a great find… and even better since you are up in the wee hours of the morning to help get it sorted when we both ought to be down the pub on a friday night.
thanks for everything!
Ooops. Haha. Yeah we don’t normally get much sun even in the summer but it’s been hitting 30 degrees. The problem Is we aren’t used to it so it kills us. We are all used to the crappy weather. Hehe.
I didn’t realise the was the only tutorial on how to ajaxify a WP them admin. I’ll go through it tomorrow & clean it up a bit.
Nah, not tonight. I’m saving all my drinking points for the England Vs Germany match on Sunday. Not usually a football fan but I make an exception for the world cup.
Don’t worry about it, always glad to help. Nod it’s 2:37 in the morning here so I’m of to bed.
Okay the post has been updated to include the updated information. I’ve also added the code on how to pull the options back into the form.
hi again paul. just dropping back in to point out an error in your new code.
get_options should be get_option otherwise you get a fatal call to an undefined function
here is a pastebin of all the pieces smushed together for anyone who needs it:
http://pastebin.com/vfxiPyMv
too bad about our footie teams.
Bollocks… That’s what you get for touch typing & then not proof reading your code.
I’ve fixed it now.
Yeah, it’s not like we can be suprised about the football though really.