I'm running asp.net 4 mvc and I've created a DropDownList of dates that defaults to the first entry in the list. When I select an entry, I invoke a controller function and do some processing. However, when my page does the PostBack, instead of displaying the list item I selected, it displays the original default list item again.
How do I get my page to display the last item I selected from the list? I've spent two full days searching this site and the Internet for a solution but nothing I try seems to work. Any assistance would be greatly appreciated.
My Html View
#Html.DropDownList("selectList", Model.ReverseMonthsLists(),
new { #onchange = "CallChangefunc(this.value)" })
<script>
function CallChangefunc(val) {
window.location.href = "/dashboard/Report_Performance?id=" + val;
}
</script>
My ViewModel
public SelectList MonthList { get; set; }
public IEnumerable<SelectListItem> ReverseMonthsLists()
{
var selectListItems = GetDates()
.Select(_ => _.ToString("MMM yyyy"))
.Select((dateString, index) => new SelectListItem { Selected = index == 0, Text = dateString, Value = dateString })
.ToList();
return selectListItems;
}
public static IEnumerable<DateTime> GetDates()
{
DateTime startDate = new DateTime(2017, 6, 1).Date;
var currentDate = DateTime.Now.Date;
int numberOfMonthsToShow = (currentDate.Year - startDate.Year) * 12 + currentDate.Month - startDate.Month;
var dates = new List<DateTime>(numberOfMonthsToShow);
currentDate = currentDate.AddMonths(-1);
for (int i = 0; i < numberOfMonthsToShow; i++)
{
dates.Add(currentDate);
currentDate = currentDate.AddMonths(-1);
}
return dates;
}
My Controller
[RequireLogin]
public ActionResult Report_Performance(string id)
{
DateTime newDate = DateTime.Now.Date.AddMonths(-1);
if (id != null)
newDate = DateTime.Parse(id);
var aVar = Models.Reporting.ListingStatsReportingViewModel.GetStats(userCurrentService.CompanyId.Value, Models.Reporting.DateTimePeriod.Monthly, newDate);
return this.View(aVar);
}
You can change your code as follows:
Let's say your model class that is being returned by GetStats method in the Report_Performance action is MyStats which contains a string property named SelectedDateString (you need to add this property to your view model class).
Updated view markup:
#model MyStats
#Html.DropDownList("SelectedDateString", Model.ReverseMonthsLists(),
new { #onchange = "CallChangefunc(this.value)" })
<script>
function CallChangefunc(val) {
window.location.href = "/dashboard/Report_Performance?id=" + val;
}
</script>
Updated controller:
[RequireLogin]
public ActionResult Report_Performance(string id)
{
DateTime newDate = DateTime.Now.Date.AddMonths(-1);
if (id != null)
newDate = DateTime.Parse(id);
var aVar = Models.Reporting.ListingStatsReportingViewModel.GetStats(userCurrentService.CompanyId.Value, Models.Reporting.DateTimePeriod.Monthly, newDate);
//This will make sure that the model returns the correct value of the property as a string.
aVar.SelectedDateString = id;
return this.View(aVar);
}
A Html.DropDownList() works by getting data from a string property in the model which is of the same name as the name of the DropDownList itself.
In your case, you need to set the DropDownList value using javascript or jquery as it's not connected to a model property.
Let me give you an example:
A drop down list in MVC can be created by using either
#Html.DropDownListFor(m => m.PreferredContactMethod, Model.PreferredContactMethods, "")
or
#Html.DropDownList("PreferredContactMethod", Model.PreferredContactMethods, "")
In both cases, PreferredContactMethod is a string property in my model that is connected to the view - which is done by specifying #model PreferredContactModel at the top of the view.
In your case, your list name is selectList and if the specified model is connected to the view and if there's a property in the model that gets the selected date, then you need to change the name of your drop down list to it.
I hope it makes sense, if there's any issue, please comment back. I want to help with this.
The problem is here:
window.location.href = "/dashboard/Report_Performance?id=" + val;
This essential tells the browser to navigate to a new page, which is an HttpGet operation. Thus, there is no correlation between your current settings and those of a new page.
It's as if you had just gone up to the address bar and hit enter. It issues a new page with all new defaults.
There are many ways you can address this problem. The easiest would be to have some javascript that looks at the URL and extracts the id query parameter, then selects the item in the dropdown box that corresponds with the id.
Another option is to set the dropdownlist's selected value based on the ID in your controller.
In controller:
ViewBag.SelectedItem = id;
In View:
#Html.DropDownList("SelectedItem", Model.ReverseMonthsLists(), ...)
Related
I'm still pretty new to .NET, but I think I've read everything there is to read on this subject (including similar questions on SO, which is where I got some of the things I've tried). I feel like I've tried everything possible and I still can't get it to work.
I have a Note class and a Category class. Pretty straightforward, each note has a Category property, so I want to have a dropdown list in my Create view that displays categories. I can get a list to display the category names correctly, but that's it. It keeps telling me there's no IEnumerable in my ViewData called "Categories" when there definitely, 1000% for sure is...
The Create action in my NoteController looks like this:
// GET: Create
public ActionResult Create()
{
SelectList items = (new CategoryService()).GetCategories().Select(c => new SelectListItem
{
Value = c.CategoryId.ToString(),
Text = c.Name
}) as SelectList;
ViewData["Categories"] = items;
return View();
}
And I've tried a few variations in the view:
#Html.DropDownListFor(e=>e.CategoryId , (IEnumerable<SelectListItem>) ViewData["Categories"])
#Html.DropDownList("Categories", "Select a Category")
My Create view uses a NoteCreate model, which has this:
public class NoteCreate {
...
[Display(Name = "Category")]
[Required]
public string CategoryId { get; set; }
And my NoteService has a CreateNote method like so:
public bool CreateNote(NoteCreate model)
{
using (var ctx = new ApplicationDbContext())
{
bool isValid = int.TryParse(model.CategoryId, out int id);
if (!isValid)
{
id = 0;
}
var entity =
new Note()
{
OwnerId = _userId,
Title = model.Title,
Content = model.Content,
CreatedUtc = DateTimeOffset.Now,
Status = model.Status,
CategoryId = id
};
ctx.Notes.Add(entity);
return ctx.SaveChanges() == 1;
}
}
I figured I have to turn the ID into a string for the sake of the dropdown list (because SelectListItem's Value and Text are strings), which is why I parse it back into an int here
I tried attaching the list to the ViewBag instead, and I've tried variations of both DropDownListFor and DropDownList
One of those combinations resulted in a dropdown list actually showing, and I don't remember what it was, but selecting an item resulted in a null being passed to the NoteCreate method (model.CategoryId)
Can anyone help me, and potentially many others who will struggle with this in the future because the documentation is so terrible?
UPDATE:
My controller has been refactored to:
// GET: Create
public ActionResult Create()
{
List<SelectListItem> li = new List<SelectListItem>();
List<Category> Categories = (new CategoryService()).GetCategories().ToList();
var query = from c in Categories
select new SelectListItem()
{
Value = c.CategoryId.ToString(),
Text = c.Name
};
li = query.ToList();
ViewBag.Categories = li;
return View();
}
and my view has been refactored to:
#Html.DropDownList("Categories", ViewBag.Categories as SelectList, new { #class = "form-control" })
This is closer, as I can now load the view and see the Category names in the dropdown. However, when I save, model.CategoryId in my CreateNote method is null, so the CategoryId value isn't actually being passed from the dropdown into the model.
If ViewModel is used in the view then its better to paa the data through model properties to the view. No need to put the collection for Dropdownlist in ViewData or ViewBag.
For the detail way of using Dropdownlist through SelectList and pass to the view through, I would refer an answer I had posted:
MVC C# Dropdown list Showing System.Web.SelectListItem on the model and can not blind to controller
The model passed to your view needs a property for CategoryId.
Your Html Helper is looking for CategoryId here:
#Html.DropDownListFor(e=>e.CategoryId
Ok... I figured it out.
It's so stupid.
The key you use to store the SelectList in your ViewData HAS to be the same as the name of the property on the model, even though you can explicitly tell it to use the list using a different key....
So even if you wanted to use the same SelectList for a few different properties (but process them differently in your service, say), you'd have to pass it to the ViewData redundantly for each property
So instead of passing my SelectList through as ViewBag.Categories, I passed it in as ViewBag.CategoryId, and that worked.
I'm going to go drink a lot of alcohol now.
In Controller
List<SelectListItem> li = new List<SelectListItem>();
var query = from of in your_context.Categories
select new SelectListItem()
{
Value = of.CategoryId.ToString(),
Text = of.Name
};
li = query.ToList();
ViewBag.Category_ = li;
View
<div class="col-md-10">
#Html.DropDownList("Categories", ViewBag.Category_ as List<SelectListItem>, new { #class = "form-control" })
</div>
I already got the default value and the value already became the default, but the default value has no highlight or focus in the dropdown list.
I am using SelectItemList function.
Controller:
private List<SelectListItem> SelectWeek() {
using(var db = new UsersContext()) {
int currentYear = DateTime.Now.Year;
selectListItems = db.WorkWeekDetails
.Where(item = >item.YEAR == currentYear).OrderBy(item = >item.WORKWEEK)
.Select(a = >new SelectListItem() {
Text = a.WORKWEEK,
Value = a.WORKWEEK
}).ToList();
selectListItems.Insert(0, new SelectListItem() {
Text = www, //www = "WW13"
Value = www, //www = "WW13"
Selected = true
});
}
return ViewBag.WorkWeek = selectListItems;
}
CSHTML:
#Html.DropDownList("WorkWeek", (List<SelectListItem>)ViewBag.WorkWeek, new { #class = "form-control", required = "required" })
I want "WW13" to be the default value and that value to be highlighted in the dropdown list.
//Example list of dropdownlist :
WW13 // This value will be the default.
ww01
ww02
ww03
ww04
ww05
ww06
ww07
ww08
ww09
ww10
ww11
ww12
ww13 // This value will be highlighted.
ww14
...
Dropdown list:
[
As #Aarif said you can use jquery to hightlight the selected option... something like this
Also I think you should probably pass your drop down list through a View model rather than a viewbag
window.onload = function() {
highlightSelected()
};
function highLightSelected(){
var model = #Html.Raw(Json.Encode(ViewBag.WorkWeek));
foreach(var item in model){
if(item.selected == true){
$("#mydropdownlist").val(item.value);
$("#mydropdownlist").addClass('active');
}
}
}
The syntax might not be exact but the idea is to check your list of SelectedListItem to find the one that is selected and set the drop list's value to that and also add the class which will highlight the selected value, but remember to remove this class on change of the dropdownlist value
I have dropdownlists populated from my controller and passed in the view through viewbags. The binding seems okay until I submit the form and gets null value. But when I put the codes in the view itself, it works. Am I missing a cast?
a snippet of my dropdownlist for "Year":
#Html.DropDownListFor(model => model.Year, new SelectList(ViewBag.Year, "Value", "Text"), htmlAttributes: new { #class = "form-control"})
Whereas the ViewBag.Year data is from my controller that has the ff codes:\
List<SelectListItem> year = new List<SelectListItem>();
for (var i = 1990; i <= DateTime.Now.Year; i++)
{
year.Add(new SelectListItem
{
Text = i.ToString(),
Value = i.ToString(),
Selected = false
});
}
ViewBag.Year = year;
the error I get:
Consider a case when user submitting the form and validation mechanism fails (returning same view), ViewBag.Year has null value when passed to DropDownListFor (since it's not repopulated after POST request) and throws ArgumentNullException.
To solve this issue, simply repopulate ViewBag.Year when returning the same view in POST method, by replicating how to populate ViewBag.Year in GET method like this example:
[HttpPost]
public ActionResult SubmitForm(FormModel model)
{
if (ModelState.IsValid)
{
// other stuff
}
List<SelectListItem> year = new List<SelectListItem>();
for (var i = 1990; i <= DateTime.Now.Year; i++)
{
year.Add(new SelectListItem
{
Text = i.ToString(),
Value = i.ToString(),
Selected = false
});
}
ViewBag.Year = year; // this line repopulates ViewBag when submit failed
return View(model);
}
Similar issues:
Value cannot be null. Parameter name: items (DrodownList)
Value cannot be null. Parameter name: items (in Dropdown List) ASP.NET MVC5
I know there's a lot of these kind of post but I wasn't able to find any that suited me. I don't have knowledge of ajax and jquery (in fact I've just started with MVC and ASP.NET) so I need your help in this little thing.
There must be almost everywhere this kind of silly thing, I want to write a city name in a combobox, dropdownlist (or whatever) and using a method that I've already created which returns a list of locations (city, country and state names) that match the entered city. I want it to be dinamyc that's why I thought AJAX would solve this (but any other solution is accepted)
I found this jQuery autocomplete but I don't understand where to implement it. I want the combobox to match the bootstrap theme. Could someone tell me if this is an appropiate solution and if so where do I put the ajax content and else? (by where I mean, is it in the view, or controller or where?)
Or you could give mi a hint here is the method I've created for getting the elements from the database:
public List<LocationsViewModel> GetHeadquarter(string query)
{
var context = new HeadquarterContext();
//var city = context.Cities.Where(p => p.Name == query).Single();
//var province = context.Provinces.Where(p => p.ProvinceID == city.Province).ToList();
//foreach(Province prov in province) {
//}
var hq =
from c in context.Cities
join p in context.Provinces on c.Province equals p.ProvinceID
join co in context.Countries on p.Country equals co.CountryID
where c.Name == query
select new { country = co.Name, province = p.Name, city = c.Name };
List<LocationsViewModel> listLocation = new List<LocationsViewModel>();
foreach (var hqe in hq)
{
LocationsViewModel loc = new LocationsViewModel();
loc.City = hqe.city;
loc.Country = hqe.country;
loc.Province = hqe.province;
listLocation.Add(loc);
}
return listLocation;
}
Lets see if we can get it to work.
View:
This is added in your view, #Url.Action(Action, Controller) is the Action that is the source for the autocomplete function.
<input type="search" class="form-control ui-autocomplete"
data-autocomplete="#Url.Action("Autocomplete", "Home")" />
Controller (Home):
As you can see the Action Autocomplete was used to search for a product. I have an instance of my database entity called '_db' and have select a table called 'product_data' (can also use a Stored Procedure). I'm using LINQ to query the datasource and store it in the variable 'model', so it's querying where the 'term' StartsWith what is typed in the textbox, it takes the top 10 and for each one it add label and product. [{"label":value}]
public ActionResult Autocomplete(string term)
{
try
{
var model = _db.product_data // your data here
.Where(p => p.product.StartsWith(term))
.Take(10)
.Select(p => new
{
// jQuery UI needs the label property to function
label = p.product.Trim()
});
// Json returns [{"label":value}]
return Json(model, JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
Settings.ReportException(ex);
return Json("{'ex':'Exception'}");
}
}
JavaScript:
This code is when you select a value from the list that is displayed from your search. The 'window.location.href' redirects to a different controller once a value from the autocomplete has been selected.
// submits form upon selecting a value
var submitAutocompleteForm = function (event, ui) {
var $input = $(this); // the HTML element (Textbox)
// selected value
$input.val(ui.item.label); // ui.item.label = the label value (product)
window.location.href = "/Product/Details?id=" + ui.item.label;
};
The next function sets up the autocomplete API. You declare your options, the above is optional and it comes under select, the source is required and it points to the data-attribute on the HTML element.
var createAutocomplete = function () {
var $input = $(this); // the HTML element (Textbox)
var options = {
// selecting the source by finding elements with the 'data-' attribute
source: $input.attr("data-autocomplete"), // Required
select: submitAutocompleteForm // Optional
};
// apply options
$input.autocomplete(options);
};
// targets input elements with the 'data-' attributes and each time the input changes
// it calls the 'createAutocomplete' function
$("input[data-autocomplete]").each(createAutocomplete);
You'll have to reference the jQueryUI file for autocomplete to work.
I have a model that contains a datetime field.
The column in the DB which it prepresents is of datatype 'date', so it has no time value.
The model date field is bound to a jquery-ui datepicker in my view.
When the page loads, it has time value: 1989/02/14 12:00:00 AM
How can I prevent the time value from being added?
Do I have to manually strip out the time portion with jQuery for every date field?
EDIT:
There is no point in editing the model, when the page loads its still there
Controller:
ClientModel c = DBContext.Clients.find(id);
//Doing any kind of date formatting here to c.DateOfBirth is ignored
return PartialView("_ClientDetailsView", c);
View:
#Html.TextBoxFor(model => model.DateOfBirth , new { #class = "date-field" })
I'm thinking that the solution would be something like a model attribute or a HtmlHelper parameter.
You can use the DataType attribute for this. Decorate your DateOfBirth property in the ClientModel with it:
public class ClientModel
{
[DataType(DataType.Date)]
public DateTime DateOfBirth { get; set; }
}
Also see the DataType enum.
You can also use the DisplayFormat attribute if you the DataType attribute doesn't fit your needs:
[DisplayFormat(DataFormatString = "dd MM, yyyy")]
public DateTime DateOfBirth { get; set; }
Thanks for the help Henk
In the end I grew tired of trying to find a 'proper' solution so I wrote the following jquery function and calling it on each page as required.
function DropTimeSegments() {
$(".datepicker").each(function (index, item) {
$(item).val(FormatDate(new Date($(item).val())));
});
function FormatDate(Date) {
return Date.getFullYear() + '-' + pad(Date.getMonth(), 2) + '-' + pad(Date.getDate(), 2);
}
function pad(num, size) {
var s = num + "";
while (s.length < size) s = "0" + s;
return s;
}