MVC 5 Html.ValidationSummary still visible after AJAX post - javascript

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.

Related

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.

C# MVC not calling correct action method when adding form action dynamically through JavaScript

First of all, please forgive me if this looks a silly question but I am not being able to solve this.
I have a MVC page with the following structure (not using HTML.BeginForm):
<form id="frmMiniSearch" method="post">
... search controls are here ...
<%: Html.DropDownListFor(model => model.VehicleCondition, Model.Condition, new {#class = "form-control",#required="true"}) %>
<input type="submit" value="Search" />
</form>
<script type="text/javascript">
jQuery(document).ready(function(){
$("#VehicleCondition").bind("change", function(){
$("#frmMiniSearch").attr("action", "/quick-search/used/honda/civic/1968-2018")
});
});
</script>
When the above JavaScript executes, I can see the form (frmMiniSearch) action changes to /quick-search/used/honda/civic/1968-2018. But when I click on the Submit button, rather than calling the right action method, it is calling some other.
The above URL pattern is set in my RouteConfig.cs file:
routes.MapRoute(
name: "VehicleMiniSearchResult",
url: "quick-search/{cond}/{makeSlug}/{model}/{yearRange}",
defaults: new { controller = "SearchResult", action = "VehicleQuickSearch", id = UrlParameter.Optional }
);
I also tried this:
$(document).ready(function(){
$("submit").click(function( e ) {
e.preventDefault();
$("#frmMiniSearch").submit();
});
});
Result is the same. Still calling wrong method.
What I am doing wrong? Any suggestion would save my life.
NB: If I want to use Html.BeginForm to call the right method, how do I call it using the route pattern (i.e. quick-search/used/honda/civic/1968-2018)? Something like:
<% using(Html.BeginForm, "Action", "Controller", <something here?>) {} %>
I tried Html.BeginRouteForm but that did not seem to work either.
Thank you for your suggestion.
EDIT:
Here are the routes in the order of they appear in RouteConfig.cs
routes.MapRoute(
name: "VehicleSearchResult",
url: "vehicles/search/",
defaults: new { controller = "SearchResult", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "VehicleMiniSearchResult",
url: "quick-search/{cond}/{makeSlug}/{model}/{yearRange}",
defaults: new { controller = "SearchResult", action = "VehicleQuickSearch", id = UrlParameter.Optional }
);
As far as I know route order is something we need to keep in mind in Orchard CMS, but does this true for pure MVC applications as well?
By the way, when I type in the URL in browser directly the route (quick-search/used/honda/civic/1968-2018) works and I get the desired result. Then it is not calling another method. It is only happening when I am trying to submit the form via JS.
This is not the accurate solution, its just a suggestion.
Why can't you use static action and pass the parameter in query string or in body?
in that way you can avoid dynamic uris.
or call the uri from the javascript function as
$(document).ready(function(){
$("submit").click(function( e ) {
e.preventDefault();
//$("#frmMiniSearch").submit();
//Call your uri here like window.location="";
});
});

Pass hidden model data to the controller using ajax in mvc

Once again stumped on a partial view issue using ajax to swap out partial views.
I have a partial view that is loaded onto a page when the page is loaded. This partial view has a button on it that when clicked, will replace that partial view with another partial view. Previously I was accomplishing this with #Ajax.ActionLink and passing in the page's Model as a parameter to be passed to the controller. This was working correctly.
However, the next step was to fill in a form on the new partial view and submit it which would return another partial view. I asked how to do that on here and got it to work using jquery event delegation.
Now I'm trying to replace the #Ajax.ActionLink with a $.ajax function and am running into an issue with my js script where the model data already in the view or partial view is not being passed to the controller by the js.
Here's what I mean:
I have a page called ReviewPage that on load, will call an action that returns a partial view. This partial view is determined based on a value from the page's model that is passed to the controller. More often than not, the view returned is _NoNotes:
#model GuestPointerAppV4.Models.ViewModels.NotesOnCompanyViewModel
<div id="no_company_notes">
<h4>Notes on Company</h4>
<div class="row col-sm-6 col-sm-offset-6">
<div class="text-center">
#Html.HiddenFor(item => Model.RequestId)
<div class="primary-action-bttn-border">
<div class="primary-action-bttn-bkg">
<button data-gp-ajax="true" data-gp-target="#no_company_notes" value="Save" action="#Url.Action("_CompanyNotesEditGet", "NewSignUpRequestReview")" method="get" class="btn btn-default primary-action-bttn">
Add Note
</button>
#*#Ajax.ActionLink("Add Note", "_CompanyNotesEditGet", Model, new AjaxOptions { UpdateTargetId = "company_notes", InsertionMode = InsertionMode.Replace, HttpMethod = "GET" }, new { #class = "btn btn-default primary-action-bttn" })*#
</div>
</div>
</div>
</div>
<div class="row text-center padding-25-top">
<p>There are no notes for this company. Do you want to add some?</p>
</div>
</div>
You'll notice the commented out ajax action link in the code above. This was how I was previously able to get the model passed to the controller.
When the button is clicked on this partial view, the following js responds:
$(document).on('click', 'button[data-gp-ajax="true"]', function () {
var $form = $(this);
var options = {
url: $form.attr("action"),
type: $form.attr("method"),
data: $form.serialize()
};
$.ajax(options).done (function (data) {
var $target = $($form.attr("data-gp-target"));
$target.replaceWith(data);
});
return false;
});
I want to reuse this js as much as possible so I utilize custom data- attributes to help call the right function (data-gp-ajax="true") and tell the script where to return the results (data-gp-target="#targetid").
The problem is that in debugging this, I found that while Model.RequestId is not 0 on the page, this value is not getting picked up by the js and passed to the controller. So, when my controller looks for the data it doesn't find it and returns a null or 0 depending on the type of data I'm trying to pass.
I did a bit of research and tried to encode the Model in the partial view itself using the following at the top of the partial view's code just after the model typing:
#{
var val = Json.Encode(Model);
}
<script type="text/javascript">
var model1 = #Html.Raw(val)
</script>
Then, in my js function, I tried passing model1 to the controller by using it like so:
$(document).on('click', 'button[data-gp-ajax="true"]', function () {
var $form = $(this);
var $model = $(model1);
var options = {
url: $form.attr("action"),
type: $form.attr("method"),
data: $model.serialize()
};
$.ajax(options).done (function (data) {
var $target = $($form.attr("data-gp-target"));
$target.replaceWith(data);
});
return false;
});
I also tried data: $model and data: $model.first() but no such luck. I managed to debug and see the data I was expecting to see in the js on the $model variable so I know it's getting in there but it isn't getting passed to my controller when it's called.
Really what I'm looking for is a clean and reliable way way to pass the Model for the page into the JavaScript and have it pass it along to the Controller to execute some action on before a partial view is returned to replace the original partial view.
Thoughts?
Update
Here is an example of the controller:
[HttpGet]
public ActionResult _CompanyNotesEditGet(NotesOnCompanyViewModel notesOnCompanyViewModel)
{
//Some actions are taken on the model passed in and then repopulated to notesOnCompanyViewModel
return PartialView("~/Views/NotesOnCompany/_CompanyNotesEdit.cshtml", notesOnCompanyViewModel);
}
However, when I debug and the action is called by $.ajax, the notesOnCompanyViewModel is blank. Even when I can see there is data that should be getting passed by the js function.
All you need is to send the unique Id. for that, you can simply override the button click event and make an ajax call with the Id in the request url. If you would like to take advantage of the Url helper method to generate the correct relative path to the action method, you can do that and set the result of that to html5 data attribute on the button.
<button id="addNote"
data-url="#Url.Action("_CompanyNotesEditGet", "NewSignUpRequestReview",
new { id=Model.RequestId})" >
Add Note
</button>
Assuming your _CompanyNotesEditGet action method accepts this Id and return the value as needed
public ActionResult _CompanyNotesEditGet(int id)
{
var vm= new NotesOnCompanyViewModel { RequestId=id };
//load other properties of vm as needed.
return PartialView(""~/Views/NotesOnCompany/_CompanyNotesEdit.cshtml",vm);
}
And the script to ajaxify the click event. You can use the load method to make the ajax call and update the DOM element.
$(function(){
$(document).on("click","#addNote",function(e){
e.preventDefault();
var url=$(this).data("url");
$("#no_company_notes").load(url);
});
});
If you are editing a note, Inside your action method, you can read your note entity from the db using the Id passed in.
This should work assuming you have no script errors in the page.

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

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.

Ajax.BeginForm not returning to on failure if action name has authorize attribute

I am working on Ajax.BeginForm in MVC with EF. I submitted this form to controller with action name
[HttpPost,Authorize(Roles = "admin")]
public ActionResult Index(Register r)
{
--------
}
My cshtml code is
Ajax.BeginForm("Index", "Admin", new AjaxOptions { OnSuccess = "Success", OnFailure = "Failure" })
So in my scenario User need to submit form only if he is authenticated, So my action name in controller has authorize attribute. Now when the user doesn't login and try to submit a form, control doesn't go to action name and coming to "success" js function.
Please some one help to get this done.
Thanks in advance..

Categories

Resources