Archive for the 'Tips and Tricks' Category Page 2 of 2



Unobtrusive Javascript pop-up links

One of the things I dislike the most about writing HTML is creating links that open in new windows. Since I try to make all my HTML standards-compliant, I can’t use the target="_blank" attribute in my links anymore, since it’s been deprecated in XHTML. Many have followed the new standard of making an inline onclick attribute of the link open the window, including myself. But what a pain it is to type it all out and remember the exact code to do it. Instead of this:

  <a href="http://www.example.com" onclick="window.open(this.href); return false;">External Link</a>

Wouldn’t it be easier to type this?

  <a href="http://www.example.com" rel="external">External Link</a>

I’m sure you’ll agree it’s much cleaner, easier to type, easier to remember, and to top it off, it’s (kind of) symantically correct. While the possible attribute values of rel do not include external, using it as a keyword to denote the relationship between an external page and your link makes sense. Best of all it validates with the W3C validator with flying colors.

How to Make it Happen

So how do we make the magic happen? The answer is with our good old friend Javascript. Basically we’re going to make a function that will crawl the document after the page loads and check for links that have rel="external" in it, then attach an Event.observe to each link that will cause it to open in a new window (note that you’ll need Prototype.js for this to work).

Here’s the code:

  function external_link(link) {
    var el = Event.element(link);
    if (el.tagName == 'IMG') {
      if (el.parentNode.tagName == 'A') {
        var el = el.parentNode;
      } else {
        return false;
      }
    }
    window.open(el.href);
    Event.stop(event);
    return false;
  }
  function init() {
    var links = $A(document.getElementsByTagName('a'));
    links.each(function(link) {
      if (link.getAttribute('rel') == 'external') {
        Event.observe(link,'click',function(e){ external_link(e); },false);
      }
    });
  }
  Event.observe(window,'load',init);

Note in the external_link function it checks if the item clicked was an image tag. If it is, then it will look up one node in the DOM tree to see if the image was enclosed in an anchor tag, and attach the event to that instead (since it contains the href it needs to open). If it doesn’t find anything, it will exit quietly.

Load this in your document header and off you go. You should be able to add rel="external" to any link on your page and it will open in a new window. Remember you need a copy of Prototype loaded as well. Leave a comment if have any questions or improvements.

Make Graticule failover to other geocoding services

The Graticule Ruby gem makes it super easy to geocode addresses using multiple services. One thing I’ve found in developing Unthirsty is that sometimes a geocoding service either fails to be reached or the address couldn’t be found. So as a precaution, I’ve set up a way for Graticule to failover to another service if a lookup fails. Here’s how.

First, set up a list of your preferred geocoding services. Graticule expects a service name and optionally an access key. Some geocoding services like Ontok don’t require a key. However Google does, and it’s probably the one you’ll want to use over the others. Order your geocoders so your preferred geocoders are listed first.

  PREFERRED_GEOCODERS = [
    {:name => :google, :key => '[your key here]'},
    {:name => :yahoo, :key => 'My Service'}, # yahoo will take any string for a key
    {:name => :geocoder_us, :key => 'My Service'} # same with geocoder.us
  ]

Take your pick of geocoding services. Graticule supports quite a few:

Now it’s time to put those geocoders to use. This method loops through your PREFERRED_GEOCODERS array and sets up a new Graticule instance with the service name and key you provided. It will then try to locate it with a lookup to that service. Calling the locate method on a service instance will raise an exception if an error occurs, so it’s possible to rescue all the different errors you can get while doing a lookup.

Note that this was used in a Rails project, so I’m using logger, a built in Rails logging facility. You may want to substitute your own logging method if you’re not using Rails.

  def geocode(query)
    PREFERRED_GEOCODERS.each{|service|
      begin
        geocoder = Graticule.service(service[:name]).new(service[:key])
        result = geocoder.locate(query)
        logger.info("Found address [#{query}] with service [#{service[:name]}]")
        return result unless result.precision == :country # probably not worth keeping
      rescue Graticule::AddressError => detail
        logger.error("Nothing found [#{query}] with service [#{service[:name]}] (#{detail})")
      rescue Graticule::Error => detail
        logger.error("Nothing found [#{query}] with service [#{service[:name]}] (#{detail})")
      rescue Graticule::CredentialsError => detail
        logger.error("Invalid credentials [#{service[:name]}] with [#{service[:key]}] (#{detail})")
      rescue Errno::ECONNREFUSED
        logger.error("Connection refused to #{service}")
      end
    }
    raise Graticule::AddressError.new("Couldn't find address [#{query}] with any service")
  end

The failover works because since we are in a begin..rescue block, a raised exception will cause it to skip over the return result line, log the exception, then move on to the next service. But if a lookup succeeds, it will return a new Graticule::Location instance that contains the results, and not go any further. If no lookups are successful, it will raise a new Graticule::AddressError exception that you can trap while calling the method in your code.

Also notice I don’t return a result if the :precision attribute is :country There’s pretty much no use for it if you’re going for specific addresses. You may want to adjust it based on your needs. Maybe you only want results that have :address precision.

You now have a failsafe method for geocoding locations in your Rails app. Leave a comment if you found this useful, or if you have any improvements. Enjoy!

How to get HTML elements by partial class name

In all modern browsers there is a way to get all elements that have a specific class name using Javascript. It’s really useful when manipulating elements on a page. But what if you want to get all elements that start with a certain prefix? Well there’s a way to to do by including this snippet in your Javascript files. It requires Prototype to work.

  document.getElementsByPartialClassName = function(value, parentElement) {
    var children = ($(parentElement) || document.body).getElementsByTagName('*');
    var elements = [], child;
    regexp = new RegExp('(?:^|\s)('+value+'.*?)(?:\s|$)');
    for (var i = 0, length = children.length; i < length; i++) {
      child = children[i];
      if (matches = Element.classNames(child).toString().match(regexp)) {
        elements.push(Element.extend(child));
      }
    }
    return elements;
  };

Now, you can get all of your HTML elements that, for instance, have a class name that begins with ‘comment_’:

  var els = document.getElementsByPartialClassName('comment_');

And then do something with them:

  for(var i = 0; i < els.length; i++) {
    el.style.color = '#F00';
  }

I built this function for another project I’ve been working on (which I’ll post about soon). In the project, I was looking for elements that started with a certain prefix, but had different endings (e.g. ‘comment_one’, ‘comment_two’). I used the leftover values to further manipulate the elements. Hopefully this will come in handy for other people as well. Remember you need Prototype for it to work.

Simple weblogUpdates.ping client in Rails

Almost every Rails app I’ve worked on has had some kind of blog component. Not only are blogs an easy way to keep your site updated, but it also brings in search engine traffic. One of the best ways to get your blog indexed fairly quickly is by using ping services such as Technorati. These services come by after you ping them and read your RSS feed, indexing your content and ultimately getting your site out in front of a lot of people.

Luckily Rails comes with a built in XMLRPC client, so adding in ping functionality to your app is dead easy. Below is a snippet I use regularly. You can change out the listed services to include any of these ping services. I choose Technorati and Google first, and add in any others that might be more specific to the type of site I’m developing.

  def xmlrpc_ping
    services = ["http://rpc.technorati.com/rpc/ping","http://blogsearch.google.com/ping/RPC2"]
    services.each{|service|
      begin
        server = XMLRPC::Client.new2(service)
        server.call2('weblogUpdates.ping',"My Blog",'http://www.myblog.com')
      rescue => detail
        logger.info("ping failed for server #{service} (#{detail})")
      end
    }
  end

You may choose to set your ping service list and ping parameters in your environment file. Or, set it to pull from a database so you or your client can edit them through a browser.

  PING_SERVICES = ["http://rpc.technorati.com/rpc/ping","http://blogsearch.google.com/ping/RPC2"]
  PING_BLOG_NAME = 'My Blog'
  PING_BLOG_URL = 'http://www.myblog.com'

Then rewrite the method:

  def xmlrpc_ping
    PING_SERVICES.each{|service|
      begin
        server = XMLRPC::Client.new2(service)
        server.call2('weblogUpdates.ping',PING_BLOG_NAME,PING_BLOG_URL)
      rescue => detail
        logger.info("ping failed for server #{service} (#{detail})")
      end
    }
  end