Search code examples
jqueryruby-on-railsajaxujs

Rails UJS (jQuery) not wiring up data-remote Links


Rails Version: 3.2.1

Ruby Version: 1.9.3p125

Browser: Chrome 18.0.1025.162

Dev OS: Mac OS/X Lion

Server OS: CentOS 5

I'm trying to use :remote in my link_to call so that I can request HTML content using AJAX, and the populate a section of the page with the returned content.

Rails doesn't appear to be wiring up the link properly. The browser just sees the link as a regular link tag, and does not appear to be processing any of the Rails UJS click event handlers.

I have a link with a data-confirm associated with it that is working, so this indicates that UJS is doing at least some of the necessary wireups.

I am attaching other click event listeners to the same link to hide/show a section of the page that would be displaying the returned HTML from the AJAX call.

Apologies if this is posted elsewhere, but I've been searching for several days now and haven't found a solution that addresses this specific issue. I've seen some articles that relate, but they all assume that the AJAX call is being made, and theres some Rails render flow or routing issue, and in my case there doesn't appear to be any wireup at all.

Here's some code:

My link in the View:

<%= link_to image_tag("icons/24x24/attachment.png"), task_notes_path(task), :class => "section-link task-notes-link", :title => "View recent notes", :remote => true, 'data-section' => "task-notes" %>

The output generated by RoR:

<a href="/tasks/7/notes" class="section-link task-notes-link" data-remote="true" data-section="task-notes" title="View recent notes"><img alt="Attachment" src="/assets/icons/24x24/attachment.png"></a>

Here's the /task/:task_id/notes Controller code:

def index
store_return_to

@project = nil
@tasks = nil

if (params.has_key?(:project_id))
  @project = Project::find(params[:project_id])

  sort = [
    { :field => 'status', :direction => 'asc'},
    { :field => 'priority', :direction => 'asc' }
  ]

  filter = { :status => Task::STATUS_ACTIVE.to_s }

  @tasks = Task::filter_sort(:project => @project, :sort => sort, :filter => filter, :per_page => 0)
else
  @task_sort = params.has_key?(:task_sort) ? params[:task_sort] : { :field => 'priority', :direction => 'asc'}
  @task_filter = params.has_key?(:task_filter) ? params[:task_filter] : { :status => Task::STATUS_ACTIVE.to_s }
  @task_search = params.has_key?(:task_search) ? params[:task_search] : ""

  @tasks = Task::filter_sort(:search => params[:task_search], :sort => @task_sort, :filter => @task_filter, :page => params[:tasks_page], :per_page => 15)      
end

respond_to do |format|
  format.html # index.html.erb
  format.js
  format.json { render json: @tasks }
end
end

And finally, my code in the index.js.erb file:

<% if ([email protected]?) %>
    $('#project-<%[email protected]%>-tasks').html('<%= escape_javascript(render(@tasks)) %>');
<% else %>
    $('#project-tasks').html('<%= escape_javascript(render(@tasks)) %>');
<% end %>

Solution

  • Okay, so I figured it out.

    Examining this HTML for the related :remote links:

    <div class="list-item-row">
    <div class="list-item-expander action-icon">
      <a href="#" title="Show/hide content pane">
        <%= image_tag "icons/24x24/plus.png", :class => "expander-image closed" %>
        <%= image_tag "icons/24x24/minus.png", :class => "expander-image opened" %>
      </a>
    </div><div class="action-icon">
      <a href="#" data-section="task-description" class="section-link" title="Show details"><%= image_tag "icons/24x24/page.png" %></a>
    </div><div class="action-icon">
      <%= link_to image_tag("icons/24x24/attachment.png"), task_notes_path(task), :class => "section-link task-notes-link", :title => "View recent notes", :remote => true, 'data-section' => "task-notes" %>
    </div><div class="action-icon">
      <%=task_image_tag(task, 24)%>
    </div><div class="action-icon">
      <div class="task-priority task-priority-<%=task.priority_str.downcase%>" title="<%=task.priority_str%> Priority"></div>
    </div><div class="action-icon">
      <% unless (task.assigned_developer.nil?) %><%= link_to image_tag(task.assigned_developer.avatar.icon.url), developer_path(task.assigned_developer), :title => "Assigned to: " + task.assigned_developer.full_name %><%end%>
    </div><div style="width:280px;">
      <%=link_to truncate(task.name, :length => 40, :omission => "..."), task_path(task), :title => task.name, :class => "item-show-link" %>
    </div><div style="width:300px;font-size:10px;color:#777;">
      <%=truncate(task.description, :length => 50, :omission => "...")%>
    </div><div style="width:90px;float:right;text-align:center;">
      <%=task.status_str%>
    </div></div>
    

    The :remote links are in a DIV with the id "list-item-expander". This row had an onclick event that would expand the details of the current item below it. Here's the JS code that managed the interaction:

    elem.find(".list-item .list-item-row").click(function(evt) {
        evt.stopPropagation();
        $.tf.listItem.showHideItem($(this));
    });
    

    Apparently the evt.stopPropation() was not allowing the :remote link's click event to bubble up high enough in the event chain to be handled by UJS. I fixed the problem by filtering out the srcElements related to the :remote link. Here's the code:

    elem.find(".list-item .list-item-row").click(function(evt) {
        if ($(evt.srcElement).not('a').not('img').size() > 0) {
            evt.stopPropagation();
            $.tf.listItem.showHideItem($(this));
        }
    });
    

    By filtering the srcElements, the Img/A clicks are allowed to bubble up.