AJAX in WordPress

Note: ‘AJAX’ simply meaning ‘getting data via JavaScript’, not Jesse James Garrett’s more holistic approach.

For a project I’ve been working on I needed a way to easily grab data from the backend without reloading the page. I didn’t want to fight with any of the big AJAX plugins, as the ones I looked into seemed to have their own complicated approaches involving magic markup or pre-formatted output, or didn’t seem to work. I also wasn’t interested in spending the time to learn about writing my own plugin.

After some hunting I came across a explanation (dead; temporary cache) stolen from another site (also dead) that led me to my eventual solution.

The essence is as follows: if you want to dynamically pull in, say, search results, add the following to the top of your search.php file:

<?php
  if ('1' == $_GET['ajax']) {
?>
<ul>
<? php
    if (have_posts()) {
      while (have_posts()) : the_post();
?>
 <li id='post-<?php the_ID() ?>'><a href='<?php the_permalink() ?>'><?php the_title() ?></a></li>
<?php
      endwhile;
    } else {
?>
 <li class='empty'>No results.</li>
<?php
    }
?>
</ul>
<?php
  } else {
?>

(See also: simplified WordPress post loop)

This means that if you load /index.php?s=foo&ajax=1 you might get something like:

<ul>
 <li id='post-5'><a href='http://example.com/?p=5'>foo baz!</a></li>
 <li id='post-3'><a href='http://example.com/?p=3'>foo bar!</a></li>
</ul>

Let’s hook it up

Assume our search form’s search box has the id #s (as is standard for WordPress). With the assistance of Andreas Lagerkvist’s liveSearch jQuery plugin (also dead currently, but code here) we can get a dynamically-updated list of search results.

$("#s").liveSearch({url: 'index.php?ajax=1&s='});

Make it multi-purpose

With a slight adaptation, the above approach can be used to get various other data out. For example, if we use WordPress’ built-in wp_list_categories() function…

<div id='categories'>
 <?php wp_list_categories() ?>
 <div id='in_category'></div>
</div>

The wp_list_categories() function will give markup like the following:

<li class='categories'>Categories
<ul>
 <li class='cat-item cat-item-3'><a href='http://example.com/?cat=3' title='View all posts filed under Demo'>Demo</a></li>
 <li class='cat-item cat-item-2'><a href='http://example.com/?cat=2' title='View all posts filed under Sample'>Sample</a></li>
 <li class='cat-item cat-item-1'><a href='http://example.com/?cat=1' title='View all posts filed under Uncategorized'>Uncategorized</a></li>
</ul>
</li>

If we copy the PHP we used for our search results into the top of the category.php file, we can pull in a list of a given category’s entries:

$(".categories .cat-item a").click(function () {
  theURL = $(this).attr("href") + '?ajax=1';
  $.get(theURL), function (data) {
    $("#in_category").html(data);
    return false;
  });
});

This means that if the user clicks a link for a category, instead of going to the actual page it’ll get the link’s address, tack on our ajax parameter, and stick the resultant data into the #in_category <div>. (return false cancels the click event, thus preventing the link from clicking through.)

A complication involving nice permalinks

All the above examples have assumed that the WordPress installation is using the default permalink structure (i.e. ?p=n for posts, ?cat=n for categories, and so on). Thus we used &ajax=1, because we our pages will have already have something like ?cat=3. But this assumption breaks the moment the user switches away from the default permalink structure, because our links will now have the form /category/uncategorized. Note: the old URLs will still work, they just won’t be exposed — so our search code is unaffected.

So what can we do?

Well, the easiest solution is to simply check if the URL we’re working with has a ?:

if (undefined === $(this).attr("href").split('?')[1]) {
  // using nice permalinks
} else {
  // default permalinks
}

This works by splitting the URL string into an array, using ? as a separator. Nice permalinks won’t have a question mark, so the array will only have one element (remember, arrays are zero-indexed).

Rather than repeating this code everywhere, we can write a little function that simplifies the process:

var ajaxURL = function (url) {
  var token = (undefined === url.split('?')[1] ? '?' : '&';
  return url + token + 'ajax=1';
}

And we adapt our jQuery to:

$(".categories .cat-item a").click(function () {
  theURL = ajaxURL($(this).attr("href"));
  $.get(theURL), function (data) {
    $("#in_category").html(data);
    return false;
  });
});

Finally

This approach of listening for ajax=1 will work on probably any page type. In the project that inspired this entry, I’m using it for searching, categories, pages, and date-based archives.

written 19 January, 02010 Comments

Follow-up on vendor-specific CSS and the DOM

Yesterday I wrote about my difficulty in getting certain CSS properties that I needed for my code. Today I discovered the solution.

After unsuccessfully poking around at a simplified version of my code, I decided to try looking at the complete list of the relevant element’s DOM properties, to see if I was simply asking for the wrong thing. I quickly discovered that element.style doesn’t give back an array; instead, it gives a CSSStyleDeclaration.

This wasn’t much use, so instead I went to the ever-helpful DOM reference provided by Mozilla. I hopped to the reference for element.style, and from there to window.getComputedStyle. Here I found what I wanted:

var element = document.getElementById("elemId");
var style = document.defaultView.getComputedStyle(element, pseudoElt).getPropertyValue(property);

This was too unwieldy to be used three different times (column-gap and variants), so I made slight rewrite and put it in my dom namespace:

var dom = {
  …

  getCSSValue: function(element, property) {
    var cs = document.defaultView.getComputedStyle(element, null);
    return cs.getPropertyValue(property);
  }
}

Now I can simply call dom.getCSSValue(paged, '-webkit-column-gap'); and move on.

written 8 April, 02009 Comments

How can I get vendor-specific CSS properties from the DOM?

When I first released pagering, I wrote:

It’s not bug free — the ‘pages’ don’t all appear at the same spot, and so on — but it’s written, and it works. Now I can move on to making it better.

Today I decided to work on making it better, and quickly figured out why the ‘pages’ render inconsistently. It turns out there are three variables involved:

  1. the width of the ‘paged’ element in the CSS box model;
  2. the padding of the element that holds the paged element; and
  3. the CSScolumn-gap of the paged element.

I was only accounting for the first one. The second was easy to add, but the third is proving quite difficult. Currently, column-gap has only experimental implementations (in the WebKit and Gecko engines), so I can’t simply test against column-gap; I also have to check -webkit-column-gap and -moz-column-gap.

Fortunately, this much works — I can test against undefined and get a result from the different browsers. Unfortunately, that result is always a null string. This means that it doesn’t matter whether I’ve specified something like -webkit-column-gap: 2em; I’ll always get the same unhelpful answer. Here’s the code I’m using:

if (text.style.columnGap !== undefined) {
  columnGap = text.style.columnGap;
} else if (text.style.webkitColumnGap !== undefined) {
  columnGap = text.style.webkitColumnGap;
} else if (text.style.MozColumnGap !== undefined) {
  columnGap = text.style.MozColumnGap;
}

alert(columnGap.length); // always 0

Without knowing the column gap, I can’t entirely fix the rendering problem.

This brings us back to the entry’s title: how can I get vendor-specific CSS properties from the DOM? Have I overlooked something simple?

Update: I found a solution! Read about it here.

written 7 April, 02009 Comments

Using twitter_oauth from the Command Prompt

Several days ago I began work on Turpentine, a Twitter client written in Ruby that currently runs as a CLI. After I got things working to my satisfaction, I decided to add OAuth — partly because the whole project is meant to give me exposure to a variety of stuff I’m not familiar with, and partly because Twitter will remove basic auth at some point in the future.

After going through the Ruby on Rails example, I got stuck at the post-authorization point — the example assumes the user will be in a browser, which obviously doesn’t work for me at the command prompt.

I tried to figure out the solution using the sample code and the OAuth gem it uses, but it was beyond my level. (Though I did learn that all the OAuth information is passed around using query strings.) At this point I decided I’d give up on trying to write the code myself, and switched over to the twitter_oauth gem. The sample code was tremendously helpful, and needed only a little modification to handle my scenario.


Because I’m working purely in Ruby in the moment, I’ve kept things simple by using YAML to manage login information.

Step one is to load the config.

CONFIG_FILE = 'config.yaml'
CONFIG = YAML::load(File.read(CONFIG_FILE))

The relevant part of the config looks like this:

oauth:
    consumer_key:
    consumer_secret:
    request_token: 
    request_secret:

I’ve registered my application for Oauth, so I’ve filled in the consumer_key and consumer_secret fields. (Not shown because the secret must be kept secret.)

Note: if you’re going to use the command prompt like I am, specify that your application is a client, not a browser.

Back to code. I begin by loading the values:

consumer_key = CONFIG['oauth']['consumer_key']
consumer_secret = CONFIG['oauth']['consumer_secret']
request_token = CONFIG['oauth']['request_token']
request_secret = CONFIG['oauth']['request_secret']

Check if the application has been authorized (i.e. check if the request_ fields have any information):

if request_token.nil? and request_secret.nil?
  # not yet authorized
else
  # authorized
end

Authorization is the most code-heavy part of the process.

if request_token.nil? and request_secret.nil?
  client = TwitterOAuth::Client.new(
       :consumer_key => consumer_key,
       :consumer_secret => consumer_secret
       )
  request_token = client.request_token

  …

Here, we give twitter_oauth the application’s key and secret and let it work out the address that we’ll use to authorize Twitter access.

  …

  puts "Please open the following address in your browser to authorize this application:"
  puts "#{request_token.authorize_url}\n"

  puts "Hit enter when you have completed authorization."
  STDIN.gets

  …

The user is then prompted to open the address, which will ask if they really do want to authorize the application:

(Screenshot: Twitter asks if the application should be allowed access.)

Finally, we put the user’s new request token and secret into the configuration file:

  …

  access_token = client.authorize(
      request_token.token,
      request_token.secret
  )

  File.open(CONFIG_FILE, 'w') do |out|
    CONFIG['oauth']['request_token'] = access_token.token
    CONFIG['oauth']['request_secret'] = access_token.secret
    YAML::dump(CONFIG, out)
  end
else
…

Things are a lot simpler if we’re already registered:

…
else
  client = TwitterOAuth::Client.new(
    :consumer_key => consumer_key,
    :consumer_secret => consumer_secret,
    :token => request_token,
    :secret => request_secret
  )
end

Now that we have a functional login, we can use the gem’s built-in functionality to do useful things like get the friends timeline:

puts client.friends_timeline

And proceed as if everything used basic auth.

written 23 March, 02009 Comments

ricardo theme ported to WordPress

See it on GitHub.

The process took about two hours, including updating WordPress and fixing a configuration issue. There is some slight mismatch between the two, largely due to platform differences, but I was able to simply copy and paste a good deal of the code.

Issues

  • Is there a template tag to check if next/previous pagination will be generated? I don’t want to have unnecessary markup rendered. Tumblr allows me to do this by wrapping all my pagination code in {block:Pagination}…{/block:Pagination} tags, and the next/previous links in {block:NextPage}…{NextPage}…{/block:NextPage}. WordPress only seems to provide a way to generate the links directly with next_posts_link.
  • I’m unclear on exactly how to set up archives. The instructions in the Codex seem to be a version behind.
  • Bizarrely, the page renders differently when I use local CSS, even though it’s exactly the same as the remote copy I use with Tumblr. For now the style.css file is present simply because it’s required to give metadata.

written 4 March, 02009 Comments