Begin tracking association between components and DOM elements so that components can be refresh their own display
This commit is contained in:
parent
dfd6c4a1c2
commit
f0a78d13bf
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)}");
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue