Im using RadioButtonList to generate my radio buttons, in a MVC Razor project.
Here's an example of my code:
#Html.RadioButtonList(n => n.HouseType)
For some reason my radio button lists gets a preselected value. The first checkbox is always checked, which makes my UI kinda confusing.
How do I disable this in a good way?
One way is to loop through the whole page with Jquery and unselect each box. But thats not a pretty work around imho.
EDIT:
Here's more info about HouseType, which is a custom enum.
public enum HouseType
{
House,
Apartment,
Garage
};
and its called upon by using this line
public HouseType HouseType { get; set; }
You could make the HouseType property a nullable type on your view model. For example if it is an enum type:
public HouseTypes? HouseType { get; set; }
or if it is an integer:
public int? HouseType { get; set; }
UPDATE:
It seems that you are using the following helper. This helper doesn't support nullable enum values. So adapt it:
public static class RaidioButtonListHelper
{
/// <summary>
/// Create a radiobutton list from viewmodel.
/// </summary>
public static MvcHtmlString RadioButtonList<TModel, TResult>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TResult>> expression, IEnumerable<SelectListItem> listOfValues = null)
{
var typeOfProperty = expression.ReturnType;
// Added by Darin Dimitrov to support nullable enums
var underlyingType = Nullable.GetUnderlyingType(typeOfProperty);
if (underlyingType != null)
{
typeOfProperty = underlyingType;
}
// End of addition
if (listOfValues == null && typeOfProperty.IsEnum)
{
listOfValues = new SelectList(Enum.GetValues(typeOfProperty));
}
var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
// Ctreat table
TagBuilder tableTag = new TagBuilder("table");
tableTag.AddCssClass("radio-main");
// Create tr:s
var trTagLable = new TagBuilder("tr id=\"" + metaData.PropertyName + "Lables\"");
var trTagRadio = new TagBuilder("tr id=\"" + metaData.PropertyName + "Radios\"");
foreach (SelectListItem item in listOfValues)
{
var text = item.Text;
var value = item.Value ?? text;
// Generate an id to be given to the radio button field
var id = string.Format("{0}_{1}", metaData.PropertyName, value);
// Create the radiobuttons
var radioTag = htmlHelper.RadioButtonFor(expression, value, new { id = id }).ToHtmlString();
// Create the label for the radiobuttons.
var labelTag = htmlHelper.Label(id, HttpUtility.HtmlEncode(text));
// Add the lables and reaiobuttons to td:s
var tdTagLable = new TagBuilder("td style=\"padding-left: 10px; text-align: center\"");
var tdTagRadio = new TagBuilder("td style=\"padding-left: 10px; text-align: center\"");
tdTagLable.InnerHtml = labelTag.ToString();
tdTagRadio.InnerHtml = radioTag.ToString();
// Add tds: to tr:s
trTagLable.InnerHtml += tdTagLable.ToString();
trTagRadio.InnerHtml += tdTagRadio.ToString();
}
// Add tr:s to table
tableTag.InnerHtml = trTagLable.ToString() + trTagRadio.ToString();
//Return the table tag
return new MvcHtmlString(tableTag.ToString());
}
}
Now it's gonna work with a nullable enum and it won't select any radio button if the value of the corresponding property is null.
Related
I've have this Dropdown that is generated by my Enum
#Html.DropDownList("MyType",
EnumHelper.GetSelectList(typeof(C_Survey.Models.QuestionType)),
"Select My Type",
new { #class = "form-control N_Q_type" })
Enum:
public enum QuestionType {
Single_Choice,
Multiple_Choice,
Range
}
My question is, how can I replace the _ with a space ?
I don't know much details of GetSelectList method there, but I assumed it receives a System.Enum and returning a SelectList collection like this:
public static SelectList GetSelectList(this Enum enumeration)
{
var source = Enum.GetValues(enumeration);
// other stuff
...
return new SelectList(...);
}
There are 2 approaches to solve this issue:
First Approach (Using Custom Attribute)
This approach involves creating a custom attribute to define display name (set attribute target to field or others which fit to entire enum members):
public class DisplayNameAttribute : Attribute
{
public string DisplayName { get; protected set; }
public DisplayNameAttribute(string value)
{
this.DisplayName = value;
}
public string GetName()
{
return this.DisplayName;
}
}
Hence, the enum structure should be modified to this:
public enum QuestionType
{
[DisplayName("Single Choice")]
Single_Choice,
[DisplayName("Multiple Choice")]
Multiple_Choice,
[DisplayName("By Range")]
Range
}
Later, it is necessary to modify GetSelectList method to accept custom attribute created above which includes DisplayName property:
public static SelectList GetSelectList<T>(this T enumeration)
{
var source = Enum.GetValues(typeof(T));
var items = new Dictionary<Object, String>();
var displaytype = typeof(DisplayNameAttribute);
foreach (var value in source)
{
System.Reflection.FieldInfo field = value.GetType().GetField(value.ToString());
DisplayNameAttribute attr = (DisplayNameAttribute)field.GetCustomAttributes(displaytype, false).FirstOrDefault();
items.Add(value, attr != null ? attr.GetName() : value.ToString());
}
return new SelectList(items, "Key", "Value");
}
Second Approach (Using Direct Type Cast & Lambda)
Similar to first approach, GetSelectList method will return SelectList from an enum, however instead of using custom attribute this approach uses member names to build select list items as shown below (T is enum type parameter):
public static SelectList GetSelectList<T>(this T enumeration)
{
var source = Enum.GetValues(typeof(T)).Cast<T>().Select(x => new SelectListItem() {
Text = x.ToString(),
Value = x.ToString().Replace("_", " ")
});
return new SelectList(source);
}
Probably GetSelectList method contents in your side is slightly different, but the basics should be same with those approaches.
Similar issues:
How do I populate a dropdownlist with enum values?
Display enum in ComboBox with spaces
enum with space property for dropdownlist
My model is:
public class DynamicEnum
{
public string Name {get; set;}
public int Value {get; set;}
}
public class ViewModel
{
public DynamicEnum DynamicEnum {get; set;}
}
Public ActionResult MyAction
{
var model = new ViewModel();
model.DynamicEnum = new DynamicEnum(){ Name = "System.DayOfWeek", Value = 2};
return View(model);
}
So in the view I need a HtmlHelper to dynamically generate DropDownListFor like:
#Html.EnumDropDownListFor(m => m.DynamicEnum)
I used MVC 5.2.3, Does any one have any idea?
One way to achieve this would be to use reflection:
public static class HtmlExtensions
{
public static IHtmlString EnumDropDownListFor<TModel>(
this HtmlHelper<TModel> html,
Expression<Func<TModel, DynamicEnum>> expression)
{
var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
var dynamicEnum = (DynamicEnum)metadata.Model;
var enumType = Type.GetType(dynamicEnum.Name, true);
if (!enumType.IsEnum)
{
throw new Exception(dynamicEnum.Name + " doesn't represent a valid enum type");
}
// TODO: You definetely want to cache the values here to avoid the expensive
// reflection call: a ConcurrentDictionary<Type, IList<SelectListItem>> could be used
var enumNames = Enum.GetNames(enumType);
var values = enumNames.Select(x => new SelectListItem
{
Text = x,
Value = ((int)Enum.Parse(enumType, x)).ToString(),
}).ToList();
string name = ExpressionHelper.GetExpressionText(expression) + ".Value";
return html.DropDownList(name, values);
}
}
Remark: The HtmlHelper.EnumDropDownListFor extension method already exists in ASP.NET MVC so make sure that you bring the namespace in which you declared your custom extension method into scope to avoid collisions. Or just use a different method name.
I'm trying to write a helper for my ASP.NET MVC3 website which will be able to return a new SelectList containing all the Description attribute tag of an Enum
For example, with the following enum :
public enum Test
{
[Display(Name = "Membre 1")]
Member1,
[Display(Name = "Membre 2")]
Member2
}
I would like to be able to fill a DropDownListFor with something like :
#Html.DropDownListFor(m => m.MyTest, MyHelper(Test))
(with MyTest is a Test variable).
and I expect my DropDownList contains :
Membre 1
Membre 2
I used to use this working helper :
public static string GetEnumDescription(this Enum value)
{
Type enumType = value.GetType();
var enumValue = Enum.GetName(enumType, value);
MemberInfo member = enumType.GetMember(enumValue)[0];
var attrs = member.GetCustomAttributes(typeof(DisplayAttribute), false);
var outString = ((DisplayAttribute)attrs[0]).Name;
if (((DisplayAttribute)attrs[0]).ResourceType != null)
{
outString = ((DisplayAttribute)attrs[0]).GetName();
}
return outString;
}
... but I can't get it work in a SelectList
How can I modify this to directly "incorporate" it directly in my #Html.DropDownListFor helper ?
I have seen some helper over the Internet, especially here or here, but no one works for me. Does anyone is able to share a short and elegant helper which returns all the Display attributes of the members of an Enum in order to put them in a DropDownListFor ?
The following is what I use. It's a slightly modified version of something I found online at one point. I'd give credit where credit is due, but I don't remember where I found it originally at this point:
public static SelectList ToSelectList(this Enum enumeration)
{
var list = (from Enum d in Enum.GetValues(enumeration.GetType())
select new { Value = Enum.GetName(enumeration.GetType(), d), Text = d.GetDescription() }).ToList();
var selectedValue = (int)Enum.Parse(enumeration.GetType(), Enum.GetName(enumeration.GetType(), enumeration));
return new SelectList(list, "Value", "Text");
}
public static string GetDescription(this Enum en)
{
Type type = en.GetType();
System.Reflection.MemberInfo[] memInfo = type.GetMember(en.ToString());
if (memInfo != null && memInfo.Length > 0)
{
object[] attrs = memInfo[0].GetCustomAttributes(typeof(System.ComponentModel.DataAnnotations.DisplayAttribute), false);
if (attrs != null && attrs.Length > 0)
return ((System.ComponentModel.DataAnnotations.DisplayAttribute)attrs[0]).GetName();
}
return en.ToString();
}
In your view, you'd use it:
#Html.DropDownListFor(m => m.MyEnumProperty, Model.MyEnumProperty.ToSelectList())
For implementing Enum type data, I think the easiest way is to use custom Enum helper and Templates. Below is how I implement them in my project.
1) Create Enum Helper
public static class EnumHelper
{
public static IEnumerable<SelectListItem> GetItems(this Type enumType, int? selectedValue)
{
if (!typeof (Enum).IsAssignableFrom(enumType))
{
throw new ArgumentException("Type must be an enum");
}
string[] names = Enum.GetNames(enumType);
IEnumerable<int> values = Enum.GetValues(enumType).Cast<int>();
IEnumerable<SelectListItem> items = names.Zip(values, (name, value) =>
new SelectListItem
{
Text = GetName(enumType, name),
Value = value.ToString(),
Selected = value == selectedValue
}
);
return items;
}
// Get Display Name
private static string GetName(Type enumType, string name)
{
string result = name;
DisplayAttribute attribute = enumType.GetField(name)
.GetCustomAttributes(false)
.OfType<DisplayAttribute>()
.FirstOrDefault();
if (attribute != null)
{
result = attribute.GetName();
}
return result;
}
public static string GetItemName(this Type enumType, int selectedValue)
{
if (!typeof (Enum).IsAssignableFrom(enumType))
{
throw new ArgumentException("Type must be an enum");
}
var itemName = GetName(enumType, Enum.GetNames(enumType)[selectedValue]);
return itemName;
}
}
2) Create folder call "DisplayTemplates" in Shared folder.
3) Create View inside "DisplayTemmplates". The view will look like below:
#using Demo.Web.Helper
#{
var itemName = typeof(Test).GetItemName((int)Model);
}
4) Create floder call "EditorTemplates" in Shared folder.
5) Create View inside "EditorTemplates". The view will look like below:
#using Demo.Web.Helper
#{
var items = typeof (Test).GetItems((int?)Model);
}
#Html.DropDownList("",items)
Here you have finished all of helper and templates, ready for use. When you want to implement Enum Type data, just use it like below:
Model
public class MyModel
{
public int Id { get; set; }
//
public Test Test { get; set; }
}
View
#Html.DisplayFor(m => m.Test)
or
#Html.EditorFor(m => m.Test)
Hope it helps.
Right ok, I'm offically useless at this (see previous questions)
Why, when I use the following do I get a lovely checkboxlistfor that works all nicely:
(using a extension method)
Model
public class My : BusinessCategory
{
public class MyTypes
{
public int MyTypeId { get; set; }
public string MyTypeName { get; set; }
public bool? MyTypeValue { get; set; }
}
public List<MyTypes> MyTypeListModel { get; set; }
}
View
<fieldset>
<legend>My Management</legend>
<div style="text-align: left; padding-left: 47%;">
#Html.CheckBoxListForListType(model => model.MyTypeListModel, (MultiSelectList)ViewBag.MyType, Model.ReviewId)
</div>
<p>
<input type="submit" value="Continue" />
</p>
</fieldset>
CheckBoxListForListType Extension (awful naming there I know)
public static MvcHtmlString CheckBoxListForListType<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, IEnumerable<TProperty>>> expression, MultiSelectList multiSelectList, Guid reviewId, object htmlAttributes = null)
{
//Derive property name for checkbox name
MemberExpression body = expression.Body as MemberExpression;
string propertyName = body.Member.Name;
//Get currently select values from the ViewData model
IEnumerable<TProperty> list = expression.Compile().Invoke(htmlHelper.ViewData.Model);
//Convert selected value list to a List<string> for easy manipulation
List<string> selectedValues = new List<string>();
if (list != null)
{
selectedValues = new List<TProperty>(list).ConvertAll<string>(delegate(TProperty i) { return i.ToString(); });
}
//Create div
TagBuilder divTag = new TagBuilder("div");
divTag.MergeAttributes(new RouteValueDictionary(htmlAttributes), true);
//Add checkboxes
foreach (SelectListItem item in multiSelectList)
{
divTag.InnerHtml += String.Format("<div><input type=\"checkbox\" name=\"{0}\" id=\"{0}_{1}\" " +
"value=\"{1}\" {2} /><label for=\"{0}_{1}\">{3}</label></div>",
propertyName,
item.Value,
GetValueFromDatabaseList(reviewId, propertyName, item.Text.ToLower()),
item.Text);
}
return MvcHtmlString.Create(divTag.ToString());
}
Controller
public ActionResult My(Guid id)
{
try
{
var model = Model(id);
SetMyTypeList(model.My);
ViewBag.MyType = new MultiSelectList(model.My.MyTypeListModel, "MyTypeValue", "MyTypeName");
return View(model.My);
}
catch (Exception ex)
{
ExceptionHelper.WriteLog(ex);
return RedirectToAction("Error");
}
}
private void SetMyTypeList(My model)
{
model.MyTypeListModel = new List<My.MyTypes>();
model.MyTypeListModel.Add(new My.MyTypes { MyTypeId = 1, MyTypeName = GetName.GetDisplayName(model, m => m.Option1), MyTypeValue = model.Option1});
model.MyTypeListModel.Add(new My.MyTypes { MyTypeId = 2, MyTypeName = GetName.GetDisplayName(model, m => m. Option2), MyTypeValue = model.Option2 });
model.MyTypeListModel.Add(new My.MyTypes { MyTypeId = 3, MyTypeName = GetName.GetDisplayName(model, m => m. Option3), MyTypeValue = model.Option3});
model.MyTypeListModel.Add(new My.MyTypes { MyTypeId = 4, MyTypeName = GetName.GetDisplayName(model, m => m.Option4), MyTypeValue = model.Option4});
}
Yet when I perform a HttpPost action result, MyTypeListModel 'loses' it's properties i.e. I can see that, say two selections have been made but their names, values, id's are not present.
[HttpPost]
public ActionResult My(Guid id, My model)
{
try
{
model.ReviewId = id;
var sessionModel = Model(id);
sessionModel.My = model;
MapCheckBoxListResultswithDatabaseBoolsMy(model);
UpdateDb(Categories.catMy, sessionModel);
return RedirectToAction("BusinessSummary", new { id = sessionModel.ReviewId });
}
catch (Exception ex)
{
ExceptionHelper.WriteLog(ex);
return RedirectToAction("Error");
}
}
Do I have to recast to the type and if so where?
As per usual, many apologies for what appears to be a continuous thread of my ramblings. Hopefully things will start to click soon :'(
The process of wiring up you action parameters is called model binding, and by default MVC interrogates the form, query string and route for the data to try and fill your type. In your case the problem will be that your form fields are not named in the default way that it expects for a list. You can find some information on binding lists in this article by Phil Haack. Keep in mind that you are looking for a subproperty so you need to prefix the type appropriately.
One thing that I have found useful is to get the source code to ASP.NET MVC and then debug that locally to see what it is looking for when model binding.
Don't forget that if you don't want to follow the normal conventions (although that is sensible) you can specify your own custom model binder for your type.
I am trying to create (or even better find) a custom html extension method that works in a similar manor to the ListBoxFor but renders a group of checkboxes instead of a select multiple.
What is the best way of going about this? I am curious how the ListBoxFor method works, when it is only passed an expression and an enumerable of SelectListItems, which only have the default items selected. It is not passed the model, so how does it know when to select the correct items (somehow it seems able to do that)? Also, how does it write html attributes based on an anonymous object?
You haven't provided any details and specific example of what you are trying to achieve. You haven't provided any source code you have tried so far in order to solve the problem you are having. Next time you ask a question on StackOverflow please do so in order to make your question more meaningful and focused on a specific problem.
Anyway, here's an example. Let's suppose that you have defined a view model:
public class MyViewModel
{
public IEnumerable<string> SelectedValues { get; set; }
public IEnumerable<SelectListItem> Values { get; set; }
}
and a controller that will populate this view model with values and hopefully in the POST action obtain the selected values in the view:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new MyViewModel
{
SelectedValues = new[] { "1", "3" },
Values = new[]
{
new SelectListItem { Value = "1", Text = "item 1" },
new SelectListItem { Value = "2", Text = "item 2" },
new SelectListItem { Value = "3", Text = "item 3" },
}
};
return View(model);
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
// model.SelectedValues will contain the ids of items that were checked
// in the checkbox list
}
}
and then a view:
#model MyViewModel
#using (Html.BeginForm())
{
#Html.CheckBoxListFor(x => x.SelectedValues, Model.Values, null)
<button type="submit">OK</button>
}
OK, so far so good. The last part is to try to implement this CheckBoxListFor helper. Obviously depending on your specific requirements and context there could be many possible ways to do this (see my remark in the beginning of the answer). So here's just some sample implementation that could get you started:
public static class HtmlExtnsions
{
public static IHtmlString CheckBoxListFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> selectList,
object htmlAttributes
)
{
var name = ExpressionHelper.GetExpressionText(expression);
string fullHtmlFieldName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
var values = GetModelStateValue(htmlHelper.ViewData, fullHtmlFieldName, typeof(string[]));
if (values == null)
{
values = htmlHelper.ViewData.Eval(fullHtmlFieldName);
}
if (values != null)
{
var collection =
from object value in values as IEnumerable
select Convert.ToString(value, CultureInfo.CurrentCulture);
var hashSet = new HashSet<string>(collection, StringComparer.OrdinalIgnoreCase);
var list = new List<SelectListItem>();
foreach (var item in selectList)
{
item.Selected = ((item.Value != null) ? hashSet.Contains(item.Value) : hashSet.Contains(item.Text));
list.Add(item);
}
selectList = list;
}
var sb = new StringBuilder();
foreach (var item in selectList)
{
var checkbox = new TagBuilder("input");
checkbox.Attributes["type"] = "checkbox";
checkbox.Attributes["name"] = fullHtmlFieldName;
checkbox.Attributes["value"] = item.Value;
checkbox.GenerateId(fullHtmlFieldName);
if (item.Selected)
{
checkbox.Attributes["checked"] = "checked";
}
sb.Append(checkbox.ToString(TagRenderMode.SelfClosing));
sb.Append(item.Value);
}
return new HtmlString(sb.ToString());
}
private static object GetModelStateValue(ViewDataDictionary viewData, string key, Type destinationType)
{
ModelState modelState;
if (viewData.ModelState.TryGetValue(key, out modelState) && modelState.Value != null)
{
return modelState.Value.ConvertTo(destinationType, null);
}
return null;
}
}