Archive for the 'Snippets' Category

Large Hit State Menus with Javascript and CSS

A growing trend I have been noticing while developing web sites is the need (or want) to create areas on the page that are clickable. Generally these are areas that contain a headline and a snippet of text, and sometimes an image. I really like the idea of having areas of that page that are clickable because I believe it encourages users to click through right away, and can make those worn out ‘click here’ links obsolete.

Typically these large hit state links are created using images. This is both good and bad. Since it’s an image, we can control how the area looks very easily. But the bad thing is it doesn’t do you any good for accessibility and search engine visibility.

Good hit states should:

  • Allow a user to click anywhere in the area to follow the link;
  • Visibly cue the person that the area is a link;
  • Degrade gracefully to users without CSS or Javascript enabled; and finally
  • Integrate semantically into the document to make sense in the structure.

In this example we’re going to create a navigation menu for a web site. Our customer wants to have menu items that have a title and a small description. The designer wants the whole thing to be a link. Here’s how we pull it off.

Step One: Set up the menu

Typically while creating navigation menus using CSS, I tend to use unordered lists. For this example, it won’t work very well because there will be elements in each menu item that won’t work inside a li tag. To keep the document semantically correct, I want to use something like an h4 element for the title and paragraph tag for the description. Embedding these in a li element is technically valid HTML, but it’s a bad way to structure a document. I would even go so far as to say it’s similar to embedding structure inside nested tables. So we’re going to create a menu items out of div elements containing a headline and a paragraph element.

  <div class="menu_item">
  <h4><a href="index.html">Home</a></h4>
  <p>Go to the home page</p>
  </div>
  <div class="menu_item">
  <h4><a href="about.html">About</a></h4>
  <p>Read about how rad we are</p>
  </div>
  <div class="menu_item">
  <h4><a href="http://www.myfriendssite.com" rel="external">My Friend</a></h4>
  <p>My Friend's Site</p>
  </div>

That was simple, clean and valid HTML. Using h4 tags denotes importance, but not enough importance on the page to overrule your headlines. We have a short description in there with a paragraph tag. Notice we add a menu_item class to each div element. This is how we’ll work the magic later with Javascript.

Step Two: Set up the CSS

Now we’ll write some basic CSS to style the menu. Of course you’ll want to style it differently, so I’ll keep it super simple.

  div.menu_item {
    border: 1px solid #CCC;
    padding: 10px;
    width: 100px;
  }

Each menu item will be 180 pixels with a border and 10 pixels padding. Now let’s add a class for a hover state of the menu item. We’ll turn the background a slight gray and make the border darker. Then we’ll change the cursor to the pointer icon to make it appear to be a link.

  div.hover {
    background: #CCC;
    border: 1px solid #999;
    cursor: pointer;
  }

If Internet Explorer 6 played nice we’d add a :hover pseudo-class to the menu item in the CSS and be done. However, it doesn’t support CSS hover states on anything but links. Internet Explorer 7 in strict standards compliant mode does, but it’s currently a smaller subset of browser share. So we’ll need a little help from Javascript.

Step Three: Set up the Javascript

As with most of my Javascript snippets, you’ll need Prototype included in your page. For those just wanting to copy and paste the snippet and be done, I’ll throw it out now:

  function init_menu() {
    var items = document.getElementsByClassName('menu_item');
    items.each(function(item) {
      Event.observe(item,'mouseover',function() {
        Element.addClassName(this,'hover');
      }.bind(item));
      Event.observe(item,'mouseout',function() {
        Element.removeClassName(this,'hover');
      }.bind(item));
      Event.observe(item,'click',function() {
        var link = this.getElementsByTagName('a')[0];
        if (link.getAttribute('rel') == 'external') {
          window.open(link.href);
        } else {
          window.location = link.href;
        }
      }.bind(item));
    });
  }
  Event.observe(window,'load',init_menu);

For those who are still reading and want to know how it works, let’s go through it a few lines at a time.

How it works

First we get all the items in the document with the class name menu_item.

  var items = document.getElementsByClassName('menu_item');

Next we loop through each item found and apply event listeners to each one. Instead of the typical for (var i = 0; i < items.length; i++) pattern, we use Prototype’s each iterator, which cleans things up a lot in our code.

  items.each(function(item) {

The first thing we want to do to our menu item is apply a mouseover and mouseout state to it. We’ll add event listeners for the element to apply the hover CSS class to change the appearance. Since Prototype comes with a built in addClassName and removeClassName functionality, as well as event listeners using Event.observe, we don’t have to worry about supporting how it works across multiple browsers. Prototype handles that for us.

  Event.observe(item,'mouseover',function() {
    Element.addClassName(this,'hover');
  }.bind(item));
  Event.observe(item,'mouseout',function() {
      Element.removeClassName(this,'hover');
  }.bind(item));

Notice the bind call at the end of each even listener we set up. This allows us to reference the item as this inside the function block we pass into the event listener call. Otherwise it wouldn’t know what item you were referring to.

Now you might be thinking ‘how do you turn to the whole thing into a link when the link is only in the headline?’. Here’s how we do it: we parse the menu item and find the first link, then make the menu item listen for a click. When it’s clicked, it will open the link as though the real link inside the menu item had been clicked.

  Event.observe(item,'click',function() {
    var link = this.getElementsByTagName('a')[0];
    if (link.getAttribute('rel') == 'external') {
      window.open(link.href);
    } else {
      window.location = link.href;
    }
  }.bind(item));

It’s even set up to check for the rel="external" attribute of a link (covered in my previous post) and will open it in a new window if it’s present.

Finally we’ll set up an event listener to call the function after the page is loaded:

  Event.observe(window,'load',init_menu);

Stick this in an external Javascript file on your page or into the head of your document, and your menu items that can contain anything you want, and still appear as one large link. You can also apply it to more than than just menu items: use it on special callouts on your pages, such as download links, lists of previous blog posts, or product ads.

Users without Javascript enabled will still be able to view and click on links inside the menu items, they just won’t be as stylized as when Javascript is enabled. Search engines will also love your h4 headlines and description for each link, so your site could possibly rank for more relevant searches. What’s not to like?

I’ve uploaded a very basic example so you can see it in action. If you found this useful and applied it to your site, leave a comment and link to where you used it.

Superchomp gonna eat you!

No newlines are safe!!

  class String
    def superchomp
      self.strip.gsub(/^(n|r|rn)+|(n|r|rn)+$/,'')
    end
  end

Please only use it for good and not evil.

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.

Hacking Rails HTML form helpers to add CSS classes

I really like using Rails form helpers, but one thing I found myself doing often is assigning classes to elements that I want to style a bit differently than the rest. But, I have to remember to do that for every element that I want it applied to. Luckily the nature of Ruby lets you open up classes and modules to redefine methods, so I was able to hack up the ActionView tag helpers directly in my Rails app. The result is that by default, all form elements generated by the helpers get a CSS class name based on what type of element it is.

How is this useful? Well for instance, I like to style my HTML submit buttons to be fixed width and center aligned. With CSS, I can’t style a submit button specifically because it’s an input element. There are also input elements that can be text fields, radio buttons, and checkboxes, but I don’t want it to apply to them as well. So the path of least resistance to style a submit button exclusively is to assign it a class every time I use it. It can get tedious to have to remember that every time I build a new form in my application.

So here is my solution: hacking the ActionView tag helper module to make any form element automatically have a class assigned to it based on what type of element it is. In the case of a submit element, it will have a class submit_button. Then all I have to do is define the selector for input.submit_button in my stylesheet and I’m done. All submit buttons will now be styled the way I want, and I don’t have to remember to assign them a class every time I place one in a template.

Add this code to your application.rb file (or anywhere you’d like) and your form elements will get nice CSS classes by default. It will even keep any class names you assign to your tags and append the default one at the end. I’ve verified it working using ActionPack-1.12.5 and later (Rails 1.1.6 – 1.2.3).

  module ActionView
    module Helpers
      module TagHelper        def tag(name, options = nil, open = false)
          add_default_class(name,options)
          "<#{name}#{tag_options(options) if options}" + (open ? ">" : " />")
        end        def content_tag(name, content_or_options_with_block = nil, options = nil, &block)
          if block_given?
            options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
            add_default_class(name,options)
            content = capture(&block)
            concat(content_tag_string(name, content, options), block.binding)
          else
            add_default_class(name,options)
            content = content_or_options_with_block
            content_tag_string(name, content, options)
          end
        end        private        def add_default_class(name,options)
          options = Hash.new if options.nil?
          class_name = case name.to_s
          when 'textarea'
            'text_area'
          when 'select'
            'select'
          else
            nil
          end          if class_name.nil? && options.has_key?('type')
            class_name = case options['type']
            when 'text'
              'text_field'
            when 'password'
              'password_field'
            when 'file'
              'file_field'
            when 'radio'
              'radio_button'
            when 'checkbox'
              'check_box'
            when 'submit'
              'submit_button'
            when 'image'
              'submit_image'
            when 'button'
              'button'
            else
              nil
            end
          end
          return options if class_name.nil?
          if options.has_key?('class')
            options['class'] += " #{class_name}"
          else
            options['class'] = "#{class_name}"
          end
        end
      end
    end
  end

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!