I'm trying to submit an ajax form from my razor view, and I want the controller to return a JSON object. When I use ("#form0").submit(alert("hi");); the data goes to the controller and I get an alert. However, when I use ("#form0").submit(function(){alert("hi");}); the data does not get passed, and I do not get an alert. I get the feeling that this is something minor with my syntax that I'm missing. Here's the relevant code:
jquery:
$(function () {
//setting up the schedule modal dialoag.
$("#schedModal").dialog({
buttons: {
Submit:
function () {
$("#form0").ajaxSubmit(function () {
//this is where I want to put the magic, but I need the alert to fire first.
alert("hi");
return false;
});
},
Cancel:
function () {
$(this).dialog("close");
}
},
autoOpen: false,
minHeight: 350,
modal: true,
resizable: false
});
the targeted view:
#model FSDS.DataModels.Schedule
#using (Ajax.BeginForm("scheduleNew", null, new AjaxOptions { UpdateTargetId = "partial" }, new {}))
{
#Html.ValidationSummary(true)
<div class="editor-label">
#Html.LabelFor(model => model.ScheduleName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.ScheduleName)
#Html.ValidationMessageFor(model => model.ScheduleName)
</div>
#* tons of other labels and editor fields go in here, omitted for brevity. *#
}
The controller, if that matters:
[HttpPost]
public ActionResult scheduleNew(Schedule schedule)
{
if (Request.HttpMethod == "POST")
{
FSDSDBEntities context = new FSDSDBEntities();
if (ModelState.IsValid)
{
context.Schedules.AddObject(schedule);
context.SaveChanges();
}
return Json(schedule);
}
else
{
return PartialView();
}
}
Simply use $('#form0').submit();:
Submit: function () {
$('#form0').submit();
}
Then define an OnSuccess handler in your AjaxForm that will be invoked when the AJAX request succeeds:
#using (Ajax.BeginForm("scheduleNew", null, new AjaxOptions { OnSuccess = "success", UpdateTargetId = "partial" }, new {}))
and finally success javascript handler:
function success(data) {
// the form was successfully submitted using an AJAX call.
// here you could test whether the data parameter
// represents a JSON object or a partial view
if (data.ScheduleName) {
// the controller action returned the schedule JSON object
// => act accordingly
} else {
// the controller action returned a partial view
// => act accordingly
}
}
Related
Okay I'm totally new to this so I'm not going to get a lot of the terminology right, but it's an ASP.NET MVC application where a create view is supposed to have an autocomplete function on the "StudentID" text box. It doesn't work. On the console window I get an error message "Autocomplete is not a function". This is strange because the intellisense at the beginning does give me autocomplete. I don't know what I'm supposed to paste below but I put in the javascript as well as the applicable Controller code. LMK if there's anything else you need to see. Thanks in advance.
P.S. If you know a better way to do autocomplete (this is done through a class that we got the code from some website and looks more complicated than similar things I did in the past) please let me know that too.
<script>
$("#Student_FirstName").autocomplete(
{
source: function (request, response)
{
$.ajax(
{
url: "/Enrollments/GetStudents",
dataType: "json",
type: "POST",
data:
{
term: request.searchTerm
},
success: function (data) {
console.log(data);
response($.map(data, function (item) {
return {
label: item.Name, value: item.Name, id: item.id
};
}))
}
});
},
minLength: 2,
select: function (event, query)
{
console.log(query);
$("#StudentID").val(query.item.id);
}
});
</script>
And now from later on in the Create view:
<div class="form-group">
#Html.LabelFor(model => model.StudentID, "StudentID", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Student.FirstName, new { htmlAttributes = new {#class = "form-control"}})
#Html.HiddenFor(model => model.StudentID)
#Html.ValidationMessageFor(model => model.StudentID, "", new { #class = "text-danger" })
</div>
</div>
And finally from the Controller Class:
// GET: Enrollments/Create
public ActionResult Create()
{
ViewBag.CourseID = new SelectList(db.Courses, "CourseID", "Title");
ViewBag.StudentID = new SelectList(db.Students, "StudentID", "LastName");
ViewBag.InstructorID = new SelectList(db.Instructors, "ID", "FirstName");
return View();
}
// POST: Enrollments/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see https://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "EnrollmentID,Grade,CourseID,StudentID,InstructorID")] Enrollment enrollment)
{
if (ModelState.IsValid)
{
db.Enrollments.Add(enrollment);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.CourseID = new SelectList(db.Courses, "CourseID", "Title", enrollment.CourseID);
ViewBag.StudentID = new SelectList(db.Students, "StudentID", "LastName", enrollment.StudentID);
ViewBag.InstructorID = new SelectList(db.Instructors, "ID", "FirstName", enrollment.InstructorID);
return View(enrollment);
}
I am using an ajax.beginform to create a partial view within another view.
I the user enters a correct sn everything works fine.
But if the user enters an invalid number, I want to redirect to the index view.
Now the index page is submitted as a partial view in itself.
How can I avoid that.
Here is a part of my view and 2 simplified actionresults.
#using (Ajax.BeginForm("MachineInfo", "QrCreate", new AjaxOptions() {
HttpMethod = "POST", UpdateTargetId = "form-content", InsertionMode =
InsertionMode.ReplaceWith }))
{
#Html.AntiForgeryToken()
<input type="text" id="sn" name="sn" class="inputsn"
placeholder="Enter your serial number here..." />
<input type="submit" value="Search" class="search btn btn-success btn-lg" />
}
</div>
</div>
<div id="form-content"></div>
my Controller
public ActionResult Index(bool? isValidMachine = null)
{
ViewBag.invalidSerialNumber = isValidMachine;
return View();
}
[HttpPost]
public ActionResult MachineInfo(string sn)
{
if(string.IsNullOrEmpty(sn))
RedirectToAction("Index", new { isValidMachine = false });
QrCreateViewModel qrCreateVM;
using (var machineService = new MachineApiService())
{
var machine = machineService.GetMachineFromSerialNumber(sn);
if (machine == null)
return RedirectToAction("Index", new { isValidMachine = false });
else
qrCreateVM = new QrCreateViewModel(machine, GetBasePath());
}
if (qrCreateVM.IsValid())
{
qrCreateVM.Viewurl = qrCreateVM.QrCreateUrlOrDefaultNull();
return PartialView(qrCreateVM);
}
else
return RedirectToAction("Index", new { isValidMachine = false });
}
Ajax calls do not redirect (the purpose of making them is to stay on the same page).
In your controller method, replace the instances of return RedirectToAction(...) to return a HttpStatusCodeResult indicating an error, which you can then handle in the OnFailure option to redirect to the Index() method.
For example
[HttpPost]
public ActionResult MachineInfo(string sn)
{
if (string.IsNullOrEmpty(sn))
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest, "Bad Request");
}
....
Then in the Ajax.BeginForm()
#using (Ajax.BeginForm("MachineInfo", "QrCreate", new AjaxOptions() {
HttpMethod = "POST",
UpdateTargetId = "form-content",
InsertionMode = InsertionMode.ReplaceWith,
OnFailure = "redirect"
}))
{
....
and add the following script to redirect
function redirect(ajaxContext) {
location.href = '#Url.Action("Index")';
}
let's suppose I have a form like this:
#using (Ajax.BeginForm("ChangePassword", "User", new { area = "UserSettings" }, new
AjaxOptions
{
HttpMethod = "POST",
OnSuccess = "ShowMessage('Password successfully changed')"
}, null))
{
#Html.TextBoxFor(m => m.Password, new { placeholder = "Password", #class = "form-control", #type = "password" })<br />
#Html.ValidationMessageFor(model => model.Password, "", new { #class = "text-danger", #style = "float:right;" })
#Html.TextBoxFor(m => m.PasswordConfirm, new { placeholder = "Confirm password", #class = "form-control", #type = "password" })<br />
#Html.ValidationMessageFor(model => model.PasswordConfirm, "", new { #class = "text-danger", #style = "float:right;" })
<button type="submit" class="btn btn-primary">Change Password</button>
}
Essentially it's a form to change a password with validation setup in place.
Now my Question here is, in the case where I have an action like this:
[HttpPost]
[ActionName("ChangePassword")]
public ActionResult ChangePassword(MyObjectClass model)
{
if(!ModelState.IsValid)
{
return View(model);
}
else{
ViewBag.MyData = // some data here;
return View();
}
}
The questions that I have are:
When I return a View with all data in it, how do I actually then inject that returned data into the user's DOM that the user can see the changes reflected when OnSuccess method is called? Where is the "data" object that I can inject into like in done function in jQuery( .done(function(data)))?
What is the easiest way here to transfer now all the data from my controller action into the OnSuccess method and actually let the user see the outcome of the method that they called?
Can someone help me out with this ? What is the best way to solve this issue ?
I used to work with pure jQuery before and I Was doing this in this manner:
$.post("/linktomymethod",{/*some parameters*/).done(function(data){
// then inject the returned data into the browser's DOM
}));
If you want to use #Ajax.BeginForm(), then you specify the UpdateTargetId property of AjaxOptions, for example
<div id="changepassword">
#using (Ajax.BeginForm(..., new AjaxOptions { UpdateTargetId = "changepassword" }, null))
{
....
}
<div>
which would replace your <form> element with the view returned by your ChangePassword() method. Note also that your should be returning a PartialView.
However, you should stick with the $.ajax() methods as these give you far more flexibility. Typically you handle the forms .submit() event, check the .valid() method (and cancel the ajax call if not so client side validation messages are displayed), and then update the DOM with the partial view that the method returns.
Replace your #using (Ajax.BeginForm(..) { code with a simple
<div id="changepassword">
#using (Html.BeginForm()) {
....
and add the following script
var url = '#Url.Action("ChangePassword", "User", new { area = "UserSettings" })';
$('form').submit(function (ev) {
ev.preventDefault(); // prevent the default submit
if (!$(this).valid()) { // check if the data is valid
return; // exit the function
}
var formdata = $(this).serialize();`
$.post(url, formdata, function(response) {
$('changepassword').html(response);
});
});
To improve performance further, your could just return a JsonResult containing the invalid properties and their associated error, and update the placeholder elements generated by #Html.ValidationMessageFor(). You controller code would be
if (!ModelState.IsValid)
{
var errors = ModelState.Keys.Where(k => ModelState[k].Errors.Count > 0).Select(k => new { propertyName = k, errorMessage = ModelState[k].Errors[0].ErrorMessage });
return Json(errors);
}
// Save data
return Json(null);
In the success callback, you can then loop through the collection, find the corresponding ValidationMessageFor() placeholder based on the property name, and add the error message and appropriate class names.
I am trying to submit an ajax form that is loaded via a partial page. The submit comes from a button that is created on a JQuery UI Dialog. The form data is included in the request but when I step through the code on the server side the view model is not being populated with the data.
Javascript that loads the partial and after load is complete creates the dialog.
$('#test').load('#Url.Action("NewIntegrationLevel", "IntegrationLevelConfig", null, Request.Url.Scheme)',
function() {
var dialog = $('#addEditForm');
dialog.dialog({
resizable: false,
modal: true,
width: 500,
title: 'New Integration Level',
buttons: [{
text: 'Save',
click: function(e) {
e.preventDefault();
$('#addEditForm').submit();
dialog.dialog('close');
},
type: 'submit',
form: 'addEditForm'
},
{
text: 'Close',
click: function(e) {
e.preventDefault();
dialog.dialog('close');
}
}
]
});
});
My partial page:
#model Zodiac.Cmt.UI.Models.IntegrationLevelConfigViewModel
#using (Ajax.BeginForm("Save", "IntegrationLevelConfig", FormMethod.Post, null, new { id = "addEditForm" }))
{
<div id="addEdit-integration-dialog">
<div>
<div>
#Html.LabelFor(model => model.Name)
#Html.TextBoxFor(model => model.Name)
</div>
</div>
</div>
}
Server side code:
[HttpPost]
public ActionResult Save(IntegrationLevelConfigViewModel viewModel)
{
return PartialView("_AddEditIpl", viewModel);
}
Since the JQuery dialog creates the submit button outside the form I am using the "form" attribute on the button so that it knows which form to submit.
This is what the request looks like after submitting the page:
So, why doesn't the viewmodel get populated with the form data on the server side???
ViewModel:
public class IntegrationLevelConfigViewModel
{
[Description("Name")]
public string Name;
}
I figured it out.
In my view model
public string Name;
needs to change to:
public string Name {get;set;}
After reviewing many tutorials and various approaches to Cascading DropDownLists, I decided to create a ViewModel for my View and then populate my DropDownLists based on this post:
MVC3 AJAX Cascading DropDownLists
The goal here is the most basic and covered in many tutorials, but I still can't get it quite right... to populate a City dropdown based on the value of a State dropdown.
EDIT:
Since posting this request for help, I discovered Firebug (yes, that's how new I am to doing any sort of programming), and I was able to determine that I am successfully calling my controller, and pulling the necessary data. I believe the problem is the second half of my JavaScript that returns the data to my View.
Here is my View:
<label>STATE HERE:</label>
#Html.DropDownListFor(x => x.States, Model.States, new { #class = "chzn-select", id = "stateID" })
<br /><br />
<label>CITY HERE:</label>
#Html.DropDownListFor(x => x.Cities, Enumerable.Empty<SelectListItem>(), new { id = "cityID" })
Here is the JavaScript within my View, and somehow I'm not handling my results correctly once I get them:
$(function () {
$("#stateID").change(function () {
var stateId = $(this).val();
// and send it as AJAX request to the newly created action
$.ajax({
url: '#Url.Action("GetCities")',
type: 'GET',
data: { Id: stateId },
cache: 'false',
success: function (result) {
var citySelect = $('#cityID');
$(citySelect).empty();
// when the AJAX succeeds refresh the ddl container with
$.each(result, function (result) {
$(citySelect)
.append($('<option/>', { value: this.simpleCityID })
.text(this.cityFull));
});
},
error: function (result) {
alert('An Error has occurred');
}
});
});
});
Here is my controller called by the JavaScript:
public JsonResult GetCities(int Id)
{
return Json(GetCitySelectList(Id), JsonRequestBehavior.AllowGet);
}
private SelectList GetCitySelectList(int Id)
{
var cities = simpleDB.simpleCity.Where(x => x.simpleStateId == Id).ToList();
SelectList result = new SelectList(cities, "simpleCityId", "cityFull");
return result;
}
Here are my results from Firbug, which tell me I'm building and getting the data without issue, just not populating my DropDownList correctly:
[{"Selected":false,"Text":"Carmel","Value":"IN001"},{"Selected":false,"Text":"Fishers","Value":"IN002"}]
If anyone has any suggestions as to why the JavaScript fails to populate the dropdrown, please comment, thanks!
I have done this several times with something like this:
Create a partial to popolate dropdown list. Name it DropDownList and put in Shared folder of Views
#model SelectList
#Html.DropDownList("wahtever", Model)
Your create view should be something like this (skipped irrelevant parts)
<script type="text/javascript">
$(function() {
$("#StateId").change(function() {
loadLevelTwo(this);
});
loadLevelTwo($("#StateId"));
});
function loadLevelTwo(selectList) {
var selectedId = $(selectList).val();
$.ajax({
url: "#Url.Action("GetCities")",
type: "GET",
data: {stateId: selectedId},
success: function (data) {
$("#CityId").html($(data).html());
},
error: function (result) {
alert("error occured");
}
});
}
</script>
#Html.DropDownList("StateId")
<select id="CityId" name="CityId"></select>
Carefully note the Empty Select item for CityId and the call of loadLevelTwo at document.ready
And your controller should be like:
public ActionResult Create()
{
ViewBag.StateId = new SelectList(GetAllCities(), "Id", "Name");
return View();
}
public ActionResult GetCities(int stateId) {
SelectList model = new SelectList(GetCitiesOfState(stateId), "Id", "Name");
return PartialView("DropDownList", model);
}
Thank you for your assistance,
It turns out that in my JavaScript below, I was attempting to directly reference the simpleCityID and cityFull fields associated with my data model:
$.each(result, function (result) {
$(citySelect)
.append($('<option/>', { value: this.simpleCityID })
.text(this.cityFull));
Instead, I needed to keep it generic and inline with JavaScript standards of referencing Value and Text:
$.each(modelData, function (index, itemData) {
select.append($('<option/>', {
value: itemData.Value,
text: itemData.Text