Jump To Content

LearnHub




Cleaning Up Your RJS with Helpers

The Problem (Unreadable RJS)

I've been finding my RJS for my web-app markadee becoming increasingly more complicated and unmanageable . Just take a look at my RJS for marking a task:

check.js.rjs (orginal)


page["task_item_#{@task_list.id}_#{@task.id}"].remove
if @created_mark
  page.insert_html :top, 
    "marks", 
    :partial => 'calendars/mark', 
    :object => @mark, 
    :locals => { :calendar => @calendar }
  page["day_#{@mark.day}"].className = 'marked'
else
  page.insert_html :bottom, 
    "mark_tasks_#{@mark.id}", 
    :partial => 'tasks/task', 
    :object => @task, 
    :locals => { :calendar => @calendar, 
                 :task_list => @mark,
                 :checked => true, 
                 :marked_today => @mark.today? }
  page << "Task.watch_checked_box('#{form_authenticity_token}','#{@task.id}')" 
  page << "if($('checked_task_items_#{@task_list.id}')){" 
    page.insert_html :bottom, 
      "checked_task_items_#{@task_list.id}", 
      :partial => 'tasks/checked_task', 
      :object => @task, 
      :locals => { :calendar => @calendar, 
                   :task_list => @task_list,
                   :checked => true,
                   :marked_today => false }
  page << "}else if($('view_all_#{@task_list.id}')){" 
   page.replace "view_all_#{@task_list.id}", 
     :partial => 'task_lists/view_all', 
     :locals => { :calendar => @calendar, :task_list => @task_list }    
  page << "}else{" 
    page.insert_html :after, "new_task_forms_#{@task_list.id}", 
      :partial => 'task_lists/view_all', 
      :locals => { :calendar => @calendar, :task_list => @task_list }
  page << "}" 
end

The Solution (Refactor RJS into Helper Files)

We can move RJS code into helpers files just like any other view. Thats the helpers job; to simplify views. Check out the rails doc for moving RJS code to helper files.

Before

Its pretty obvious that what's happening is here a checked task is being inserted but we should be more immediately readable that thats what its doing.

page.insert_html :bottom, 
  "checked_task_items_#{@task_list.id}", 
  :partial => 'tasks/checked_task', 
  :object => @task, 
  :locals => { :calendar => @calendar, 
               :task_list => @task_list,
               :checked => true,
               :marked_today => false }

The Helper

So I take that code and and move into a new function in my helper.

def insert_checked_task(calendar,task_list,task)
  page.insert_html :bottom, 
    "checked_task_items_#{task_list.id}", 
    :partial => 'tasks/checked_task',
    :locals => {  
      :checked_task => task,
      :calendar => calendar, 
      :task_list => task_list }
end

After

Back in our view you simply call the function from 'page' and it works! Talk about cleaner.

page.insert_checked_task(@calendar,@task_list,@task)

The Results

So after moving more of my RJS to helpers that is my final result

check.js.rjs (with helpers)



page.remove_task(@calendar,@task_list,@task)

page.ef_current_month
  if @marked_today
    page.insert_mark(@calendar,@mark)
    page["day_#{mark.day}"].className = 'marked'
  else
    page.insert_task(@calendar,@task_list,@task,@mark)
    page.watch_task('uncheck',@calendar,@task_list,@task,form_authenticity_token)
  end
page.en

page.ef "checked_task_items_#{@task_list.id}" 
  page.insert_checked_task(@calendar,@task_list,@task)
page.esf "view_all_#{@task_list.id}" 
  page.replace_view_all(@calendar,@task_list)
page.els
  page.insert_view_all(@calendar,@task_list)
page.en

If Statements in RJS

Ugly RJS IFs

If Statements have always been ugly in RJS since in order to evaluate if an element exists you have to write raw javascript.

page << "if($('checked_task_items_#{@task_list.id}')){" 

page << "}else if($('view_all_#{@task_list.id}')){" 

page << "}else{" 

page << "}" 

Pretty RJS IFs

So it just makes sense to clean them up using RJS. So I throw these functions in my application_helper.rb I'm not calling the functions 'if, elsif, else or end' since they are reserved words.

def ef(e)
  page << "if($('#{e}')){" 
end

def esf(e)
  page << "}else if($('#{e}')){" 
end

def els
  page << "}else{" 
end

def en
  page << "}" 
end

You can see that this is much easier to read

page.ef "checked_task_items_#{@task_list.id}" 

page.esf "view_all_#{@task_list.id}" 

page.els

page.en

and if you needs something more specific

  def ef_current_month
    page << " if('#{Time.current.year}_#{Time.current.month}' == $('month_value').readAttribute('value')){"  
  end

so I can do this

page.ef_current_month
  if @marked_today
    page.insert_mark(@calendar,@mark)
    page["day_#{mark.day}"].className = 'marked'
  else
    page.insert_task(@calendar,@task_list,@task,@mark)
    page.watch_task('uncheck',@calendar,@task_list,@task,form_authenticity_token)
  end
page.en

Thats pretty much all there it is too it. Enjoy your clean RJS. Now for some shameless plugging of my web-app!


Your Comment
Textile is Enabled (View Reference)