Rails: How to add an object through a bootstrap modal? - javascript

I'm new to rails and I have this web application that allows users to create new print Jobs Using Rails 4
app/models/job.erb
class Job < ActiveRecord::Base
belongs_to :job_type
end
app/models/job_type.erb
class Job < ActiveRecord::Base
has_many :jobs
end
In the new job creation form user must choose a job type for his new job from a list, Which I managed to get it throw the following code.
app/views/jobs/_form.html.erb
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for(#job) do |f| %>
.....
<div class="field">
<%= f.label :job_type_id %><br>
<%= f.collection_select :job_type_id, JobType.all,:id,:name %>
</div>
<!-- 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">Modal title</h4>
</div>
<div class="modal-body">
...
</div>
<div class="modal-footer">
</div>
</div>
</div>
</div>
<% end %>
</div>
</div>
And through some coffee script I managed to add "Other" Option to the Job Types menu, Which fires the modal #myModal to add a new JobType to the database if its not exist...
app/assets/javascript/jobs.coffee
$('#job_job_type_id').append("<option>Other</option>")
$('#job_job_type_id').change ->
jobType = $('#job_job_type_id :selected').text()
if jobType == "Other"
$('#myModal').modal('show')
else
$('#myModal').modal('hide')
The Code is working good and fires the bootstrap modal. But that's it I don't know what to do next?
I've tried a lot of code and reviewed a lot of questions, but I didn't get to make this modal able to add a new JobType and update the list, I've figured that needs some modification to controllers and fancy AJAX code beyond my knowledge...
I have some questions here
1) What should I put in the modal code to be able to add a new JobType to the database, Then return to the new Job creation form and the newly created JobType selected
2) Which controllers need to be modified? and How? What AJAX Code need?
3) How to re-factor the Job _form ? Can I put the modal code in a new Partial? If yes how to implement this ?
I hope you can help me, I've been struggling to solve this issue for days.
Thank you

Ok, first, to answer your questions:
1) What should I put in the modal code to be able to add a new JobType to the database, Then return to the new Job creation form and the newly created JobType selected
Your modal will add a form, essentially the form you probably have under app/views/job_types/_form.html.erb, but it will have a button that will submit via AJAX instead of form submit.
2) Which controllers need to be modified? and How? What AJAX Code need?
You will need to add a method to the job_types_controller that can handle the aforementioned AJAX call. It will need to:
Save the new job type
Return a success status and the newly created entity to the caller.
3) How to re-factor the Job _form ? Can I put the modal code in a new Partial? If yes how to implement this ?
You need to have a method that, when called, adds a new option to the listbox. You can put the modal code in a partial, or not, up to you; that decision has no consequence in regard to the functionality of all this.
So, what do you need to do here:
SERVER (CONTROLLER):
1) Create a method in config/routes.rb that can handle an AJAX call.
resources :job_types do
post :append, on: :collection
end
This adds a custom resource route. Because we add it this way, we automatically get the URLHelper function append_job_types_path.
2) Implement this method in controllers/job_types_controller.rb to save a new JobType and return it (and, most importantly, it's ID) to the caller.
def append
job_type = JobType.new
job_type.name = params[:job_type_name]
if job_type.save
render :status => 201, :json => { id: job_type.id, name: job_type.name }
else
err_msg = job_type.errors.empty?? 'Save failed' : job_type.errors.full_messages.join('; ')
render :status => 400, :json => { message: err_msg }
end
end
If save goes well, the ID and name of the new entity will be returned as JSON to the caller. If not, we return an error and any validation messages.
Now, we are ready to utilize these methods...
CLIENT (VIEW):
1) Create a button that can launch the modal
You've already done this!
2) Add a form to the modal that can submit a job type
<%= form_tag(append_job_types_path) do %>
Enter Job Name:<br/>
<input type="text" name="new_job_type_name" id="new_job_type_name" />
<br/>
<input type="button" value="Submit" id="append_job_type_submit" />
<% end %>
And really, it doesn't need to be in a form since we're submitting via AJAX, but you'll probably get some styling help by using one. However, the id attributes here are important for the next steps. Note that I'm using form_tag here instead of form_for. That's because I won't be attaching anything to the form (or submitting it for that matter).
3) On submit (er, on button click), send the name entered by the user to new AJAX method
Here, we'll use unobtrusive javascript to hook a listener method to the submit button. You can put this code at the bottom of the view, or you can move it to coffeescript:
javascript:
$("#append_job_type_submit").click(function() {
var name = $("#new_job_type_name").val();
//TODO: validation on the name, ensure it's not blank, etc
$.ajax({
url: '/job_types/append',
method: 'POST',
data: {job_type_name: name},
success: function(data) {
//TODO: Handle success
},
error: function(err) {
//TODO: Handle error
}
});
});
Here, we are sending an AJAX call to the server method, passing the name the user entered. Note that I left space for simple validation you can do prior to submission
4) Upon response, append the new option to the listbox.
This continues the function from above:
javascript:
$("#append_job_type_submit").click(function() {
var name = $("#new_job_type_name").val();
//TODO: validation on the name, ensure it's not blank, etc
$.ajax({
url: '/job_types/append',
method: 'POST',
data: {job_type_name: name},
success: function(data) {
var sel = $("#job_type_id");
sel.append('<option value="' + data.id + '">' + data.name + '</option>');
$("#myModal").modal('hide');
alert("New job type " + data.name + " created.");
//TODO: probably be nice to auto-select this option; I'll leave that exercise to the alert reader
},
error: function(err) {
alert(err.responseJSON.message);
}
});
});
Now, the user can pick the option from the select box, and the new option has been saved to the database (regardless of whether or not they create the new job).
Disclaimer: I have not tested this code, or even checked to see if it compiles. But it should work, and regardless, this is the pattern you want to follow, so if nothing else, at least you have the direction now.
Now, all that said, I would still recommend the other approach without AJAX that I suggested, as it cuts out most of these steps, but that's just me. Feel free to accept whichever answer helps YOU get YOUR task done the way YOU want to do it.
Note: I decided to post this in addition to aldrien's answer as mine has a few differences that I found significant enough to warrant it:
A success and failure response should return success/failure status code. Having it always return success is a bit misleading. I think status codes are an important part of any REST design, even something as small as this.
I'd only recommend using match routes as a last resort. Resourceful routes are cleaner and clearer, and they give you more out the box as well. Also, I like the route under /job_types and not just floating at the root.
Also more of a standards thing, should be a POST request instead of a GET; we are creating a new entity after all.
Bug: The append method should yield data.id for the option value, not data.title (which should be data.name according to the original code). Otherwise, the value will not be the ID, and attempted save will fail.

If I may offer an alternative...
Your approach is going to require some advanced coding methods and, if you are uncomfortable with AJAX, this may be a difficult road.
As an alternative, I would suggest that instead of showing a modal, just show/hide a textbox within the existing form to house the Other name:
<input type="text" name="other_job_type" />
Then, when you submit the form, as you know it will go to JobController#create (or #update for existing jobs). In there, you can get this field:
def create
save_successful = false
#job = Job.new params[:job]
other_job_type = params[:other_job_type]
if other_job_type
new_job_type = JobType.new
new_job_type.name = other_job_type
save_successful = new_job_type.save
#job.job_type = new_job_type
end
if save_successful && #job.save
# redirect to success page
else
# render new/edit with error messages
end
end
By doing it this way, you only create a new job type if the user actually submits the job form, which is nice.
Use the save_successful pattern if you want to enforce validations on the JobType, such as a unique name. Then the save will fail if the user attempts to save with an existing job type. You could also just select the existing one for them, but I'll leave that to you if you choose to do that.
Again, all this follows the same pattern you are using now, just a new text field and a little more processing in the controller.
If you want, I can detail the answer to your question in terms of using AJAX methods to get this to work, as the steps, though much lengthier are pretty deterministic, but it's probably overkill for your use case. That said, I hate not actually answering the given question, regardless of my personal opinion of the approach, so just let me know.

Firstly, set up the button or form in your modal for triggering submit function.
Secondly, set up/check Routes/URL to be used for AJAX method.
As well as setting Controller function for saving new job type.
example in controller:
def create
job_type = JobType.new
job_type.title = params[:job_type]
if job_type.save
render :json => job_type
else
render :json => "some error here."
end
end
Finally, make AJAX function for sending data.
$("#add_new_job_type").click(function(){
$.ajax({
url: '/add_new_job_type',
type: 'GET', // or POST
data: {job_type: $("#field_contains_new_value").val()}
}).done(function(data){
// Do some validation for checking error response (like if statement)
// Append the new data (job type to select option tag)
new_job_type = "<option value="'+data.title+'">" + data.title + "</option>"
$('#job_job_type_id').append(new_job_type);
....
$("#myModal").modal('hide');
});
});
Notes: add_new_job_type used in AJAX url is the custom routes.
In config/routes.rb (custom routes):
match 'add_new_job_type' => 'job_types#add_new_job_type', :via => :post #or :get
In your JobTypes Controller, you must have:
def add_new_job_type
job_type = JobType.new
job_type.title = params[:job_type]
if job_type.save
render :json => job_type
else
render :json => "some error here."
end
end
Modify the code as you wish.

Related

Rails update element based on AJAX request?

I've been reading a lot about Rails and AJAX and 5.1 Unobtrusive javascript. It explains a lot about responding to Rails version of AJAX calls with a .js file for example.
However what im wanting to do isn't serving up an entire .js file, it's simply updating an element after a <% link_to %> POST request. From my understanding setting remote: true submits it as a AJAX request.
Essentially I have a "Post" which a user can like via a linked Like button. This sends a POST request to the "Post" controller which updates a post to liked and adds a like to the post.
Unfortunately to see the effects of the post being liked (Which is simply that the link changes color as well as the font-awesome icon) you need to refresh the page. I basically want it to update without needing refresh.
I "think" based off what i've read I need to make a respond do and respond via .js to the request with a .js file in the view I want to update (for instance if the controller action is called "like", maybe a like.js.erb file in the view im updating?). But I don't want to serve an entire new page..or would this simply just run the .js?
Then I could do something like $('i.fa-icon#id').style.color = "blue" or something? (Im assuming I can send data from the controller to the .js.erb file?). Not sure the best way to do this, don't rails elements a lot of times have some sort of data-attribute or something (Im still a beginner at this).
Your description is quite correct!
Opposed to the other answer, you don't even need a event listener but as you said you want to have a respond_to in the controller.
So starting from the html:
# post/index.html.erb
<div id="like-button">
<%= button_to "Like this post", post_path(#post), remote: true %>
</div>
Note, that when you use a button_to helper it'll be a POST request by default.
If you click it, it'll go to the controller#update, which you want to change to this:
#posts_controller.rb
...
def update
#post.save
respond_to do |format|
format.html { redirect_to post_path(#post) }
format.js # <-- will render `app/views/posts/update.js.erb`
end
end
Note: the format.html is rendered when JS is disabled.
Now in the scenario that JS is enabled, it executes the app/views/posts/update.js.erb file. It can look like this:
const likeButton = document.getElementById('like-button');
likeButton.innerHTML = '<%= j render "posts/liked-link", post: #post %>';
What is the last line doing? Of course, you can change the style directly with the JavaScript, but you can also render a new partial - and this you will create in a new html file:
# app/views/posts/liked_link.html.erb
<div id="like-button ">
<p>"You liked this post!" </p>
</div>
I just changed the link/button to ap now, but of course you can do whatever you want.
Hope that makes sense :)
Not sure if I understand the question, but if you want to update like button:
What you want to do is to add an event listener to the button, and when clicked it makes a POST request to whatever route handles the likes(with the correct parameters) and your controller should respond with the like object (or whatever in the database gets stored). Have your post request on success method to grab the like button and change it to whatever you want it to look like
$(“#like-btn”).click(function(){
Rails.ajax({
url: "/some/url/to/like/controller",
type: "post",
data: [your post data],
success: function(data) { $(`#${ data[“btn-name”] }`).attr(“color”, “blue”; }
})
}
You can stick this script right in the bottom of the html page
You don’t have to do it exactly like this, just giving you an idea of how to set up the pattern of having JavaScript and Ajax handle the post request and updating of the frontend instead of using html buttons

Updating instance variables through AJAX call in Rails to display in form

Here's what I'm trying to do:
The user pastes a URL.
The input box that the user pastes in has an :onpaste that triggers urlPasted() function.
urlPasted() function submits the form that input box is in, which does an AJAX call to a custom function named lookup_profile.
In the controller, lookup_profile function does some web requests, and then updates some instance variables.
Once those variables are updated (takes ~5 seconds), the view has a function that waits 20 seconds and updates textboxes on the modal with the results of those instance variables.
Here's what I have thus far in the view:
<%= form_tag url_for(:controller => 'users', :action => 'lookup_profile'), id: "profileLookupForm", :method => 'post', :remote => true, :authenticity_token => true do %>
<div class="form-group row">
<div class="col-sm-12">
<%= text_field_tag "paste_data", nil, onpaste: "profileURLPasted();", class: "form-control"%>
</div>
</div>
<% end %>
<script type="text/javascript">
function profileURLPasted() {
// Once the user pastes data, this is going to submit a POST request to the controller.
setTimeout(function () {
document.getElementById("profileLookupForm").submit();
}, 100);
setTimeout(function () {
prefillForm();
}, 20000);
};
function prefillForm() {
// Replace company details.
$('#companyNameTextBox').val("<%= #company_name %>");
};
</script>
Here's what the controller looks like:
def lookup_profile
# bunch of code here
#company_name = "Random"
end
Now here's the problem I have. When the user pastes the data, it submits perfectly to the custom_action lookupProfile. However, after lookupProfile runs its code, rails doesn't know what to do afterwards. By that, I mean it gives me this error:
Users#lookup_profile is missing a template for this request format and
variant. request.formats: ["text/html"] request.variant: []
When in fact, I actually have a file at views/users/lookup_profile.js.erb. For some reason, it's trying to render the HTML version. I don't know why.
Secondly, I've tried putting this in the controller towards the end:
respond_to do |format|
format.js { render 'users/lookup_profile'}
end
but that results in this error:
ActionController::UnknownFormat
Any help would be greatly appreciated. I just want the custom function to run, update the instance variables, and let me update the current form with that data.
Here's another stackoverflow reference of something similar I'm trying to do: Rails submitting a form through ajax and updating the view but this method doesn't work (getting the actioncontroller error)
* EDIT 1 *
Ok, so I fixed the ActionController error by replacing my form_tag with:
<%= form_tag(lookup_profile_users_path(format: :js), method: :post, :authenticity_token => true, id: 'profileLookupForm', remote: true) do %>
But now it's actually rendering the actual javascript into the view, and I don't want that. I simply want to be able to access the instance variables that were updated in the lookup_profile action, not display the view.
* EDIT 2 *
So I think my problem comes down to this: Placing a button in the form and submitting from IT is different than my javascript code that submits the form. If I can figure out what's up with that, then I think I may be in good shape.
You are mixing a few things there. First of all, instead of doing document.getElementById("profileLookupForm").submit() you should do an ajax request, I guess the submit() method ignores the remote: true directive from rails.
So, change the submission to:
form = getElementById("profileLookupForm");
$.post(form.action, {paste_data: this.value}, 'script')
// form.action is the url, `this` is the input field, 'script' tells rails it should render a js script
That way the request is done async and the response does not replace the current page.
Now, what I think you are mixing is that #company_name won't change with that ajax request. When you render the form and everything else, #company_name is replaced with the actual value IN THAT MOMENT and will not change after your post request since the reference is lost. So this line:
$('#companyNameTextBox').val("<%= #company_name %>");
will be
$('#companyNameTextBox').val("");
al the time.
What you want is to respond with a script that updates the field with the value that you set to #company_name (also, waiting arbitrarilly X seconds is a really bad practice).
So, instead of responding with:
format.js { render 'users/lookup_profile'}
create a view lookup_profile.js with the code that you want to execute
$('#companyNameTextBox').val("<%= #company_name %>");
here, #company_name will actually be the value obtained with those requests you told before, the script is generated at the moment and excecuted as a response of the request.

AJAX request can't get instance variable from controller in Rails application

I'm trying to get data from my controller into a javascript file in a rails application. The html immediately invokes an ajax request using the select option and the organization id as parameters.
In a before_filter I have
def set_org_id
if params[:id].present?
#org_id = klass.find(params[:id]).id
else
#org_id = 0
end
end
And in the js file I have:
$.ajax({ url: "/admin/analytics/filter",
type: 'GET',
data: {
time_frame: selectedId,
organization_id: <%= #org_id %>
}
})
If I hard code a number as the organization_id everything works fine, but when I try to pass the data from the before filter, I end up with no id in the page.
I should add that the index route is admin/analytics/. The filter action is admin/analytics/filter. The user never navigates to that page, only ajax hits the filter route to get relevant data. Could this be that the ajax request is being send before the instance variable is set? If so, what is the proper solution?
Your JS won't be able to access your #instance variablesunless you call them from your controller itself. The problem here is that if you're loading the ajax to access an instance varialbe - which simply won't work.
Let me explain...
JS
Javascript is known as a client side language - meaning it provides you with the ability to access elements in your HTML / DOM with relative impunity. The problem here is that Rails / Ruby, much like PHP, is server-side, and consequently is only able to provide rendered data to your browser
This means that calling the following simply won't work:
data: {
time_frame: selectedId,
organization_id: <%= #org_id %>
}
As explained, the reason for this is that you cannot access the #org_id from your Javascript. Firstly, you don't know if the #org_id variable will be set (in case you want to use the Rails helpers), and secondly, your JS won't be able to access the variable anyway (because it's Rails)
--
Fix
The purest fix for this is to somehow create the data you need in the DOM. I see from your answer that you have set a hidden field for your form. A much better way to do this is to set an HTML5 "data" attribute, or to use an id
You'd be better doing this:
<%= form_tag route_path, data: { org_id: #org_id } %>
This will give you the ability to call:
$(document).on("submit", "form", function(){
$.ajax({
...
data: {
time_frame: selectedId,
organization_id: $(this).data("org_id")
}
});
});
I was able to solve this by passing the data to a hidden field in the html.erb page.
<%= hidden_field_tag('org_id', #org_id) %>
Then in the javascript refer to the data through the selector.
organization_id: $('#org_id').val()
I don't love this, but at least it works.

MVC 5 Html.ValidationSummary still visible after AJAX post

When using Html.ValidationSummary and Html.BeginForm, you typically reload or navigate away from the page when all fields validate and the controller can perform the next steps. In this situation, there is no need to clear the validation summary.
However, when using Ajax.BeginForm the user might never leave the page. Once the form is validated, control goes to the controller live above but the controller may send the user back to the calling form as some server-side validation needs to be addressed. In this situation, an apparent bug in .NET does not clear the ValidationSummary. Yes, I can hide/clear it via JavaScript/JQuery using the OnBegin property of the form, but then it never unhides if it's needed on the form at a later step, say when you invalidate the client side validation.
A snippet of my form:
#using (Ajax.BeginForm("ExistingLogin", "Home", null, new AjaxOptions { HttpMethod = "Post", OnBegin="onBegin", OnSuccess = "onSuccess", OnFailure = "onError" }, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary("Please correct the following errors:", new { #class = "alert alert-danger", #role = "alert" })
<div id="controllerResponse" class="alert alert-danger" role="alert" style="display:none;"></div>
How do you handle this?
Rather than hide/show the validator, follow what jQuery does, change the class:
function onComplete() {
$(".alert").removeClass("validation-summary-errors");
$(".alert").addClass("validation-summary-valid");
}
Just not sure if it's better to do this in the OnComplete or in the OnBegin.

Unable to bind ajax:success to form created with form_for .... :remote => true

I'm using Rails 3.1.1
I have the following in a haml view:
= form_for booking.notes.build, :remote => true do |f|
= f.text_area(:content)
= f.hidden_field(:noteable_id)
= f.hidden_field(:noteable_type)
= f.submit('Add note')
Which creates new notes on submission. Also the response from my controller is appearing correctly in a Chrome console (Network tab). But I cannot seem to grab the response.
I want to update the note list on the page after submission. I've been trying to bind to the ajax response so I can grab the response, but I am failing. For example, I think this should work but does not:
$('#new_note').bind('ajax:success', function() {
alert('Hi');
});
But no alert is triggered. Which I think explains why this also doesn't work.
$('#new_note').bind("ajax:success", function(evt, data, status, xhr){
// Insert response partial into page below the form.
$(this).parent.append(xhr.responseText);
})
Can you please point me as to what might be going wrong?
Did you try 'ajax:complete'?
Other things that can go wrong here:
Status code was not really "successful". This triggers success in jQuery
if ( status >= 200 && status < 300 || status === 304 ) {
Or the event handler was evaluated before the form was rendered. Try event delegation:
$("body").on('ajax:success', '#new_note', function(){...})
(careful, this is the new jQuery syntax. If using old jQuery, adjust accordingly)
if you want, you can put your javascript in create.js.erb (code in this file will be executed after response will come to your browser)
And in this file you can use if statement, like
<% if #ok %>
//your code
<% end %>
if in your controller's action set #ok to false the response will be empty!
This is generally done by doing a create.js.erb file in your controller's view section (if haven't done so already) in which you have access to whatever variables come out of the create action, and there you can render your html
in your create.js.erb file you could write something like
$("#new_note").html('<%= escape_javascript(render partial: "path/to/partial") %>');
read more in this post I wrote some time ago, it pretty much explains the whole flow

Categories

Resources