Forked from apuchkov/ExtendedSelectExtensions.cs
Last active
December 20, 2020 22:13
-
-
Save dsidirop/211bce7e800756e1b44ed43fc003637b to your computer and use it in GitHub Desktop.
ASP.NET MVC HTML DropList helper with a way to add custom attributes to options http://stackoverflow.com/a/34096927/1689049
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.Globalization; | |
using System.Linq; | |
using System.Linq.Expressions; | |
using System.Text; | |
using System.Web; | |
using System.Web.Mvc; | |
// to future maintainers derived from https://stackoverflow.com/questions/7536631/adding-html-class-tag-under-option-in-html-dropdownlist/34096927#34096927 | |
// to future maintainers | |
// to future maintainers @Html.ExtendedDropDown() is a nifty little codegem which supports adding css-classes and data-attributes on the rendered <options> | |
// to future maintainers its a smarter alternative to @Html.DropDown() which lacks support for these kinds of things | |
namespace Extensions | |
{ | |
public class ExtendedSelectListItem : SelectListItem | |
{ | |
public object HtmlAttributes { get; set; } | |
} | |
public static class HtmlHelperExtendedDropDown | |
{ | |
internal static object GetModelStateValue(this HtmlHelper htmlHelper, string key, Type destinationType) | |
{ | |
var modelState = (ModelState)null; | |
return !htmlHelper.ViewData.ModelState.TryGetValue(key: key, value: out modelState) | |
? null | |
: modelState.Value?.ConvertTo(type: destinationType, culture: null); | |
} | |
public static MvcHtmlString ExtendedDropDown(this HtmlHelper htmlHelper, string name, IEnumerable<ExtendedSelectListItem> selectList) | |
{ | |
return ExtendedDropDown( | |
name: name, | |
htmlHelper: htmlHelper, | |
selectList: selectList, | |
optionLabel: null, | |
htmlAttributes: null | |
); | |
} | |
public static MvcHtmlString ExtendedDropDown(this HtmlHelper htmlHelper, IEnumerable<ExtendedSelectListItem> selectList, object htmlAttributes) | |
{ | |
return ExtendedDropDown( | |
name: null, | |
htmlHelper: htmlHelper, | |
selectList: selectList, | |
optionLabel: null, | |
htmlAttributes: CloneAnonymousObjectAsHtmlAttributesDict(htmlAttributes) | |
); | |
} | |
public static MvcHtmlString ExtendedDropDown(this HtmlHelper htmlHelper, string name, IEnumerable<ExtendedSelectListItem> selectList, object htmlAttributes) | |
{ | |
return ExtendedDropDown( | |
name: name, | |
htmlHelper: htmlHelper, | |
selectList: selectList, | |
optionLabel: null, | |
htmlAttributes: CloneAnonymousObjectAsHtmlAttributesDict(htmlAttributes) | |
); | |
} | |
public static MvcHtmlString ExtendedDropDown(this HtmlHelper htmlHelper, IEnumerable<ExtendedSelectListItem> selectList, IDictionary<string, object> htmlAttributes) | |
{ | |
return ExtendedDropDown( | |
name: null, | |
htmlHelper: htmlHelper, | |
selectList: selectList, | |
optionLabel: null, | |
htmlAttributes: htmlAttributes | |
); | |
} | |
public static MvcHtmlString ExtendedDropDown(this HtmlHelper htmlHelper, string name, IEnumerable<ExtendedSelectListItem> selectList, IDictionary<string, object> htmlAttributes) | |
{ | |
return ExtendedDropDown( | |
name: name, | |
htmlHelper: htmlHelper, | |
selectList: selectList, | |
optionLabel: null, | |
htmlAttributes: htmlAttributes | |
); | |
} | |
public static MvcHtmlString ExtendedDropDown(this HtmlHelper htmlHelper, string name, IEnumerable<ExtendedSelectListItem> selectList, string optionLabel, IDictionary<string, object> htmlAttributes) | |
{ | |
return ExtendedDropDownHelper( | |
metadata: null, | |
expression: name, | |
htmlHelper: htmlHelper, | |
selectList: selectList, | |
optionLabel: optionLabel, | |
htmlAttributes: htmlAttributes | |
); | |
} | |
public static MvcHtmlString ExtendedDropDownHelper(this HtmlHelper htmlHelper, ModelMetadata metadata, string expression, IEnumerable<ExtendedSelectListItem> selectList, string optionLabel, IDictionary<string, object> htmlAttributes) | |
{ | |
return SelectInternal( | |
name: expression, | |
metadata: metadata, | |
htmlHelper: htmlHelper, | |
selectList: selectList, | |
optionLabel: optionLabel, | |
allowMultiple: false, | |
htmlAttributes: htmlAttributes | |
); | |
} | |
public static MvcHtmlString ExtendedDropDownFor<TModel, TProperty>( | |
this HtmlHelper<TModel> htmlHelper, | |
Expression<Func<TModel, TProperty>> expression, | |
IEnumerable<ExtendedSelectListItem> selectList, | |
object htmlAttributes | |
) | |
{ | |
return ExtendedDropDownFor( | |
selectList: selectList, | |
htmlHelper: htmlHelper, | |
expression: expression, | |
optionLabel: null, | |
htmlAttributes: htmlAttributes | |
); | |
} | |
public static MvcHtmlString ExtendedDropDownFor<TModel, TProperty>( | |
this HtmlHelper<TModel> htmlHelper, | |
Expression<Func<TModel, TProperty>> expression, | |
IEnumerable<ExtendedSelectListItem> selectList, | |
string optionLabel, | |
object htmlAttributes | |
) | |
{ | |
if (expression == null) | |
throw new ArgumentNullException(paramName: nameof(expression)); | |
var metadata = ModelMetadata.FromLambdaExpression(expression: expression, viewData: htmlHelper.ViewData); | |
return SelectInternal( | |
name: ExpressionHelper.GetExpressionText(expression: expression), | |
metadata: metadata, | |
htmlHelper: htmlHelper, | |
selectList: selectList, | |
optionLabel: optionLabel, | |
allowMultiple: false, | |
htmlAttributes: CloneAnonymousObjectAsHtmlAttributesDict(htmlAttributes) | |
); | |
} | |
private static MvcHtmlString SelectInternal( | |
this HtmlHelper htmlHelper, | |
ModelMetadata metadata, | |
string optionLabel, | |
string name, | |
IEnumerable<ExtendedSelectListItem> selectList, | |
bool allowMultiple, | |
IDictionary<string, object> htmlAttributes | |
) | |
{ | |
if (selectList == null) | |
throw new ArgumentException(nameof(selectList)); | |
var fullName = htmlHelper | |
.ViewContext | |
.ViewData | |
.TemplateInfo | |
.GetFullHtmlFieldName(partialFieldName: name); | |
if (string.IsNullOrEmpty(value: fullName)) | |
throw new ArgumentException(message: "No name"); | |
var defaultValue = ( | |
allowMultiple | |
? htmlHelper.GetModelStateValue(key: fullName, destinationType: typeof(string[])) | |
: htmlHelper.GetModelStateValue(key: fullName, destinationType: typeof(string)) | |
) ?? htmlHelper.ViewData.Eval(expression: fullName); | |
// If we havent already used ViewData to get the entire list of items then we need to | |
// use the ViewData-supplied value before using the parameter supplied value | |
if (defaultValue != null) | |
{ | |
var defaultValues = allowMultiple | |
? defaultValue as IEnumerable | |
: new[] {defaultValue}; | |
var values = | |
from object value in defaultValues | |
select Convert.ToString(value: value, provider: CultureInfo.CurrentCulture); | |
var selectedValues = new HashSet<string>(collection: values, comparer: StringComparer.OrdinalIgnoreCase); | |
var newSelectList = new List<ExtendedSelectListItem>(32); | |
foreach (var item in selectList) | |
{ | |
item.Selected = item.Value == null | |
? selectedValues.Contains(item: item.Text) | |
: selectedValues.Contains(item: item.Value); | |
newSelectList.Add(item: item); | |
} | |
selectList = newSelectList; | |
} | |
var listItemBuilder = new StringBuilder(); // Convert each ListItem to an <option> tag | |
if (optionLabel != null) // Make optionLabel the first item that gets rendered | |
{ | |
listItemBuilder.Append( | |
value: ListItemToOption( | |
item: new ExtendedSelectListItem() | |
{ | |
Text = optionLabel, | |
Value = "", | |
Selected = false | |
} | |
) | |
); | |
} | |
foreach (var item in selectList) | |
{ | |
listItemBuilder.Append(value: ListItemToOption(item: item)); | |
} | |
var tagBuilder = new TagBuilder(tagName: "select") | |
{ | |
InnerHtml = listItemBuilder.ToString() | |
}; | |
tagBuilder.MergeAttributes(attributes: htmlAttributes); | |
tagBuilder.MergeAttribute(key: "name", value: fullName, replaceExisting: true); | |
tagBuilder.GenerateId(name: fullName); | |
if (allowMultiple) | |
{ | |
tagBuilder.MergeAttribute(key: "multiple", value: "multiple"); | |
} | |
var modelState = (ModelState) null; // if there are any errors for a named field we add the css attribute | |
if ( | |
htmlHelper.ViewData.ModelState.TryGetValue(key: fullName, value: out modelState) //order | |
&& modelState.Errors.Any() //order | |
) | |
{ | |
tagBuilder.AddCssClass(value: HtmlHelper.ValidationInputCssClassName); | |
} | |
tagBuilder.MergeAttributes(attributes: htmlHelper.GetUnobtrusiveValidationAttributes(name: fullName, metadata: metadata)); | |
return MvcHtmlString.Create(value: tagBuilder.ToString(renderMode: TagRenderMode.Normal)); | |
} | |
internal static string ListItemToOption(ExtendedSelectListItem item) | |
{ | |
var builder = new TagBuilder(tagName: "option") | |
{ | |
InnerHtml = HttpUtility.HtmlEncode(s: item.Text) | |
}; | |
if (item.Value != null) | |
{ | |
builder.Attributes[key: "value"] = item.Value; | |
} | |
if (item.Selected) | |
{ | |
builder.Attributes[key: "selected"] = "selected"; | |
} | |
builder.MergeAttributes( | |
attributes: CloneAnonymousObjectAsHtmlAttributesDict(item.HtmlAttributes) | |
); | |
return builder.ToString(renderMode: TagRenderMode.Normal); | |
} | |
private static IDictionary<string, object> CloneAnonymousObjectAsHtmlAttributesDict(object htmlAttributes) | |
{ | |
var dictionary = (IDictionary)null; | |
var dictionaryStringObject = (IDictionary<string, object>)null; | |
try | |
{ | |
dictionaryStringObject = htmlAttributes as IDictionary<string, object>; | |
dictionary = dictionaryStringObject == null | |
? htmlAttributes as IDictionary | |
: null; | |
} | |
catch | |
{ | |
// ignored | |
} | |
if (dictionaryStringObject == null && dictionary == null) | |
return HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes); | |
var result = (Dictionary<string, object>)null; | |
if (dictionary != null) | |
{ | |
result = new Dictionary<string, object>(dictionary.Count); | |
foreach (var x in dictionary.Keys) | |
{ | |
result[x.ToString()] = dictionary[x]; //vital to clone | |
} | |
} | |
else | |
{ | |
result = dictionaryStringObject.ToDictionary( | |
x => x.Key, | |
x => x.Value | |
); | |
} | |
return result; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment