Begin tracking association between components and DOM elements so that components can be refresh their own display

This commit is contained in:
Steve Sanderson 2018-01-05 14:31:06 +00:00
parent dfd6c4a1c2
commit f0a78d13bf
8 changed files with 131 additions and 30 deletions

View File

@ -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");

View File

@ -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);
}

View File

@ -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
{
/// <summary>
/// Provides mechanisms for displaying Blazor components in a browser Document
/// Object Model (DOM).
/// </summary>
public static class DOM
{
/// <summary>
/// Associates the specified component with the specified DOM element, causing the
/// component to be displayed there.
/// </summary>
/// <param name="elementSelector">A CSS selector that identifies a unique DOM element.</param>
/// <param name="component">The component to be displayed in the DOM element.</param>
public static void AttachComponent(string elementSelector, IComponent component)
{
var renderState = DOMComponentRenderState.GetOrCreate(component);
RegisteredFunction.InvokeUnmarshalled<string, string, object>(
"_blazorAttachComponentToElement", elementSelector, renderState.DOMComponentId);
RefreshComponentInDOM(renderState);
}
private static void RefreshComponentInDOM(DOMComponentRenderState renderState)
{
var tree = renderState.UpdateRender();
RegisteredFunction.InvokeUnmarshalled<string, UITreeNode[], int, object>(
"_blazorRender",
renderState.DOMComponentId,
tree.Array,
tree.Count);
}
}
}

View File

@ -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
{
/// <summary>
/// Tracks the rendering state associated with an <see cref="IComponent"/> that is
/// being displayed in the DOM.
/// </summary>
internal class DOMComponentRenderState
{
private static ConditionalWeakTable<IComponent, DOMComponentRenderState> _stateInstances
= new ConditionalWeakTable<IComponent, DOMComponentRenderState>();
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<UITreeNode> 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();
}
}
}

View File

@ -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<string, UITreeNode[], int, object>(
"_blazorRender", elementSelector, tree.Array, tree.Count);
}
}
}

View File

@ -11,9 +11,9 @@ namespace Microsoft.Blazor.Components
public interface IComponent
{
/// <summary>
/// Renders the component.
/// Builds a <see cref="UITree"/> representing the current state of the component.
/// </summary>
/// <param name="builder">A <see cref="UITreeBuilder"/> to which the rendered nodes should be appended.</param>
void Render(UITreeBuilder builder);
void BuildUITree(UITreeBuilder builder);
}
}

View File

@ -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);
}
}
}

View File

@ -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)}");
}