Update-able DOM Components
The last few weeks I’ve been working on an Agile process management tool. We’re building it on Ruby, Rails, and Prototype. If you haven’t tried any of these, you’re missing out. The tool itself uses Prototype’s AJAX framework extensively, which brought me to the topic for this post:
What if elements in the DOM knew how to update themselves?
One of the primary components in this application is a virtualization of an XP war room. Instead of post-it notes with a few scribbles of text you get the first few words in a story’s title. The stories themselves can be dragged around the board to and from various states: planned, development, QA, complete. They can also be dragged into the next iteration, or into a project backlog.
To accomplish all this magic I needed three methods in my controller: move_future, move_next_iteration, and move_status. Each of these required different elements on the page to be updated. I had a primitive implementation working, but it contained a lot of duplication that seemed hard to factor out. For each request I had to know the story’s previous location or the story’s new location or both and how to render a number of components on the page.
The solution I settled on isn’t perfect or complete by any means, but it did help me stop repeating myself. I ended up with a single partial for rendering a “box” of stories that looked something like this:
<div class=”iteration_status” id=”iterStatus_1_1″>
Update-able content
</div>
<script type=”text/javascript”>
//<[CDATA[
$(’iterStatus_1_1′).update = function() {
new Ajax.Updater(’iterStatus_1_1′, ‘/iterations/story_box’, {asynchronous:true, evalScripts:true, parameters:’iteration=’ + ‘iterStatus_1_1′})
}
//]]>
</script>
In turn this allowed me to consolidate all my render-that-box code into one action in the controller, which you’ll see below.
def story_box
dropbox = params[:iteration]
@iteration = Iteration.find(dropbox.split(’_')[1])
@status = Status.find(dropbox.split(’_')[2])
stories = @iteration.stories_by_status[@status]
render :partial => ’stories/drop_box’, :locals => {:iteration => @iteration, :status => @status, :stories => stories, :dom_id => dropbox}
end
The move_story actions are still around, but only concerned with what they should be: setting a story’s attributes and forwarding control. These rjs templates used to re-render all the necessary elements on the page, now they just ask:
page.send :record, “$(’iterStatus_#{@last_iteration.id}_#{@last_status.id}’).update();”
page.send :record, “$(’story_box’).update();”
page.send :record, “init_lbOn();”
By the way, if anyone knows of a better way to make rjs spit out verbatim javascript, I’d be pleased to find out.
The one sigificant drawback to this techinque is it fires a request for every update call. In my application this is at most three and they’re small. Additionally, the applicaiton we’re building will only be used on the local network. I’m not terribly worried.
November 2nd, 2006 at 12:04 am
You can get JavaScript function calls via RJS with this:
http://api.rubyonrails.com/classes/ActionView/Helpers/PrototypeHelper/JavaScriptGenerator/GeneratorMethods.html#M000440
You’d have to change your code around a bit, but it’s probably worth it.
November 2nd, 2006 at 12:05 am
Oops, the real answer to your question is this:
http://api.rubyonrails.com/classes/ActionView/Helpers/PrototypeHelper/JavaScriptGenerator/GeneratorMethods.html#M000440
Here it is:
(Edited by Tyler)
page << “Some javascript”
January 28th, 2007 at 5:27 pm
Hi,
I found your blog via google by accident and have to admit that youve a really interesting blog
Just saved your feed in my reader, have a nice day