Conditionally resetting input element value before submitting form to MVC3 action - asp.net-mvc-3

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.

Related

Redirect to partial view on another controller

I am new in asp.net mvc programming, please be gentle... :)
Please notice that the following views are all PARTIAL views! Methods are called through Ajax and redirect to partial views with lists, forms are posted through Ajax, etc. OK, here we go...
1st controller named AlertsController. One of the methods is ResolveAlert(Guid id) which returns RedirectToAction -> UnresolvedAlerts() which is just a list of unresolved alerts.
2nd contoller named FrontDeskController. One of the methods is CustomerDetails(Guid id) which lists the customer and alerts that he might have.
I want to be able to "Resolve an alert" (thus use the method of the 1st controller) but return to the page that I was before instead of going to the redirected page that the method returns.
I added a second parameter to the ResolveAlert() method which lists a returnUrl string. I manage to send the Url that I want it to redirect to but I get just the partial (not rendered inside the whole page as it should)...
Here's my ResolveAlert method on my AlertsController:
// Resolve Alert POST
[HttpPost]
public async Task<ActionResult> Resolve(AlertModel model, string redirectUrl)
{
await _AlertsService.ResolveAsync(model);
if (!string.IsNullOrWhiteSpace(redirectUrl))
return Redirect(redirectUrl);
return RedirectToAction("Unresolved");
}
...and here is my CustomerDetails() method on my FrontDeskController:
// Display Customer Alerts
public async Task<PartialViewResult> CustomerDetails(AttendanceModel model, Guid id)
{
var customer = await _CustomersService.ReadAsync(id);
ViewData["Customer"] = await _CustomersService.ReadCustomerExtendedAsync(id);
var alerts = await _AlertsService.ReadCustomerAlertsAsync(id);
ViewData["Alerts"] = alerts.Where(x => x.IsResolved == false).ToList();
return PartialView("_CustomerDetails", model);
}
The ResolveAlert() method of the first controller is called in two steps... 1st I call a modal from the CustomerDetails view:
function resolveAlert(alertId, customerId) {
var returnTo = '/FrontDesk/CustomerDetails/' + customerId;
$.ajax({
method: 'GET',
url: '/Alerts/Resolve/' + alertId,
data: {returnUrl : returnTo},
dataType: 'html'
}).then(function (html) {
$('#dialog-container').html(html);
showDialog();
});
}
...then on the modal I have:
#{
var data = Request.Params["returnUrl"];
}
#using (Ajax.BeginForm("Resolve", "Alerts", new { redirectUrl = data}, new AjaxOptions() { HttpMethod = "POST", InsertionMode = InsertionMode.Replace, UpdateTargetId = "partial", OnSuccess = "hideDialog" }, new { id = "form", #class = "form-horizontal" }))
{ ..... textbox with some notes that I can post while resolving the alert ..... }
... and (finally) here is the final part at the bottom of my modal:
<script type="text/javascript">
$('#form').validate({
rules: {
AlertNotes: {
required: true
}
},
submitHandler: function (form) {
$.ajax({
url: $(form).attr("action"),
data: $(form).serialize(),
type: $(form).attr("method")
}).then(function (result) {
$("#partial").html(result);
hideDialog();
});
}
});
</script>
I think that in order for the returned partial to get rendered correctly inside its container I should be returning a RedirectToAction from the ResolveAlert() method but the problem is that it belongs on a different controller...
Is it possible to get this working somehow or should I just bite the bullet and forget about having those pages as partials, get rid of the Ajax calls and use normal Url.Action() links?
It was just a Javascript/Ajax bug in my code.... Please disregard the question...
For those wondering, I had 2 forms using the same id. JS died silently allowing the form to be posted normally and not through Ajax. It had me scratching my head for a while now. Too bad that web development tools and VS in particular can't snipe such errors and provide a meaningful hint to assist you in debugging...

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);
}

I want to handle crud operations on single view in mvc3 with different buttons along with javascript in mvc3

I want to handle crud operations on single view in mvc3 with different buttons along with javascript in mvc3.
Actually i have a view with account code and description fields.
i want to add,edit and delete record into sql server 2008 r2 database by using wcf services.
i want to use javascript for client side scripting.
i want to call controller's method by javascript button click event.
Please tell me how i do it.
currently i have following javascript function.
$(document).ready(function () {
var today = new Date();
var dd = today.getDate();
$('#sve').click(function () {
var person = { AcCode: $('#AcCode').val(), Descrip: $('#Descrip').val(), AddOn: dd };
$.ajax({
url: '/Home/Save',
type: "POST",
data: JSON.stringify(person),
dataType: "json",
contentType: "application/json; charset=utf-8",
success: function (result) {
// $('#message').html('Record saved successfully' + result).fadeIn();
alert("Record saved successfully");
},
error: function () {
// $('#message').html('Error Occurred').fadeIn();
alert("Record not saved successfully");
}
});
return false;
});
});
below is my controller code for save button
[Authorize]
// [Authorize(Roles = "Administrators")]
[HttpPost]
[MultiButton(MatchFormKey = "action", MatchFormValue = "Save")]
public ActionResult Save(AccntBD model)
{
CBSWCF.Account useInfo = new CBSWCF.Account();
if (ModelState.IsValid)
{
if (!model.IsAcCodeExist(model.AcCode))
{
string ObjUser = User.Identity.Name;
string ObjUid = string.Empty;
AcntEnt.AcCode = model.AcCode;
AcntEnt.Descrip = model.Descrip;
objSvc.ACodeSave2(AcntEnt);
}
else
{
ModelState.AddModelError("CustomError", "Account Code Already Exist");
}
}
else
{
ModelState.AddModelError("", "");
}
return View(model);
}
i use following code to use multiple buttons in single view.
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class MultiButtonAttribute : ActionNameSelectorAttribute
{
public string MatchFormKey { get; set; }
public string MatchFormValue { get; set; }
public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo)
{
return controllerContext.HttpContext.Request[MatchFormKey] != null &&
controllerContext.HttpContext.Request[MatchFormKey] == MatchFormValue;
}
}
now problem is that my save function is not hit from javascript and message not saved successfuly is shown to me.
can anyone plz help me
There is no need to add the MultiButton attribute to the controller action if you are submitting the form via JavaScript the MultiButtonAttribute was created for when you wish to map submit Buttons to specific controller actions and this is possible because when you submit a form via a submit button the submit button name attribute is added to the post body with a its submit button specific value eg
<input type="submit" name="ButtonName" value="ButtonOne"/> <!-- would post ButtonName=ButtonOne -->
<input type="submit" name="ButtonName" value="ButtonTwo"/> <!-- would post ButtonNAme=ButtonTwo -->
If you wish to submit a form via javascript that will be routed to a different action based on the submit button you will be required to add the Button name and value to the querystring of the request
url: '/Home/Save?ButtonName=ButtonOne'

Is there a way to use AJAX on a DropDownList changed event to dynamically modify a partial view on a page?

Is there a way to use AJAX on a DropDownList changed event to dynamically modify a partial view on a page?
My main page has a DropDownList (DropDownListFor) and a partial view which ONLY contains a list of "items". The items shown in this partial view are dependent upon the item selected in the DropDownList. There's a 1 to many relationship between the DropDownList item and the items in the partial view. So, when the user changes the value of the DropDownList, the content in the partial view will dynamically change to reflect the item selected in the DropDownList.
Here's my DropDownList:
<div data-role="fieldcontain">
Choose Capsule:<br />
#Html.DropDownListFor(x => x.CapsuleFK, new SelectList(Model.Capsules, "pk", "name", "pk"), new { id = "ddlCapsules" })
<br />
</div>
Here's my Partial View declaration on the same page:
<div data-role="fieldcontain">
#Html.Partial("_FillerPartial", Model.Fillers)
</div>
I'm not very familiar with Ajax, but looking at other examples, here's what I have for my Ajax:
$(document).ready(function () {
$('#ddlCapsules').change(function () {
// make ajax call to modify the filler list partial view
var selection = $('#ddlCapsules').val();
var dataToSend = { cappk: selection };
$.ajax({
url: 'Process/GetFillersByCapsule',
data: { cappk: dataToSend },
success: function (data) {
alert("server returned: " + data);
}
});
});
});
And finally, here's a screenshot of what's going on. By changing the "Choose Capsule" drop down list, I want the Filler list to update dynamically:
You can load the drop down list as a partial view from the controller using ajax.
The controller code:
[HttpGet]
public virtual ActionResult GetFillersByCapsule(string cappk)
{
var model = //Method to get capsules by pk, this returns a ViewModel that is used to render the filtered list.
return PartialView("PartialViewName", model);
}
The main view html:
<div id="filteredList">
</div >
The partial view
#model IEnumerable<MyCapsuleModel>
foreach (var x in Model)
{
//Render the appropriate filtered list html.
}
And you can load the filtered list using ajax:
$('#ddlCapsules').change(function () {
// make ajax call to modify the filler list partial view
var selection = $('#ddlCapsules').val();
var dataToSend = { cappk: selection };
$.ajax({
url: 'Process/GetFillersByCapsule',
data: { cappk: dataToSend },
success: function (data) {
$("#filteredList").empty();
$("#filteredList").html(data);
}
});
});
Hope this helps.
You can't update the partial, per se, because the partial will never be rendered again without a page reload. Once you receive the HTML, ASP is done, you're on your own at that point.
What you can do, of course, is switch out the content of a particular div or whatever using JavaScript. Your example in particular screams Knockout, so that's what I would recommend using.
Change your HTML to add a data-bind to your containing div:
<div data-role="fieldcontain" data-bind="foreach: filler">
<button data-bind="text: name"></button>
</div>
And your DropDownList:
#Html.DropDownListFor(x => x.CapsuleFK, new SelectList(Model.Capsules, "pk", "name", "pk"), new { id = "ddlCapsules", data_bind = "event: { change: updateFillers }" })
Then, some JavaScript:
var FillersViewModel = function () {
var self = this;
self.fillers = ko.observableArray([]);
self.updateFillers = function () {
var selection = $('#ddlCapsules').val();
var dataToSend = { cappk: selection };
$.ajax({
url: 'Process/GetFillersByCapsule',
data: { cappk: dataToSend },
success: function (data) {
self.fillers(data.fillers) // where `fillers` is an array
}
});
}
}
var viewModel = new FillersViewModel();
ko.applyBindings(viewModel);
This is a very simplistic example, and you'll need to do some more work to make it do everything you need it to do in your scenario, but the general idea is that every time the dropdown list is changed, Knockout will call your updateFillers method, which will execute the AJAX and put new data into the fillers observable array. Knockout automatically tracks changes to this array (hence the "observable" part), so an update is automatically triggered to any part of your page that relies on it. In this scenario, that's your div containing the buttons. The foreach binding will repeat the HTML inside for each member of the array. I've used a simple button element here just to illustrate, but you would include the full HTML required to create your particular button like interface. The text binding will drop the content of name in between the opening and closing tag. Refer to: http://knockoutjs.com/documentation/introduction.html for all the binding options you have.
There's much more you could do with this. You could implement templates instead of hard-coding your HTML to be repeated in the foreach. And, you can use your partial view to control the HTML for this template. The important part is that Knockout takes the pain out of generating all this repeating HTML for you, which is why I recommend using it.
Hope that's enough to get you started.

jQueryUI autocomplete with MVC3: posting autocompleted value

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.

Resources