Add `DropDownList()` and `DropDownListFor()` HTML helpers
- copy over legacy MVC's `SelectExtensions`, `SelectListItem` and `SelectListGroup` - plus expected `SelectList` and `MultiSelectList` - fixup select HTML helpers to meet WebFx standards and work in new world - usual stuff: `[NotNull]`, `var`, `String` -> `string`, long lines, ... - remove `IDictionary<string, object> htmlAttributes` overloads - move longest extension method overloads into correct classes / interfaces - add `ViewDataEvaluator.Eval()` overload for an `object` container - rename lower-level helpers to make purposes more obvious - nit: move Raw() methods up from bottom of HtmlHelper.cs - use `DropDownList[For]()` in MVC sample
This commit is contained in:
parent
271c849923
commit
246bb2e3dd
|
|
@ -14,7 +14,7 @@
|
|||
<div class="col-md-4">
|
||||
<h2>Back to the main Area.</h2>
|
||||
<p>Takes you out of the area implicitly.</p>
|
||||
<p><a class="btn btn-default" href="@Url.Action("Edit", "Home")">Go to Home/Edit</a></p>
|
||||
<p><a class="btn btn-default" href="@Url.Action("Create", "Home")">Go to Home/Create</a></p>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<h2>Go to another action in the Area.</h2>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,16 @@
|
|||
using Microsoft.AspNet.Mvc;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
using Microsoft.AspNet.Mvc.Rendering;
|
||||
using MvcSample.Web.Models;
|
||||
|
||||
namespace MvcSample.Web
|
||||
{
|
||||
public class HomeController : Controller
|
||||
{
|
||||
private static readonly IEnumerable<SelectListItem> _addresses = CreateAddresses();
|
||||
private static readonly IEnumerable<SelectListItem> _ages = CreateAges();
|
||||
|
||||
public IActionResult Index()
|
||||
{
|
||||
return View("MyView", User());
|
||||
|
|
@ -22,17 +28,21 @@ namespace MvcSample.Web
|
|||
/// </summary>
|
||||
public IActionResult Create()
|
||||
{
|
||||
ViewBag.Address = _addresses;
|
||||
ViewBag.Ages = _ages;
|
||||
|
||||
return View();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Action that shows metadata when model is non-<c>null</c>.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IActionResult Edit()
|
||||
public IActionResult Edit(User user)
|
||||
{
|
||||
ViewBag.Address = _addresses;
|
||||
ViewBag.Age = _ages;
|
||||
ViewBag.Gift = "the banana";
|
||||
ViewData.Model = new User { Name = "Name", Address = "Address in a State", Age = 37, };
|
||||
|
||||
return View("Create");
|
||||
}
|
||||
|
||||
|
|
@ -98,5 +108,27 @@ namespace MvcSample.Web
|
|||
{
|
||||
return View(User());
|
||||
}
|
||||
|
||||
private static IEnumerable<SelectListItem> CreateAddresses()
|
||||
{
|
||||
var addresses = new[]
|
||||
{
|
||||
"121 Fake St., Redmond, WA, USA",
|
||||
"123 Fake St., Redmond, WA, USA",
|
||||
"125 Fake St., Redmond, WA, USA",
|
||||
"127 Fake St., Redmond, WA, USA",
|
||||
"129 Fake St., Redmond, WA, USA",
|
||||
"131 Fake St., Redmond, WA, USA",
|
||||
};
|
||||
|
||||
return new SelectList(addresses);
|
||||
}
|
||||
|
||||
private static IEnumerable<SelectListItem> CreateAges()
|
||||
{
|
||||
var ages = Enumerable.Range(27, 47).Select(age => new { Age = age, Display = age.ToString("####"), });
|
||||
|
||||
return new SelectList(ages, dataValueField: "Age", dataTextField: "Display");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -38,6 +38,46 @@
|
|||
}
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div style="float: left; border: thick solid limegreen; padding-right: 10px">
|
||||
@using (Html.BeginForm(controllerName: "Home", actionName: "Edit", method: FormMethod.Post))
|
||||
{
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<label class="control-label col-md-2">Model.Name</label>
|
||||
</td>
|
||||
<td>
|
||||
@Html.TextBox("Name")
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label class="control-label col-md-2">Model.Address</label>
|
||||
</td>
|
||||
<td>
|
||||
@Html.DropDownList("Address", "Select an Address")
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label class="control-label col-md-2">Model.Age</label>
|
||||
</td>
|
||||
<td>
|
||||
@Html.DropDownListFor(model => model.Age, (IEnumerable<SelectListItem>)ViewBag.Ages, htmlAttributes: new { @class = "form-control" })
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<input type="submit" value="Save" class="btn btn-default" style="margin-left: 10px" />
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@helper PropertyListItem(ModelMetadata property)
|
||||
{
|
||||
var propertyName = property.PropertyName;
|
||||
|
|
|
|||
|
|
@ -3,4 +3,4 @@
|
|||
|
||||
<strong>Hello @Model.Name from Partial</strong>
|
||||
|
||||
<a href="@Url.Action("Edit", "Home")">Edit Something!</a>
|
||||
<a href="@Url.Action("Create", "Home")">Create Something!</a>
|
||||
|
|
@ -141,6 +141,7 @@
|
|||
<Compile Include="Rendering\HtmlHelperLinkExtensions.cs" />
|
||||
<Compile Include="Rendering\HtmlHelperNameExtensions.cs" />
|
||||
<Compile Include="Rendering\HtmlHelperPartialExtensions.cs" />
|
||||
<Compile Include="Rendering\HtmlHelperSelectExtensions.cs" />
|
||||
<Compile Include="Rendering\HtmlHelperValidationExtensions.cs" />
|
||||
<Compile Include="Rendering\HtmlHelperValueExtensions.cs" />
|
||||
<Compile Include="Rendering\HtmlString.cs" />
|
||||
|
|
@ -156,7 +157,11 @@
|
|||
<Compile Include="Rendering\IHtmlHelperOfT.cs" />
|
||||
<Compile Include="Rendering\IView.cs" />
|
||||
<Compile Include="Rendering\IViewEngine.cs" />
|
||||
<Compile Include="Rendering\MultiSelectList.cs" />
|
||||
<Compile Include="Rendering\MvcForm.cs" />
|
||||
<Compile Include="Rendering\SelectList.cs" />
|
||||
<Compile Include="Rendering\SelectListGroup.cs" />
|
||||
<Compile Include="Rendering\SelectListItem.cs" />
|
||||
<Compile Include="Rendering\ViewEngineResult.cs" />
|
||||
<Compile Include="RouteConstraintAttribute.cs" />
|
||||
<Compile Include="RouteDataActionConstraint.cs" />
|
||||
|
|
|
|||
|
|
@ -426,6 +426,54 @@ namespace Microsoft.AspNet.Mvc.Core
|
|||
return GetString("HtmlHelper_NotContextualized");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// There is no ViewData item of type '{0}' that has the key '{1}'.
|
||||
/// </summary>
|
||||
internal static string HtmlHelper_MissingSelectData
|
||||
{
|
||||
get { return GetString("HtmlHelper_MissingSelectData"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// There is no ViewData item of type '{0}' that has the key '{1}'.
|
||||
/// </summary>
|
||||
internal static string FormatHtmlHelper_MissingSelectData(object p0, object p1)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("HtmlHelper_MissingSelectData"), p0, p1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The parameter '{0}' must evaluate to an IEnumerable when multiple selection is allowed.
|
||||
/// </summary>
|
||||
internal static string HtmlHelper_SelectExpressionNotEnumerable
|
||||
{
|
||||
get { return GetString("HtmlHelper_SelectExpressionNotEnumerable"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The parameter '{0}' must evaluate to an IEnumerable when multiple selection is allowed.
|
||||
/// </summary>
|
||||
internal static string FormatHtmlHelper_SelectExpressionNotEnumerable(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("HtmlHelper_SelectExpressionNotEnumerable"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ViewData item that has the key '{0}' is of type '{1}' but must be of type '{2}'.
|
||||
/// </summary>
|
||||
internal static string HtmlHelper_WrongSelectDataType
|
||||
{
|
||||
get { return GetString("HtmlHelper_WrongSelectDataType"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ViewData item that has the key '{0}' is of type '{1}' but must be of type '{2}'.
|
||||
/// </summary>
|
||||
internal static string FormatHtmlHelper_WrongSelectDataType(object p0, object p1, object p2)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("HtmlHelper_WrongSelectDataType"), p0, p1, p2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -20,6 +20,12 @@ namespace Microsoft.AspNet.Mvc.Rendering.Expressions
|
|||
return EvalComplexExpression(viewData, expression);
|
||||
}
|
||||
|
||||
public static ViewDataInfo Eval(object indexableObject, [NotNull] string expression)
|
||||
{
|
||||
// Run through same cases as other Eval() overload but allow a null container.
|
||||
return (indexableObject == null) ? null : EvalComplexExpression(indexableObject, expression);
|
||||
}
|
||||
|
||||
private static ViewDataInfo EvalComplexExpression(object indexableObject, string expression)
|
||||
{
|
||||
foreach (var expressionPair in GetRightToLeftExpressions(expression))
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Mvc.Core;
|
||||
|
|
@ -224,6 +225,17 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
additionalViewData);
|
||||
}
|
||||
|
||||
public HtmlString DropDownList(string name, IEnumerable<SelectListItem> selectList, string optionLabel,
|
||||
object htmlAttributes)
|
||||
{
|
||||
return GenerateDropDown(
|
||||
metadata: null,
|
||||
expression: name,
|
||||
selectList: selectList,
|
||||
optionLabel: optionLabel,
|
||||
htmlAttributes: htmlAttributes);
|
||||
}
|
||||
|
||||
public HtmlString Hidden(string name, object value, object htmlAttributes)
|
||||
{
|
||||
return GenerateHidden(metadata: null, name: name, value: value, useViewData: (value == null),
|
||||
|
|
@ -304,6 +316,16 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
htmlAttributes: htmlAttributes);
|
||||
}
|
||||
|
||||
public HtmlString Raw(string value)
|
||||
{
|
||||
return new HtmlString(value);
|
||||
}
|
||||
|
||||
public HtmlString Raw(object value)
|
||||
{
|
||||
return new HtmlString(value == null ? null : value.ToString());
|
||||
}
|
||||
|
||||
public virtual HtmlString ValidationSummary(bool excludePropertyErrors, string message, IDictionary<string, object> htmlAttributes)
|
||||
{
|
||||
var formContext = ViewContext.ClientValidationEnabled ? ViewContext.FormContext : null;
|
||||
|
|
@ -522,6 +544,13 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
htmlAttributes: htmlAttributeDictionary);
|
||||
}
|
||||
|
||||
protected HtmlString GenerateDropDown(ModelMetadata metadata, string expression,
|
||||
IEnumerable<SelectListItem> selectList, string optionLabel, object htmlAttributes)
|
||||
{
|
||||
return GenerateSelect(metadata, optionLabel, expression, selectList, allowMultiple: false,
|
||||
htmlAttributes: htmlAttributes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an opening <form> tag to the response. When the user submits the form,
|
||||
/// the request will be processed by an action method.
|
||||
|
|
@ -713,6 +742,78 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
htmlAttributes: htmlAttributeDictionary);
|
||||
}
|
||||
|
||||
protected virtual HtmlString GenerateSelect(ModelMetadata metadata,
|
||||
string optionLabel, string name, IEnumerable<SelectListItem> selectList, bool allowMultiple,
|
||||
object htmlAttributes)
|
||||
{
|
||||
var fullName = ViewData.TemplateInfo.GetFullHtmlFieldName(name);
|
||||
if (string.IsNullOrEmpty(fullName))
|
||||
{
|
||||
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, "name");
|
||||
}
|
||||
|
||||
var usedViewData = false;
|
||||
|
||||
// If we got a null selectList, try to use ViewData to get the list of items.
|
||||
if (selectList == null)
|
||||
{
|
||||
selectList = GetSelectListItems(name);
|
||||
usedViewData = true;
|
||||
}
|
||||
|
||||
var defaultValue = (allowMultiple) ?
|
||||
GetModelStateValue(fullName, typeof(string[])) :
|
||||
GetModelStateValue(fullName, typeof(string));
|
||||
|
||||
// If we haven't 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 && !string.IsNullOrEmpty(name))
|
||||
{
|
||||
if (!usedViewData)
|
||||
{
|
||||
defaultValue = ViewData.Eval(name);
|
||||
}
|
||||
else if (metadata != null)
|
||||
{
|
||||
defaultValue = metadata.Model;
|
||||
}
|
||||
}
|
||||
|
||||
if (defaultValue != null)
|
||||
{
|
||||
selectList = UpdateSelectListItemsWithDefaultValue(selectList, defaultValue, allowMultiple);
|
||||
}
|
||||
|
||||
// Convert each ListItem to an <option> tag and wrap them with <optgroup> if requested.
|
||||
var listItemBuilder = GenerateGroupsAndOptions(optionLabel, selectList);
|
||||
|
||||
var tagBuilder = new TagBuilder("select")
|
||||
{
|
||||
InnerHtml = listItemBuilder.ToString()
|
||||
};
|
||||
tagBuilder.MergeAttributes(AnonymousObjectToHtmlAttributes(htmlAttributes));
|
||||
tagBuilder.MergeAttribute("name", fullName, true /* replaceExisting */);
|
||||
tagBuilder.GenerateId(fullName, IdAttributeDotReplacement);
|
||||
if (allowMultiple)
|
||||
{
|
||||
tagBuilder.MergeAttribute("multiple", "multiple");
|
||||
}
|
||||
|
||||
// If there are any errors for a named field, we add the css attribute.
|
||||
ModelState modelState;
|
||||
if (ViewData.ModelState.TryGetValue(fullName, out modelState))
|
||||
{
|
||||
if (modelState.Errors.Count > 0)
|
||||
{
|
||||
tagBuilder.AddCssClass(ValidationInputCssClassName);
|
||||
}
|
||||
}
|
||||
|
||||
tagBuilder.MergeAttributes(GetValidationAttributes(name, metadata));
|
||||
|
||||
return tagBuilder.ToHtmlString(TagRenderMode.Normal);
|
||||
}
|
||||
|
||||
protected virtual HtmlString GenerateTextBox(ModelMetadata metadata, string name, object value, string format,
|
||||
IDictionary<string, object> htmlAttributes)
|
||||
{
|
||||
|
|
@ -838,10 +939,9 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
return tagBuilder.ToHtmlString(TagRenderMode.SelfClosing);
|
||||
}
|
||||
|
||||
|
||||
protected virtual HtmlString GenerateValue(string name, object value, string format, bool useViewData)
|
||||
{
|
||||
var fullName = ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
|
||||
var fullName = ViewData.TemplateInfo.GetFullHtmlFieldName(name);
|
||||
var attemptedValue = (string)GetModelStateValue(fullName, typeof(string));
|
||||
|
||||
string resolvedValue;
|
||||
|
|
@ -892,14 +992,145 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
}
|
||||
}
|
||||
|
||||
public HtmlString Raw(string value)
|
||||
private StringBuilder GenerateGroupsAndOptions(string optionLabel, IEnumerable<SelectListItem> selectList)
|
||||
{
|
||||
return new HtmlString(value);
|
||||
var listItemBuilder = new StringBuilder();
|
||||
|
||||
// Make optionLabel the first item that gets rendered.
|
||||
if (optionLabel != null)
|
||||
{
|
||||
listItemBuilder.AppendLine(GenerateOption(new SelectListItem()
|
||||
{
|
||||
Text = optionLabel,
|
||||
Value = string.Empty,
|
||||
Selected = false,
|
||||
}));
|
||||
}
|
||||
|
||||
// Group items in the SelectList if requested.
|
||||
// Treat each item with Group == null as a member of a unique group
|
||||
// so they are added according to the original order.
|
||||
var groupedSelectList = selectList.GroupBy<SelectListItem, int>(
|
||||
item => (item.Group == null) ? item.GetHashCode() : item.Group.GetHashCode());
|
||||
foreach (var group in groupedSelectList)
|
||||
{
|
||||
var optGroup = group.First().Group;
|
||||
|
||||
// Wrap if requested.
|
||||
TagBuilder groupBuilder = null;
|
||||
if (optGroup != null)
|
||||
{
|
||||
groupBuilder = new TagBuilder("optgroup");
|
||||
if (optGroup.Name != null)
|
||||
{
|
||||
groupBuilder.MergeAttribute("label", optGroup.Name);
|
||||
}
|
||||
|
||||
if (optGroup.Disabled)
|
||||
{
|
||||
groupBuilder.MergeAttribute("disabled", "disabled");
|
||||
}
|
||||
|
||||
listItemBuilder.AppendLine(groupBuilder.ToString(TagRenderMode.StartTag));
|
||||
}
|
||||
|
||||
foreach (var item in group)
|
||||
{
|
||||
listItemBuilder.AppendLine(GenerateOption(item));
|
||||
}
|
||||
|
||||
if (optGroup != null)
|
||||
{
|
||||
listItemBuilder.AppendLine(groupBuilder.ToString(TagRenderMode.EndTag));
|
||||
}
|
||||
}
|
||||
|
||||
return listItemBuilder;
|
||||
}
|
||||
|
||||
public HtmlString Raw(object value)
|
||||
private string GenerateOption(SelectListItem item)
|
||||
{
|
||||
return new HtmlString(value == null ? null : value.ToString());
|
||||
var builder = new TagBuilder("option")
|
||||
{
|
||||
InnerHtml = Encode(item.Text)
|
||||
};
|
||||
|
||||
if (item.Value != null)
|
||||
{
|
||||
builder.Attributes["value"] = item.Value;
|
||||
}
|
||||
|
||||
if (item.Selected)
|
||||
{
|
||||
builder.Attributes["selected"] = "selected";
|
||||
}
|
||||
|
||||
if (item.Disabled)
|
||||
{
|
||||
builder.Attributes["disabled"] = "disabled";
|
||||
}
|
||||
|
||||
return builder.ToString(TagRenderMode.Normal);
|
||||
}
|
||||
|
||||
private IEnumerable<SelectListItem> GetSelectListItems(string name)
|
||||
{
|
||||
var value = ViewData.Eval(name);
|
||||
if (value == null)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.FormatHtmlHelper_MissingSelectData(
|
||||
"IEnumerable<SelectListItem>", name));
|
||||
}
|
||||
|
||||
var selectList = value as IEnumerable<SelectListItem>;
|
||||
if (selectList == null)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.FormatHtmlHelper_WrongSelectDataType(
|
||||
name, value.GetType().FullName, "IEnumerable<SelectListItem>"));
|
||||
}
|
||||
|
||||
return selectList;
|
||||
}
|
||||
|
||||
private IEnumerable<SelectListItem> UpdateSelectListItemsWithDefaultValue(
|
||||
IEnumerable<SelectListItem> selectList,
|
||||
object defaultValue,
|
||||
bool allowMultiple)
|
||||
{
|
||||
IEnumerable defaultValues;
|
||||
if (allowMultiple)
|
||||
{
|
||||
defaultValues = defaultValue as IEnumerable;
|
||||
if (defaultValues == null || defaultValues is string)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
Resources.FormatHtmlHelper_SelectExpressionNotEnumerable("expression"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
defaultValues = new[] { defaultValue };
|
||||
}
|
||||
|
||||
var values = from object value in defaultValues
|
||||
select Convert.ToString(value, CultureInfo.CurrentCulture);
|
||||
|
||||
// ToString() by default returns an enum value's name. But selectList may use numeric values.
|
||||
var enumValues = from Enum value in defaultValues.OfType<Enum>()
|
||||
select value.ToString("d");
|
||||
values = values.Concat(enumValues);
|
||||
|
||||
var selectedValues = new HashSet<string>(values, StringComparer.OrdinalIgnoreCase);
|
||||
var newSelectList = new List<SelectListItem>();
|
||||
foreach (SelectListItem item in selectList)
|
||||
{
|
||||
item.Selected = (item.Value != null) ?
|
||||
selectedValues.Contains(item.Value) :
|
||||
selectedValues.Contains(item.Text);
|
||||
newSelectList.Add(item);
|
||||
}
|
||||
|
||||
return newSelectList;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,6 +56,16 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
htmlAttributes: htmlAttributes);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public HtmlString DropDownListFor<TProperty>([NotNull] Expression<Func<TModel, TProperty>> expression,
|
||||
IEnumerable<SelectListItem> selectList, string optionLabel, object htmlAttributes)
|
||||
{
|
||||
var metadata = ExpressionMetadataProvider.FromLambdaExpression(expression, ViewData, MetadataProvider);
|
||||
|
||||
return GenerateDropDown(metadata, ExpressionHelper.GetExpressionText(expression), selectList,
|
||||
optionLabel, htmlAttributes);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public HtmlString DisplayFor<TValue>([NotNull] Expression<Func<TModel, TValue>> expression,
|
||||
string templateName,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Rendering
|
||||
{
|
||||
public static class SelectExtensions
|
||||
{
|
||||
public static HtmlString DropDownList<TModel>([NotNull] this IHtmlHelper<TModel> htmlHelper, string name)
|
||||
{
|
||||
return htmlHelper.DropDownList(name, selectList: null, optionLabel: null, htmlAttributes: null);
|
||||
}
|
||||
|
||||
public static HtmlString DropDownList<TModel>([NotNull] this IHtmlHelper<TModel> htmlHelper, string name,
|
||||
string optionLabel)
|
||||
{
|
||||
return htmlHelper.DropDownList(name, selectList: null, optionLabel: optionLabel, htmlAttributes: null);
|
||||
}
|
||||
|
||||
public static HtmlString DropDownList<TModel>([NotNull] this IHtmlHelper<TModel> htmlHelper, string name,
|
||||
IEnumerable<SelectListItem> selectList)
|
||||
{
|
||||
return htmlHelper.DropDownList(name, selectList, optionLabel: null, htmlAttributes: null);
|
||||
}
|
||||
|
||||
public static HtmlString DropDownList<TModel>([NotNull] this IHtmlHelper<TModel> htmlHelper, string name,
|
||||
IEnumerable<SelectListItem> selectList, object htmlAttributes)
|
||||
{
|
||||
return htmlHelper.DropDownList(name, selectList, optionLabel: null, htmlAttributes: htmlAttributes);
|
||||
}
|
||||
|
||||
public static HtmlString DropDownList<TModel>([NotNull] this IHtmlHelper<TModel> htmlHelper, string name,
|
||||
IEnumerable<SelectListItem> selectList, string optionLabel)
|
||||
{
|
||||
return htmlHelper.DropDownList(name, selectList, optionLabel, htmlAttributes: null);
|
||||
}
|
||||
|
||||
public static HtmlString DropDownListFor<TModel, TProperty>([NotNull] this IHtmlHelper<TModel> htmlHelper,
|
||||
[NotNull] Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList)
|
||||
{
|
||||
return htmlHelper.DropDownListFor(expression, selectList, optionLabel: null, htmlAttributes: null);
|
||||
}
|
||||
|
||||
public static HtmlString DropDownListFor<TModel, TProperty>([NotNull] this IHtmlHelper<TModel> htmlHelper,
|
||||
[NotNull] Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList,
|
||||
object htmlAttributes)
|
||||
{
|
||||
return htmlHelper.DropDownListFor(expression, selectList, optionLabel: null,
|
||||
htmlAttributes: htmlAttributes);
|
||||
}
|
||||
|
||||
public static HtmlString DropDownListFor<TModel, TProperty>([NotNull] this IHtmlHelper<TModel> htmlHelper,
|
||||
[NotNull] Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList,
|
||||
string optionLabel)
|
||||
{
|
||||
return htmlHelper.DropDownListFor(expression, selectList, optionLabel, htmlAttributes: null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -141,7 +141,6 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
/// Returns HTML markup for each property in the object that is represented by the specified expression, using the
|
||||
/// template, an HTML field ID, and additional view data.
|
||||
/// </summary>
|
||||
/// <typeparam name="TDisplayModel">The type of the model.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the value.</typeparam>
|
||||
/// <param name="expression">An expression that identifies the object that contains the properties to display.</param>
|
||||
/// <param name="templateName">The name of the template that is used to render the object.</param>
|
||||
|
|
@ -175,6 +174,43 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
/// <returns>The HTML markup for each property in the model.</returns>
|
||||
HtmlString DisplayForModel(string templateName, string htmlFieldName, object additionalViewData);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a single-selection HTML {select} element using the specified name of the form field,
|
||||
/// list items, option label, and HTML attributes.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the form field to return.</param>
|
||||
/// <param name="selectList">A collection of <see href="SelectListItem"/> objects that are used to populate the
|
||||
/// drop-down list.</param>
|
||||
/// <param name="optionLabel">The text for a default empty item. This parameter can be null.</param>
|
||||
/// <param name="htmlAttributes">An object that contains the HTML attributes to set for the {select} element.
|
||||
/// Alternatively, an <see cref="IDictionary{string, object}"/> instance containing the HTML attributes.
|
||||
/// </param>
|
||||
/// <returns>An HTML {select} element with an {option} subelement for each item in the list.</returns>
|
||||
HtmlString DropDownList(
|
||||
string name,
|
||||
IEnumerable<SelectListItem> selectList,
|
||||
string optionLabel,
|
||||
object htmlAttributes);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a single-selection HTML {select} element for the object that is represented
|
||||
/// by the specified expression using the specified list items, option label, and HTML attributes.
|
||||
/// </summary>
|
||||
/// <typeparam name="TProperty">The type of the value.</typeparam>
|
||||
/// <param name="expression">An expression that identifies the value to display.</param>
|
||||
/// <param name="selectList">A collection of <see href="SelectListItem"/> objects that are used to populate the
|
||||
/// drop-down list.</param>
|
||||
/// <param name="optionLabel">The text for a default empty item. This parameter can be null.</param>
|
||||
/// <param name="htmlAttributes">An object that contains the HTML attributes to set for the {select} element.
|
||||
/// Alternatively, an <see cref="IDictionary{string, object}"/> instance containing the HTML attributes.
|
||||
/// </param>
|
||||
/// <returns>An HTML {select} element with an {option} subelement for each item in the list.</returns>
|
||||
HtmlString DropDownListFor<TProperty>(
|
||||
[NotNull] Expression<Func<TModel, TProperty>> expression,
|
||||
IEnumerable<SelectListItem> selectList,
|
||||
string optionLabel,
|
||||
object htmlAttributes);
|
||||
|
||||
/// <summary>
|
||||
/// Converts the value of the specified object to an HTML-encoded string.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,180 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Mvc.Rendering.Expressions;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Rendering
|
||||
{
|
||||
public class MultiSelectList : IEnumerable<SelectListItem>
|
||||
{
|
||||
private IList<SelectListGroup> _groups;
|
||||
|
||||
public MultiSelectList([NotNull] IEnumerable items)
|
||||
: this(items, selectedValues: null)
|
||||
{
|
||||
}
|
||||
|
||||
public MultiSelectList([NotNull] IEnumerable items, IEnumerable selectedValues)
|
||||
: this(items, dataValueField: null, dataTextField: null, selectedValues: selectedValues)
|
||||
{
|
||||
}
|
||||
|
||||
public MultiSelectList([NotNull] IEnumerable items, string dataValueField, string dataTextField)
|
||||
: this(items, dataValueField, dataTextField, selectedValues: null)
|
||||
{
|
||||
}
|
||||
|
||||
public MultiSelectList(
|
||||
[NotNull] IEnumerable items,
|
||||
string dataValueField,
|
||||
string dataTextField,
|
||||
IEnumerable selectedValues)
|
||||
: this(items, dataValueField, dataTextField, selectedValues, dataGroupField: null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the MultiSelectList class by using the items to include in the list,
|
||||
/// the data value field, the data text field, the selected values, and the data group field.
|
||||
/// </summary>
|
||||
/// <param name="items">The items used to build each <see cref="SelectListItem"/> of the list.</param>
|
||||
/// <param name="dataValueField">The data value field. Used to match the Value property of the corresponding
|
||||
/// <see cref="SelectListItem"/>.</param>
|
||||
/// <param name="dataTextField">The data text field. Used to match the Text property of the corresponding
|
||||
/// <see cref="SelectListItem"/>.</param>
|
||||
/// <param name="selectedValues">The selected values field. Used to match the Selected property of the
|
||||
/// corresponding <see cref="SelectListItem"/>.</param>
|
||||
/// <param name="dataGroupField">The data group field. Used to match the Group property of the corresponding
|
||||
/// <see cref="SelectListItem"/>.</param>
|
||||
public MultiSelectList(
|
||||
[NotNull] IEnumerable items,
|
||||
string dataValueField,
|
||||
string dataTextField,
|
||||
IEnumerable selectedValues,
|
||||
string dataGroupField)
|
||||
{
|
||||
Items = items;
|
||||
DataValueField = dataValueField;
|
||||
DataTextField = dataTextField;
|
||||
SelectedValues = selectedValues;
|
||||
DataGroupField = dataGroupField;
|
||||
|
||||
if (DataGroupField != null)
|
||||
{
|
||||
_groups = new List<SelectListGroup>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the data group field.
|
||||
/// </summary>
|
||||
public string DataGroupField { get; private set; }
|
||||
|
||||
public string DataTextField { get; private set; }
|
||||
|
||||
public string DataValueField { get; private set; }
|
||||
|
||||
public IEnumerable Items { get; private set; }
|
||||
|
||||
public IEnumerable SelectedValues { get; private set; }
|
||||
|
||||
public virtual IEnumerator<SelectListItem> GetEnumerator()
|
||||
{
|
||||
return GetListItems().GetEnumerator();
|
||||
}
|
||||
|
||||
internal IList<SelectListItem> GetListItems()
|
||||
{
|
||||
return (!string.IsNullOrEmpty(DataValueField)) ?
|
||||
GetListItemsWithValueField() :
|
||||
GetListItemsWithoutValueField();
|
||||
}
|
||||
|
||||
private IList<SelectListItem> GetListItemsWithValueField()
|
||||
{
|
||||
var selectedValues = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
if (SelectedValues != null)
|
||||
{
|
||||
selectedValues.UnionWith(from object value in SelectedValues
|
||||
select Convert.ToString(value, CultureInfo.CurrentCulture));
|
||||
}
|
||||
|
||||
var listItems = from object item in Items
|
||||
let value = Eval(item, DataValueField)
|
||||
select new SelectListItem
|
||||
{
|
||||
Group = GetGroup(item),
|
||||
Value = value,
|
||||
Text = Eval(item, DataTextField),
|
||||
Selected = selectedValues.Contains(value)
|
||||
};
|
||||
return listItems.ToList();
|
||||
}
|
||||
|
||||
private IList<SelectListItem> GetListItemsWithoutValueField()
|
||||
{
|
||||
var selectedValues = new HashSet<object>();
|
||||
if (SelectedValues != null)
|
||||
{
|
||||
selectedValues.UnionWith(SelectedValues.Cast<object>());
|
||||
}
|
||||
|
||||
var listItems = from object item in Items
|
||||
select new SelectListItem
|
||||
{
|
||||
Group = GetGroup(item),
|
||||
Text = Eval(item, DataTextField),
|
||||
Selected = selectedValues.Contains(item)
|
||||
};
|
||||
return listItems.ToList();
|
||||
}
|
||||
|
||||
private static string Eval(object container, string expression)
|
||||
{
|
||||
var value = container;
|
||||
if (!string.IsNullOrEmpty(expression))
|
||||
{
|
||||
var viewDataInfo = ViewDataEvaluator.Eval(container, expression);
|
||||
value = viewDataInfo.Value;
|
||||
}
|
||||
|
||||
return Convert.ToString(value, CultureInfo.CurrentCulture);
|
||||
}
|
||||
|
||||
private SelectListGroup GetGroup(object container)
|
||||
{
|
||||
if (_groups == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var groupName = Eval(container, DataGroupField);
|
||||
if (string.IsNullOrEmpty(groupName))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// We use StringComparison.CurrentCulture because the group name is used to display as the value of
|
||||
// optgroup HTML tag's label attribute.
|
||||
var group = _groups.FirstOrDefault(g => string.Equals(g.Name, groupName, StringComparison.CurrentCulture));
|
||||
if (group == null)
|
||||
{
|
||||
group = new SelectListGroup() { Name = groupName };
|
||||
_groups.Add(group);
|
||||
}
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
#region IEnumerable Members
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
using System.Collections;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Rendering
|
||||
{
|
||||
public class SelectList : MultiSelectList
|
||||
{
|
||||
public SelectList([NotNull] IEnumerable items)
|
||||
: this(items, selectedValue: null)
|
||||
{
|
||||
}
|
||||
|
||||
public SelectList([NotNull] IEnumerable items, object selectedValue)
|
||||
: this(items, dataValueField: null, dataTextField: null, selectedValue: selectedValue)
|
||||
{
|
||||
}
|
||||
|
||||
public SelectList([NotNull] IEnumerable items, string dataValueField, string dataTextField)
|
||||
: this(items, dataValueField, dataTextField, selectedValue: null)
|
||||
{
|
||||
}
|
||||
|
||||
public SelectList(
|
||||
[NotNull] IEnumerable items,
|
||||
string dataValueField,
|
||||
string dataTextField,
|
||||
object selectedValue)
|
||||
: base(items, dataValueField, dataTextField, ToEnumerable(selectedValue))
|
||||
{
|
||||
SelectedValue = selectedValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the SelectList class by using the specified items for the list,
|
||||
/// the data value field, the data text field, a selected value, and the data group field.
|
||||
/// </summary>
|
||||
/// <param name="items">The items used to build each <see cref="SelectListItem"/> of the list.</param>
|
||||
/// <param name="dataValueField">The data value field. Used to match the Value property of the corresponding
|
||||
/// <see cref="SelectListItem"/>.</param>
|
||||
/// <param name="dataTextField">The data text field. Used to match the Text property of the corresponding
|
||||
/// <see cref="SelectListItem"/>.</param>
|
||||
/// <param name="selectedValue">The selected values. Used to match the Selected property of the corresponding
|
||||
/// <see cref="SelectListItem"/>.</param>
|
||||
/// <param name="dataGroupField">The data group field. Used to match the Group property of the corresponding
|
||||
/// <see cref="SelectListItem"/>.</param>
|
||||
public SelectList(
|
||||
[NotNull] IEnumerable items,
|
||||
string dataValueField,
|
||||
string dataTextField,
|
||||
object selectedValue,
|
||||
string dataGroupField)
|
||||
: base(items, dataValueField, dataTextField, ToEnumerable(selectedValue), dataGroupField)
|
||||
{
|
||||
SelectedValue = selectedValue;
|
||||
}
|
||||
|
||||
public object SelectedValue { get; private set; }
|
||||
|
||||
private static IEnumerable ToEnumerable(object selectedValue)
|
||||
{
|
||||
return (selectedValue != null) ? new[] { selectedValue } : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
|
||||
namespace Microsoft.AspNet.Mvc.Rendering
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the optgroup HTML element and its attributes.
|
||||
/// In a select list, multiple groups with the same name are supported.
|
||||
/// They are compared with reference equality.
|
||||
/// </summary>
|
||||
public class SelectListGroup
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a value that indicates whether this <see cref="SelectListGroup"/> is disabled.
|
||||
/// </summary>
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Represents the value of the optgroup's label.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
|
||||
namespace Microsoft.AspNet.Mvc.Rendering
|
||||
{
|
||||
public class SelectListItem
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a value that indicates whether this <see cref="SelectListItem"/> is disabled.
|
||||
/// </summary>
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Represents the optgroup HTML element this item is wrapped into.
|
||||
/// In a select list, multiple groups with the same name are supported.
|
||||
/// They are compared with reference equality.
|
||||
/// </summary>
|
||||
public SelectListGroup Group { get; set; }
|
||||
|
||||
public bool Selected { get; set; }
|
||||
|
||||
public string Text { get; set; }
|
||||
|
||||
public string Value { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -195,6 +195,15 @@
|
|||
<data name="HtmlHelper_NotContextualized" xml:space="preserve">
|
||||
<value>Must call 'Contextualize' method before using this HtmlHelper instance.</value>
|
||||
</data>
|
||||
<data name="HtmlHelper_MissingSelectData" xml:space="preserve">
|
||||
<value>There is no ViewData item of type '{0}' that has the key '{1}'.</value>
|
||||
</data>
|
||||
<data name="HtmlHelper_SelectExpressionNotEnumerable" xml:space="preserve">
|
||||
<value>The parameter '{0}' must evaluate to an IEnumerable when multiple selection is allowed.</value>
|
||||
</data>
|
||||
<data name="HtmlHelper_WrongSelectDataType" xml:space="preserve">
|
||||
<value>The ViewData item that has the key '{0}' is of type '{1}' but must be of type '{2}'.</value>
|
||||
</data>
|
||||
<data name="TemplateHelpers_TemplateLimitations" xml:space="preserve">
|
||||
<value>Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions.</value>
|
||||
</data>
|
||||
|
|
|
|||
Loading…
Reference in New Issue