From f7cb54121b691adee9a5a2f7fb6a45b2c5b163c2 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Fri, 5 Jan 2018 17:50:03 +0000 Subject: [PATCH] Add DOM rendering capability for attributes --- .../src/Rendering/Renderer.ts | 23 +++++++++++++++++-- .../src/Rendering/UITreeNode.ts | 7 ++++-- .../Tests/ComponentRenderingTest.cs | 13 +++++++++++ .../testapps/BasicTestApp/RedTextComponent.cs | 20 ++++++++++++++++ 4 files changed, 59 insertions(+), 4 deletions(-) create mode 100644 test/testapps/BasicTestApp/RedTextComponent.cs diff --git a/src/Microsoft.Blazor.Browser.JS/src/Rendering/Renderer.ts b/src/Microsoft.Blazor.Browser.JS/src/Rendering/Renderer.ts index 91415fd055..2f8dea2e49 100644 --- a/src/Microsoft.Blazor.Browser.JS/src/Rendering/Renderer.ts +++ b/src/Microsoft.Blazor.Browser.JS/src/Rendering/Renderer.ts @@ -58,6 +58,8 @@ function insertNode(intoDomElement: Element, tree: System_Array, node: UITreeNod case NodeType.text: insertText(intoDomElement, node); break; + case NodeType.attribute: + throw new Error('Attribute nodes should only be present as leading children of element nodes.'); default: const unknownType: never = nodeType; // Compile-time verification that the switch was exhaustive throw new Error(`Unknown node type: ${ unknownType }`); @@ -69,9 +71,26 @@ function insertElement(intoDomElement: Element, tree: System_Array, elementNode: const newDomElement = document.createElement(tagName); intoDomElement.appendChild(newDomElement); - // Recursively insert children + // Apply attributes const descendantsEndIndex = uiTreeNode.descendantsEndIndex(elementNode); - insertNodeRange(newDomElement, tree, elementNodeIndex + 1, descendantsEndIndex); + for (let descendantIndex = elementNodeIndex + 1; descendantIndex <= descendantsEndIndex; descendantIndex++) { + const descendantNode = getTreeNodePtr(tree, descendantIndex); + if (uiTreeNode.nodeType(descendantNode) === NodeType.attribute) { + applyAttribute(newDomElement, descendantNode); + } else { + // As soon as we see a non-attribute child, all the subsequent child nodes are + // not attributes, so bail out and insert the remnants recursively + insertNodeRange(newDomElement, tree, descendantIndex, descendantsEndIndex); + break; + } + } +} + +function applyAttribute(toDomElement: Element, attributeNode: UITreeNodePointer) { + toDomElement.setAttribute( + uiTreeNode.attributeName(attributeNode), + uiTreeNode.attributeValue(attributeNode) + ); } function insertText(intoDomElement: Element, textNode: UITreeNodePointer) { diff --git a/src/Microsoft.Blazor.Browser.JS/src/Rendering/UITreeNode.ts b/src/Microsoft.Blazor.Browser.JS/src/Rendering/UITreeNode.ts index 30ec4503aa..4cdbf46c40 100644 --- a/src/Microsoft.Blazor.Browser.JS/src/Rendering/UITreeNode.ts +++ b/src/Microsoft.Blazor.Browser.JS/src/Rendering/UITreeNode.ts @@ -1,6 +1,6 @@ import { System_String, System_Array, Pointer } from '../Platform/Platform'; import { platform } from '../Environment'; -const uiTreeNodeStructLength = 16; +const uiTreeNodeStructLength = 24; // To minimise GC pressure, instead of instantiating a JS object to represent each tree node, // we work in terms of pointers to the structs on the .NET heap, and use static functions that @@ -16,12 +16,15 @@ export const uiTreeNode = { elementName: (node: UITreeNodePointer) => _readStringProperty(node, 4), descendantsEndIndex: (node: UITreeNodePointer) => _readInt32Property(node, 8) as NodeType, textContent: (node: UITreeNodePointer) => _readStringProperty(node, 12), + attributeName: (node: UITreeNodePointer) => _readStringProperty(node, 16), + attributeValue: (node: UITreeNodePointer) => _readStringProperty(node, 20), }; export enum NodeType { // The values must be kept in sync with the .NET equivalent in UITreeNodeType.cs element = 1, - text = 2 + text = 2, + attribute = 3, } function _readInt32Property(baseAddress: Pointer, offsetBytes: number) { diff --git a/test/Microsoft.Blazor.E2ETest/Tests/ComponentRenderingTest.cs b/test/Microsoft.Blazor.E2ETest/Tests/ComponentRenderingTest.cs index e5db024972..5982691853 100644 --- a/test/Microsoft.Blazor.E2ETest/Tests/ComponentRenderingTest.cs +++ b/test/Microsoft.Blazor.E2ETest/Tests/ComponentRenderingTest.cs @@ -36,6 +36,19 @@ namespace Microsoft.Blazor.E2ETest.Tests Assert.Equal("Hello from TextOnlyComponent", appElement.Text); } + [Fact] + public void CanRenderComponentWithAttributes() + { + Navigate("/", noReload: true); + MountTestComponent("BasicTestApp.RedTextComponent"); + + var appElement = Browser.FindElement(By.TagName("app")); + var styledElement = appElement.FindElement(By.TagName("h1")); + Assert.Equal("Hello, world!", styledElement.Text); + Assert.Equal("color: red;", styledElement.GetAttribute("style")); + Assert.Equal("somevalue", styledElement.GetAttribute("customattribute")); + } + private void MountTestComponent(string componentTypeName) { WaitUntilDotNetRunningInBrowser(); diff --git a/test/testapps/BasicTestApp/RedTextComponent.cs b/test/testapps/BasicTestApp/RedTextComponent.cs new file mode 100644 index 0000000000..7a4a31dfca --- /dev/null +++ b/test/testapps/BasicTestApp/RedTextComponent.cs @@ -0,0 +1,20 @@ +// 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.Blazor.Components; +using Microsoft.Blazor.UITree; + +namespace BasicTestApp +{ + public class RedTextComponent : IComponent + { + public void BuildUITree(UITreeBuilder builder) + { + builder.OpenElement("h1"); + builder.AddAttribute("style", "color: red;"); + builder.AddAttribute("customattribute", "somevalue"); + builder.AddText("Hello, world!"); + builder.CloseElement(); + } + } +}