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

Corrections to Auto-print Regex

Several months ago I published a regular expression that would strip auto-print links from webpages. Unfortunately, the regex doesn’t actually catch everything, so here’s a corrected version:

replace(/((window|self)\.print(\(\))?|print\(\));?/ig, "");

This will catch all the following:

print()
print();
self.print
self.print()
self.print();
window.print
window.print()
window.print();

Further, the addition of the g after the / means all instances of the print command will be replaced, rather than just the first, and the i means this will work regardless of whether or not the command is written entirely in lowercase text.

I’ve also noticed a separate issue that I’m still not sure how to resolve — if the rule is set to run on all content-types, it will blindly strip out instances regardless of whether they’re actually operational JavaScript (e.g. the above ‘all the following’ section); if it is set to only run on the JavaScript content-type it will miss any instances that don’t appear in a JavaScript file.

I suppose an even more complicated variation could be made that checks for things like javascript: or <script>…</script>, but it really begins to spiral out of control. If anybody has a solution for this problem, I’d love to know.

written 27 November, 02009 Comments

Quick Tip: Stop Auto-printing with GlimmerBlocker

As part of my high-volume web reading habits, I click through to the print versions of articles whenever possible. Annoyingly, sites will often use JavaScript to automatically make the page print. Here’s a simple way to stop it.

  1. Download and install GlimmerBlocker.
  2. Create a new Filter group (top left of preference pane).
  3. Add a new Rule.
  4. Set the Action to “Whitelist URL, optionally modifying content”.
  5. Set Host: to “all hosts”.
  6. Go to the transform tab.
  7. Paste in the following text: replace(/(window|self)\.print(\(\))?;/, "");
  8. Save the rule.

The regex here will look for window.print or self.print (with or without parentheses) and strip it, thereby stopping the automatic print command.

I forget exactly how I came up with this, but it works great.

UPDATE: an improved version is available.

written 13 August, 02009 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

pagering

Screenshot: pagering demo text

When I made Design-a-day #7 I said:

I like multi-column layouts, even though they don’t really fit with unpredictable screen height. I have an idea of how that could be fixed, but I want to try doing it before I talk about it.

For Design-a-day #8 I made a mockup of how I envisioned this looking.

Screenshot: design #8

Today I decided to try writing the code that would make the mockup actually run. It turned out to be pretty simple; I achieved full functionality within a few hours.

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.

So go ahead: grab the code!

written 26 March, 02009 Comments

quiettube as Custom CSS

There’s a bookmarklet going around called quiettube, which hides everything on YouTube’s video pages except the video. About a year ago I made a custom stylesheet that does the same thing, but never shared it.

CSS

Because I’m using Safari, I need this trick. The CSS file I use is simply named youtube.css. The stylesheet itself is fairly simple; it’s just a massive collection of stuff I want to be hidden:

#completeIFrame, #completeTable, #copyright, #footer, #masthead,
#search-advanced-form, #search-options-container, #search-pva,
#search-related-terms, #search-section-header, #searchFooterBox,
#translate-checkbox-span, #watch-buy-urls, #watch-this-vid-info,
#watch-other-vids, #watch-vid-title, #watch-video-main-area,
#watch-video-response,
.addtoQL90, .confirmBox, .marT10, .quicklist-inlist, .searchFooterBox,
.video-alt-query, .video-clear-list, .video-description, .video-facets,
.video-long-title, .video-short-title, .video-translation-links { display: none; }

But this really messes up how search results look, so there’s some lines to handle that:

#results-main-content {
 margin: 20px 0;
 width: 780px !important;
}

.video-cell {
 float: left;
 width: 49.5% !important;
}

.video-entry {
 margin: 10px 0;
 position: relative;
}

.video-time {
 left: 140px;
 position: absolute;
 top: 40px;
}

.video-title {
 left: 140px;
 position: absolute;
 top: 0;
}

… and a few rules to show hi-def videos in full glory:

body {
 background: #000;
 margin: 0;
}

#baseDiv {
 padding: 0 !important;
 width: auto !important;
}
#watch-this-vid.watch-wide-mode { width: 854px; }
#watch-this-vid.watch-wide-mode #watch-player-div { padding: 0 !important; }
#watch-this-vid.watch-wide-mode #watch-player-div #movie_player{
 height: 720px;
 width: 1280px;
}

The big downside to this is that every time YouTube changes their markup (once or twice a year) I need to rewrite the entire display: none; rule. But it’s definitely worth it to get this:

Screen capture of customized YouTube

A little further tweaking

In addition to the custom stylesheet, I use clicktoflash and another Greasemonkey script that forces everything to HD mode. The code is a variation of this userscript and another one I can’t find at the moment.

// ==UserScript==
// @name           720p YouTube
// @namespace      http://userscripts.org/users/23652
// @description    Forces YouTube to 720p mode
// @include        http://*.youtube.com/*
// @include        http://youtube.com/*
// @copyright      JoeSimmons/stilist
// ==/UserScript==

var quality = 22; // 6 = default; 18 = better; 22 = 720p

function setfmt(address) {
 if (address.indexOf('&fmt=') == -1)
  address += '&fmt=' + quality;
 else
  address = address.replace(/\&fmt=\d+/, '&fmt=' + quality);

 return address;
}

/* force hd quality at YouTube itself */
var loc = location.href;
var watch = /^http:\/\/(\w+\.)?youtube\.com\/watch\?v=.+/;
var isfmt = /\&fmt=22/;
if (watch.test(loc)) {
 if (isfmt.test(loc) === false) {
  location.href = setfmt(loc);
 }
}

/* fix links */
var l, ytlinks = document.evaluate("//a[contains(@href, 'watch?v=')]", document, null, 6, null);
for(var i=ytlinks.snapshotLength-1; i>=0; i--) {
 l = ytlinks.snapshotItem(i);
 l.href = setfmt(l.href);
}

What this does is:

  1. Force all video pages to 720p mode.
  2. Tweak links on the search results page to go directly to 720p mode.

Together, these customizations mean I get a high definition, distraction-free YouTube experience.

written 21 March, 02009 Comments

Update to Google Analytics Bookmarklet

Back on the fourteenth of January, I published a bookmarklet to jump to the current day’s stats in Analytics.

This originally used google.com rather than www.google.com. Somewhere around the ninth of February this stopped working. I initially thought this was due to a missing referrer header, but today I discovered the issue was the missing www. I don’t know what changed on Google’s end, but I’m glad to have the functionality back.

The code in the original entry has been updated, so if you’re using this bookmarklet you’ll want to go back there and get the revised version.

written 17 February, 02009 Comments

Bookmarklet to show today’s stats in Google Analytics

I currently use Google Analytics for traffic stats here. Although the interface defaults to only showing information through the previous day, clicking the down arrow on the date selector allows showing today’s information. After doing this for a week or so, I noticed that the date range is actually stored in the URL, and thought ‘well why can’t I have that automatically done for me?’.

The code is a bit longer than usual this time, partly because the URL involved is lengthy, and partly because this has a higher number of method calls. Even so, 205 characters isn’t too far above my general goal of being able to fit my bookmarklets in a tweet. It’s possible to save 17 characters by not initiating variables, but that tends to cause unpleasant surprises.

The code

var b, d, m, y = '';

x = new Date();
d = x.getDate();
if (d < 10) d = '0' + d; // needs to be two digits
m = x.getMonth() + 1; // zero-indexed
if (m < 10) m = '0' + m; // again, two digits
y = x.getFullYear();

b = y + m + d;

location.href = 'https://www.google.com/analytics/reporting?pdr=' + b + '-' + b;

The bookmarklet

Simply drag the following link to your bookmarks bar, or click to see it in action: today’s analytics.

As ever, this code is yours to do with as you will.

written 14 January, 02009 Comments

JavaScript bookmarklet for a word count on selected text

Sometimes I read lengthy articles or essays online and I’m curious how just how much the author has put down. I could copy the text into a text editor and find out, but why leave the browser?

It’s pretty easy to write a code snippet that gives a dumb count—meaning that it simply assumes a space indicates a new word. The answer probably won’t be exactly correct, but I’m not editing a magazine so I don’t care. Rough approximation is good enough.

The code

// initialize variables
var count = 0;
var text = '';
var words = [];

// ask for the selected text
text = document.getSelection().toString();
// is anything selected?
if (text != '') {
  // make an array based on spaces as word separators
  words = text.split(' ');
  // the number of elements is the number of words
  count = words.length;

  alert(count + ' words.');
}

The bookmarklet

Simply drag the following link to your bookmarks bar (or select some text and click to see it in action): word count

For added convenience, Safari provides shortcuts to items in the bookmarks bar—if the bookmarklet is the third item on the bar, ⌘+3 will run it.

A final note

This code is too simple to bother licensing. Use as you wish.

written 1 January, 02009 Comments

Older →