How to I convert my simple MVC4 todo list application to AJAX?
The ideal answer would give me the steps that would lead to a successful conversion of this example to AJAX.
Note: I don't need an AJAX tutorial so much as an understanding of how the ASP.NET MVC architecture supports it.
Side Question: Why does #Html.EditorFor(model => model.TodoItemToCreate) bring back the value that was typed in even though the view model sets with this.TodoItemToCreate = null?
Model
public class TodosViewModel
{
List<string> todoItems;
public List<string> TodoItems
{
get { return this.todoItems ?? (todoItems = new List<string>()); }
}
[Display(Name="What do you need to do?")]
public string TodoItemToCreate { get; set; }
public bool AcceptTodoItem()
{
bool isThereAnItemToAccept = !string.IsNullOrWhiteSpace(this.TodoItemToCreate);
if (isThereAnItemToAccept)
{
this.TodoItems.Add(this.TodoItemToCreate);
this.TodoItemToCreate = null;
}
return isThereAnItemToAccept;
}
}
Controller
public class TodosController : Controller
{
public ActionResult Index()
{
return View(new TodosViewModel());
}
public ActionResult Create(TodosViewModel todosViewModel)
{
todosViewModel.AcceptTodoItem();
return View("Index", todosViewModel);
}
}
Index View
#model Programming.LearnWeb.Models.TodosViewModel
#{
ViewBag.Title = "Todos";
}
#using (Html.BeginForm("Create", "Todos"))
{
#Html.Partial("List")
#Html.LabelFor(model => model.TodoItemToCreate)
#Html.EditorFor(model => model.TodoItemToCreate)
<input type="submit" value="Create" />
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
List View
#model Programming.LearnWeb.Models.TodosViewModel
#{ int i = 0; }
<table>
#foreach (var todoItem in Model.TodoItems)
{
<tr>
<td>
#Html.Hidden("TodoItems[" + i++ + "]", todoItem)
#todoItem
</td>
</tr>
}
</table>
I got this done on my own - the result is at https://github.com/gabrielgreen/Todos.Mvc if anyone is interested or has any comments.
I put a fair amount of effort in and would appreciate any feedback that indicates if I did it right.
Related
I want to use 3 partial views to display my search result(Client name) in 3 different tables. I do not want to show any info from the tables before the search has been completed. As i have understood you can only use one model per view, unless you make some workarounds.
I have tried to make a view model so i can reference the multiple tables from both the index and the partial views. But can't figure it out. Since the methods for the 3 different tables will be the pretty much same i am only going to post the code for the client table. Any help or pointers would be much appreciated
ViewModel:
public class ViewModel
{
public List<Clients> allClients { get; set; }
public List<OrderLines> allOrders { get; set; }
public List<ViewNewOrderSum> allViewNewOrderSum { get; set; }
}
HomeController:
using testForAutofill.Models;
public class HomeController : Controller
{
test_Db_Context db = new test_Db_Context();
// GET: Home
public ActionResult Index()
{
ViewModel vm = new ViewModel();
vm.allClients = GetClients();
return View();
}
private List<Clients> GetClients()
{
List<Clients> clientList = new List<Clients>();
clientList = db.Clients.ToList();
return clientList;
}
[HttpPost]
public ActionResult Index(string searchTerm)
{
Scaleit_Db_Context db = new Scaleit_Db_Context();
List<Clients> orderSums;
if (string.IsNullOrEmpty(searchTerm))//Fix this!
{
orderSums = db.Clients.ToList();
}
else
{
orderSums = db.Clients.Where(x => x.Name.Equals(searchTerm)).ToList();
}
return View(orderSums);
}
IndexView:
#using testForAutofill.Models;
#model testForAutofill.Models.ViewModel
#if (Model.allClients != null && Model.allClients.Count() > 0)
{
#using (Html.BeginForm())
{
<b>Kundenavn:</b>
#Html.TextBox("searchTerm", null, new { id = "txtSearch" })
<input type="submit" value="🔍 Search" class="btn btn-primary" id="btn-search" />
#using (Html.BeginForm())
{
<div class="card-container">
<div class="card border-primary mb-3 card-client" style="max-width: 40rem;">
<div class="card-header">Kunde</div>
<div class="card-body">
<table class="table table-hover">
#foreach (Clients clients in Model.allClients)
{
#Html.Partial("_Client", clients)
}
</table>
</div>
</div>
</div>
}
_Client:
#model testForAutofill.Models.ViewModel
//Tried both DisplayFor and not.
<tr>
<th>Kunde:</th>
<td>#Html.Model.allClients.Name</td>// This is where the error gets
// thrown.
</tr>
<tr>
<th>Org.nr:</th>
<td>#Html.DisplayFor(modelItem => clients.OrgNr)</td>
</tr>
<tr>
<th>Adresse:</th>
<td>#Html.DisplayFor(modelItem => clients.Address1)</td>
#if (clients.Address2 != null)
{
<td>#Html.DisplayFor(modelItem => clients.PostNr)</td>
}
</tr>
The program/webpage doesnt run, and i get the error message:
" CS1061: 'HtmlHelper' does not contain a definition for
'Model' and no extension method 'Model' accepting a first argument of
type 'HtmlHelper' could be found (are you missing a using
directive or an assembly reference?)".
At the commented line in the Partial view.
TRY:
Set object on viewData
inside controller:
ActionResult SomeView(){
ViewData["object"] = theObj;
return View();
}
inside cshtml:
#using objectNamespace
#(((objectType)ViewData["object"]).name)
I'm very new to MVC and I'm not sure quite how the forms work. Here's what i want to do:
So I've got two text boxes and an enter button in a form I want to put two values into the textboxes and use them in my external link. Here's the code I am trying to use, i know it's not correct at all but should show you what I'm trying to do:
View:
#model Test.Models.Home
#{
ViewBag.Title = "Main";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using (Html.BeginForm("GetMessage()", "Home", FormMethod.Post))
{
#Html.Label("Location1: ")
#Html.TextBoxFor(model => model.Location1)
#Html.ValidationMessageFor(model => model.Location1)
#Html.Label("Location2: ")
#Html.TextBoxFor(model => model.Location2)
#Html.ValidationMessageFor(model => model.Location2)
<button type="submit">Enter</button>
}
Controller:
using System.Web.Mvc;
using BBWebApp.Models;
namespace Test.Controllers
{
public class HomeController : Controller
{
public ActionResult Main()
{
//var name = new Home() { Name = "Google maps API" };
return View();
}
[HttpPost]
public ActionResult Location(Home model)
{
if (ModelState.IsValid)
{
//TODO: SubscribeUser(model.Email);
}
return View("Main", model);
}
[HttpPost]
public ActionResult GetMessage()
{
return Redirect("https://example.com/" + Location1 + "/" + Location2);
}
}
}
Model:
public class Home
{
public string Location1 { get; set; }
public string Location2 { get; set; }
public string Name { get; set; }
}
As you can see I'm trying to get the form to trigger the redirect function under getMessage() but obviously this code is flawed as I don't fully understand how it works. As i said I'm very new to MVC so any help with this would be much appreciated.
This code is very good for starting. Only have some minor problems:
On your view's using block, remove paranthesis from action method
GetMessages
like that
#using (Html.BeginForm("GetMessage", "Home", FormMethod.Post))
{
#Html.Label("Location1: ")
#Html.TextBoxFor(model => model.Location1)
#Html.ValidationMessageFor(model => model.Location1)
#Html.Label("Location2: ")
#Html.TextBoxFor(model => model.Location2)
#Html.ValidationMessageFor(model => model.Location2)
<button type="submit">Enter</button>
{
Then you need to get your model and use variables on your controller's method
[HttpPost]
public ActionResult GetMessage(Home model)
{
return Redirect("https://example.com/" + model.Location1 + "/" + model.Location2);
}
and you are good to go.
I have the following viewModel
public class ExerciceViewModel
{
public string Code { get; set; }
public string Titre { get; set; }
public int QuestionCourante { get; set; }
}
the following view
#model MonEcoleVirtuelle.ViewModel.ExerciceViewModel
#{
ViewBag.Title = "Test";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Test</h2>
#using (Html.BeginForm("test", "exercice", FormMethod.Post))
{
#Model.Code <br />
#Model.Titre<br />
#Model.QuestionCourante<br />
<br />
<br />
Model.Code = "Code Modifie";
<input type="submit" value="Post Moi Ca!" name="fini" />
}
and the following controller methods
[HttpPost]
public ActionResult Test(ViewModel.ExerciceViewModel model)
{
if (ModelState.IsValid)
{
return Content(model.Code);
}
return View(model);
}
[HttpGet]
public ActionResult Test()
{
var vm = new ViewModel.ExerciceViewModel { Code = "code1", Titre = "Mon titre", QuestionCourante = 1 };
return View(vm);
}
When I submit the form, the model passed is empty, all properties are reset, not keeping the original values. What am I missing.
thanks
well, instead of #Model.Code which just display the values, you need some inputs.
So #Html.TextBoxFor(m => m.Code) for example
To manage a collection, you can do something like that :
#for (var i = 0; i < Model.Collection.Count; i++) {
Html.TextBoxFor(m => Model.Collection[i].Property)
}
You have not included any input fields in your view.
The #Model.Code etc only output the value of the field. To be able to post back elements they need to be form elements, like inputs. Use something like
#Html.TextBoxFor(p=>p.Code)
to create input fields that can then be posted back.
For a more complete guide see MSDN at http://msdn.microsoft.com/en-us/library/dd410596(v=vs.100).aspx
In my view this is what I have
#foreach (var match in Model.CommonMatches)
{
<tr>
<td>#match.StartDateTime</td>
<td>#match.EndDateTime</td>
<td>#match.AvailableAttendees.Count()</td>
<td>#Html.ActionLink("Accept", "AcceptAppointment", "Appointment", new {commonMatch = #match })</td>
</tr>
}
Model.CommonMatches is of type List<Window>
public class Window
{
public DateTime StartDateTime { get; set; }
public DateTime EndDateTime { get; set; }
public IEnumerable<DataModels.Attendee> AvailableAttendees { get; set; }
}
This is how the value is being passed from controller
[HttpGet]
public ActionResult ViewStatus(Guid appointmentId)
{
var status = new ViewStatus
{
AttendeesWhoResponded = _appointmentRepository.GetAppointmentDetails(appointmentId).Attendees.Where(a=>a.HasResponded == true).ToList(),
NotAttending = _appointmentRepository.GetAppointmentDetails(appointmentId).Attendees.Where(a=>a.HasResponded == true && a.Responses == null).ToList(),
CommonMatches = _appointmentRepository.FindCommonMatches(appointmentId)
};
return View(status);
}
ViewStatus class
public class ViewStatus
{
public ViewStatus()
{
AttendeesWhoResponded = new List<DataModels.Attendee>();
NotAttending = new List<DataModels.Attendee>();
}
public List<DataModels.Attendee> AttendeesWhoResponded { get; set; }
public List<DataModels.Attendee> NotAttending { get; set; }
public IEnumerable<Window> CommonMatches { get; set; }
}
The action in controller that ActionLink of view is calling is:
[HttpGet]
public ActionResult AcceptAppointment(Window commonMatch)
{
return Content("ac");
}
When I inspect the value of commonMatch in controller's action. I'm getting the StartDateTime and EndDateTime but i'm not getting all the value of AvailableAttendees. It is currently shown as null.
AvailableAttendees that I'm expecting is of type IEnumerable<Attendee>. Is is not possible to pass the object the way I'm passing?
What should I do, to also get all the values of AvailableAttendees in controller along with dates?
Edit 1:
<table class ="table-hover table-striped">
<thead>
<tr>
<td>Start time</td>
<td>End time</td>
<td>Number of Attendees</td>
</tr>
</thead>
#for (var count = 0; count < Model.CommonMatches.Count();count++ )
{
using (Html.BeginForm("AcceptAppointment", "Appointment", FormMethod.Post))
{
<tr>
<td>#Model.CommonMatches[count].StartDateTime</td>
<td>#Model.CommonMatches[count].EndDateTime</td>
<td>#Model.CommonMatches[count].AvailableAttendees.Count()</td>
#*<td>#Html.ActionLink("Accept", "AcceptAppointment", "Appointment", new { commonMatch = #match })</td>*#
#for(var j=0;j<Model.CommonMatches[count].AvailableAttendees.Count();j++)
{
<td>#Model.CommonMatches[count].AvailableAttendees[j].FirstName</td>//to check if the value is null or not, just a test
<td>#Html.HiddenFor(m=>Model.CommonMatches[count].AvailableAttendees[j].FirstName)</td>
<td>#Html.HiddenFor(m=>Model.CommonMatches[count].AvailableAttendees[j].LastName)</td>
<td>#Html.HiddenFor(m=>Model.CommonMatches[count].AvailableAttendees[j].Email)</td>
<td>#Html.HiddenFor(m=>Model.CommonMatches[count].AvailableAttendees[j].AttendeeId)</td>
}
<td><input type="submit" value="Accept"/></td>
</tr>
}
}
</table>
You need to post your model back, this would involve changing your controller method to this:
Controller
[HttpPost]
public ActionResult AcceptAppointment(List<Window> model)
{
return Content("ac");
}
View
You view would need a form and a submit button rather than an ActionLink. I have take the table formatting out to simplify the below.
Indexing your collections with a for loop so the model binder knows how to handle them, this is actually two loops as it is a collection within a collection. The hidden values have to be rendered too in order to be posted back (please forgive any typos).
#for(var i = 0; i < Model.CommonMatches.Count; i ++)
{
<div>
#using (Html.BeginForm("AcceptAppointment", "Appointment", FormMethod.Post)
{
#Html.HiddenFor(m => Model.CommonMatches[i].StartDateTime)
#Html.HiddenFor(m => Model.CommonMatches[i].EndDateTime)
#Model.CommonMatches[i].StartDateTime <br/>
#Model.CommonMatches[i].EndDateTime <br/>
#for(var j = 0; Model.CommonMatches[i].AvailableAttendees.Count; j++)
{
#Html.HiddenFor(m => Model.CommonMatches[i].AvailableAttendees[j].Prop1)<br/>
#Html.HiddenFor(m => Model.CommonMatches[i].AvailableAttendees[j].Prop2)<br/>
}
<input type="submit" value="Accept" />
</div>
}
}
There are plenty of things you need to taken care
<td>#Html.ActionLink("Accept", "AcceptAppointment", "Appointment", new {commonMatch = #match })</td>
Calls
[HttpGet]
public ActionResult AcceptAppointment(Window commonMatch)
{
return Content("ac");
}
Here you are navigating using a link <a href>. Basically you are issuing a get request. In get request, you can pass the data to server only via Query String. But your case, preparing a query string dynamically before navigating to url is bit more complex. But you can do it with a JavaScript like onclick=prepareHref(this);
#Html.ActionLink("Accept", "AcceptAppointment", "Appointment",
new {commonMatch = #match }, new {onclick=prepareHref(this)})
Then in Javascript
function prepareHref(obj)
{
var qsData="?StartDateTime='2014-02-25'&EndDateTime='2014-02-25'&AvailableAttendees[0].prop1=value1, etc"; // data should be obtained from other td elements
obj.href=obj.href+qsData;
}
But this is not a suggested way of doing it.
In case, if you want to open other page and show the url, better pass the id and load data again.
Option 1:
The better way could be submit the detail in the hidden field as explained by #hutchonoid.
Option 2:
or submit the details in jQuery ajax $.post method. Either way you need to use POST
#Html.ActionLink("Accept", "AcceptAppointment", "Appointment",
new {commonMatch = #match }, new {onclick=postMyData()})
function postMyData(){
var postData={};
postData.StartDateTime='';
postData.EndDateTime='';
postData.AvailableAttendees=[];
//for each AvailableAttendees prepare object
postData.AvailableAttendees[0]= {};
postData.AvailableAttendees[0].prop1=value1;
$.post('/Appointment/AcceptAppointment/',{data:postData},function(data){
});
return false;
}
[HttpPost]
public ActionResult AcceptAppointment(Window commonMatch)
{
return Content("ac");
}
I am using Razor HtmlHelpers.
using the code below i tried to print the value of ViewItemDto but it always show 0 for using #Html.TextBoxFor or any #Html...For(x=>..) method.
Does anybody have a idea why this is happening?
Note - The method is returning proper value as i can debug and see the value in debug window on views side.
[DataContract]
[Serializable]
public class ViewItemDto
{
[DataMember]
public decimal ViewItemId { get; set; }
[DataMember]
public string ItemName { get; set; }
}
From Controller Return
public ActionResult Index()
{
ViewItemDto viewItem = new ViewItemDto();
viewItem.ItemName = "OldData";
viewItem.ViewItemId = 10;
return View(viewItem);
}
public PartialViewResult SaveData(ViewItemDto viewItem)
{
viewItem.ItemName = "NewData";
viewItem.ViewItemId = 100;
return PartialView("ViewUserControl1", viewItem);
}
On View ViewUserControl1
#model Testing.Models.ViewItemDto
<div id="divSave">
#using (Ajax.BeginForm("SaveData", "Home", new AjaxOptions()
{
UpdateTargetId = "divSave",
OnSuccess = "OnSuccess",
InsertionMode = InsertionMode.Replace
}))
{
#Html.TextBoxFor(x => x.ViewItemId)//Shows 10
#Html.TextBox("ID", Model.ViewItemId) //Shows proper value 100
#Html.TextBoxFor(x => x.ItemName)//Shows oldData value
<input type="submit" name="name" value="Submit" />
}
and Index.cshtml
#model Testing.Models.ViewItemDto
#{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Index</h2>
#Html.Partial("ViewUserControl1", Model);
Shouldn't you be:
return PartialView(viewItem);
instead of:
return viewItem;
By changing the code of your controller to the following it worked here:
public PartialViewResult SaveData(ViewItemDto viewItem)
{
viewItem.ViewItemId = 100;
return viewItem;
}
I was unabled to reproduce your problem, since the code provided not even compiled.