Skip to content

Commit 5594b84

Browse files
Prerender value of textarea element as text content (#46326)
* prerender value attribute of textarea element as text content Co-authored-by: Steve Sanderson <[email protected]>
1 parent 6051e46 commit 5594b84

File tree

2 files changed

+108
-4
lines changed

2 files changed

+108
-4
lines changed

src/Mvc/Mvc.ViewFeatures/src/RazorComponents/HtmlRenderer.cs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,10 @@ private int RenderElement(
132132
var result = context.HtmlContentBuilder;
133133
result.AppendHtml("<");
134134
result.AppendHtml(frame.ElementName);
135-
var afterAttributes = RenderAttributes(context, frames, position + 1, frame.ElementSubtreeLength - 1, out var capturedValueAttribute);
135+
int afterElement;
136+
var isTextArea = string.Equals(frame.ElementName, "textarea", StringComparison.OrdinalIgnoreCase);
137+
// We don't want to include value attribute of textarea element.
138+
var afterAttributes = RenderAttributes(context, frames, position + 1, frame.ElementSubtreeLength - 1, !isTextArea, out var capturedValueAttribute);
136139

137140
// When we see an <option> as a descendant of a <select>, and the option's "value" attribute matches the
138141
// "value" attribute on the <select>, then we auto-add the "selected" attribute to that option. This is
@@ -145,7 +148,7 @@ private int RenderElement(
145148
}
146149

147150
var remainingElements = frame.ElementSubtreeLength + position - afterAttributes;
148-
if (remainingElements > 0)
151+
if (remainingElements > 0 || isTextArea)
149152
{
150153
result.AppendHtml(">");
151154

@@ -155,7 +158,17 @@ private int RenderElement(
155158
context.ClosestSelectValueAsString = capturedValueAttribute;
156159
}
157160

158-
var afterElement = RenderChildren(context, frames, afterAttributes, remainingElements);
161+
if (isTextArea && !string.IsNullOrEmpty(capturedValueAttribute))
162+
{
163+
// Textarea is a special type of form field where the value is given as text content instead of a 'value' attribute
164+
// So, if we captured a value attribute, use that instead of any child content
165+
result.Append(capturedValueAttribute);
166+
afterElement = position + frame.ElementSubtreeLength; // Skip descendants
167+
}
168+
else
169+
{
170+
afterElement = RenderChildren(context, frames, afterAttributes, remainingElements);
171+
}
159172

160173
if (isSelect)
161174
{
@@ -200,7 +213,7 @@ private int RenderChildren(HtmlRenderingContext context, ArrayRange<RenderTreeFr
200213

201214
private static int RenderAttributes(
202215
HtmlRenderingContext context,
203-
ArrayRange<RenderTreeFrame> frames, int position, int maxElements, out string capturedValueAttribute)
216+
ArrayRange<RenderTreeFrame> frames, int position, int maxElements, bool includeValueAttribute, out string capturedValueAttribute)
204217
{
205218
capturedValueAttribute = null;
206219

@@ -223,6 +236,11 @@ private static int RenderAttributes(
223236
if (frame.AttributeName.Equals("value", StringComparison.OrdinalIgnoreCase))
224237
{
225238
capturedValueAttribute = frame.AttributeValue as string;
239+
240+
if (!includeValueAttribute)
241+
{
242+
continue;
243+
}
226244
}
227245

228246
switch (frame.AttributeValue)

src/Mvc/Mvc.ViewFeatures/test/RazorComponents/HtmlRendererTest.cs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,92 @@ public void RenderComponentAsync_MarksSelectedOptionsAsSelected()
316316
AssertHtmlContentEquals(expectedHtml, result);
317317
}
318318

319+
[Fact]
320+
public void RenderComponentAsync_RendersValueAttributeAsTextContentOfTextareaElement()
321+
{
322+
// Arrange
323+
var expectedHtml = "<textarea rows=\"10\" cols=\"20\">Hello &lt;html&gt;-encoded content!</textarea>";
324+
var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
325+
{
326+
rtb.OpenElement(0, "textarea");
327+
rtb.AddAttribute(1, "value", "Hello <html>-encoded content!");
328+
rtb.AddAttribute(2, "rows", "10");
329+
rtb.AddAttribute(3, "cols", "20");
330+
rtb.CloseElement();
331+
})).BuildServiceProvider();
332+
var htmlRenderer = GetHtmlRenderer(serviceProvider);
333+
334+
// Act
335+
var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(ParameterView.Empty)));
336+
337+
// Assert
338+
AssertHtmlContentEquals(expectedHtml, result);
339+
}
340+
341+
[Fact]
342+
public void RenderComponentAsync_RendersTextareaElementWithoutValueAttribute()
343+
{
344+
// Arrange
345+
var expectedHtml = "<textarea rows=\"10\" cols=\"20\">Hello &lt;html&gt;-encoded content!</textarea>";
346+
var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
347+
{
348+
rtb.OpenElement(0, "textarea");
349+
rtb.AddAttribute(1, "rows", "10");
350+
rtb.AddAttribute(2, "cols", "20");
351+
rtb.AddContent(3, "Hello <html>-encoded content!");
352+
rtb.CloseElement();
353+
})).BuildServiceProvider();
354+
var htmlRenderer = GetHtmlRenderer(serviceProvider);
355+
356+
// Act
357+
var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(ParameterView.Empty)));
358+
359+
// Assert
360+
AssertHtmlContentEquals(expectedHtml, result);
361+
}
362+
363+
[Fact]
364+
public void RenderComponentAsync_RendersTextareaElementWithoutValueAttributeOrTextContent()
365+
{
366+
// Arrange
367+
var expectedHtml = "<textarea rows=\"10\" cols=\"20\"></textarea>";
368+
var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
369+
{
370+
rtb.OpenElement(0, "textarea");
371+
rtb.AddAttribute(1, "rows", "10");
372+
rtb.AddAttribute(2, "cols", "20");
373+
rtb.CloseElement();
374+
})).BuildServiceProvider();
375+
var htmlRenderer = GetHtmlRenderer(serviceProvider);
376+
377+
// Act
378+
var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(ParameterView.Empty)));
379+
380+
// Assert
381+
AssertHtmlContentEquals(expectedHtml, result);
382+
}
383+
384+
[Fact]
385+
public void RenderComponentAsync_ValueAttributeOfTextareaElementOverridesTextContent()
386+
{
387+
// Arrange
388+
var expectedHtml = "<textarea>Hello World!</textarea>";
389+
var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
390+
{
391+
rtb.OpenElement(0, "textarea");
392+
rtb.AddAttribute(1, "value", "Hello World!");
393+
rtb.AddContent(3, "Some content");
394+
rtb.CloseElement();
395+
})).BuildServiceProvider();
396+
var htmlRenderer = GetHtmlRenderer(serviceProvider);
397+
398+
// Act
399+
var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(ParameterView.Empty)));
400+
401+
// Assert
402+
AssertHtmlContentEquals(expectedHtml, result);
403+
}
404+
319405
[Fact]
320406
public void RenderComponentAsync_MarksSelectedOptionsAsSelected_WithOptGroups()
321407
{

0 commit comments

Comments
 (0)