jQueryUI autocomplete with MVC3: posting autocompleted value - asp.net-mvc-3

I'm new to jQueryUI+MVC3 (Razor) and this is probably a trivial question, anyway: I am trying to let autocomplete work so that as soon as an item is selected from the popup my form is submitted back to its Index action.
Here are my steps (this fake sample refers to an index of Person's):
1) I create a PersonFilter wrapper like:
public sealed class PersonFilter
{
public string LastName { get; set; }
public int RoleId { get; set; }
// ... etc.
}
2) I create a PersonList model to hold the list of Person's together with some filters.
3) my Index action is like (it is serving data to a view using MvcContrib data grid, whence page and sort):
public ViewResult Index(int? page, GridSortOptions sort, PersonFilter filter)
{
var persons = _repository.GetPersons(filter);
if (sort.Column != null)
persons = persons.OrderBy(sort.Column, sort.Direction);
ViewBag.Sort = sort;
PersonList list = new PersonList
{
persons = persons.AsPagination(page ?? 1, 10),
LastName = filter.LastName,
RoleId = filter.RoleId,
Roles = _repository.GetRoles(),
// ...
};
ViewBag.Filter = filter;
return View(list);
}
I also have a FindPerson action which gets a LastName parameter and is used to autocomplete on the person name filter.
4) my view relevant code:
...
#model PersonList
...
#using (Html.BeginForm("Index", "Person", FormMethod.Post, new { id = "TheForm" }))
{
...
<input type="text" id="LastName"/>
#Html.DropDownListFor(m => m.RoleId, new SelectList(Model.Roles, "Id", "Title", 0),
new {onchange = "document.getElementById('TheForm').submit();"})
...
}
<script type="text/javascript" language="javascript">
$(function () {
$("#LastName").autocomplete({
source: function (request, response) {
$.ajax({
url: "/Person/FindPerson", type: "POST", dataType: "json",
data: { LastName: request.term },
success: function (data) {
response($.map(data, function (item) {
return { label: item.LastName, value: item.LastName, id: item.Id };
}));
}
});
},
select: function (event, ui) {
$("#LastName").val(ui.item.value);
//alert($("#LastName").val());
$(this).closest("form").submit();
}
});
});
</script>
Now the autocomplete works fine, I can type and get a popup and select an item from it; in this case my select handler is called, and the form is posted to the Index action. Anyway, this action does not get its filter LastName member filled (its name is equal to the autocompleted input), while it regularly gets its RoleId and other members filled as expected.
I tried explicitly setting the LastName input value as shown in the select handler (even if this should be redundant), but nothing changes. Yet, if I uncomment the alert line I can view the correct value shown. If I break into the Index action, the filter LastName is not set and if I add the FormCollection object in its parameters I find NO key named LastName. A key appears only if I add a hidden field like:
#Html.HiddenFor(m => m.LastName)
but this is just a test for finding out what's wrong with my autocompleted input. Could anyone explain what I'm doing wrong here?

You should give a name to your LastName textbox:
<input type="text" id="LastName" name="LastName" />
Also I would recommend you using helpers to generate it. If you used helpers you wouldn't have had such problems:
#Html.TextBoxFor(x => x.LastName)
Without a name nothing will be posted to the server.

Related

Ajax call returning old ASP.NET MVC partial view instead of updated view

I have an Ajax call triggered by a button, that calls a controller to update a partial view. The controller returns an updated partial view, but the partial view received in the success function of the Ajax call is the original view, not the updated view.
I created a sample ASP.NET MVC program to reproduce the problem. The program displays a list of customers in a table as follows.
Snapshot of UI
The text boxes are rendered in the partial view _Status. When the Toggle Status button is clicked, controller is called via Ajax to toggle the customer's status between true and false, and refresh the partial view of the corresponding text box. The problem is that the status never changes. Why is that?
UPDATE
I just added the following line of code in the Status action of the controller, and now, the Ajax success function correctly receives the updated partial view!
this.ModelState.Clear();
Can someone explain why?
Here is the Index.cshtml view that displays the initial view.
#model IEnumerable<ComboModel.Models.CustomerSummary>
<script type="text/javascript">
function updatePartialView(id) {
debugger;
$.ajax({
url: "/CustomerSummary/Status",
data: $('#' + id + ' :input').serialize(),
dataType: "HTML",
type: "POST",
success: function (partialView) {
// Here the received partial view is not the one created in
// the controller, but the original view. Why is that?
debugger;
$('#' + id).replaceWith(partialView);
},
error: function (err) {
debugger;
},
failure: function (err) {
debugger;
}
});
}
</script>
<h2>Customer Summary</h2>
<table>
<tr>
<th>Name</th>
<th>Active?</th>
<th>Toggle</th>
</tr>
#foreach (var summary in Model)
{
<tr>
<td>#summary.FirstName #summary.LastName</td>
#Html.Partial("_Status", summary.Input)
<td><button type="button" name="#("S" + summary.Input.Number)" onclick="updatePartialView(this.name)">Toggle Status</button></td>
</tr>
}
</table>
The _Status.cshtml partial view.
#model ComboModel.Models.CustomerSummary.CustomerSummaryInput
<td id="#("S" + Model.Number)">
#Html.TextBoxFor(model => model.Active)
<input type="hidden" value="#Model.Number" name="Number" />
</td>
The CustomerSummaryController.cs.
using System.Collections.Generic;
using System.Web.Mvc;
using ComboModel.Models;
namespace ComboModel.Controllers
{
public class CustomerSummaryController : Controller
{
private readonly CustomerSummaries _customerSummaries = new CustomerSummaries();
public ViewResult Index()
{
IEnumerable<CustomerSummary> summaries = _customerSummaries.GetAll();
return View(summaries);
}
public PartialViewResult Status(CustomerSummary.CustomerSummaryInput input)
{
this.ModelState.Clear(); // If I add this, things work. Why?
input.Active = input.Active == "true" ? "false" : "true";
return PartialView("_Status", input);
}
}
public class CustomerSummaries
{
public IEnumerable<CustomerSummary> GetAll()
{
return new[]
{
new CustomerSummary
{
Input = new CustomerSummary.CustomerSummaryInput {Active = "true", Number = 0},
FirstName = "John",
LastName = "Smith"
},
new CustomerSummary
{
Input = new CustomerSummary.CustomerSummaryInput {Active = "false", Number = 1},
FirstName = "Susan",
LastName = "Power"
},
new CustomerSummary
{
Input = new CustomerSummary.CustomerSummaryInput {Active = "true", Number = 2},
FirstName = "Jim",
LastName = "Doe"
},
};
}
}
}
And finally, the CustomerSummary.cs model.
namespace ComboModel.Models
{
public class CustomerSummary
{
public string FirstName { get; set; }
public string LastName { get; set; }
public CustomerSummaryInput Input { get; set; }
public class CustomerSummaryInput
{
public int Number { get; set; }
public string Active { get; set; }
}
}
}
Thanks!
This is a duplicate of Asp.net MVC ModelState.Clear.
In the current scenario, because there is no validation of the status field, it is ok to clear the ModelState. However, the MVC correct way would be to pass a status value (true or false) to a GET action in the controller, toggle the value, return the result string, and update the text box on the page.

JQuery AJAX Post to Controller data incorrect

Here is what I am trying to do:
My goal is to display a list of Trending Opinions (A custom Model) from the page's model when the page loads. If a user clicks the "Show more Trending Opinions" button, it uses ajax to call a method on a controller that will then retrieve an additional number of items, come back to the page and display them. Then it adds say 20 more. Then they can repeat the process and click it again, etc.
Exactly the same as a normal site does when you click "Show More" on a list of items.
If the way I am approaching this is incorrect and you know of any tutorial (or just out of your head) showing the correct way to do this in MVC 4, please let me know. I am not dead-set on the way I am doing it at the moment, this is just the "correctest" way I have found.
I followed the answer to a similar question: How to Update List<Model> with jQuery in MVC 4
However, the data coming through to my controller is incorrect and I can't figure out what the issue is.
Let me put as much info as I can, because I have no idea where the error may be.
Model for page (OpinionModel has a few public properties):
public class IndexModel
{
public IList<OpinionModel> TopTrendingOpinions { get; set; }
}
The View:
<div id="TrendingOpinions">
<p>What is trending at the moment</p>
#using (Html.BeginForm("LoadMoreTrendingOpinions", "AjaxHelper",
method: FormMethod.Post,
htmlAttributes: new { #class = "form-horizontal", id = "LoadTrendingOpinionsForm" }))
{
#Html.EditorFor(x => x.TopTrendingOpinions)
<input type="submit" value="Load More Trending Opinions" />
}
<script type="text/javascript">
$('#LoadTrendingOpinionsForm').submit(function () {
$.ajax({
url: this.action,
type: this.method,
data: {
topTrendingOpinions: $(this).serialize()
},
success: function (result) {
alert(result);
}
});
return false;
});
</script>
</div>
**There is also an EditorTemplate for my model.
The Controller:**
[HttpPost]
public ActionResult LoadMoreTrendingOpinions(IList<MyGoldOpinionMVC.Models.OpinionModel> topTrendingOpinions)
{
var dataHelper = new Data.DataHelper();
var moreTrendingOpinions = dataHelper.LoadMoreTrendingOpinions(topTrendingOpinions.LastOrDefault().Id);
// var partialView = PartialView("../PartialViews/_ListOfPostedOpinion", moreTrendingOpinions);
return View(moreTrendingOpinions);
}
So here is the order of events:
When running the site, the form shows a list of OpinionModels (Using the Editor Template displaying correct data). When I click the SUBMIT button, it goes to the controller (I have a breakpoint) and the data for the "topTrendingOpinions" parameter is a List with one item in it, but that item is null. So in other words, it is not passing through the list that is clearly being used to populate the form.
The only way I have been able to get a list to post back to the controller is to build it manually with jquery. my understanding is this.serialize on a form click is going to try to serialize the whole from which would get very ugly. How I would do this is
<input type="button" class="btnMore" value="Load More Trending Opinions" />
$('.btnMore').on('click', function () {
$.ajax({
url: '#Url.Action("LoadMoreTrendingOpinions", "AjaxHelper")',
type: 'post',
contentType: 'application/json; charset=utf-8',
data: {
Id: '#ViewBag.Id'
},
success: function (result) {
//add results to your table
}
});
});
and set the id of the last record sent through the view bag on your controller so you have a reference to go off of for pulling the next chunk. Let me know if you have any questions
When posting lists you have to be really careful that your inputs are named correctly. If they are not, the default model binder fails to parse them into classes when posted resulting the object being null in the controller.
In your case you are posting a list of models inside a model, but not the whole model. I'd use PartialView instead of editortemplate, just to make working with field names easier. In my example we are posting a list of FooModels contained in IndexModel
Model
public class FooModel
{
public string Foo { get; set; }
public string Bar { get; set; }
}
public class IndexModel
{
public IList<FooModel> Foos { get; set; }
}
View
#using (Html.BeginForm("LoadMoreTrendingOpinions","AjaxHelper",
method: FormMethod.Post,
htmlAttributes: new { #class = "form-horizontal", id = "LoadTrendingOpinionsForm" }))
{
#Html.Partial("FooModelsPartial", Model.Foos)
<input type="submit" value="Load More Trending Opinions" />
}
FooModelsPartial
#model IList<FooModel>
#for (int i = 0; i < Model.Count(); i++)
{
#Html.EditorFor(model => model[i].Foo)
#Html.EditorFor(model => model[i].Bar)
}
Notice how we are using for instead of foreach loop. This is because editors in foreach loop are not named correctly. In this case we want our fields to be [0].Foo, [0].Bar, [1].Foo, [1]. Bar etc.
Controller:
[HttpPost]
public ActionResult LoadMoreTrendingOpinions(IList<FooModel> topTrendingOpinions)
{
// do something with toptrending thingy
var model = new IndexModel();
model.Foos = topTrendingOpinions;
return View("Index", model);
}
Now the real question in my opinion is do you really want to post the whole list of models to get bunch of new ones related to one of them? Wouldn't it be more convenient to post the id of opinion you'd want to read more of, returning partialview containing the requested more trending opinions and appending that to some element in the view with jquery?
Html:
#using (Html.BeginForm("LoadMoreTrendingOpinions","AjaxHelper",
method: FormMethod.Post,
htmlAttributes: new { #class = "form-horizontal", id = "LoadTrendingOpinionsForm" }))
{
<div id="more">#Html.Partial("FooModelsPartial", Model.Foos)</div>
<input type="submit" value="Load More Trending Opinions" />
}
Javascript:
$('#LoadTrendingOpinionsForm').submit(function () {
$.ajax({
url: this.action,
type: this.method,
data: {
id: 1 /* The id of trending item you want to read more of */
},
success: function (result) {
$("#more").html(result)
}
});
return false;
});
Controller:
[HttpPost]
public ActionResult LoadMoreTrendingOpinions(int id)
{
var moreTrendingOpinions = dataHelper.LoadMoreTrendingOpinions(id);
return PartialView("FooModelsPartial", moreTrendingOpinions);
}

Bind my form to a Model

I have a ViewModel which contains a List of my Model, like so:
public class OrderConfirm
{
public ICollection<DayBookQuoteLines> SalesLines { get; set; }
public ICollection<DayBookQuoteLines> LostLines { get; set; }
public string Currency { get; set; }
}
I then use this ViewModel in my View like so:
#model btn_intranet.Areas.DayBook.Models.ViewModels.OrderConfirm
#{
ViewBag.Title = "Daybook - Order Confirmation";
}
<h6>Sales Lines</h6>
<div id="SalesOrders">
#using (Ajax.BeginForm("ConfirmSalesOrder", new AjaxOptions()
{
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "SalesOrders",
OnBegin = "SalesOrderConfirm"
}))
{
#foreach(var item in Model.SalesLines)
{
<p>#item.ItemName</p>
<p>#item.Qty</p>
#* Other Properties *#
}
<input type="submit" value="Submit Sales Order" />
}
</div>
<h6>Lost Lines</h6>
<div id="LostOrders">
#using (Ajax.BeginForm("ConfirmLostOrder", new AjaxOptions()
{
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "LostOrders",
OnBegin = "LostOrderConfirm"
}))
{
#foreach(var item in Model.SalesLines)
{
<p>#item.ItemName</p>
<p>#item.Qty</p>
#* Other Properties *#
}
<input type="submit" value="Submit Lost Order" />
}
</div>
The problem is, in my [HttpPost] actions, both ConfirmSalesOrder and ConfirmLostOrder. The value of my Model passed as a parameter is null:
[HttpPost]
public ActionResult ConfirmSalesOrder(List<DayBookQuoteLines> quoteLines)
{
// Process order...
return PartialView("Sales/_ConfirmSalesOrder");
}
so quoteLines is null. How can I bind the form to my model?
You don't have any input field in your form that will send the values to the server. You are only displaying them. That's why they are null when you submit the form => nothing is sent to the server.
But if inside this form the user is not supposed to modify any of the values all you need to do is to pass an id to the controller action that will allow you to fetch the model from the exact same location from which you fetched it in your GET action that rendered this form.
In this case your action will look like this:
[HttpPost]
public ActionResult ConfirmSalesOrder(int id)
{
List<DayBookQuoteLines> quoteLines = ... fetch them the same way as in your GET action
// Process order...
return PartialView("Sales/_ConfirmSalesOrder");
}
If on the other hand the user is supposed to modify the values in the form you need to provide him with the necessary input fields: things like textboxes, checkboxes, radio buttons, dropdownlists, textereas, ... And in order to generate proper names for those input fields I would recommend you using editor templates instead of writing foreach loops in your views.
UPDATE:
Seems like the user is not supposed to edit the data so there are no corresponding input fields. In this case in order to preserve the model you could during the AJAX request you could replace the Ajax.BeginForm with a normal Html.BeginForm an then manually wire up the AJAX request with jQuery. The advantage of this approach is that now you have far more control and you could for example send the entire model as a JSON request. To do this you could store the model as a javascript encoded variable inside the view:
<script type="text/javascript">
var model = #Html.Raw(Json.Encode(Model));
</script>
and then AJAXify the form:
$('#formId').submit(function() {
$.ajax({
url: this.action,
type: this.method,
contentType: 'application/json',
data: JSON.stringify({ quoteLines: model }),
success: function(result) {
$('#someTargetIdToUpdate').html(result);
}
});
return false;
});

MVC3 Text Box Text change Event

I have the following scenario, using mvc3:
I have a database table which holds a RecordID, RecordName and RecordType. Displayed are three text boxes, one for each of the fields mentioned previously.
My Question is, when i enter a RecordID into the relevant text box, i want to be able to show the RecordName and RecordType for that particular RecordID. How can i achieve this?
In View:
#Html.TextBoxFor(model => model.RecordId)
#Html.TextBoxFor(model => model.RecordName)
#Html.TextBoxFor(model => model.RecordType)
<script language="javascript">
$('#RecordId').change(function(){
var recordId = this.value;
$.getJSON("/MyController/GetRecordById",
{
id: recordId
},
function (data) {
$('RecordName').val(data.Name);
$('RecordType').val(data.Type);
});
});
</script>
In Controller:
public JsonResult GetRecordById(int id)
{
var record = recordRepository.GetById(id);
var result = new {
Name = record.Name,
Type = record.Type
}
return Json(result, JsonRequestBehavior.AllowGet);
}

Conditionally resetting input element value before submitting form to MVC3 action

this is probably a simple question but I'm new to jQuery with MVC3. I have an MVC3 application where an Index action lists some papers with their authors. Users can filter the list by (among other parameters) author name, so I have an input element using jQueryUI autocomplete to let them type some letters and pick the desired author. When this happens my JS code stores its ID into a hidden element and posts the form; the ID is then passed via the model binder to an object representing all my filters.
The filters object is like:
public sealed class PaperFilter
{
public int? AuthorId { get; set; }
// ... other params here
}
The controller action receives page number and sort parameters (the view uses the MvcContrib grid) and this filter. It then creates a view model including the list of papers and a number of properties representing the filter properties, and passes it back to the view:
public ViewResult Index(int? page, GridSortOptions sort, PaperFilter filter)
{
var papers = _repository.GetPapers(filter);
if (sort.Column != null)
papers = papers.OrderBy(sort.Column, sort.Direction);
ViewBag.Sort = sort;
PaperIndexModel model = new PaperIndexModel(filter)
{ Papers = papers.AsPagination(page ?? 1, 10) };
if (filter.AuthorId.HasValue)
{
Author author = _repository.GetAuthor((int)filter.AuthorId);
model.AuthorName = author.FirstName + " " + author.LastName;
}
return View(model);
}
where the returned model contains the papers list together with a copy of the filter properties.
The view form relevant code is:
...
#Html.TextBoxFor(m => m.AuthorName)
#Html.HiddenFor(m => m.AuthorId)
...
and its JS code:
<script type="text/javascript">
$(function () {
$("#AuthorName").autocomplete({
source: function (request, response) {
$.ajax({
url: "/Author/FindAuthor", type: "POST", dataType: "json",
data: { LastName: request.term },
success: function (data) {
response($.map(data, function (item) {
return { label: item.FirstName + ' ' + item.LastName, value: item.LastName, id: item.Id };
}));
}
});
},
select: function (event, ui) {
$("#AuthorName").val(ui.item.value);
$("#AuthorId").val(ui.item.id);
$(this).closest("form").submit();
}
});
});
</script>
This works fine, anyway I'd like my users to reset the author filter by simply clearing the input box (AuthorName) and pressing enter; but to do this I'd need to reset the AuthorId value, i.e. do something like $("#AuthorId").val("") before the form is posted. I tried to do this on keypress but it does not seem to fire before the post happens, because my action still gets the filter with its AuthorId populated. Could anyone suggest a solution?
check it right before your .ajax() call. If the length is 0 you don't have to .ajax either and just return.

Resources