Skip to content
This repository has been archived by the owner on Dec 14, 2018. It is now read-only.

Commit

Permalink
[Fixes #4047] SelectTagHelpers: asp-items shouldn't require/depend on…
Browse files Browse the repository at this point in the history
… asp-for.
  • Loading branch information
kichalla committed Feb 19, 2016
1 parent fd3ee49 commit 4123d83
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 5 deletions.
21 changes: 17 additions & 4 deletions src/Microsoft.AspNetCore.Mvc.TagHelpers/SelectTagHelper.cs
Expand Up @@ -15,9 +15,11 @@
namespace Microsoft.AspNetCore.Mvc.TagHelpers
{
/// <summary>
/// <see cref="ITagHelper"/> implementation targeting &lt;select&gt; elements with an <c>asp-for</c> attribute.
/// <see cref="ITagHelper"/> implementation targeting &lt;select&gt; elements with <c>asp-for</c> and/or
/// <c>asp-items</c> attribute(s).
/// </summary>
[HtmlTargetElement("select", Attributes = ForAttributeName)]
[HtmlTargetElement("select", Attributes = ItemsAttributeName)]
public class SelectTagHelper : TagHelper
{
private const string ForAttributeName = "asp-for";
Expand Down Expand Up @@ -70,6 +72,13 @@ public override void Init(TagHelperContext context)
throw new ArgumentNullException(nameof(context));
}

if (For == null)
{
// Informs contained elements that they're running within a targeted <select/> element.
context.Items[typeof(SelectTagHelper)] = null;
return;
}

// Note null or empty For.Name is allowed because TemplateInfo.HtmlFieldPrefix may be sufficient.
// IHtmlGenerator will enforce name requirements.
if (For.Metadata == null)
Expand Down Expand Up @@ -101,9 +110,6 @@ public override void Init(TagHelperContext context)

/// <inheritdoc />
/// <remarks>Does nothing if <see cref="For"/> is <c>null</c>.</remarks>
/// <exception cref="InvalidOperationException">
/// Thrown if <see cref="Items"/> is non-<c>null</c> but <see cref="For"/> is <c>null</c>.
/// </exception>
public override void Process(TagHelperContext context, TagHelperOutput output)
{
if (context == null)
Expand All @@ -119,6 +125,13 @@ public override void Process(TagHelperContext context, TagHelperOutput output)
// Ensure GenerateSelect() _never_ looks anything up in ViewData.
var items = Items ?? Enumerable.Empty<SelectListItem>();

if (For == null)
{
var options = Generator.GenerateGroupsAndOptions(optionLabel: null, selectList: items);
output.PostContent.AppendHtml(options);
return;
}

var tagBuilder = Generator.GenerateSelect(
ViewContext,
For.ModelExplorer,
Expand Down
Expand Up @@ -1457,7 +1457,8 @@ private static string GetInputTypeString(InputType inputType)
return newSelectList;
}

private IHtmlContent GenerateGroupsAndOptions(string optionLabel, IEnumerable<SelectListItem> selectList)
/// <inheritdoc />
public IHtmlContent GenerateGroupsAndOptions(string optionLabel, IEnumerable<SelectListItem> selectList)
{
var listItemBuilder = new HtmlContentBuilder();

Expand Down
Expand Up @@ -307,6 +307,19 @@ public interface IHtmlGenerator
bool allowMultiple,
object htmlAttributes);

/// <summary>
/// Generates &lt;optgroup&gt; and &lt;option&gt; elements.
/// </summary>
/// <param name="optionLabel">Optional text for a default empty &lt;option&gt; element.</param>
/// <param name="selectList">
/// A collection of <see cref="SelectListItem"/> objects used to generate &lt;optgroup&gt; and &lt;option&gt;
/// elements.
/// </param>
/// <returns>
/// An <see cref="IHtmlContent"/> instance for &lt;optgroup&gt; and &lt;option&gt; elements.
/// </returns>
IHtmlContent GenerateGroupsAndOptions(string optionLabel, IEnumerable<SelectListItem> selectList);

TagBuilder GenerateTextArea(
ViewContext viewContext,
ModelExplorer modelExplorer,
Expand Down
Expand Up @@ -356,6 +356,80 @@ public class SelectTagHelperTest
Assert.Equal(savedValue, items.Select(item => item.Value));
}

[Fact]
public async Task ProcessAsync_WithItems_AndNoModelExpression_GeneratesExpectedOutput()
{
// Arrange
var originalAttributes = new TagHelperAttributeList
{
{ "class", "form-control" },
};
var originalPostContent = "original content";

var expectedAttributes = new TagHelperAttributeList(originalAttributes);
var selectItems = new SelectList(Enumerable.Range(0, 5));
var expectedOptions = "<option>HtmlEncode[[0]]</option>" + Environment.NewLine
+ "<option>HtmlEncode[[1]]</option>" + Environment.NewLine
+ "<option>HtmlEncode[[2]]</option>" + Environment.NewLine
+ "<option>HtmlEncode[[3]]</option>" + Environment.NewLine
+ "<option>HtmlEncode[[4]]</option>" + Environment.NewLine;

var expectedPreContent = "original pre-content";
var expectedContent = "original content";
var expectedPostContent = originalPostContent + expectedOptions;
var expectedTagName = "select";

var tagHelperContext = new TagHelperContext(
allAttributes: new TagHelperAttributeList(
Enumerable.Empty<TagHelperAttribute>()),
items: new Dictionary<object, object>(),
uniqueId: "test");
var output = new TagHelperOutput(
expectedTagName,
originalAttributes,
getChildContentAsync: (useCachedResult, encoder) =>
{
var tagHelperContent = new DefaultTagHelperContent();
tagHelperContent.AppendHtml("Something");
return Task.FromResult<TagHelperContent>(tagHelperContent);
})
{
TagMode = TagMode.SelfClosing,
};
output.PreContent.AppendHtml(expectedPreContent);
output.Content.AppendHtml(expectedContent);
output.PostContent.AppendHtml(originalPostContent);

var metadataProvider = new TestModelMetadataProvider();
var htmlGenerator = new TestableHtmlGenerator(metadataProvider);
var viewContext = TestableHtmlGenerator.GetViewContext(
model: null,
htmlGenerator: htmlGenerator,
metadataProvider: metadataProvider);

var tagHelper = new SelectTagHelper(htmlGenerator)
{
Items = selectItems,
ViewContext = viewContext,
};

// Act
tagHelper.Init(tagHelperContext);
await tagHelper.ProcessAsync(tagHelperContext, output);

// Assert
Assert.Equal(TagMode.SelfClosing, output.TagMode);
Assert.Equal(expectedAttributes, output.Attributes);
Assert.Equal(expectedPreContent, output.PreContent.GetContent());
Assert.Equal(expectedContent, output.Content.GetContent());
Assert.Equal(expectedPostContent, HtmlContentUtilities.HtmlContentToString(output.PostContent));
Assert.Equal(expectedTagName, output.TagName);

var kvp = Assert.Single(tagHelperContext.Items);
Assert.Equal(typeof(SelectTagHelper), kvp.Key);
Assert.Null(kvp.Value);
}

[Theory]
[MemberData(nameof(GeneratesExpectedDataSet))]
public async Task ProcessAsyncInTemplate_WithItems_GeneratesExpectedOutput_DoesNotChangeSelectList(
Expand Down

0 comments on commit 4123d83

Please sign in to comment.