Skip to content

Commit

Permalink
Allow the use of the Prompt property of DisplayAttribute for the plac…
Browse files Browse the repository at this point in the history
…eholder attribute of input fields (addresses aspnet#3723)
  • Loading branch information
tuespetre committed Jan 27, 2016
1 parent 8c4bcf1 commit eae6fd1
Show file tree
Hide file tree
Showing 11 changed files with 165 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,11 @@ public string PropertyName
/// <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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,12 +203,12 @@ public override string Description
{
get
{
if (DisplayMetadata.Description == null)
if (DisplayMetadata.Description != null)
{
return null;
return DisplayMetadata.Description();
}

return DisplayMetadata.Description();
return null;
}
}

Expand All @@ -226,12 +226,12 @@ public override string DisplayName
{
get
{
if (DisplayMetadata.DisplayName == null)
if (DisplayMetadata.DisplayName != null)
{
return null;
return DisplayMetadata.DisplayName();
}

return DisplayMetadata.DisplayName();
return null;
}
}

Expand Down Expand Up @@ -432,6 +432,20 @@ public override int Order
}
}

/// <inheritdoc />
public override string Placeholder
{
get
{
if (DisplayMetadata.Placeholder != null)
{
return DisplayMetadata.Placeholder();
}

return null;
}
}

/// <inheritdoc />
public override ModelPropertyCollection Properties
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,12 @@ public class DisplayMetadata
/// </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"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ public void GetDisplayMetadata(DisplayMetadataProviderContext context)
if (displayAttribute != null)
{
displayMetadata.Description = () => displayAttribute.GetDescription();
displayMetadata.Placeholder = () => displayAttribute.GetPrompt();
}

// DisplayFormatString
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ public class DefaultHtmlGenerator : IHtmlGenerator
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;
Expand Down Expand Up @@ -1159,12 +1163,24 @@ protected virtual TagBuilder GenerateInput(
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 (placeholder != null)
{
tagBuilder.MergeAttribute("placeholder", placeholder);
}
}

var valueParameter = FormatValue(value, format);
var usedModelState = false;
switch (inputType)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,14 @@ public override int Order
}
}

public override string Placeholder
{
get
{
throw new NotImplementedException();
}
}

public override ModelPropertyCollection Properties
{
get
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ public void DefaultValues()
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public static TheoryData<object, Func<DisplayMetadata, object>, object> DisplayD
{ 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" },

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,9 @@ public static TheoryData<object, Func<ModelMetadata, string>> ExpectedAttributeD
{
new DisplayAttribute { Name = "value" }, metadata => metadata.DisplayName
},
{
new DisplayAttribute { Prompt = "value" }, metadata => metadata.Placeholder
},
{
new DisplayFormatAttribute { DataFormatString = "value" },
metadata => metadata.DisplayFormatString
Expand Down Expand Up @@ -422,6 +425,22 @@ public void DisplayAttribute_Description()
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()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,21 @@ public void Password_UsesSpecifiedValue()
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())
Expand Down Expand Up @@ -443,6 +458,9 @@ public class PasswordModel
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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// 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
{
private const string InputWithPlaceholderAttribute = @"<input id=""HtmlEncode[[Property1]]"" name=""HtmlEncode[[Property1]]"" placeholder=""HtmlEncode[[placeholder]]"" type=""HtmlEncode[[{0}]]"" value="""" />";
private const string InputWithoutPlaceholderAttribute = @"<input id=""HtmlEncode[[Property1]]"" name=""HtmlEncode[[Property1]]"" type=""HtmlEncode[[{0}]]"" value="""" />";

[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 = helper.TextBoxFor(m => m.Property1, new { type });

// Assert
Assert.Equal(string.Format(InputWithPlaceholderAttribute, type), HtmlContentUtilities.HtmlContentToString(result));
}

[Theory]
[InlineData("hidden")]
[InlineData("date")]
[InlineData("time")]
[InlineData("range")]
[InlineData("color")]
[InlineData("checkbox")]
[InlineData("radio")]
[InlineData("submit")]
[InlineData("reset")]
[InlineData("button")]
// Skipping these two because they won't match the constant string,
// only because their 'value' attribute also does not get set.
[InlineData("file")]
[InlineData("image")]
public void TextBoxFor_DoesNotGeneratePlaceholderAttribute_WhenDisplayAttributePromptIsSetAndTypeIsInvalid(string type)
{
// Arrange
var model = new TextBoxModel();
var helper = DefaultTemplatesUtilities.GetHtmlHelper(model);

// Act
var result = helper.TextBoxFor(m => m.Property1, new { type });

// Assert
Assert.Equal(string.Format(InputWithoutPlaceholderAttribute, type), HtmlContentUtilities.HtmlContentToString(result));
}

private class TextBoxModel
{
[Display(Prompt = "placeholder")]
public string Property1 { get; set; }
}
}
}

0 comments on commit eae6fd1

Please sign in to comment.