Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Where I show the most inefficient way to duplicate HTML rendering for JavaScript XHR form validation callbacks.

Because Rails' Unobstrusive JavaScript (UJS) driver rails.js and jQuery won't execute any JavaScript in XMLHttpRequest's response bodies when the status code is 400 or 424 or something similar, I had to instruct the client (jQuery) myself to render error messages on form validation.

This is my first attempt to do so: instead of a JS request, respond with JSON and do the interface changes client-side. Problem is, the JavaScript code is sent to the client only once, hence ERB view templates aren't available during execution. I couldn't just use Rails helpers this way to refactor rendering flash messages. The HTML was spread across three places then: create.html.erb to handle synchroneous requests and render both flash messages and form validation error, entries.js.coffee to render errors and lastly create.js.erb to render success messages and insert the new entry.

It worked, and it was a fun ride since I didn't know any CoffeeScript before, but in the end this wouldn't be a viable solution.

<% if flash.now[:notice] %>
// Get rid of old messages and errors
$('#flash').remove();
$('form#new_event').find('.error').remove();
// Display success message
var theFlash = $('<div id="flash"></div>');
theFlash.insertAfter($('h1'));
var theNotice = $('<p class="notice"><i class="icon-info-sign"></i><%=j flash[:notice] %></p>');
theFlash.append(theNotice);
<% end %>
$('#events').append("<%=j render(:partial => 'event', :object => @event, :locals => { :is_new => true }) %>");
# app/assets/javascripts/entries.js.coffee
jQuery ->
$('form#new_event').bind('ajax:error', (evt, xhr, status, error) ->
resp = $.parseJSON(xhr.responseText)
text = resp.message # == flash[:error}
errors = resp.errors # == @event.errors
$form = $(this)
# Clean old messages
$form.find('.error').remove()
$('#flash').remove()
# Add errors to the form
for field, error of errors
field = $form.find('input[name="event[' + field + ']"]')
$('<span class="error">' + error + '</span>').insertAfter(field)
# Insert flash error
theFlash = $('<div id="flash"><p class="error"><i class="icon-warning-sign"></i>' + text + '</p></div>')
theFlash.insertAfter($('h1'))
)
# app/controllers/events_controller.rb
class EventsController < ApplicationController
# ...
def create
@event = Event.new(params[:event])
if @event.save
flash.now[:notice] = "Success!"
respond_to do |format|
format.html { redirect_to timeline_url, :status => 303, :notice => flash[:notice] }
format.xml { render :nothing => true, :status => 201 }
format.js { render :status => 201 }
end
else
flash.now[:error] = "Event could not be created."
respond_to do |format|
format.html { render :index, :status => 400 }
format.xml { render :xml => @event.errors, :status => 400 }
# Up next: some JSON because create.js.erb could be rendered but won't be executed automatically
format.js { render :json => { message: flash.now[:error], errors: @event.errors }, :status => 400 }
end
end
end
# ...
end
<h1>Timeline</h1>
<% if flash[:notice] || flash.now[:error] %>
<div id="flash">
<% if flash[:notice] %>
<p class="notice"><i class="icon-info-sign"></i><%= flash[:notice] %></p>
<% end %>
<% if flash[:error] %>
<p class="error"><i class="icon-warning-sign"></i><%= flash[:error] %></p>
<% end %>
</div>
<% end %>
<%= form_for @event, :remote => true, :html => { :'data-type' => :script } do |f| %>
<ul>
<li>
<%= f.label :weight, "Weight" %>
<%= f.text_field :weight %>
<%- if @event.errors[:weight].any? %>
<span class="error"><%=@event.errors[:weight]%></span>
<% end %>
</li>
<li>
<input type="submit" name="submit" value="Save" />
</li>
</ul>
<% end %>
<ul id="events">
<%= render @events %>
</ul>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.