Displaying form error notifications within modal - ruby-on-rails

I'm new to Rails and have been struggling with this issue for many hours - would really appreciate any help.
I have a signup form (using Devise and simple_form) inside a Bootstrap modal and would like error notifications to be displayed within the same modal when the form is submitted. Currently, when the form is submitted with errors, the page redirects to /users and the errors are rendered there. When the form is submitted without errors, the page stays on the home page and displays a 'success' flash message as desired.
I don't know whether this issue is caused by a controller/routing problem, whether I need to (first learn then) add in jQuery/Javascript/Ajax, or something else.
Modal code (contained in a partial)
<div id="signupModal" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="signupLabel" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 id="signupLabel">Sign up</h3>
</div>
<div class="modal-body">
<%= simple_form_for(resource, :as => resource_name, :url => registration_path(resource_name), html: {class: "form-horizontal"}) do |f| %>
<%= f.error_notification %>
<%= f.input :name,
placeholder: 'John Smith',
input_html: {class: 'span7'} %>
...
<div class="form-actions">
<%= f.submit "Sign up", class: "btn btn-large btn-success" %>
</div>
<% end %>
</div>
</div>
application_helper.rb - this code was added after reading this SO question
module ApplicationHelper
def resource_name
:user
end
def resource
#resource ||= User.new
end
def devise_mapping
#devise_mapping ||= Devise.mappings[:user]
end
end

You should add a :remote => true to your form, this causes the form to be submitted via Ajax. You then need to modify your controller to respond_to format.js. It should attempt to save the record. If there are errors, it should reply with create.js.erb which has something like the following in it:
$("#errors_div").html("<%= #resource.errors.full_messages %>")

If you look under the hood of simple_form you'll see that it adds some new css 'error' classes and spans with the error messages in them, based on evaluating the results of the #model.errors array.
I don't believe that the form is designed to handle error message display when the submit is done via ajax. I think your options are:
Render the resulting form to a string on the sever side via 'render_to_string' and then replace the contents on the page using a jQuery callback handler or via rendering the response as a js template.
Return the results as json, including the #model.errors and then in javascript use this info to update the form with error messages.
I'd start with option #1, as it is a little simpler.

Related

Moving a rails form into a separate view

I have a form in my rails 5 app that takes in User data and stores it in a mysql db.
Everything works great. However, I want to move this form into a separate view that does not correspond to its class name/model, controller name and view.
Can this be done by simply rendering the form into a separate view as a partial?
Any advice would be great.
Controller file:
class UserLeadsController < ApplicationController
def index
#user_lead = UserLead.all
end
def new
#user_lead = UserLead.new
#lead_reasons = LeadReason.all.map{ |r| [r.name, r.id] }
#lead_sources = LeadSource.all.map{ |s| [s.name, s.id] }
end
def create
#user_lead = UserLead.new(user_lead_params)
#user_lead.lead_reason_id = params[:lead_reason_id]
#user_lead.lead_source_id = params[:lead_source_id]
#user_lead.save
redirect_to user_leads_path
end
private
def user_lead_params
params.require(:user_lead).permit(:name, :businessname, :phone, :email, :amount)
end
end
Lets say this is UserLeadController, I want to place its form partial into a view inside of my HomeController? Can this be done?
This is the partial, it tells me #user_lead is nil or empty? I cant seem to see how it is?
<%= form_for #user_lead, url: user_leads_path, html: {class: 'col-12'}, remote: true do |f| %>
<div class="col-12 px-0 text-center">
<h4 class="fl-color-navy">Some heading here</h4>
</div>
<div class="col-12 progress-container-center">
<div class="col-12 progress-bar-block">
<div class="progress-bar">
<ol class="progress-steps">
<li class="progress-step step1 current-step" style="width: 0%;"> <span class="count highlight-index"></span></li>
<li class="progress-step step2 current-step" style="width: 100%;"> <span class="count"></span></li>
</ol>
</div>
</div>
</div> ....
Rendering the forms in a partial works fine. Submitting the form will direct the browser to the controller as normal if the view is correct (Presumably UserLeadsController#create, question didn't include the view code).
If things like #lead_reasons and #lead_sources are important to render the view, that is a little more tricky. Of course in simple cases the partial can just do it inline, you could also for example add a helper method that gets those and then renders the partial passing stuff to the locals param. e.g.
module ApplicationHelper
def user_leads_form
lead_reasons = LeadReason.all.map{ |r| [r.name, r.id] }
lead_sources = LeadSource.all.map{ |s| [s.name, s.id] }
render partial: "user_leads_form", locals: {lead_reasons: lead_reasons, lead_sources: lead_sources}
end
end
If you wanted it to go back to the previous page in some cases, e.g. for validation errors, you would need to add that, possibly by adding a parameter to the form, or inspecting the Referer request header (intentional misspelling!).
If you wanted it to not leave the page at all, then that is getting into client side scripting solutions.

Rails link_to calls helper method and loads the variables used in modal

I'm trying to show the user profile of commenters in a modal. So when the user clicks on the "username" above the comment made, a modal pops-up with the appropriate user profile info. I try to pass in (comment.id) parameter to my helper method, then return the user profile for each commenter, which I use in my modal view.
My helper:
def getuserprofileofcommenter(commentid)
specific_comment = Comment.where("id = ?", commentid)
specific_comment.each do |comment|
#commenter = Userprofile.find_by("id = ?", comment.userprofile_id)
end
end
My modal (in view):
<div id="commenterModal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
...
<div class="row">
<div class="col-sm-9">
<p style="font-weight: bold;"><%= #commenter.user_description %></p>
<p><%= #commenter.bio %></p>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-primary" data-dismiss="modal" aria-hidden="true">OK</button>
</div>
...
My view:
<%= link_to "username", getuserprofileofcommenter(comment.id), :class => "btn", :remote => false, "data-toggle" => "modal", "data-target" => "#commenterModal" %>
The link_to seems to be taking my helper method as a path. When I click on the link "username", a modal pops-up but with the first commenter's profile and then redirects to comment_path in the modal. I can't seem to get the correct user profile info in the modal.
Any advice would be greatly appreciated!
Your helper method doesn't return a user profile, it returns a specific_comment.
The last statement in the method is its return value. The fact that you assign to #commenter is completely irrelevant.
Your last statement is specific_comment.where(...), and the result of that statement is specific_comment itself. If you want to return the user (or "commenter") for the comment, you need to actually return it, not just assign it to an instance variable.
As an aside, you're misusing where and each. You're finding a record by ID, there is no need to use where or iterate over a result set. If you've set up your associations correctly, your entire method should look like this:
def getuserprofileofcommenter(commentid)
Comment.find(commentid).userprofile
end

Rails - Editing item from list by bootstrap modal

I'm new in Rails. I try to make a list of groups (/groups) where I want to have edit and delete option in each row. Editing should be implemented by modal because Group has only one attribute - name. So I don't want to open /groups/number/edit.
My problem is: I don't know how to bind chosen group with form_for. Maybe it isn't good approach indeed.
Modal is displayed, but the name field is blank. When I used debug for #group is blank too. I don't know why.
Here is my code(single group row):
<li id="group-<%= group.id %>" class="list-group-item">
<span class="group-name"><%= group.name %></span>
<%= link_to edit_group_path(group.id), "data-toggle" => "modal", "data-target" => "#myModal", :class => "edit-option btn" do %>
<i class="fa fa-pencil-square-o fa-2x" ></i>
<% end %>
<%= link_to group, method: :delete, data: { confirm: "Na pewno chcesz usunąć tę grupę?" }, :class => "delete-option btn btn-danger" do %>
<i class="fa fa-trash-o" > usuń</i>
<% end %>
<!-- Modal -->
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title" id="myModalLabel">Zmień nazwę grupy</h4>
</div>
<%= form_for(#group) do |f| %>
<div class="modal-body">
<%= debug #group %>
<p>
<%= f.label :name, "Nazwa" %>
<%= f.text_field :name, class: 'form-control'%>
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Anuluj</button>
<%= f.submit "Zapisz zmiany", class: "btn btn-primary" %>
</div>
<% end %>
</div>
</div>
</div>
Controller
def edit
#group = current_user.groups.find_by(id: params[:id])
end
def update
#group = current_user.groups.find_by(id: params[:id])
if #group.update_attributes(group_params)
flash[:success] = "Nazwa grupy została zmieniona"
redirect_to groups_path
else
redirect_to groups_path
end
end
I will assume that the answer to my question above is a yes, and so, I'll just continue to answer.
First thing: You don't want to be repeating different Modals for each group, you want only a single Modal to be used by all the groups.
Second thing: You are very correct with the idea of wanting to send the group.id to the edit method in your controller, for it to return that group back into your Modal
However, what is wrong is how you are trying to achieve this.
<%= link_to edit_group_path(group.id), "data-toggle" => "modal", "data-target" => "#myModal", :class => "edit-option btn" do %>
You are telling your link to do two things when clicked: (1) go to edit path (2) open modal. Which one will take more importance? Right now, it is the modal that is opening, and not the edit path working, which means that you are not getting to the controller at all, so accounting for why your #group is blank.
To achieve this, you will have to tell your link to do only one thing, and when that is successful, do the other.
What I'll advice is to use Ajax (by setting remote: true) on your link to go to the controller, and then open the modal on response.
Here is a breakdown of the steps:
1.Set remote: true to make the call with Ajax
<%= link_to edit_group_path(group.id), remote: true %>
2.Tell controller action (in this case, the edit action) to respond to js
def edit
#group = current_user.groups.find_by(id: params[:id])
format.js
end
Prepare an edit.js.erb file to handle the response (this will be in the same directory which your other view files are)
Populate the form with the #group from inside the response
Put the populated form in the modal
Use Javascript to show the modal
$('.modal-title').html("Edit Group")
$('.modal-body').html("<%= escape_javascript( render partial: 'form', locals: {group: #group} ) %>")
$('#myModal').modal()
As simple and as direct as that. Hope this helps a lot.

Confused as to why I am getting a nil error on a new user form before it has been called

I will try my best to be clear, I myself am confused as to what is happening or how to explain it. I am building a Rails app.
I have two forms so far, both in modals, which are in partials.
I have my app rooted to a path called home, which is defined in application controller. In my html.erb, I want a particular styling on that page, so I have set it so that if I am on the home page, the body tag gets a class of 'homepage'. Here is the code from application_controller.rb:
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
include SessionsHelper
def home
#home_page = true
# #user = User.new
end
end
As you can see, I have a code #user = User.new commented out. I added that because without it, Rails throws an error First argument in form cannot contain nil or be empty in reference to my signup form which in in a module, which is in a partial. Here is that form:
<div class="modal fade" id="loginModal" role="dialog">
<div class="modal-dialog">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h2 class="modal-title">Welcome Back!</h2>
</div>
<div class="modal-body">
<%= form_for(:session, url: sessions_path) do |f| %>
<%= label_tag :username %>:
<%= f.text_field :user_name, class: 'form-control'%>
<br>
<%= label_tag :password%>:
<%= f.password_field :password, class: 'form-control'%>
<br><br>
<%= submit_tag "Login", class: 'login-button'%>
<% end %>
</div> <!-- modal body -->
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div> <!-- modal-content -->
</div> <!-- modal-dialog -->
</div> <!-- login-modal -->
If uncomment that line in the application controller, I can go to the home page without errors. But then it messes up sessions, because from what I can tell, I have some new user waiting to be created, as that line is invoking the form I suppose. In addition, from terminal, I can see it is also finding user 1 for some reason. Here is part of the terminal output if it helps:
Started GET "/" for ::1 at 2015-08-19 23:35:30 -0400
ActiveRecord::SchemaMigration Load (0.5ms) SELECT "schema_migrations".* FROM "schema_migrations"
Processing by ApplicationController#home as HTML
Rendered application/home.html.erb within layouts/application (2.2ms)
User Load (0.8ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 1]]
Rendered users/_log_in_form.html.erb (14.2ms)
Rendered users/_new.html.erb (6.2ms)
Completed 500 Internal Server Error in 583ms (ActiveRecord: 6.2ms)
ArgumentError - First argument in form cannot contain nil or be empty:
actionview (4.2.3) lib/action_view/helpers/form_helper.rb:432:in `form_for'
app/views/users/_new.html.erb:15:in `_app_views_users__new_html_erb___2821927674300231403_70140243283980'
actionview (4.2.3) lib/action_view/template.rb:145:in `block in render'
so when I do log in, despite my user id being 1 in the database, it is being shown in terminal as 2 - which throws a bunch of undefined errors etc when I try to log my username or session[:user_id] in console(because ther IS no user 2). Of course, putting that #user = User.new line is wrong and I know this, but without it I get an error on the form as soon as I go to the home page, it seems to need to have the #user available...but with it there I get errors in sessions and cant log in or out properly...what is going on here? Here is the part of my application.html.erb that is rendering the forms, in case it helps. Also note that when I log in, I get no errors and the logout link appears, but I am unable to log out (more errors) and as I said, terminal shows me as having a id of 2 when it should be 1. I have the modal's render commented out for testing.
<% if logged_in? %>
<h1>Welcome back <%= current_user.user_name %>!</h1>
<%= link_to "Logout", sessions_path, method: :destroy %>
<% end %>
<ul>
<li> Find a Project</li>
<li lass="btn btn-info btn-lg" data-toggle="modal" data-target="#registerModal">Register</li>
<li lass="btn btn-info btn-lg" data-toggle="modal" data-target="#loginModal">Login</li>
</ul>
<div id='login-container'>
<!-- <%= render 'users/log_in_form' %> -->
</div>
<div class="signup-container">
<!-- <%= render 'users/new' %> -->
</div>
In your form do something like this:
<%= form_for(#user, url: :sessions_path) do |f| %>
and in yourApplicationController
def home
#home_page = true
#user = User.new
end
That is uncomment #user = User.new
See I will explain what is wrong with what you are doing. You didn't define the model object for the form_for method. The form_for method accepts 2 arguments and the 1st argument should be the model object like #user or #post etc. Please read more [here], (http://apidock.com/rails/ActionView/Helpers/FormHelper/form_for), and you will completely understand how form_for is exactly used.

Issue getting variable to pass from Controller Action to Mailer template in Ruby on Rails

I'm having trouble getting a variable filled with text to pass from my controller action "Submit", to my email template.
The code listed is to allow you to follow the variable named #message through my app.
To start, what I have is a basic "show" view for my model "ECN". It contains some nested information, but that is not relevant. I'm using twitter bootstrap to create a modal form. My show view file contains the following:
<button class="btn btn-primary btn-lg" data-toggle="modal" data-target="#submitModal">
Submit for Approval
</button>
<!-- Submit Modal -->
<div class="modal fade" id="submitModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">X</button>
<h4 class="modal-title" id="myModalLabel">Edit Email</h4>
</div>
<div class="modal-body">
<%= render 'submit_email_fields' %>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<%= button_to "Submit ECN", {action: "submit", :id => #ecn.id}, {class: "btn btn-primary"} %>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
Basically the modal pops up and renders the _submit_email_fields file which is shown next:
<%= form_tag :action => 'submit', :id => #ecn.id %>
<p><label for="email_message"></label><br/>
<%= text_area 'email', 'message', value: render('submitted.text.erb'), class: "span6" %></p>
<%= form_tag %>
At this point, I have created a form with a text_area that is populated with a text file "submitted.text.erb". I have a basic email set up, and when the user presses the button "Submit for Approval" in the show view, the modal pops up with a pre-filled form that they can edit before hitting the Submit ECN button. The information in the text area is then saved into the parameter email[:message].
Next lets look at the submit action in my controller:
def submit
#ecn = Ecn.find(params[:id])
#email = params[:email]
#message = #email[:message]
#email_list = EmailList.all
respond_to do |format|
EcnNotifier.submit_engineering(#ecn, #message).deliver
format.html { redirect_to home_url, alert: "Ecn has been submitted for approval." }
format.json { render json: #ecns }
end
end
This action takes the parameters sets them to instance variables before running my mailer EcnNotifier on them. I've verified that the variable #message contains the full text from the form at this point in time. However, the email sends through with no body. Let's look at the mailer method submit_engineering(#ecn, #message):
def submit_engineering(ecn, message)
#ecn = ecn
#message = message
#email = EmailList.where(department: "Engineering").all
to = []
#email.each do |e|
to.push e.email
end
mail to: to, subject: 'ECN approval', template_name: 'submitted'
end
This should take my message and continue to use it under the name #message. This mailer send out the template named "submitted". This template contains the following:
<%= #message %>
<h1>TEST</h1>
The template should simply print out the contents of #message, however the email sent is empty except for the TEST test I've added to verify the template was being used correctly.
My question is, why is my #message variable not passing correctly from the controller to the final email? Thanks for reading this far and I appreciate your help.

Resources