I have a submit form that is shown in a lightbox. I validate the form pretty heavily but there are circumstances where a playlist is already submitted, in which case I can't tell before a form is submitted.
Currently I have :disable_with => 'Submitting...'
It would be completely awesome if the error was sent back to the form, and the submit button would re-enable.
Would this be possible without too much complication?
I also have a second related question... Because my form is in a lightbox, it's code is actually ON the index page (my app is just one page pretty much). This means that there really is no 'new' action, but rather a #playlist = Playlist.new(:title => ...) in my index action (along with a #playlists = Playlist.all, etc). In my routes I did this: resources :playlists, :only => [:create]
Does this sound about right the way I did it?
EDIT: HEre is some code, although it's basically about as simple as you can imagine it.
The following kind of works... it creates the playlist if its valid, otherwise it does nothing. Both times create.js.erb is called.. i just dont now how to make this work to completion now. on success i need to close the window, on failure i need to load the errors into the form thats already on the screen. Im not sure where that goes though :/
before_filter :load_genres, :only =>[:index, :user]
before_filter :new_playlist, :only => [:index, :new]
def index
#playlists = Playlist.order('created_at DESC').page(params[:page]).per(30)
end
def create
#playlist = Playlist.new(params[:playlist])
respond_to do |format|
if #playlist.save
format.html { redirect_to root_path, :notice => "Playlist submitted" }
format.js {}
else
format.html { render :action => :new, :layout => !request.xhr? }
format.js {}
end
end
end
def new
end
def load_genres
#genres = Genre.order(:name)
end
def new_playlist
#playlist = Playlist.new(:title => params[:title], :url => params[:url], :description => params[:description])
end
Heres the first like of my form (located in index.html.erb):
<%= form_for #playlist, :remote => true do |f| %>
I currently have no html or code in create.html.erb
Here is my solution to this Issue.... I put these in all my js.erb files
$('#flash').html("<%= escape_javascript raw(flash_display) %>");
With this Helper
def flash_display
response = ""
flash.each do |name, msg|
response = response + content_tag(:div, msg, :id => "flash_#{name}")
end
flash.discard
response
end
this works well with the flash div already set up in layout
<div id="flash">
<% flash.each do |name, msg| %>
<%= content_tag :div, msg, :id => "flash_#{name}" %>
<% end %>
</div>
Hope this Helps
Without seeing your code I can only make broad suggestions.
One would be to set up a js.erb response to the controller action-- or you could do a js script tag in an if clause inside your HTML. Either way you would use jQuery to update the element.
Inside the js/html erb file:
jQuery('#flash_area).html(<flash message>);
The the flash area in the view would need an id of flash_area (or whatever you want to name it).
Related
I have a Rails app where I have a partial that I want a dropdown in so when a user is selected it will do a get method to the urls /timecards/:user_id which will be the controller's show method passing in the User's id field. I'm having a hard time with my paths in the form_tag and could use some help.
Here is my partial in the view:
<%= form_tag timecard_path, :method => :get do %>
<%= select_tag options_from_collection_for_select(User.employee.order("username ASC"), :id, :username) %>
<%= submit_tag "View Employee", class: "btn btn-primary" %>
<% end %>
From rake routes I get the following output:
timecards GET /timecards(.:format) timecards#index
POST /timecards(.:format) timecards#create
new_timecard GET /timecards/new(.:format) timecards#new
edit_timecard GET /timecards/:id/edit(.:format) timecards#edit
timecard GET /timecards/:id(.:format) timecards#show
PUT /timecards/:id(.:format) timecards#update
DELETE /timecards/:id(.:format) timecards#destroy
Here is my controller: timecards_controller.rb
class TimecardsController < ApplicationController
before_filter :disallow_clients, :disallow_medics, :disallow_employee, :disallow_supervisor
def index
#clock_events = ClockEvent.includes(:user).search(params[:search])
respond_to do |format|
format.html do
#clock_events = #clock_events.paginate(:per_page => params[:per_page] || 20, :page => params[:page]).order('users.username asc').order('clock_in desc')
end
format.csv { send_data ClockEvent.to_csv(#clock_events.order('users.username asc').order('clock_in desc')) }
end
end
def new
#clock_event = ClockEvent.new
end
def create
parse_times!
#clock_event = ClockEvent.new(params[:clock_event])
if #clock_event.save
redirect_to timecard_path(#clock_event.user.id), notice: "Entry added for #{#clock_event.user.username}".html_safe
else
render :new, notice: "Time Card Entry failed to Save".html_safe
end
end
def show
#user = User.find(params[:id])
#clock_events = #user.clock_events.search(params[:search])
respond_to do |format|
format.html do
#clock_events = #clock_events.paginate(:per_page => params[:per_page] || 5, :page => params[:page]).order('clock_in DESC')
end
format.csv { send_data ClockEvent.to_csv(#clock_events.order('clock_in desc')) }
format.pdf do
pdf = TimeCardPdf.new(#clock_events, #user)
send_data pdf.render, filename: "timecard-#{#user.username}",
type: "application/pdf",
disposition: "inline"
end
end
end
def edit
#user = User.find(params[:id])
#clock_events = #user.clock_events.search(params[:search]).order("clock_in ASC").paginate(:per_page => 10, :page => params[:page])
end
def update
parse_times!
#clock_event = ClockEvent.find(params[:clock_event][:id])
if #clock_event.update_attributes(params[:clock_event])
redirect_to edit_timecard_path(#clock_event.user.id), notice: "Updated Successfully".html_safe
else
redirect_to :back, notice: "Woops.".html_safe
end
end
private
def parse_times!
params[:clock_event].parse_time_select! :clock_in if params[:clock_event].has_key? 'clock_in(5i)'
params[:clock_event].parse_time_select! :clock_out if params[:clock_event].has_key? 'clock_out(5i)'
end
end
So I believe I'm calling the path properly in the form_tag but when I load the page I get the error: No route matches {:action=>"show", :controller=>"timecards"} Even though there is a show action in the timecards_controller.
I think something I have to set the form_tag for an explicit url and somehow pass in the :id of the user in the params. But I'm a bit stuck on how to do this.
So to summarize. When I have the dropdown, I select a user, click "View Employee", and that should go to the show action in timecards_controller.rb with the url of /timecards/3 (as an example). I've never used form_tag in this way before so passing a path or explicit url is a bit foreign to me.
A simple hotfix:
The simplest possible fix would be to change the form to a bunch of links.
<%= User.employee.order("username ASC").each |u| %>
<%= link_to u.username, timecard_path %>
<% end %>
Otherwise you could use Javascript to simply make the form redirect:
<%= form_tag timecodes_path, :method => :get, :id => 'timecode_employee' do %>
<%= select_tag options_from_collection_for_select(User.employee.order("username ASC"), :id, :username) %>
<%= submit_tag "View Employee", class: "btn btn-primary" %>
<% end %>
$("#timecode_employee").submit(function(e){
var form = $(this);
// redirect to timecards/:id
window.location = form.attr('action') + form.find('select').val();
e.preventDefault();
});
Tips for redesign
Your design could be radically improved by adding an underlying TimeCard model.
Here is one very common case which tells you why:
The client decides that they want to have managers sign off on time
cards every month.
Oh, drat. Now we need get all the ClockEvents in that scope and update a 'clock_events.state' on each.
But then the client also wants to know who has signed off the card. So you add a clock_events.signed_off_by_id and update all the clockevents. And then they want three managers to sign off, etc.
An alternative design
Note that this is a opinionated generic example.
class ClockEvent < ActiveRecord::Base
enum status: [:clocked_in, :clocked_out]
has_many :users
belongs_to :time_card
end
class TimeCard < ActiveRecord::Base
belongs_to :user
has_many :clock_events
accepts_nested_attributes_for :clock_events
end
class User < ActiveRecord::Base
has_many :time_cards
has_many :clock_events, through: :time_cards
end
A TimeCard might be automatically issued per month or if you never want to change than just stick with one TimeCard per user.
Lets go with some traditional routes here:
resources :time_cards
end
resources :clock_events do
end
resources :users, shallow: true do
resources :clock_events do
end
resources :time_cards do
end
end
Now imagine we had a classical punch card time clock.
We would punch in with:
POST /clock_events { user_id: 1, time_card_id: 5 }
And punch out with:
PATCH /clock_events/1 { status: :clocked_out }
Thats REST for you.
Nesto Presto
We have nested routes for time cards and clock_events per user:
GET /users/1/time_cards
GET /users/1/clock_events
From the we can choose to either build a dedicated UserTimeCardController or we can scope by the user id param in TimeCardsController.
class TimeCardsController
def index
#time_cards = TimeCard.all
#time_cards = #time_cards.where(user: params[:user_id]) if params[:user_id]
#users = scope.all
end
end
Filtering
But imagine if we want managers be be able filter the number of employees he sees on the index - a good architecture would look something like this:
class TimeCardsController
def index
#time_cards = TimeCard.all
#time_cards = #time_cards.where(user: params[:user_id]) if params[:user_id]
if params[:filters]
#time_cards = #time_cards.search(params[:search])
end
end
end
And on our index page we would add a form like this:
<%= form_tag(time_cards_path, method: :get) %>
<%= select_tag options_from_collection_for_select(User.employee.order("username ASC"), :id, :username), multiple: true %>
<%= submit_tag "Filter", class: "btn btn-primary" %>
<% end %>
The route that you have defined:
timecard GET /timecards/:id(.:format) timecards#show
It requires an id to show the correct timecard. But when you are invoking it in form_tag, you are just sending, timecard_path with no id. So you do need to send an id or a timecard object, and Rails will automatically extract the id out of it.
So, that should be:
form_tag #timecard do
# other code
end
#timecard must be instantiated in the action that renders your partial, and it must be a valid TimeCard object.
I'm definitely going to take #maxcal's advice and rewrite this thing, but since I had to ship fast I came up with an ugly UI hack that "works". I don't like it but it does exactly what I need it to do.
<ul class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<button class="btn btn-medium btn-primary">View Employees</button>
<b class="caret"></b>
</a>
<ul class="dropdown-menu">
<% #users.each do |u| %>
<li><%= link_to "#{u.username}", timecard_path(u) %>
<% end %>
</ul>
</ul>
I don't like all the loading and this is really dirty, but in this instance since I was breaking all sorts of Rails conventions and had to ship this afternoon it'll work until I can come back and make it better.
Let the laughs begin. :)
Okay, so I'm rebuilding an old photo gallery management app that I built a few years back in ROR 1 and running in to a few coding difficulties, since I'm rusty and apparently things have changed. My first question is I'm getting an undefined local variable or method 'gallery' error when trying to call a page. What I'm confused about is that I have the method defined in the 'gallery' controller but I'm wondering if I'm completely missing something. Here are some relevant code snippets, first is my index.html.erb page:
<% #photos.each do |photo| %>
<div>
<%= image_tag(photo.thumb_url) %>
<%= link_to 'Show', gallery %><br/>
</div>
<% end %>
<p><%= will_paginate #photos %></p>
my gallery controller:
class GalleryController < ApplicationController
skip_before_filter :authorize
# GET /gallery
# GET /gallery.xml
def index
#photos = Photo.all
#photos = Photo.paginate :page=>params[:page], :order=>'date desc',
:per_page => 2
respond_to do |format|
format.html # index.html.erb
format.json { render :json => #gallery }
end
end
# GET /gallery/1
# GET /gallery/1.xml
def show
#photo = Photo.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render :json => #photo }
end
end
end
And my error:
undefined local variable or method `gallery' for #<#<Class:0x111127b38>:0x1112d5700>
I should clarify that "Photos" is the admin section which requires login and contains all of the fields/database/record data. I have no problem using the following line:
<%= link_to 'Show', photo %><br/>
Which brings up the correct record and viewing page, but in the admin section of the site (which requires login). Hopefully that makes sense.
Nowhere do I see you defining the variable gallery, and this is what your error message is telling you: gallery is undefined in your view.
Update RE your comments:
Just because you want the photo to go to the gallery controller doesn't mean you can just type "gallery" and expect results. This is a programming langauge, where words have meanings, and all you're doing is referencing an undefined variable. This, and nothing like this, has ever worked in any version of Rails.
If you want to route your photo to the Gallery controller, you can use the automagically generated _path helpers; specifically, gallery_path, which accepts an argument for the id of the "gallery" (really a photo) to show:
<%= link_to 'Show', gallery_path(photo.id) %><br/>
Try replacing your link_to method with something like: <%= link_to 'Show', :controller => "photos", :action => :your_method, :params1 => gallery %>.
Then in your PhotoController you can use: #my_gallery = params[:params1] to access your gallery item.
Some documentation on routes:
http://guides.rubyonrails.org/routing.html
Suppose I have the following view in my rails app
<% if is_subscriber? %>
<%= render :partial => 'subscriber_page' %>
<% else %>
<%= render :partial => 'payment_form' %>
<% end %>
The payment form submits a post-rquest to the controller show page. In my routes file I have:
match ':users(/:id)', :to => 'users#submit_payment', :via => :post
My submit payment helper method is:
def submit_payment
current_user.is_subscriber = true
show
end
However when I run this I get a submit_payment template missing error. How can I make sure that the users show page is simply reloaded when the helper method is finished.
It looks like you're trying to redirect to the show page. Try this:
redirect_to user_path(#user) # Note you need to send the show page an :id param
If you're just wanting to render a specific partial, do this:
render :partial => 'show'
However, if you want something more seamless, like submitting the form VIA Ajax, which gets back a partial and then on success dumps it in to a waiting DIV or something, you can do that as well, though with a bit more complexity.
I'm making resourceful routes for youtube videos. So, a person just pastes the youtube embed link in the form. In the controller I have a normal set of resourceful actions:
class VideosController < ApplicationController
def index
#videos = Video.all
end
def new
#video = Video.new
end
def create
Video.create(params[:video])
redirect_to :action => :index
end
def destroy
Video.destroy(params[:id])
redirect_to :action => :index
end
end
And in the view I'm just displaying it: (in Haml)
- #page_title = 'Video'
#videos
%ul
= list_of(#videos) do |video|
%h1= video.title
!= video.link
= link_to "Delete", video_path(video), :method => :delete
= link_to "Add new video", new_video_path
%p#top
= link_to 'Go to top ↑', '#'
For the one who don't use Haml, != escapes the string. video.link holds the YouTube embed code
The problem is that, when I create a new video, and when it redirects me back to the index page, the newly created video isn't displayed (the other ones are normally displayed). Only after I refresh the page, it's normally displayed.
I saw in the web inspector that the src attribute is missing from the iframe (so that's why the video isn't displayed). But when I look in the page source, everything is normal there. So, thinking it may be Javascript's fault, I tried disabling it. But nothing changed.
I don't think you want to escape it using haml... I think you want to call
video.link.html_safe
Note: if the user is pasting in the link, this is very unsafe.
Update --- If you have the javascript develop console open, you'll see this error pop up:
**Refused to execute a JavaScript script. Source code of script found within request.**
Check this answer for why it's refusing to due XSS Here's a method that is both safe and works. You'll paste in the youtube ID in the text field: ibWYROwadYs
index.erb
<% if session[:youtube].present? %>
<iframe width="480" height="360" src="http://www.youtube.com/embed/<%=session[:youtube]%>" frameborder="0" allowfullscreen></iframe>
<% end %>
<%= form_tag load_path do %>
<%= text_field_tag :youtube_id %>
<%= submit_tag "Submit" %>
<% end %>
<%= link_to "Clear", clear_path, :method => :delete %>
home_controller.rb
class HomeController < ApplicationController
def index
end
def smth
session[:youtube] = params[:youtube_id]
redirect_to :action => :index
end
def clear
session.clear
redirect_to :action => :index
end
end
I'm creating a simple demo app that allows a user to enter their email address to register their interest in receiving beta access. The app then sends them a confirmation email that lets them know we've received their request. If you've ever signed up to be notified of a beta launch then you get the idea.
I'm curious about how to handle errors in Rails 3 while using AJAX. Before implementing my respond_to block I had a form that rendered a shared errors partial.
Here's the form.
<% if flash[:notice] %>
<p><%= flash[:notice] %></p>
<% end %>
<p>Sign up to be notified when the beta launches.</p>
<%= form_for #user, :remote => true do |form| %>
<%= render '/shared/errors', :target => #user %>
<%= form.label :email, "Your Email Address" %>
<%= form.text_field :email %>
<%= form.submit "Notify Me" %>
<% end %>
And here's the aforementioned errors partial.
<% if target.errors.any? %>
<ul>
<% target.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
<% end %>
Very standard stuff. The controller action looks like this.
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
format.html { redirect_to :back, flash[:notice] = "Thanks for your interest! We'll let you know when the app is in beta." }
format.js
else
format.html { render :action => :new }
format.js
end
end
end
Everything works perfectly before implementing ajax. If the form passes validation then they see the success flash message and if not then they see a list of errors. So now that i have a create.js.erb file how should I handle the errors without repeating myself or is that impossible. I obviously want to keep this as DRY as possible.
You can still render a shared partial for all .js errors in your js.erb file.
<% if #user.errors.any? %>
var el = $('#create_user_form');
// Create a list of errors
<%= render :partial=>'js_errors', :locals=>{:target=> #user} %>
<% else %>
$('#users_list').append("<%= escape_javascript(render :partial=>"users/show", :locals=>{:user => #user }) %>");
// Clear form
el.find('input:text,textarea').val('');
el.find('.validation-errors').empty();
<% end %>
And your partial could look like (Assuming jquery):
<% target.errors.full_messages.each do |error| %>
var errors = $('<ul />');
errors.append('<li><%= escape_javascript( error ) %></li>');
<% end %>
But there's also ANOTHER option...It's even DRYer.
http://www.alfajango.com/blog/rails-3-remote-links-and-forms/
If you are working your way through ajax in rails 3, this guide is really the best for understanding responses and ajax rendering as it currently stands.
I worked through this guide and posted in the comments how you can actually use your HTML partials for both HTML and AJAX request responses. I did it by accident and then followed up on how to do it.
Enjoy!
You can actually return straight-up html with your response just like before.
Here's the short version:
def create
#something = Somethng.new(params[:something])
if #something.save
respond_with( #something, :status => :created, :location => #something ) do |format|
format.html do
if request.xhr?
render :partial => "something/show", :locals => { :billable => #billable }, :layout => false
end
end
end
else
respond_with( #something.errors, :status => :unprocessable_entity ) do |format|
format.html do
if request.xhr?
render :partial => "something/new", :locals => { :something => #something }, :layout => false
else
render :action => :new
end
end
end
end
end
I'm not sure the rails remote form way to do it, but my standard mode of operation is to return objects on ajax request in this format:
{ success: true|false,
data: "html or some other data",
errors: {} } // jsonified ActiveModel::Errors object
It works very well and lets you render partials into the data field for use on the page, or you can loop through errors in the error object to insert error messages or highlight fields.
I have been facing the same problem a few days ago. I used remote => true option in my form to use Ajax in my Rails 3 application. After that, I have been looking for solution for validating my form fields. After trying a good number of jQuery / Javascript approaches (none of them worked for me though) I came to know about a superb gem called client_side_validations. It is very easy to install by following the instructions on github link (https://github.com/bcardarella/client_side_validations). It works like charm for client side validation of form fields, an awesome gem indeed. Hope this helps with people who are tired of looking for a simple solution for client side validation of model fields after using Ajax in Rails 3 application.