Adding Your Own Methods to jQuery

November 21, 2008

Lately, when doing dynamic/AJAX JavaScript stuff, I've been using the jQuery library. It's pretty easy to use as a drop in replacement for Prototype in Rails. If you do start using jQuery, make sure to download the jquery api browser for offline browsing of the jQuery docs. Not only is it much faster and handy to have when you are offline, I've found the main jQuery documentation site to be somewhat unreliable. They're probably just having a hard time handling all the traffic they are getting to due to the increased popularity of the library. Also check out the most recent Railscast, which covers using jQuery with Rails.

In that episode, Ryan shows you how to define a jQuery function to make submitting forms with AJAX easier. I'm going to show a couple more function you can add to make manipulating drop-down menus (HTML select/option element) a little easier. What I'm doing is having the contents of one drop-down updated when the value in another drop-down changes. Here's the code with standard jQuery:

<% content_for :html_head do %>
  <script type="text/javascript">
    jQuery(function($){
      var categoriesByType = <%= CategoryType.category_map.to_json %>
      $("#category_category_type_id").change(function(){
        $("#category_parent_id option").remove()
        $.each(categoriesByType[$(this).val()], function(){
          var option = $(document.createElement("option"))
          option.attr('value', this[1])
          option.html(this[0])
          $("#category_parent_id").append(option)          
        })
      })
    })
  </script>
<% end %>

There's a lot going on, so I'll go through it step-by-step. But before I do that, let me explain what this code does. This is used on a form to create a new category. Each category has a parent and a type. The form has 2 HTML select elements (drop-down menus), one of the category types and another for the category parent. The list of possible parents should be limited to categories within the selected category type.

So, first we are using the Rails content_for method to have this whole chunk end up in the head of the HTML document. I have a <%= yield :html_head %> defined at the end of my HTML head in the layout. Next, we use the syntax jQuery(function($){ ... }) to define a chunk of JavaScript code that executes on the DOM is ready.

Inside that function, the first thing we do is create a variable that is a map (Strictly speaking, it is a JavaScript Object, but JavaScript objects act as an associative data type). We call a method on our Rails model to get the data, and then call to_json on it to format the data as JavaScript. The structure of this data is that the key is the category type id and the value is an array of array. Each array is the category path and category id. This structure makes it easy for us to replace the set of options in the parent drop-down each time the value in the category type is changed.

So we define a function to be executed on change of the category type drop-down. The first thing that function does is remove all of the options from the category parent drop-down. Then, for each value we get in the array that maps to the currently selected category type, we want to append an option tag to the category parent drop-down.

So all of this works as advertised, but the one thing I don't like is there is a lot of code, 4 lines, just to create and append a new option tag. Let's shorten that up, so our new syntax looks like this:

<% content_for :html_head do %>
  <script type="text/javascript">
    jQuery(function($){
      var categoriesByType = <%= CategoryType.category_map.to_json %>
      $("#category_category_type_id").change(function(){
        $("#category_parent_id option").remove()
        $.each(categoriesByType[$(this).val()], function(){        
          $("#category_parent_id").appendElement("option", this[0], {value: this[1]})
        })
      })
    })
  </script>
<% end %>

The only part that has changed is the body of the each. As you can see, we are going to define a new function that operates on a jQuery set of elements. jQuery refers to this as a method, just to give it a name to differentiate it from a typical function that is just in the jQuery namespace. So to implement this, we are going to actually define a function and a method. First, we define a function that simply creates a new DOM element, and second, a method that uses the same syntax, but appends the element to the jQuery set:

//You can call this a few ways:
//createElement('p') => "<p/>"
//createElement('p','hi') => "<p>hi</p>"
//createElement('p', {align: 'center'}) => "<p align="center"/>"
//createElement('p','hi',{align: 'center'}) => "<p align="center">hi</p>"    
$.createElement = function(tag_name, tag_value, tag_attrs) {
  var name = tag_name
  if(typeof tag_value == "object") {
    var value = null
    var attrs = tag_value
  } else {
    var value = tag_value
    var attrs = tag_attrs
  }
  var element = $(document.createElement(tag_name))
  if(attrs) {
    $.each(attrs, function(k,v) {
      element.attr(k,v)
    })
  }
  if(value) {
    element.html(value)
  }
  return element
}

$.fn.appendElement = function(tag_name, tag_value, tag_attrs) {
  var element = $.createElement(tag_name, tag_value, tag_attrs)
  this.append(element)
}

First there is the createElement function that is defined on $, then there is the appendElement method, that is defined on $.fn. The appendElement method isn't really needed, because you could accomplish the same thing with just append and createElement:

$("#category_parent_id").append($.createElement("option", this[0], {value: this[1]}))

But I just wanted to illustrate the different between a jQuery function and a jQuery method. A jQuery function is called on just $ object (which happens to be a function), whereas a jQuery method is called on the result of calling the $ function.

Posted in Technology | Tags Javascript, jQuery

Comments

1.

Very usefull articles! Thank you!

# Posted By Sebastian on Thursday, December 11 2008 at 3:24 AM

Comments Disabled