Archive for the ‘Ajax’ Category

Rails and JavaScript: page.call gotchas

Ruby on Rails has wonderful out of the box javascript support, but sometimes implementing dynamic user interfaces is not so easy as it seems.

In an application I’m working on I have a list of people with a checkbox each. In the load event of the page I add a click handler to every checkbox using this javascript code:

  $$('.ConfermaInvitati').each(function(element) {
    Event.observe(element, 'click', clickHandler);
  });
 
// ... 
 
function clickHandler(event) {
  var e = Event.element(event);
  new Ajax.Updater(e.up(), 'my/invited/toggle', {
    parameters: { id: e.up().up().id },
    onLoading: function() { e.src = "/images/admin/spinner.gif"; },
    onSuccess: function() { new Ajax.Request('my/refresh', {}) }
  });  
}

This works fine until I add a new person via an Ajax call. That person won’t have a clickHandler because the element wasn’t on the page when I called the click handler. So I thought it was time to test page.call in the render :update block I had in the rails application.

I tried this code:

render :update do |page|
  # Do stuff that creates the new objects and adds it to the page
  # The data I need is in @invited
  page.call("Event.observe($$('##{@invited.permalink} .ConfermaInvitati').first()), 'click', clickHandler)")

Obviously that didn’t work, and it turned out I have to read documentation before doing fancy things :)

The rails docs told me that I had to use call passing the function name as the first argument and an array of parameters as the second argument, the problem is that call turns all the parameters into strings—this means I could not pass the clickHandler function to Event.observe.

I found the solution in <<. If you do page << "foo", foo will be evaluated as raw javascripts. This meant I was able to do

page << "Event.observe($$('##{@invited.permalink} .ConfermaInvitati').first()), 'click', clickHandler)

and finally have the functionality I was looking for. So remember, don’t page.call if you need to pass javascript variables to your functions.

A praise for jQuery

I’ve been doing some drupal development lately, and had to make the switch from Prototype to jQuery (by the way it’s the same switch WordPress made in 2.2).

At first I had to learn the different approach jQuery has to manipulating the DOM, but once I got the hang of it I really started appreciating jQuery.

The main difference I’ve found is that I prefer programming Prototype via the Rails helpers, while programming jQuery is so fun I prefer to do it directly in JavaScript.

Actually it turned out so fun that I keep adding content to pages and changes links in JS, while I should do that in the backend.

Here’s an example, from a drupal module I wrote:

$(document).ready(function() {
  $('#comments h2').after('<p class="slider"><a href="#" class="slide-open">Espandi tutti i commenti</a> - <a href="#" class="slide-close">Contrai tutti i commenti</a></p>');
  $('.comment .expand').html("[+]");
  $('.comment .content').hide();
	$('.comment h3').click(function() { 
		$(this).siblings('.content').slideToggle(); 
		if ($(this).children('.expand').html() == "[-]") {
			$(this).children('.expand').html("[+]");
		} else {
			$(this).children('.expand').html("[-]");
		}
		return false; 
	});
 
	$('#comments .slider .slide-open').click(function() {
		$('.content').show();
		$('.comment .expand').html("[-]");
		return false;
	});
 
	$('#comments .slider .slide-close').click(function() {
		$('.content').hide();
		$('.comment .expand').html("[+]");
		return false;
	});
})

What does this code do? Add a couple of links after the comments heading, hide all the comments and add a slidedown/up functionality to them.

The best thing of adding this functionality in the frontend is that when the user has js disabled or is using a non-js browser the site falls back to the normal behavior.

Javascript Classes

Before you go “hey, but js doesn’t have classes” let me tell I meant classes as in “school classes” :)

I don’t usually like repeating myself and writing posts about links I put on my del.icio.us, but this time I have to. I’m having a great time learning from these videos of Doug Crockford’s presentation of JS @ Yahoo!

Wonderful stuff there, after 15 minutes of the first video I already learnt some JS tricks I didn’t know (or didn’t care about).

Drag and Drop deletion with Rails

I don’t like to do Drag and Drop in Web Applications. I dislike it so much that I never did dnd in Rails, so when a customer explicitly asked for a drag and drop trashcan in an application I had to start looking through rails docs, fearing dozens of Javascript callbacks.

It turned out wonderfully easy, requiring me to add just two lines of code in the view and a short action in the controller:

# This is the line of code I added to the partial that is rendered for each draggable element
<%= draggable_element "#{picture.class}_#{picture.id}", :revert => true, :constraint => "'vertical'" %>
 
# This is the code I added to the trashcan 
<%= drop_receiving_element("trash", :url => {:controller => :immobili, :action => :destroy_image}, :hoverclass => "hover_trash") %>

That’s it, really, the controller action just deletes the element and hides is on the page via a render :update block.

Test Driven RJS with ARTS

If you’re like me, you like writing tests for your code. Writing tests is a nice thing to do and saves you a lot of headaches.

Writing tests for Ruby on Rails is really simple, with the wonderful testing framework built in Rails, but things become less ideal when you need to write tests for your RJS code.

This morning I wanted to test the following snippet:

def add_file_field
  render :update do |page|
    page.remove "link_to_add_#{params[:attachment_type]}"
    page.insert_html :bottom,
                     params[:attachment_type],
                     :partial => "#{params[:attachment_type]}_file_field", 
                     :locals => {:index => params[:index].to_i}
  end
end

But the test was failing and I didn’t know why. The ARTS Plugin came to the rescue, allowing me to test the rjs response:

  def test_add_file_field
    xhr(:get, :add_file_field, :index => 1, :attachment_type => "plan")
    assert_response :success
    assert_rjs :remove, "link_to_add_plan"
    # more code here...
  end

Unfortunately that didn’t help with my problem since the cause was not the RJS, I was simply forgetting to login in my setup method :)