Allow the use of the Prompt property of DisplayAttribute for the placeholder attribute of input fields (addresses #3723)

This commit is contained in:
Derek Gray 2016-01-26 15:45:53 -06:00 committed by Ryan Nowak
parent 78e7179221
commit 8b726ddc0c
11 changed files with 154 additions and 1 deletions

View File

@ -258,6 +258,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
/// <value>The order value of the current metadata.</value>
public abstract int Order { get; }
/// <summary>
/// Gets the text to display as a placeholder value for an editor.
/// </summary>
public abstract string Placeholder { get; }
/// <summary>
/// Gets the text to display when the model is <c>null</c>.
/// </summary>

View File

@ -432,6 +432,20 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
}
}
/// <inheritdoc />
public override string Placeholder
{
get
{
if (DisplayMetadata.Placeholder == null)
{
return null;
}
return DisplayMetadata.Placeholder();
}
}
/// <inheritdoc />
public override ModelPropertyCollection Properties
{

View File

@ -115,6 +115,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
/// </summary>
public int Order { get; set; } = 10000;
/// <summary>
/// Gets or sets a delegate which is used to get a value for the
/// model's placeholder text. See <see cref="ModelMetadata.Placeholder"/>.
/// </summary>
public Func<string> Placeholder { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not to include in the model value in display.
/// See <see cref="ModelMetadata.ShowForDisplay"/>

View File

@ -84,6 +84,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
if (displayAttribute != null)
{
displayMetadata.Description = () => displayAttribute.GetDescription();
displayMetadata.Placeholder = () => displayAttribute.GetPrompt();
}
// DisplayFormatString

View File

@ -26,6 +26,10 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
private static readonly MethodInfo ConvertEnumFromStringMethod =
typeof(DefaultHtmlGenerator).GetTypeInfo().GetDeclaredMethod(nameof(ConvertEnumFromString));
// See: (http://www.w3.org/TR/html5/forms.html#the-input-element)
private static readonly string[] _placeholderInputTypes =
new[] { "text", "search", "url", "tel", "email", "password", "number" };
private readonly IAntiforgery _antiforgery;
private readonly IClientModelValidatorProvider _clientModelValidatorProvider;
private readonly IModelMetadataProvider _metadataProvider;
@ -1134,12 +1138,24 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
nameof(expression));
}
var inputTypeString = GetInputTypeString(inputType);
var tagBuilder = new TagBuilder("input");
tagBuilder.TagRenderMode = TagRenderMode.SelfClosing;
tagBuilder.MergeAttributes(htmlAttributes);
tagBuilder.MergeAttribute("type", GetInputTypeString(inputType));
tagBuilder.MergeAttribute("type", inputTypeString);
tagBuilder.MergeAttribute("name", fullName, replaceExisting: true);
var suppliedTypeString = tagBuilder.Attributes["type"];
if (_placeholderInputTypes.Contains(suppliedTypeString))
{
modelExplorer = modelExplorer ?? ExpressionMetadataProvider.FromStringExpression(expression, viewContext.ViewData, _metadataProvider);
var placeholder = modelExplorer.Metadata.Placeholder;
if (!string.IsNullOrEmpty(placeholder))
{
tagBuilder.MergeAttribute("placeholder", placeholder);
}
}
var valueParameter = FormatValue(value, format);
var usedModelState = false;
switch (inputType)

View File

@ -520,6 +520,14 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
}
}
public override string Placeholder
{
get
{
throw new NotImplementedException();
}
}
public override ModelPropertyCollection Properties
{
get

View File

@ -62,6 +62,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
Assert.Null(metadata.NullDisplayText);
Assert.Null(metadata.TemplateHint);
Assert.Null(metadata.SimpleDisplayProperty);
Assert.Null(metadata.Placeholder);
Assert.Equal(10000, ModelMetadata.DefaultOrder);
Assert.Equal(ModelMetadata.DefaultOrder, metadata.Order);

View File

@ -26,6 +26,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
{ new DisplayAttribute() { Description = "d" }, d => d.Description(), "d" },
{ new DisplayAttribute() { Name = "DN" }, d => d.DisplayName(), "DN" },
{ new DisplayAttribute() { Order = 3 }, d => d.Order, 3 },
{ new DisplayAttribute() { Prompt = "Enter Value" }, d => d.Placeholder(), "Enter Value" },
{ new DisplayColumnAttribute("Property"), d => d.SimpleDisplayProperty, "Property" },

View File

@ -194,6 +194,9 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
{
new DisplayAttribute { Name = "value" }, metadata => metadata.DisplayName
},
{
new DisplayAttribute { Prompt = "value" }, metadata => metadata.Placeholder
},
{
new DisplayFormatAttribute { DataFormatString = "value" },
metadata => metadata.DisplayFormatString
@ -422,6 +425,22 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
Assert.Equal("description", result);
}
[Fact]
public void DisplayAttribute_PromptAsPlaceholder()
{
// Arrange
var display = new DisplayAttribute() { Prompt = "prompt" };
var provider = CreateProvider(new[] { display });
var metadata = provider.GetMetadataForType(typeof(string));
// Act
var result = metadata.Placeholder;
// Assert
Assert.Equal("prompt", result);
}
[Fact]
public void DisplayName_FromResources_GetsRecomputed()
{

View File

@ -399,6 +399,21 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
HtmlContentUtilities.HtmlContentToString(passwordResult));
}
[Fact]
public void PasswordFor_GeneratesPlaceholderAttribute_WhenDisplayAttributePromptIsSet()
{
// Arrange
var expected = @"<input id=""HtmlEncode[[Property7]]"" name=""HtmlEncode[[Property7]]"" placeholder=""HtmlEncode[[placeholder]]"" type=""HtmlEncode[[password]]"" />";
var model = new PasswordModel();
var helper = DefaultTemplatesUtilities.GetHtmlHelper(model);
// Act
var result = helper.PasswordFor(m => m.Property7, htmlAttributes: null);
// Assert
Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(result));
}
private static ViewDataDictionary<PasswordModel> GetViewDataWithNullModelAndNonEmptyViewData()
{
return new ViewDataDictionary<PasswordModel>(new EmptyModelMetadataProvider())
@ -443,6 +458,9 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
public Dictionary<string, string> Property3 { get; } = new Dictionary<string, string>();
public NestedClass Property4 { get; } = new NestedClass();
[Display(Prompt = "placeholder")]
public string Property7 { get; set; }
}
public class NestedClass

View File

@ -0,0 +1,64 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Mvc.TestCommon;
using System.ComponentModel.DataAnnotations;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Rendering
{
public class HtmlHelperTextBoxTest
{
[Theory]
[InlineData("text")]
[InlineData("search")]
[InlineData("url")]
[InlineData("tel")]
[InlineData("email")]
[InlineData("number")]
public void TextBoxFor_GeneratesPlaceholderAttribute_WhenDisplayAttributePromptIsSetAndTypeIsValid(string type)
{
// Arrange
var model = new TextBoxModel();
var helper = DefaultTemplatesUtilities.GetHtmlHelper(model);
// Act
var result = HtmlContentUtilities.HtmlContentToString(helper.TextBoxFor(m => m.Property1, new { type }));
// Assert
Assert.True(result.Contains(@"placeholder=""HtmlEncode[[placeholder]]"""));
}
[Theory]
[InlineData("hidden")]
[InlineData("date")]
[InlineData("time")]
[InlineData("range")]
[InlineData("color")]
[InlineData("checkbox")]
[InlineData("radio")]
[InlineData("submit")]
[InlineData("reset")]
[InlineData("button")]
[InlineData("image")]
[InlineData("file")]
public void TextBoxFor_DoesNotGeneratePlaceholderAttribute_WhenDisplayAttributePromptIsSetAndTypeIsInvalid(string type)
{
// Arrange
var model = new TextBoxModel();
var helper = DefaultTemplatesUtilities.GetHtmlHelper(model);
// Act
var result = HtmlContentUtilities.HtmlContentToString(helper.TextBoxFor(m => m.Property1, new { type }));
// Assert
Assert.False(result.Contains(@"placeholder=""HtmlEncode[[placeholder]]"""));
}
private class TextBoxModel
{
[Display(Prompt = "placeholder")]
public string Property1 { get; set; }
}
}
}