Add `<select/>` tag helper

- #1248
This commit is contained in:
Doug Bunting 2014-10-17 14:49:54 -07:00
parent 557974b948
commit 2fd51c82f9
4 changed files with 160 additions and 7 deletions

View File

@ -107,19 +107,35 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
}
/// <summary>
/// Cannot parse '{1}' value '{2}' for {0}. Acceptable values are '{3}', '{4}' and '{5}'.
/// Cannot determine body for {0}. '{2}' must be null if '{1}' is null.
/// </summary>
internal static string ValidationSummaryTagHelper_InvalidValidationSummaryValue
internal static string SelectTagHelper_CannotDetermineContentWhenOnlyItemsSpecified
{
get { return GetString("ValidationSummaryTagHelper_InvalidValidationSummaryValue"); }
get { return GetString("SelectTagHelper_CannotDetermineContentWhenOnlyItemsSpecified"); }
}
/// <summary>
/// Cannot determine body for {0}. '{2}' must be null if '{1}' is null.
/// </summary>
internal static string FormatSelectTagHelper_CannotDetermineContentWhenOnlyItemsSpecified(object p0, object p1, object p2)
{
return string.Format(CultureInfo.CurrentCulture, GetString("SelectTagHelper_CannotDetermineContentWhenOnlyItemsSpecified"), p0, p1, p2);
}
/// <summary>
/// Cannot parse '{1}' value '{2}' for {0}. Acceptable values are '{3}', '{4}' and '{5}'.
/// </summary>
internal static string FormatValidationSummaryTagHelper_InvalidValidationSummaryValue(object p0, object p1, object p2, object p3, object p4, object p5)
internal static string TagHelpers_InvalidValue_ThreeAcceptableValues
{
return string.Format(CultureInfo.CurrentCulture, GetString("ValidationSummaryTagHelper_InvalidValidationSummaryValue"), p0, p1, p2, p3, p4, p5);
get { return GetString("TagHelpers_InvalidValue_ThreeAcceptableValues"); }
}
/// <summary>
/// Cannot parse '{1}' value '{2}' for {0}. Acceptable values are '{3}', '{4}' and '{5}'.
/// </summary>
internal static string FormatTagHelpers_InvalidValue_ThreeAcceptableValues(object p0, object p1, object p2, object p3, object p4, object p5)
{
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelpers_InvalidValue_ThreeAcceptableValues"), p0, p1, p2, p3, p4, p5);
}
/// <summary>

View File

@ -135,7 +135,10 @@
<data name="FormTagHelper_CannotDetermineAction" xml:space="preserve">
<value>Cannot determine an '{1}' for {0}. A {0} with a URL-based '{1}' must not have attributes starting with '{3}' or a '{2}' attribute.</value>
</data>
<data name="ValidationSummaryTagHelper_InvalidValidationSummaryValue" xml:space="preserve">
<data name="SelectTagHelper_CannotDetermineContentWhenOnlyItemsSpecified" xml:space="preserve">
<value>Cannot determine body for {0}. '{2}' must be null if '{1}' is null.</value>
</data>
<data name="TagHelpers_InvalidValue_ThreeAcceptableValues" xml:space="preserve">
<value>Cannot parse '{1}' value '{2}' for {0}. Acceptable values are '{3}', '{4}' and '{5}'.</value>
</data>
<data name="TagHelpers_NoProvidedMetadata" xml:space="preserve">

View File

@ -0,0 +1,134 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using Microsoft.AspNet.Razor.TagHelpers;
namespace Microsoft.AspNet.Mvc.TagHelpers
{
/// <summary>
/// <see cref="ITagHelper"/> implementation targeting &lt;select&gt; elements.
/// </summary>
[ContentBehavior(ContentBehavior.Append)]
public class SelectTagHelper : TagHelper
{
// Protected to ensure subclasses are correctly activated. Internal for ease of use when testing.
[Activate]
protected internal IHtmlGenerator Generator { get; set; }
// Protected to ensure subclasses are correctly activated. Internal for ease of use when testing.
[Activate]
protected internal ViewContext ViewContext { get; set; }
/// <summary>
/// An expression to be evaluated against the current model.
/// </summary>
public ModelExpression For { get; set; }
/// <summary>
/// Specifies that multiple options can be selected at once.
/// </summary>
/// <remarks>
/// Passed through to the generated HTML if value is <c>multiple</c>. Converted to <c>multiple</c> or absent if
/// value is <c>true</c> or <c>false</c>. Other values are not acceptable. Also used to determine the correct
/// "selected" attributes for generated &lt;option&gt; elements.
/// </remarks>
public string Multiple { get; set; }
/// <summary>
/// A collection of <see cref="SelectListItem"/> objects used to populate the &lt;select&gt; element with
/// &lt;optgroup&gt; and &lt;option&gt; elements.
/// </summary>
public IEnumerable<SelectListItem> Items { get; set; }
/// <inheritdoc />
/// <remarks>Does nothing if <see cref="For"/> is <c>null</c>.</remarks>
public override void Process(TagHelperContext context, TagHelperOutput output)
{
if (For == null)
{
// Regular HTML <select/> element. Just make sure Items wasn't specified.
if (Items != null)
{
var message = Resources.FormatSelectTagHelper_CannotDetermineContentWhenOnlyItemsSpecified(
"<select>",
nameof(For).ToLowerInvariant(),
nameof(Items).ToLowerInvariant());
throw new InvalidOperationException(message);
}
// Pass through attribute that is also a well-known HTML attribute.
if (!string.IsNullOrEmpty(Multiple))
{
output.CopyHtmlAttribute(nameof(Multiple), context);
}
}
else
{
// Note null or empty For.Name is allowed because TemplateInfo.HtmlFieldPrefix may be sufficient.
// IHtmlGenerator will enforce name requirements.
var metadata = For.Metadata;
if (metadata == null)
{
throw new InvalidOperationException(Resources.FormatTagHelpers_NoProvidedMetadata(
"<select>",
nameof(For).ToLowerInvariant(),
nameof(IModelMetadataProvider),
For.Name));
}
bool allowMultiple;
if (string.IsNullOrEmpty(Multiple))
{
// Base allowMultiple on the instance or declared type of the expression.
var realModelType = For.Metadata.RealModelType;
allowMultiple =
typeof(string) != realModelType && typeof(IEnumerable).IsAssignableFrom(realModelType);
}
else if (string.Equals(Multiple, "multiple", StringComparison.OrdinalIgnoreCase))
{
allowMultiple = true;
// Copy exact attribute name and value the user entered. Must be done prior to any copying from a
// TagBuilder. Not done in next case because "true" and "false" aren't valid for the HTML 5
// attribute.
output.CopyHtmlAttribute(nameof(Multiple), context);
}
else if (!bool.TryParse(Multiple.ToLowerInvariant(), out allowMultiple))
{
throw new InvalidOperationException(Resources.FormatTagHelpers_InvalidValue_ThreeAcceptableValues(
"<select>",
nameof(Multiple).ToLowerInvariant(),
Multiple,
bool.FalseString.ToLowerInvariant(),
bool.TrueString.ToLowerInvariant(),
nameof(Multiple).ToLowerInvariant())); // acceptable value (as well as attribute name)
}
// Ensure GenerateSelect() _never_ looks anything up in ViewData.
var items = Items ?? Enumerable.Empty<SelectListItem>();
var tagBuilder = Generator.GenerateSelect(
ViewContext,
For.Metadata,
optionLabel: null,
name: For.Name,
selectList: items,
allowMultiple: allowMultiple,
htmlAttributes: null);
if (tagBuilder != null)
{
output.SelfClosing = false;
output.Merge(tagBuilder);
}
}
}
}
}

View File

@ -42,7 +42,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
if (!Enum.TryParse(ValidationSummaryValue, ignoreCase: true, result: out validationSummaryValue))
{
throw new InvalidOperationException(
Resources.FormatValidationSummaryTagHelper_InvalidValidationSummaryValue(
Resources.FormatTagHelpers_InvalidValue_ThreeAcceptableValues(
"<div>",
"validation-summary",
ValidationSummaryValue,