pass input text from view to controller - asp.net-mvc-3

i know there are a lot questions like this but i really cant get the explanations on the answers... here is my view...
<script type="text/javascript" language="javascript">
$(function () {
$(".datepicker").datepicker({ onSelect: function (dateText, inst) { },
altField: ".alternate"
});
});
</script>
#model IEnumerable<CormanReservation.Models.Reservation>
#{
ViewBag.Title = "Index";
}
<h5>
Select a date and see reservations...</h5>
<div>
<div class="datepicker">
</div>
<input name="dateInput" type="text" class="alternate" />
</div>
i want to get the value of the input text... there's already a value in my input text because the datepicker passes its value on it... what i cant do is to pass it to my controller... here is my controller:
private CormantReservationEntities db = new CormantReservationEntities();
public ActionResult Index(string dateInput )
{
DateTime date = Convert.ToDateTime(dateInput);
var reservations = db.Reservations.Where(r=>r.Date==date).Include(r => r.Employee).Include(r => r.Room).OrderByDescending(r => r.Date);
return View(reservations.ToList());
}
i am trying to list in my home page the reservations made during the date the user selected in my calender in my home page....

I don't see a Form tag in your View...or are you not showing the whole view? hard to tell.. but to post to your controller you should either send the value to the controller via an ajax call or post a model. In your case, your model is an IEnumerable<CormanReservation.Models.Reservation> and your input is a date selector and doesn't look like it is bound to your ViewModel. At what point are you posting the date back to the server? Do you have a form with submit button or do you have an ajax call that you aren't showing?
Here is an example of an Ajax request that could be called to pass in your date
$(function () {
$(".datepicker").onselect(function{
searchByDate();
})
});
});
function searchbyDate() {
var myDate = document.getElementById("myDatePicker");
$.ajax({
url: "/Home/Search/",
dataType: "json",
cache: false,
type: 'POST',
data: { dateInput: myDate.value },
success: function (result) {
if(result.Success) {
// do something with result.Data which is your list of returned records
}
}
});
}
Your datepicker control needs something to reference it by
<input id="myDatePicker" name="dateInput" type="text" class="alternate" />
Your action could then look something like this
private CormantReservationEntities db = new CormantReservationEntities();
public JsonResult Search(string dateInput) {
DateTime date = Convert.ToDateTime(dateInput);
var reservations = db.Reservations.Where(r=>r.Date==date).Include(r => r.Employee).Include(r => r.Room).OrderByDescending(r => r.Date);
return View(reservations.ToList());
return Json(new {Success = true, Data = reservations.ToList()}, JsonRequestBehaviour.AllowGet());
}
Update
If you want to make this a standard post where you post data and return a view then you need to make changes similar to this.
Create a ViewModel
public class ReservationSearchViewModel {
public List<Reservation> Reservations { get; set; }
public DateTime SelectedDate { get; set; }
}
Modify your controller actions to initially load the page and then be able to post data return the View back with data
public ActionResult Index() {
var model = new ReservationSearchViewModel();
model.reservations = new List<Reservation>();
return View(model);
}
[HttpPost]
public ActionResult Index(ReservationSearchViewModel model) {
if(ModelState.IsValid)
var reservations = db.Reservations.Where(r => r.Date = model.SelectedDate).Include(r => r.Employee).Include(r => r.Room).OrderByDescending(r => r.Date);
}
return View(model)
}
Modify your view so that you have a form to post to the Index HttpPost action
#model CormanReservation.Models.ReservationSearchViewModel
<h5>Select a date and see reservations...</h5>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
#Html.EditorFor(model => model.SelectedDate)
#Html.EditorFor(model => model.Reservations) // this may need to change to a table or grid to accomodate your data
<input type="submit" value="Search" />
}

Related

Reload partial view via Ajax: controls in partial are renamed

I'm following this standard pattern for using Ajax to reload a partial view. However, when the partial view is reloaded, the controls in the view have different IDs. They lose the name of the parent model. This means that when the form is posted back, model binding won't work.
So in the example below, when the page is first loaded, the checkbox id is "PenguinEnclosure_IsEnoughPenguins" but after the partial is reloaded, the checkbox id is "IsEnoughPenguins" The ID must be "PenguinEnclosure_IsEnoughPenguins" for model binding to correctly bind this to the PenguinEnclosure property of the VM.
Model:
public class ZooViewModel
{
public string Name { get; set; }
public PenguinEnclosureVM PenguinEnclosure { get; set; }
}
public class PenguinEnclosureVM
{
public int PenguinCount { get; set; }
[Display(Name = "Is that enough penguins for you?")]
public bool IsEnoughPenguins { get; set; }
}
Controller:
public ActionResult Index()
{
var vm = new ZooViewModel
{
Name = "Chester Zoo",
PenguinEnclosure = new PenguinEnclosureVM { PenguinCount = 7 }
};
return View(vm);
}
public ActionResult UpdatePenguinEnclosure(int penguinFactor)
{
return PartialView("DisplayTemplates/PenguinEnclosureVM", new PenguinEnclosureVM { PenguinCount = penguinFactor * 10 });
}
View:
#model PartialProblem.Models.ZooViewModel
#Scripts.Render("~/bundles/jquery")
<p>
Welcome to #Model.Name !
</p>
<p>
<div id="penguin">
#Html.DisplayFor(m => m.PenguinEnclosure)
</div>
</p>
<button id="refresh">Refresh</button>
<script>
$(document).ready(function () {
$("#refresh").on("click", function () {
$.ajax({
url: "/Home/UpdatePenguinEnclosure",
type: "GET",
data: { penguinFactor: 42 }
})
.done(function (partialViewResult) {
$("#penguin").html(partialViewResult);
});
});
});
</script>
Partial View:
#model PartialProblem.Models.PenguinEnclosureVM
<p>
We have #Model.PenguinCount penguins
</p>
<p>
#Html.LabelFor(m => m.IsEnoughPenguins)
#Html.CheckBoxFor(m => m.IsEnoughPenguins)
</p>
An approach I have used is to set the "ViewData.TemplateInfo.HtmlFieldPrefix" property in the action that responds to your ajax call (UpdatePenguinEnclosure). This tells Razor to prefix your controls names and/or Ids.
You can choose whether to hard code the HtmlFieldPrefix, or pass it to the action in the ajax call. I tend to do the latter. For example, add a hidden input on the page with its value:
<input type="hidden" id="FieldPrefix" value="#ViewData.TemplateInfo.HtmlFieldPrefix" />
Access this in your ajax call:
$.ajax({
url: "/Home/UpdatePenguinEnclosure",
type: "GET",
data: { penguinFactor: 42, fieldPrefix: $("#FieldPrefix").val() }
})
Then in your action:
public ActionResult UpdatePenguinEnclosure(int penguinFactor, string fieldPrefix)
{
ViewData.TemplateInfo.HtmlFieldPrefix = fieldPrefix;
return PartialView("DisplayTemplates/PenguinEnclosureVM", new PenguinEnclosureVM { PenguinCount = penguinFactor * 10 });
}
Try this:
Controller:
public ActionResult UpdatePenguinEnclosure(int penguinFactor)
{
PenguinEnclosureVM pg = new PenguinEnclosureVM { PenguinCount = penguinFactor * 10 };
return PartialView("DisplayTemplates/UpdatePenguinEnclosure", new ZooViewModel { PenguinEnclosure = pg });
}
Your Partial:
#model PartialProblem.Models.ZooViewModel
<p>
We have #Model.PenguinEnclosure.PenguinCount penguins
</p>
<p>
#Html.LabelFor(m => m.PenguinEnclosure.IsEnoughPenguins)
#Html.CheckBoxFor(m => m.PenguinEnclosure.IsEnoughPenguins)
</p>
I Think this will do the trick

AJAX pagedlist with partial view

I can't quite figure out how to get a partial view to render a paged list using ajax.
The closest I've got it to working is the example from Using paging in partial view, asp.net mvc
I'm basically trying to create a page with a list of comments per user where the page can be changed in the same way as the answers tab on the stackoverflow users page.
The paging works fine the on the first pager click, but then the the partial view is all that is returned once I click on the pager again.
Controller:
public class ProductController : Controller
{
public IQueryable<Product> products = new List<Product> {
new Product{ProductId = 1, Name = "p1"},
new Product{ProductId = 2, Name = "p2"},
new Product{ProductId = 3, Name = "p3"},
new Product{ProductId = 4, Name = "p4"},
new Product{ProductId = 5, Name = "p5"}
}.AsQueryable();
public object Index()
{
return View();
}
public object Products(int? page)
{
var pageNumber = page ?? 1; // if no page was specified in the querystring, default to the first page (1)
var onePageOfProducts = products.ToPagedList(pageNumber, 3); // will only contain 25 products max because of the pageSize
ViewBag.OnePageOfProducts = onePageOfProducts;
return PartialView("_Products");
}
}
Views:
Index.cshtml:
<link href="/Content/PagedList.css" rel="stylesheet" type="text/css" />
<h2>List of Products</h2>
<div id="products">
#Html.Action("Products", "Product")
</div>
#section scripts{
<script type="text/javascript">
$(function() {
$('#myPager').on('click', 'a', function() {
$.ajax({
url: this.href,
type: 'GET',
cache: false,
success: function(result) {
$('#products').html(result);
}
});
return false;
});
});
</script>
}
_Products.cshtml:
#using PagedList.Mvc;
#using PagedList;
<ul>
#foreach(var product in ViewBag.OnePageOfProducts){
<li>#product.Name</li>
}
</ul>
<!-- output a paging control that lets the user navigation to the previous page, next page, etc -->
<div id="myPager">
#Html.PagedListPager((IPagedList)ViewBag.OnePageOfProducts, page => Url.Action("Products", new { page }))
</div>
Model
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
}
Can anyone show me what I'm doing wrong?
I ended up using the unobtrusive ajax example from the pagedlist source [https://github.com/troygoode/PagedList][1]
partial view:
#using PagedList;
#using PagedList.Mvc;
<ul id="names" start="#ViewBag.Names.FirstItemOnPage">
#foreach(var i in ViewBag.Names){
<li>#i</li>
}
</ul>
#Html.PagedListPager((IPagedList)ViewBag.Names, page => Url.Action("Index", new { page }), PagedListRenderOptions.EnableUnobtrusiveAjaxReplacing( new AjaxOptions(){ HttpMethod = "GET", UpdateTargetId = "unobtrusive"}))
Index:
#{
ViewBag.Title = "Unobtrusive Ajax";
}
#using PagedList;
#using PagedList.Mvc;
#Styles.Render("~/Content/PagedList.css")
<h2>Unobtrusive Ajax</h2>
<p>Example of paging a list:</p>
<div id="unobtrusive">
#Html.Partial("UnobtrusiveAjax_Partial")
</div>
Controller:
public class UnobtrusiveAjaxController : BaseController
{
// Unobtrusive Ajax
public ActionResult Index(int? page)
{
var listPaged = GetPagedNames(page); // GetPagedNames is found in BaseController
if (listPaged == null)
return HttpNotFound();
ViewBag.Names = listPaged;
return Request.IsAjaxRequest()
? (ActionResult)PartialView("UnobtrusiveAjax_Partial")
: View();
}
}
Just in case, since the original question wasn't answered. I guess the problem was that on click handlers weren't reattached to the new pager elements generated by AJAX request. I also don't like unobstrusive AJAX solution in this case, since pager id is hardcoded in the nested view while passing it in some other way may be too cumbersome.
<script type="text/javascript">
// better not to clutter global scope of course, just for brevity sake
var attachHandlers = function() {
$('#myPager a').click(function() {
$('#myPager').load(this.href, function() {
attachHandlers();
});
return false;
});
};
$(document).ready(function () {
attachHandlers();
});
</script>

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

MVC Knockout validation display and

I am using knockout for the first time and I am struggling to get my head around a problem.
I have a page with multiple sections and want to be able to edit a section and submit to the controller, then display the saved details.
Each section is a partial view which contains the display information and the form. They are shown and hidden as required. I have the code working for submitting, but the problem is when the ModelState is not valid. I need to return to the form with the validation message displayed
How can I display the form again when the server validation fails? When the validation fails it currently goes back to the display section.
Also I have noticed the validation message does not display.
I am sure this must be a common problem with a simple fix. I know there are knockout validation tools, but will need to do more complex business logic validation later on and need to get the technique working.
ViewModel:
[Required]
public DateTime? InterviewDate { get; set; }
View:
<div data-bind="if: showAdminInterviewDisplay" id="Display">
<div>
<button data-bind="click: showInterviewForm" id="EditButton">Edit</button>
</div>
<div>
#Html.Label("Inteview Date") :
<label data-bind="text: interviewDate"></label>
</div>
</div>
<div data-bind="if: showAdminInterviewForm" id="Form">
<div>
#Html.Label("Interview Date")
<input data-bind="value: interviewDate" id="interviewDatePicker" />
#Html.ValidationMessageFor(m => m.InterviewDate)
</div>
<div>
<button data-bind="click: saveInterviewDate">Submit</button>
</div>
Knockout ViewModel:
function InterviewViewModel() {
//Data
var self = this;
var jsonDate = #Html.Raw(Json.Encode(#Model.InterviewDate));
var date = new Date(parseInt(jsonDate.substr(6)));
self.interviewDate = ko.observable(dateFormat(date, "dd/mm/yyyy"));
self.showAdminInterviewDisplay = ko.observable(true);
self.showAdminInterviewForm = ko.observable();
self.showInterviewForm = function () {
self.showAdminInterviewDisplay(false);
self.showAdminInterviewForm(true);
$("#interviewDatePicker").datepicker({dateFormat: 'dd/mm/yy'});
};
//Operations
self.saveInterviewDate = function() {
$.ajax("#Url.Action("SaveInterview")", {
data: ko.toJSON(self),
type: "post",
contentType: "application/json",
success: function(data) {
self.showAdminInterviewDisplay(true);
self.showAdminInterviewForm(false);
}
});
};
};
ko.applyBindings(new InterviewViewModel());
Controller:
public ActionResult SaveInterview(KnockoutViewModel model)
{
if (ModelState.IsValid)
{
return Json(model);
}
return PartialView("_AdminInterview", model);
}
Instead of returning a Partial View from your Action Method, return a serialised error model to the success function in the AJAX call. The error model will contain all the errors in the ModelState.
See this post on how to get and consume the errors from Model State:
ASP.NET MVC How to convert ModelState errors to json (JK's answer)
So you would have something like:
Error Model:
public class JsonErrorModel
{
public JsonErrorModel()
{
HasFailed = true;
}
public bool HasFailed { get; set; }
public IEnumerable ErrorList { get; set; }
}
Controller:
if(ModelState.IsValid)
{
//Do whatever here
return Json(new { model });
}
return Json(new JsonErrorModel {ErrorList = ModelState.Errors()});
Success function of AJAX call:
success: function (result) {
if(result.HasFailed) {
self.showAdminInterviewDisplay(false);
self.showAdminInterviewForm(true);
DisplayErrors(result.ErrorList);
}
else {
self.showAdminInterviewDisplay(true);
self.showAdminInterviewForm(false);
}
}
So now, if the server side validation failed, the view will show the form and the validation errors.

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

Resources