I am working with a list of string items in mvc that needs to be selected from a drop down list. The drop down list is binding fine, and value's are setting fine, but even though the current item being iterated matches an item in the drop down list it isn't being pre-selected as it's value, can anyone point me in the right direction?
#for (var i = 0; i < Model.StringList.Count; i++)
{
if (BL.Helpers.StringHelpers.Validate(Model.DisplayStringSegments[i]))
{
<div id="editor-field">
#Html.DropDownListFor(m => m.StringList[i], Model.PosterOptions, String.Empty, new { })
</div>
}
else
{
<div id="editor-label">#Model.StringList[i]</div>
#Html.HiddenFor(model => model.StringList[i])
}
}
So for this case, the Options is a list of strings holding only one value, "Test" -> set both as Text and Value;
PosterOptions.Add(new SelectListItem() { Text = "Test", Value = "Test" });
Can anyone tell me why the current StringList[i] isn't being pre selected, even though it has the value of "Test" ?
For anyone that comes across this;
I had to "Hack" a solution, I did this by:
Changing my ViewModel's (Model.Options)
List<SelectListItem> to a List<string>
Changing my drop down list selection to the following, forcing the selected value;
<div id="editor-field">
#{
string currentString = Model.StringList.ElementAt(i).ToString();
}
#Html.DropDownListFor(m => m.StringList[i], new SelectList(Model.Options, currentString), String.Empty, new {})
</div>
Perhaps there is a better way, but this works!
Another way could be setting the current selected item during the list creation, like this:
PosterOptions.Add(new SelectListItem() { Text = "Test", Value = "Test", Selected = true });
I had the same issue and your response helped me. I don't think that's a "hack" though. Because in your question you were using the same SelectList for all the dropdownlists so even though you mention you didn't want to create multiple lists for the drop downs I can't see another way when you have multiple drop downs as you need to specify different selected values.
As a small refactoring you can get rid of the temp variable and access the selected value directly like this:
#Html.DropDownListFor(m => m.StringList[i], new SelectList(Model.Options, Model.StringList[i]), String.Empty, new {})
In your example you don't need to distinguish between text and value but in my case it was required. When that's necessary it can be accomplished by providing the value and text field names for the SelectList. For example, say you need multiple dropdowns with Country values such as:
Country class:
public class Country
{
public string Code { get; set; }
public string Name { get; set; }
}
Model:
public List<string> CustomerCountryList { get; set; }
public IEnumerable<Country> CountryList { get; set; }
and the View:
#Html.DropDownListFor(m => m.CustomerCountryList[i], new SelectList(Model.CountryList, "Code", "Name", Model.CustomerCountryList[i]))
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 have a hard coded select list, pulling Value and Text from the db.
I have a specific item I want to set as the "default", so I order the list to specifically put it first in line ("Wip"). However, when the list is rendered in HTML, the order of the list is correct, but the selected value is the item with the smallest value, even though it is at the bottom of the list due to ordering descending.
Model:
public IEnumerable<SelectListItem> BinOptionsList { get; set; }
Data Access:
public IEnumerable<SelectListItem> PopulateBinList()
{
using (var db = new InventoryContext())
{
var data = db.StorageBin.OrderByDescending(s => s.BinCode == "Wip").ThenBy(s => s.BinCode).Select(s => new SelectListItem()
{
Text = s.BinCode,
Value = s.BinId.ToString()
}).ToList();
return data;
}
}
Controller:
model.BinOptionsList = dal.PopulateBinList()
View:
#Html.DropDownListFor(model => model.BinId, Model.BinOptionsList, new { #class = "form-control" })
The list renders in the View with "Wip" as the first selection, but the rendered "selected=selected" option is the item with id=0.
I know I can set the 'Selected' value in the Data Access method, but then I cant use this in an edit form that would already have a selected value.
Update - Rendered HTML:
<select class="form-control" data-val="true" data-val-number="The field Bin Code must be a number." data-val-required="The Bin Code field is required." id="BinId" name="BinId">
<option value="582">WIP</option>
<option value="595">0888</option>
<option selected="selected" value="0">0919</option>
<option value="1">1</option>
<option value="2">1A1</option>
<option value="3">1A10</option>
<option value="4">1A11</option>
</select>
I'm pretty sure BindId is equal to 0. If BindId is a simple Integer then it will always be equal to 0 by default. You'll need to set it to 582 yourself or set it as Integer? which would set the default as null.
So you may not prefer this answer but let me outline one quick thing. Your select list should not be of your model. The value selected from your select this should be part of your model. You are putting too much overhead into your model and it is having in impact on your view. Refactor it. Put your select list in your ViewBag for you to use in your view, stop passing it back and forth. This will keep your model compact and allow you to set your options properly.
Here is a simple example.
What would be your Data Access (just stubbing it out for example sake and yes setting it to static for example simplicity):
public static IEnumerable<SelectListItem> PopulateBinList()
{
return new List<SelectListItem>{ new SelectListItem {
Text = "WIP", Value = "582"
}, new SelectListItem {
Text = "0888", Value = "595"
} , new SelectListItem {
Text = "0", Value = "0919"
}, new SelectListItem {
Text = "1", Value = "1"
}};
}
Then your model (just adding the one property for example)
public class BinModel
{
public string BinId { get; set; }
}
In your controller
public ActionResult Index()
{
//load up your select list into the view bag
ViewBag.BinOptions = ViewHelper.PopulateBinList();
//preset the value of your desired object
var bm = new BinModel { BinId = "582" };
return View(bm);
}
The in your view you are going to bind to model property but use your temp list defined in the view bag to fill the options. Presetting your model's property will auto select which value is defaulted in the select list:
#Html.DropDownListFor(x => x.BinId, (IEnumerable<SelectListItem>)ViewBag.BinOptions, null, new { #class ="form-control" })
Please let me know if this does not make sense or needs to be expanded on.
I have created a DropDownList which is initialized from code behind like that :
Code Behind :
List<SelectListItem> ddlIsActivated = new List<SelectListItem>
{
new SelectListItem
{
Text = "Activated",
Value = "0"
},
new SelectListItem
{
Text = "Not activated",
Value = "1"
}
};
ViewBag.ddlIsActivated = ddlIsActivated;
View :
<div class="row">
<div class="form-group col-xs-3">
#Html.DropDownList("IsActivated", ViewBag.ddlIsActivated as List<SelectListItem>, "Default")
</div>
</div>
When I click directly on the search button after the load, the DropDownList has "Default" as the first item and my URL looks like that :
http://localhost:51817/Log?SearchString=&IsActivated=
Is it possible to specify that all parameters with an empty value might not be passed on the URL ?
In case of the DropDownList, is it possible to avoid the param "IsActivated" when the "Default" is Selected ?
Sure one way is when the submit button is clicked or activated, you set the fields that are the default value to disabled. then they don't go in the post.
Example of using jQuery onsubmit:
How to use jQuery to onsubmit and check if the value is 2 - 5 characters?
You can try having a separate viewmodel, with properties for the list of options and the selected option. In the GET request action method, you can populate this list and render the view, and in the POST action method, you can exclude this property during model binding, so that only the selected item property will get bound. Thus, you don't need ViewBag or routeValues or querystring parameters, and your URL will always look clean.
EDIT:
This is how you might do it.
public class ViewModel
{
public int SelectedId { get; set; }
public Dictionary<int,string> OptionList { get; set; }
}
And then in your view,
#Html.DropDownListFor(model => model.SelectedId, new SelectList(Model.OptionList, "Key", "Value"), null, null)
In your GET action method,
Dictionary<int,string> OptionList = GetOptionList(); // Populate from DB
return View(new ViewModel { OptionList = OptionList });
Also, remember to [Bind(Exclude="OptionList")] in your POST action method.
I have tried various ways to get this to work. I think maybe I have something missing in my model or controller, so I'm posting all three parts.
I have data in a database that shows some advising topics chosen by an advisor in a previous appointment. When the advisor calls up that appointment, the app should display a list of all possible topics with the ones previously chosen highlighted. Everything works except that last bit.
I know I'm retrieving the right information because I can display the selected items separately. I just can't get them to show up selected. Here's the code. I'm cutting out irrelevant parts.
public class AppointmentModel
{ ...
public string AdvisingTopicId { get; set; }
public List<SelectListItem> AdvisingIdList { get; set; }
public SelectList AdvisingTopicNames { get; set; }
}
public class HomeController : AdvisorBaseController
{ ...
var topicCodes = appointment.advising_topic.ToList();
var advisingTopics = new SelectList((from t in topicCodes
select t.name).ToList(), "name");
var topicsList = (from t in db.advising_topic
select new SelectListItem
{
Selected = false,
Text = t.name,
Value = SqlFunctions.StringConvert((double)t.advising_topic_id).Trim()
}).ToList();
foreach (var topicCode in topicCodes)
{
var selTopic = topicsList.Find(x => x.Value == topicCode.advising_topic_id.ToString());
if (selTopic != null)
{
selTopic.Selected = true;
}
} ...
var appointmentModel = new AppointmentModel
{ ...
AdvisingTopicNames = advisingTopics,
AdvisingIdList = topicsList,
};
and then the view
#model AcademicAdvising.Models.AppointmentModel
<h3>Advising Topics</h3>
<ul>
#foreach (var item in Model.AdvisingTopicNames)
{
<li>#Html.DisplayFor(x => item)</li>
}
</ul>
#Html.ListBoxFor(m=>m.AdvisingIdList, new SelectList(Model.AdvisingTopicNames, "Value", "Text", Model.AdvisingTopicNames.SelectedValue))
Note that the foreach correctly displays the selected items. That's just for testing and will be pulled out. the ListBoxFor is where I'm struggling. What I have here doesn't work (shows the full list with nothing highlighted). And that's the bit where I have tried various approaches, with all fails.
It looks like you may have accidentely went a level too deep. You already have a select list which is all the listboxfor function wants.
#Html.ListBoxFor(m=>m.AdvisingIdList, Model.AdvisingTopicNames)
But honestly looking at how you are defining your lists I think what you really want might be
#Html.ListBoxFor(m=>m.AdvisingIdList, Model.AdvisingIdList)
I have an MVC3 application with Razor and I created a View that inside renders a Partial View. This is how the main View looks like:
#{Html.RenderPartial("_SearchFilters", Model.SearchFilters);}
#* Other HTML elements *#
Inside the _SearchFilters Partial View I have the following DropDownLists inside a Form element:
Choose Year
#Html.DropDownListFor(m => m.Year, new SelectList(Model.YearsList, "Value", "Text"), DateTime.Now.Year)
Choose Month
#Html.DropDownListFor(m => m.Month, new SelectList(Model.MonthsList, "Value", "Text"), Model.Month.ToString(), new { #disabled = "disabled" })
<input type="submit" value="Display" />
I would like that upon Submit the two DropDownLists keep their status, namely the value selected by the user, when the View is reloaded with the filtered data.
Is there any way to do it without using AJAX?
UPDATE
The ViewModel is as follows:
public class TableSearchFiltersViewModel
{
public bool YTM { get; set; }
public int? Month { get; set; }
public int? Year { get; set; }
public IEnumerable<SelectListItem> YearsList
{
get
{
return Enumerable.Range(2011, (DateTime.Now.Year - 2011 + 4)).Select(m => new SelectListItem
{
Value = m.ToString(),
Text = m.ToString(),
}).OrderBy(m => m.Value);
}
}
public IEnumerable<SelectListItem> MonthsList
{
get
{
return Enumerable.Empty<SelectListItem>();
}
}
}
Thanks
Francesco
When you submit the form to the corresponding controller action, this action should take as input parameter some view model. This view model's properties will be bound from the input fields contained in the form including the selected value of the two dropdowns. Then the controller action could return the same view which will preserve the selected values of the dropdown boxes.
I would recommend you to use Editor Templates though instead of rendering partials as this will ensure proper naming of the dropdowns and eventually preserve selected values:
#Html.EditorFor(x => x.SearchFilters)
I don't have IDE at this time so couldn't test but this might work:
Choose Month
EDIT:
#Html.DropDownListFor(m => m.Month,
Model.MonthsList.Select(
t => new SelectListItem {
Text = t.Name,
Value = t.Value,
Selected = t.Value == Model.Month,
},
Model.Month.ToString(), new { #disabled = "disabled" })
Without ajax not, or you will have to repost the whole form. MVC is a web framework which is not dynamic like a winforms application. You will have to post the changes to your controller and reload the page with the necessary changes, or use ajax to reload these changes.
You could provide the default values for Year and Month properties (to be selected at the first request) and bind those instead of the hardcoded startup values you provided.
So instead of:
#Html.DropDownListFor(m => m.Year, new SelectList(Model.YearsList, "Value", "Text"), DateTime.Now.Year)
Which btw seems erroneous, as selected value (which I suppose DateTime.Now.Year is in your example) should be provided as SelectList's constructor (instead of DropDownListFor method's) argument. DropDownListFor method doesn't have a 'selected value' argument.
You could write:
#Html.DropDownListFor(m => m.Year, new SelectList(Model.YearsList, "Value", "Text", Model.Year))
and analogously in the second dropdown.
This will make dropdowns keeps the selected values when rendered using the posted model (as Model.Year and Model.Month would hold those). So you should make sure those values won't get overwritten with default ones after subsequent submits.