// 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.AspNetCore.Blazor.Components; using Microsoft.AspNetCore.Blazor.RenderTree; using System; namespace Microsoft.AspNetCore.Blazor.Rendering { /// /// Tracks the rendering state associated with an instance /// within the context of a . This is an internal implementation /// detail of . /// internal class ComponentState { private readonly int _componentId; // TODO: Change the type to 'long' when the Mono runtime has more complete support for passing longs in .NET->JS calls private readonly IComponent _component; private readonly Renderer _renderer; private readonly RenderTreeDiffComputer _diffComputer; private RenderTreeBuilder _renderTreeBuilderCurrent; private RenderTreeBuilder _renderTreeBuilderPrevious; /// /// Constructs an instance of . /// /// The with which the new instance should be associated. /// The externally visible identifer for the . The identifier must be unique in the context of the . /// The whose state is being tracked. public ComponentState(Renderer renderer, int componentId, IComponent component) { _componentId = componentId; _component = component ?? throw new ArgumentNullException(nameof(component)); _renderer = renderer ?? throw new ArgumentNullException(nameof(renderer)); _diffComputer = new RenderTreeDiffComputer(renderer); _renderTreeBuilderCurrent = new RenderTreeBuilder(renderer); _renderTreeBuilderPrevious = new RenderTreeBuilder(renderer); } /// /// Regenerates the and adds the changes to the /// . /// public void Render(RenderBatchBuilder batchBuilder) { // Swap the old and new tree builders (_renderTreeBuilderCurrent, _renderTreeBuilderPrevious) = (_renderTreeBuilderPrevious, _renderTreeBuilderCurrent); _renderTreeBuilderCurrent.Clear(); _component.BuildRenderTree(_renderTreeBuilderCurrent); _diffComputer.ApplyNewRenderTreeVersion( batchBuilder, _componentId, _renderTreeBuilderPrevious.GetFrames(), _renderTreeBuilderCurrent.GetFrames()); } /// /// Invokes the handler corresponding to an event. /// /// The index of the current render tree frame that holds the event handler to be invoked. /// Arguments to be passed to the event handler. public void DispatchEvent(int renderTreeIndex, UIEventArgs eventArgs) { if (eventArgs == null) { throw new ArgumentNullException(nameof(eventArgs)); } var frames = _renderTreeBuilderCurrent.GetFrames(); var eventHandler = frames.Array[renderTreeIndex].AttributeValue as UIEventHandler; if (eventHandler == null) { throw new ArgumentException($"The render tree frame at index {renderTreeIndex} does not specify a {nameof(UIEventHandler)}."); } eventHandler.Invoke(eventArgs); // After any event, we synchronously re-render. Most of the time this means that // developers don't need to call Render() on their components explicitly. _renderer.RenderNewBatch(_componentId); } /// /// Notifies the component that it is being disposed. /// public void NotifyDisposed() { // TODO: Handle components throwing during dispose. Shouldn't break the whole render batch. if (_component is IDisposable disposable) { disposable.Dispose(); } } } }