Using Ajax.BeginForm with MVC 4 - adding to my model collection asynchronously isn't working - asp.net-mvc-4

I am trying to make a small football site where the user can create a new team and then asynchronously in another div it shows all the teams the user has created. So basically a team is created then added to the list of teams. All of this is in the model.
Now, I would like to do this asynchronously because its a nice to have but it's not working in my code. I am either missing something or it's not possible with what I am doing.
Controller
public ActionResult TeamManagement()
{
modelTeamSelect modelTeamSelect = new modelTeamSelect();
return View(modelTeamSelect);
}
[HttpPost]
public ActionResult TeamManagement(string btnSubmit, modelTeamSelect modelTeamSelect)
{
switch (btnSubmit)
{
case "Add Team":
// For now - add to collection but not use DAL
modelTeamSelect.teams.Add(modelTeamSelect.team);
//modelTeamSelect.team.TeamName = string.Empty;
break;
}
return View(modelTeamSelect);
}
View
#model Website.Models.modelTeamSelect
#{
ViewBag.Title = "Football App";
}
#section featured {
}
#using (Ajax.BeginForm(new AjaxOptions
{
HttpMethod = "POST",
Url = "Home/TeamManagement",
OnComplete = "teamAdded()"
}))
{
<div id="divTeams" style="float:left">
<h3>Create a new team:</h3>
#Html.LabelFor(m => m.team.TeamName)
#Html.TextBoxFor(m => m.team.TeamName)
<input type="submit" value="Add Team" name="btnSubmit" />
</div>
<div id="divCreatedTeams" style="float:left">
<h3>Your created teams:</h3>
#if (Model.teams.Count > 0)
{
for (int i = 0; i < Model.teams.Count; i++)
{
#Html.TextBoxFor(m => m.teams[i].TeamName)
}
}
</div>
<div id="divLeagues">
</div>
}
Model
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace Website.Models
{
public class modelTeamSelect
{
public modelTeamSelect()
{
teams = new List<modelTeam>();
team = new modelTeam();
}
public List<modelTeam> teams { get; set; }
public modelTeam team { get; set; }
}
}
I have the right javascript references being used in the project as I recently fixed this.
Why isn't my UI changing to reflect new contents of list?

I dont get the idea of passing the submit button string to the Action. But in order to pass a ViewModel to the Action I think you have to write your own model binder. If you want you can try getting the models seperately in the action and combining them in the Action
public ActionResult TeamManagement(List<modelTeam> teams, modelTeam team)
and combine them in the action in the viewModel.
Just a sugestion If you want to retrieve them async with ajax what I do is return partial view (i think better in your case) or json

Related

Returning Different Data To Various View Sections

I have a "Most Popular" div currently displaying the most popular auction items and an "Ending Soon" div that I want to display those auctions ending soon. I have successfully queried the database for the "Most Popluar" and returned those results to the page. And I know what the query is for those ending soon, but how do I return both sets of data to the page for the partial view "_AuctionTile" to use?
The following update reflects the suggestions made by Vinutha N --
The View
#model IEnumerable<MyAuctionApp.Models.Auction>
#{
ViewBag.Title = "Home Page";
}
<div class="row">
<div id="popular" class="col-md-4 col-lg-4">
<h2>Most Popular Items</h2>
#foreach (var item in Model.mostPopularItems)
{
#Html.Partial("_AuctionTile", item)
}
</div>
<div id="ending" class="col-md-4 col-lg-4">
<h2>Auctions Ending Soon</h2>
#foreach (var item in Model)
{
#Html.Partial("_AuctionTile", item)
}
</div>
The ViewModel
using MyAuctionApp.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace MyAuctionApp.ViewModels
{
public class AuctionViewModel
{
public static List<Auction> mostPopularItems;
public static List<Auction> endingSoon;
public AuctionViewModel(AuctionsDataContext db)
{
mostPopularItems = db.Auctions.Where(x => x.EndTime >
DateTime.Today).OrderByDescending(x => x.viewCount).ToList();
endingSoon = db.Auctions.Where(x => x.EndTime >
DateTime.Today).OrderByDescending(x => x.EndTime).ToList();
}
}
}
The Controller
using MyAuctionApp.Models;
using MyAuctionApp.ViewModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace MyAuctionApp.Controllers
{
[AllowAnonymous]
public class HomeController : Controller
{
[AllowAnonymous]
public ActionResult Index()
{
var db = new AuctionsDataContext();
var dataModuleObject = new AuctionViewModel(db);
return View(dataModuleObject);
//var auctions = db.Auctions.ToArray();
//var auctions = db.Auctions.Where(x => x.EndTime >
//DateTime.Today).OrderByDescending(x => x.viewCount);
//return View(auctions);
}
}
As you can see from the attached screenshot however, the 'mostPopularItems'
property that was initialized in the AuctionViewModel, is not being picked
up by Intellisense as existing, in the line
#foreach (var item in Model.mostPopularItems)
What do I still not have quite right?
Thanks,
CM
Use child actions. First, create actions like the following on your AuctionController:
[ChildActionOnly]
public ActionResult MostPopular()
{
// get most popular auctions
return PartialView("_AuctionTile", auctions);
}
[ChildActionOnly]
public ActionResult EndingSoon()
{
// get auctions ending soon
return PartialView("_AuctionTile", auctions);
}
Then, in your view, where you want each to display:
#Html.Action("MostPopular")
#Html.Action("EndingSoon")
Your main view's model should only focus on what it needs to do, not asides like these.
UPDATE
Sorry, I wasn't paying attention to your HTML. Basically, you would need to create a partial for each section, then. For example:
_MostPopular.cshtml
#model IEnumerable<Auction>
<div id="popular" class="col-md-4 col-lg-4">
<h2>Most Popular Items</h2>
#foreach (var item in Model)
{
#Html.Partial("_AuctionTile", item)
}
</div>
Then, change the child action to return this instead:
return PartialView("_MostPopular", auctions);
The key here is that the child action creates a separate context, where you can build a view model specifically for this partial view, without affecting what's going on in the main view.
BONUS
Display templates can help a lot here too. For example, if you could create a view like Views\Shared\DisplayTemplates\Auction.cshtml. Then, any time you call Html.DisplayFor with an instance of Auction, it would automatically use that view to render it. Additionally, Html.DisplayFor can handle enumerables as well. It will simply use the display template for the contained type to render each item in the list. So in your code, instead of iterating over the items in Model and rendering a partial view for each, you could then simply do:
#model IEnumerable<Auction>
<div id="popular" class="col-md-4 col-lg-4">
<h2>Most Popular Items</h2>
#Html.DisplayForModel()
</div>
Firstly you need to declare 2 list they are,
list Popularitem and list endingSoon in view model which you are using(if not create and use its a best practice. because u cannot do in the controller)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace DataViewModel.ViewModels
{
public class DataViewModel
{
public list<Auctions> Popularitem { get; set; }
public list<Auctions> endingSoon { get; set; }
public CustomerViewModel((datatype)db)
{
list<Auctions> Popularitem=db.Auctions.Where(x => x.EndTime > DateTime.Today).OrderByDescending(x => x.viewCount);
list<Auctions> endingSoon =db.Auctions.Where(x => x.EndTime > DateTime.Today).OrderByDescending(x => x.viewCount);
}
}
}
in controller
public ActionResult Index()
{
var db = new AuctionsDataContext();
datamoduleobject= new Dataviewmodel(db);
return View(datamoduleobject);
}
in view :
#MyProject.Web.ViewModels.DataViewModel
<div id="popular" class="col-md-4 col-lg-4">
<h2>Most Popular Items</h2>
#foreach (var item in Model.Popularitem)
{
#Html.Partial("_AuctionTile", item)
}
</div>
<div id="ending" class="col-md-4 col-lg-4">
<h2>Auctions Ending Soon</h2>
#foreach (var item in Model.endingSoon)
{
#Html.Partial("_AuctionTile", item)
}
</div>
and please go through the link i have provided which help u to understand http://tutlane.com/tutorial/aspnet-mvc/how-to-use-viewmodel-in-asp-net-mvc-with-example
In your model declare 2 list and assign the data to lists. In html page use the specific list property in foreach.
Like,
model.Popularitems
Model.Endingitems
Either create view model as suggested above or you can pass extra data in view bag as in controller you can set
ViewBag.endingSoon= yourCollection
and use it in your view
hope It'll help

How can I link a DropDown and Textbox to my Model's data?

Here is the code for my Model. ListBuilder.DropDown is part of a common class of functions, which simply returns a List when provided the string name of a stored procedure that will be called on the database.
There is some more shared common class code (stored procedure related) with in the try statement, but that implementation is irrelevant to the problem I'm having. The data is successfully retrieved and stored into the model.
public class PositionViewModel
{
[Display(Name = "Series")]
public string series { get; set; }
public int seriesID { get; set; }
public List<SelectListItem> list_Series { get; set; }
}
public PositionViewModel(string id)
{
Get(id);
this.list_Series = ListBuilder.DropDown(AppConstants.StoredProc_GetSeries);
}
public Position Get(string id)
{
ExecStoredProcedure sp = null;
DataTable dt = new DataTable();
try
{
sp = new ExecStoredProcedure(AppConstants.SP_POSITION_GET, new ConnectionManager().GetConnection(), AppConstants.SP_POSITION_GET);
sp.SPAddParm("#PD_ID", SqlDbType.Char, id, ParameterDirection.Input);
dt = sp.SPselect();
if (dt.Rows.Count > 0)
{
this.pd_id = dt.Rows[0]["PD_ID"].ToString();
this.official_title = dt.Rows[0]["label"].ToString();
this.series = dt.Rows[0]["Series"].ToString();
this.grade = dt.Rows[0]["Grade"].ToString();
this.PDType = dt.Rows[0]["PDType"].ToString();
}
}
catch (Exception ex)
{
throw ex;
}
finally
{
sp.dbConnection.Close();
}
return this;
}
Here is the code for my Controller
[HttpGet]
public ActionResult PositionEdit(string id)
{
PositionViewModel model = new PositionViewModel(id);
return View("PositionEdit", model);
}
[HttpPost]
public ActionResult PositionEdit(PositionViewModel model)
{
if (ModelState.IsValid)
{
int rc = model.Update();
return RedirectToAction("PositionView");
}
else
{
return View("PositionEdit", model);
}
}
Here is the code for my view. What I'd like to have is a dropdownlist that contains the model.seriesID (a sequence number) but as the user selects an item, it will update the textbox with model.series (the name of the series)
#model Project.Models.PositionViewModel
#{
ViewBag.Title = "Edit Position Description";
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<div class="editor-label">
#Html.LabelFor(model => model.series)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => Model.seriesID, Model.list_Series, new { style = "width:550px" })
#Html.ValidationMessageFor(model => Model.seriesID)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.series, new { style = "width:250px;" })
#Html.ValidationMessageFor(model => model.series)
</div>
<div class="toppad20">
<input type="submit" value="Save" />
</div>
}
I am having trouble linking the dropdownlist with the textbox. Do I need some kind of onChange event? Thanks in advance for your help.
Your solution involves passing a string into your view model's constructor. However, on post, the model binder will be incapable of instantiating your view model with anything but the parameterless constructor. That's part of the reason, but not the only reason, that view models should not handle things like datastore access. That is the job of the controller.
On your view model, leave your list property as a auto-implemented property and then in your controller call ListBuilder.DropDown, which you can use data from your model to call, at that point.

ASP.NET MVC 4 - ListBoxFor, send selectedValue in ActionLink

I have a list of model. I want to retrieve the listBoxSelectedValue to send it in my actionLink to edit it.
This is my view :
#using (Html.BeginForm())
{
#Html.ListBoxFor(a => a.SelectedApplis, new SelectList(ViewBag.Applis,"ID","Name", Model.SelectedApplis))<br/>
#Html.ActionLink("Add","Create","Application")<br/>
#Html.ActionLink("Edit","Edit","Application", null, new { listAppId = Model.SelectedApplis})<br/>
#Html.ActionLink("Delete","Delete","Application")<br/>
}
I created a class "ListBoxApplication" with the List which will contain the selectedValue of the ListBox.
public class ListBoxApplication
{
public IEnumerable<int> SelectedApplis { get; set; }
public ListBoxApplication()
{
SelectedApplis = new List<int>();
}
}
I have 2 controllers : Application and Home
In HomeController, I created the model ListBoxApplication which contain the List. In my ViewBag.Applis, i have all my ApplicationModel.
public ActionResult Index()
{
ListBoxApplication listeApplis = new ListBoxApplication();
ViewBag.Applis = ApplicationModels.GetListApplications();
return View(listeApplis);
}
In my ApplicationController :
public ActionResult Edit(ListBoxApplication listAppId)
{
// I WANT TO RETRIEVE MY listAppId HERE, but it is always 'null'
return View();
}
So I think my problem is in the actionLink :
#Html.ActionLink("Edit","Edit","Application", null, new { listAppId = Model.SelectedApplis})
Me Edit Method is not is the actual controller (Home/Index). I need to send the selectedValue of my ListBox in my actionLink to (Application/Edit).
The listAppId is always 'null'. It doesn't retrieve the value... Is there a mistake in my actionLink ?
Thanks for advance
I don't believe that action links will trigger a postback to the server. Try this instead:
#Html.ActionLink("Delete","Delete","Application")<br/>
#Html.ActionLink("Add","Create","Application")<br/>
#using (Html.BeginForm("Detail","Application"))
{
#Html.ListBoxFor(a => a.SelectedApplis, new SelectList(ViewBag.Applis)) //not sure what the other params you had here were for, but it should work like this
<br/>
<input type="submit" name="Edit" value = "Edit"/>
#*added in response to comment*#
<input type="submit" name="Delete" value = "Delete"/>
<input type="submit" name="Add" value = "Add"/>
}
If you plan on having all of those buttons post back to the server, you could also use ajax (and javascript) to accomplish this same goal, without needing to write out a form for each individual button. Both ways would work just fine, multiple forms is technically easier though.
public ActionResult Detail(ListBoxApplication listAppId, bool Edit, bool Add, bool Delete)
{
if(//check your bools here){
}
return View();
}

DropDownListFor issue with selected values

I'm fairly new (okay well very new) to MVC. I'm trying to create a cascading dropdown list, where I can also preselect a value.
I've spent most of the day on this, but not really got very far. I've looked at a lot of resources, though the most useful were:
http://www.deliveron.com/blog/post/Creating-a-Cascading-Dropdown-in-ASPnet-MVC-3-and-jQuery.aspx - I used this heavily and got my own version of it working, I couldn't get there example working.
http://odetocode.com/blogs/scott/archive/2013/03/11/dropdownlistfor-with-asp-net-mvc.aspx - general oversite.
MVC DropDownList SelectedValue not displaying correctly - There is what appears to be a really good example here, following the view-model method, but for some reason it didn't seem to work for me.
What I've got so far is:
Model:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace BLPrototype.Models
{
public class VehicleModels
{
public List<vehicle_make> getVehicleMakeList()
{
using (var db = new BLEntities())
{
List<vehicle_make> vmk = db.vehicle_make.OrderBy(r => r.vmk_name).ToList();
return vmk;
}
}
public List<vehicle_group> getVehicleModelList(string vmk_id)
{
using (var db = new BLEntities())
{
List<vehicle_group> vmo = db.vehicle_group.Where(v => v.vg_vmk_id == vmk_id).OrderBy(r => r.vg_name).ToList();
return vmo;
}
}
}
public class vmCatalogue1
{
public string Title { get; set; }
[Display(Name = "Select a make")]
public IEnumerable<SelectListItem> selectVehicleMake { get; set; }
[Display(Name = "Select a model")]
public IEnumerable<SelectListItem> selectVehicleModel { get; set; }
}
}
Controller:
using BLPrototype.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace BLPrototype.Controllers
{
public class TestController : Controller
{
//
// GET: /Test/
public ActionResult Index()
{
VehicleModels vm = new VehicleModels();
var model = new vmCatalogue1();
//Which is better and why?
model.selectVehicleMake = vm.getVehicleMakeList().Select(x => new SelectListItem { Value = x.vmk_id, Text = x.vmk_name });
//model.selectVehicleMake = new SelectList(vm.getVehicleMakeList(), "vmk_id", "vmk_name");
model.selectVehicleModel = new SelectList(vm.getVehicleModelList(""), "vg_id", "vg_name");
model.Title = "Title goes here";
return View(model);
}
VehicleModels vm = new VehicleModels();
[HttpPost]
public ActionResult Makes()
{
var makes = vm.getVehicleMakeList();
return Json(new SelectList(makes, "vmk_id", "vmk_name"));
}
[HttpPost]
public ActionResult Models(string vmk_id)
{
var models = vm.getVehicleModelList(vmk_id);
return Json(new SelectList(models, "vg_id", "vg_Name"));
}
}
}
View:
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>#Model.Title </title>
<script src="~/Scripts/jquery-1.7.1.min.js"></script>
<script type="text/javascript">
function getModels(vmk_id) {
$.ajax({
url: "#Url.Action("Models")",
data: { vmk_id: vmk_id },
dataType: "json",
type: "POST",
error: function () {
alert("An error occurred.");
errorHandler("error1");
},
success: function (data) {
var items = "";
$.each(data, function (i, item) {
items += "<option value=\"" + item.Value + "\">" + item.Text + "</option>";
});
$("#selectVehicleModel").html(items);
}
});
}
$(document).ready(function () {
$("#selectVehicleMake").change(function () {
getModels($("#selectVehicleMake").val());
});
});
</script>
</head>
<body>
<div>
#model BLPrototype.Models.vmCatalogue1
#using (Html.BeginForm())
{
<div class="editor-label">
#Html.LabelFor(m => m.selectVehicleMake)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.selectVehicleMake, Model.selectVehicleMake, "Please select")
</div>
<div class="editor-label">
#Html.LabelFor(m => m.selectVehicleModel)
</div>
<div class="editor-field">
#Html.DropDownListFor(m => m.selectVehicleModel, Model.selectVehicleModel, new { style = "width: 150px" })
</div>
}
</body>
</html>
The above example shows the cascading dropdowns. I've seen lots of different ways of populating a dropdownlistfor, I would like to do it via a View-Model. I'm really not sure which is the best way. Some examples seem to show just a list, some a list of selectlistitems and others just a selectlist. Could someone please tell me the difference and what is the 'better' way to do it... I know it often depends upon the application.
I would also like to be able to preselect a Make if I wish, I've seen some examples where you include the ID in various ways, and others by tagging it on to the end of the select list. None of them seem to work.
Also the Model shouldn't really show unless the make has been selected. Would I do this via CSS or another method?
Finally I've put the "Please Select" at the end of the make, so it shows. This doesn't seem like a great way of doing this. Could you please point me in the right direction?
Thank you for your time and I'm sorry for so many stupid noobish questions!
Thanks
Jay
A list of SelectListItems is more explicit and gives you a Selected property but it's more geared at multi-select lists and you no longer have access to the original list if your view requires it elsewhere.
Take a look at SelectList. It might work better for you in some cases. You can obtain one like this:
myList.ToSelectList("ID", "Description")
or
new SelectList(myList, "ID", "Description)
Add:
data-placeholder="Please Select"
in the same object that contains your "style = ..." if you want a placeholder until an option is selected.
I finally found the solution that I was after. I'll post it here in case anyone find this post in the future with similar errors.
Model:
public class VehicleModels
{
public List<vehicle_make> getVehicleMakeList()
{
using (var db = new BLEntities())
{
List<vehicle_make> vmk = db.vehicle_make.OrderBy(r => r.vmk_name).ToList();
return vmk;
}
}
public List<vehicle_group> getVehicleModelList(string vmk_id)
{
using (var db = new BLEntities())
{
List<vehicle_group> vmo = db.vehicle_group.Where(v => v.vg_vmk_id == vmk_id).OrderBy(r => r.vg_name).ToList();
return vmo;
}
}
}
public class vmCatalogue3
{
public string Title { get; set; }
public string selectedMakeId { get; set; }
public string SelectTopLine { get; set; }
[Display(Name = "Select a make")]
public SelectList selectVehicleMake { get; set; }
[Display(Name = "Select a model")]
public SelectList selectVehicleModel { get; set; }
}
Controller:
public ActionResult Index()
{
VehicleModels vm = new VehicleModels();
var model = new vmCatalogue3();
model.selectedMakeId = "870";
model.SelectTopLine = "Please Select";
model.selectVehicleMake = new SelectList(vm.getVehicleMakeList(), "vmk_id", "vmk_name");
model.selectVehicleModel = new SelectList(vm.getVehicleModelList(""), "vg_id", "vg_name");
return View(model);
}
View:
#Html.DropDownListFor(m => m.selectedMakeId, Model.selectVehicleMake, Model.SelectTopLine )
Hope that helps someone!

Model not populated on Post

I have this in a partial view
#using (Html.BeginForm(MVC.Inventory.ActionNames.AddVehicles, MVC.Inventory.Name, new { model = Model.Items }))
{
<div><button>#AuctionControllerResource.AddToBiddingProcess</button></div>
}
The post method is this
[HttpPost]
public virtual ActionResult AddVehicles(List<VehicleViewModel> model)
{
return null;
}
When I put a breakpoint in the view I can see that Model.Items has 1 item in it as it should. However, when I hit the Post action method on button click, there are no items in the model.
I have added this in the form
#Html.HiddenFor(m => m.Items)
but it doesn't help.
What am I doing wrong?
thanks,
Sachin
EDIT
Additional code
public class ListViewModel<T> : IQuery
where T : class
{
public List<T> Items { get; set; }
...
}
The following doesn't do what you think it does:
new { model = Model.Items }
You cannot pass complex objects like that. You will have to generate hidden fields in the form if you want this to work.
I have added this in the form
#Html.HiddenFor(m => m.Items)
No, it's normal that it doesn't help. The hidden field works only with simple types. You wil have to loop through the items in the collection and generate corresponding fields for each property of each element:
#using (Html.BeginForm(MVC.Inventory.ActionNames.AddVehicles, MVC.Inventory.Name))
{
for (var i = 0; i < Model.Items.Count; i++)
{
#Html.HiddenFor(x => x.Items[i].Prop1)
#Html.HiddenFor(x => x.Items[i].Prop2)
#Html.HiddenFor(x => x.Items[i].ComplexProp3.Prop1)
#Html.HiddenFor(x => x.Items[i].ComplexProp3.Prop2)
...
}
<div>
<button>#AuctionControllerResource.AddToBiddingProcess</button>
</div>
}
But this seems quite a waste. Since the user cannot modify those values anyway in the form, I would recommend you simply passing an id which will allow you to retrieve the corresponding items from your data store in the POST action:
#using (Html.BeginForm(MVC.Inventory.ActionNames.AddVehicles, MVC.Inventory.Name, new { id = Model.ItemsId }))
{
<div>
<button>#AuctionControllerResource.AddToBiddingProcess</button>
</div>
}
and then:
[HttpPost]
public virtual ActionResult AddVehicles(int id)
{
List<VehicleViewModel> model = GetItemsFromDataStore(id);
...
}