diff --git a/samples/HostedInAspNet.Client/Program.cs b/samples/HostedInAspNet.Client/Program.cs
index c972eccd7f..2274c518cb 100644
--- a/samples/HostedInAspNet.Client/Program.cs
+++ b/samples/HostedInAspNet.Client/Program.cs
@@ -13,13 +13,13 @@ namespace HostedInAspNet.Client
{
// Temporarily render this test component until there's a proper mechanism
// for testing this.
- Renderer.Render(new MyComponent(), "app");
+ DOM.AttachComponent("app", new MyComponent());
}
}
internal class MyComponent : IComponent
{
- public void Render(UITreeBuilder builder)
+ public void BuildUITree(UITreeBuilder builder)
{
builder.OpenElement("h1");
builder.AddText("Hello from UITree");
diff --git a/src/Microsoft.Blazor.Browser.JS/src/Rendering/Renderer.ts b/src/Microsoft.Blazor.Browser.JS/src/Rendering/Renderer.ts
index 81d62cde0b..91415fd055 100644
--- a/src/Microsoft.Blazor.Browser.JS/src/Rendering/Renderer.ts
+++ b/src/Microsoft.Blazor.Browser.JS/src/Rendering/Renderer.ts
@@ -3,16 +3,36 @@ import { System_String, System_Array } from '../Platform/Platform';
import { platform } from '../Environment';
import { getTreeNodePtr, uiTreeNode, NodeType, UITreeNodePointer } from './UITreeNode';
+// TODO: Instead of associating components to parent elements, associate them with a
+// start/end node, so that components don't have to be enclosed in a wrapper
+// TODO: To avoid leaking memory, automatically remove entries from this dict as soon
+// as the corresponding DOM nodes are removed (or maybe when the associated component
+// is disposed, assuming we can guarantee that always happens).
+const componentIdToParentElement: { [componentId: string]: Element } = {};
+
+registerFunction('_blazorAttachComponentToElement', attachComponentToElement);
registerFunction('_blazorRender', renderUITree);
-function renderUITree(elementSelector: System_String, tree: System_Array, treeLength: number) {
+function attachComponentToElement(elementSelector: System_String, componentId: System_String) {
const elementSelectorJs = platform.toJavaScriptString(elementSelector);
const element = document.querySelector(elementSelectorJs);
if (!element) {
- throw new Error(`Could not find any element matching selector '${ elementSelectorJs }'.`);
+ throw new Error(`Could not find any element matching selector '${elementSelectorJs}'.`);
}
clearElement(element);
+
+ const componentIdJs = platform.toJavaScriptString(componentId);
+ componentIdToParentElement[componentIdJs] = element;
+}
+
+function renderUITree(componentId: System_String, tree: System_Array, treeLength: number) {
+ const componentIdJs = platform.toJavaScriptString(componentId);
+ const element = componentIdToParentElement[componentIdJs];
+ if (!element) {
+ throw new Error(`No element is currently associated with component ${componentIdJs}`);
+ }
+
insertNodeRange(element, tree, 0, treeLength - 1);
}
diff --git a/src/Microsoft.Blazor.Browser/DOM.cs b/src/Microsoft.Blazor.Browser/DOM.cs
new file mode 100644
index 0000000000..c355928172
--- /dev/null
+++ b/src/Microsoft.Blazor.Browser/DOM.cs
@@ -0,0 +1,42 @@
+// 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.Browser.Interop;
+using Microsoft.Blazor.Components;
+using Microsoft.Blazor.UITree;
+
+namespace Microsoft.Blazor.Browser
+{
+ ///
+ /// Provides mechanisms for displaying Blazor components in a browser Document
+ /// Object Model (DOM).
+ ///
+ public static class DOM
+ {
+ ///
+ /// Associates the specified component with the specified DOM element, causing the
+ /// component to be displayed there.
+ ///
+ /// A CSS selector that identifies a unique DOM element.
+ /// The component to be displayed in the DOM element.
+ public static void AttachComponent(string elementSelector, IComponent component)
+ {
+ var renderState = DOMComponentRenderState.GetOrCreate(component);
+
+ RegisteredFunction.InvokeUnmarshalled(
+ "_blazorAttachComponentToElement", elementSelector, renderState.DOMComponentId);
+
+ RefreshComponentInDOM(renderState);
+ }
+
+ private static void RefreshComponentInDOM(DOMComponentRenderState renderState)
+ {
+ var tree = renderState.UpdateRender();
+ RegisteredFunction.InvokeUnmarshalled(
+ "_blazorRender",
+ renderState.DOMComponentId,
+ tree.Array,
+ tree.Count);
+ }
+ }
+}
diff --git a/src/Microsoft.Blazor.Browser/DOMComponentRenderState.cs b/src/Microsoft.Blazor.Browser/DOMComponentRenderState.cs
new file mode 100644
index 0000000000..970f2f2c1b
--- /dev/null
+++ b/src/Microsoft.Blazor.Browser/DOMComponentRenderState.cs
@@ -0,0 +1,61 @@
+// 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;
+using System;
+using System.Runtime.CompilerServices;
+
+namespace Microsoft.Blazor.Browser
+{
+ ///
+ /// Tracks the rendering state associated with an that is
+ /// being displayed in the DOM.
+ ///
+ internal class DOMComponentRenderState
+ {
+ private static ConditionalWeakTable _stateInstances
+ = new ConditionalWeakTable();
+ private static long _nextDOMComponentId = 0;
+
+ private readonly UITreeBuilder _uITreeBuilder; // TODO: Maintain two, so we can diff successive renders
+
+ public string DOMComponentId { get; }
+
+ public IComponent Component { get; }
+
+ private DOMComponentRenderState(string componentId, IComponent component)
+ {
+ DOMComponentId = DOMComponentId;
+ Component = component;
+ _uITreeBuilder = new UITreeBuilder();
+ }
+
+ public static DOMComponentRenderState GetOrCreate(IComponent component)
+ {
+ lock (_stateInstances)
+ {
+ if (_stateInstances.TryGetValue(component, out var existingState))
+ {
+ return existingState;
+ }
+ else
+ {
+ var newId = (_nextDOMComponentId++).ToString();
+ var newState = new DOMComponentRenderState(newId, component);
+ _stateInstances.Add(component, newState);
+ return newState;
+ }
+ }
+ }
+
+ public ArraySegment UpdateRender()
+ {
+ _uITreeBuilder.Clear();
+ Component.BuildUITree(_uITreeBuilder);
+
+ // TODO: Change this to return a diff between the previous render result and this new one
+ return _uITreeBuilder.GetNodes();
+ }
+ }
+}
diff --git a/src/Microsoft.Blazor.Browser/Renderer.cs b/src/Microsoft.Blazor.Browser/Renderer.cs
deleted file mode 100644
index b7871525bd..0000000000
--- a/src/Microsoft.Blazor.Browser/Renderer.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-// 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.Browser.Interop;
-using Microsoft.Blazor.Components;
-using Microsoft.Blazor.UITree;
-
-namespace Microsoft.Blazor.Browser
-{
- public static class Renderer
- {
- public static void Render(IComponent component, string elementSelector)
- {
- var builder = new UITreeBuilder();
- component.Render(builder);
-
- var tree = builder.GetNodes();
- RegisteredFunction.InvokeUnmarshalled(
- "_blazorRender", elementSelector, tree.Array, tree.Count);
- }
- }
-}
diff --git a/src/Microsoft.Blazor/Components/IComponent.cs b/src/Microsoft.Blazor/Components/IComponent.cs
index 95eef828d6..22c1cbbcc9 100644
--- a/src/Microsoft.Blazor/Components/IComponent.cs
+++ b/src/Microsoft.Blazor/Components/IComponent.cs
@@ -11,9 +11,9 @@ namespace Microsoft.Blazor.Components
public interface IComponent
{
///
- /// Renders the component.
+ /// Builds a representing the current state of the component.
///
/// A to which the rendered nodes should be appended.
- void Render(UITreeBuilder builder);
+ void BuildUITree(UITreeBuilder builder);
}
}
diff --git a/test/testapps/BasicTestApp/Program.cs b/test/testapps/BasicTestApp/Program.cs
index 19a4ea56df..623ef67260 100644
--- a/test/testapps/BasicTestApp/Program.cs
+++ b/test/testapps/BasicTestApp/Program.cs
@@ -20,7 +20,7 @@ namespace BasicTestApp
{
var componentType = Type.GetType(componentTypeName);
var componentInstance = (IComponent)Activator.CreateInstance(componentType);
- Renderer.Render(componentInstance, "app");
+ DOM.AttachComponent("app", componentInstance);
}
}
}
diff --git a/test/testapps/BasicTestApp/TextOnlyComponent.cs b/test/testapps/BasicTestApp/TextOnlyComponent.cs
index 70b1108c63..9fd64cc941 100644
--- a/test/testapps/BasicTestApp/TextOnlyComponent.cs
+++ b/test/testapps/BasicTestApp/TextOnlyComponent.cs
@@ -8,7 +8,7 @@ namespace BasicTestApp
{
public class TextOnlyComponent : IComponent
{
- public void Render(UITreeBuilder builder)
+ public void BuildUITree(UITreeBuilder builder)
{
builder.AddText($"Hello from {nameof(TextOnlyComponent)}");
}