Skip to content

ElementReference FocusAsync API#23316

Merged
MackinnonBuck merged 13 commits into
masterfrom
t-mabuc/element-focus
Jun 26, 2020
Merged

ElementReference FocusAsync API#23316
MackinnonBuck merged 13 commits into
masterfrom
t-mabuc/element-focus

Conversation

@MackinnonBuck
Copy link
Copy Markdown
Member

@MackinnonBuck MackinnonBuck commented Jun 24, 2020

Summary of the changes:

  • Added FocusAsync() extension method to ElementReference that focuses the element associated with the reference.
  • Added tests to verify that FocusAsync() works properly.
  • Improved API to omit the IJSRuntime argument from FocusAsync()

Addresses #22892

@MackinnonBuck MackinnonBuck added the area-blazor Includes: Blazor, Razor Components label Jun 24, 2020
@MackinnonBuck MackinnonBuck requested a review from pranavkm June 24, 2020 17:53
@dnfadmin
Copy link
Copy Markdown

dnfadmin commented Jun 24, 2020

CLA assistant check
All CLA requirements met.

@pranavkm pranavkm requested a review from a team June 24, 2020 17:54
@SteveSandersonMS
Copy link
Copy Markdown
Member

This looks great, and it's awesome to see the E2E test in there as well!

I'm guessing this is marked draft because you're going on to do the "omit JSRuntime arg" bit, so I'll wait for updates :)

Copy link
Copy Markdown
Member

@javiercn javiercn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great!

@MackinnonBuck MackinnonBuck marked this pull request as ready for review June 24, 2020 22:15
Comment thread src/Components/Components/src/Microsoft.AspNetCore.Components.csproj Outdated
Comment thread src/Components/Components/src/Microsoft.AspNetCore.Components.csproj Outdated
Comment thread src/Components/Components/src/RenderTree/Renderer.cs Outdated
Comment thread src/Components/Components/src/RenderTree/Renderer.cs Outdated
Comment thread src/Components/Components/src/RenderTree/Renderer.cs Outdated
@pranavkm pranavkm added this to the 5.0.0-preview8 milestone Jun 24, 2020
Copy link
Copy Markdown
Contributor

@pranavkm pranavkm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really good! Just a few more things to address and we can get this in.

Comment thread src/Components/Server/test/ElementReferenceJsonConverterTest.cs Outdated
Comment thread src/Components/Web.JS/src/DomWrapper.ts Outdated
Comment thread src/Components/Components/src/ElementReference.cs Outdated
Comment thread src/Components/Components/src/ElementReference.cs Outdated
Comment thread src/Components/Components/src/ElementReference.cs Outdated
Comment thread src/Components/Web/src/ElementReferenceExtensions.cs Outdated
Comment thread src/Components/Components/src/ElementReference.cs Outdated
@MackinnonBuck MackinnonBuck requested a review from pranavkm June 25, 2020 03:43
Copy link
Copy Markdown
Contributor

@pranavkm pranavkm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost there!

Comment thread src/Components/Server/test/ElementReferenceJsonConverterTest.cs Outdated
Comment thread src/Components/Web/src/ElementReferenceExtensions.cs Outdated
Comment thread src/Components/test/E2ETest/Tests/ComponentRenderingTest.cs
Comment thread src/Components/Web.JS/src/DomWrapper.ts Outdated
Copy link
Copy Markdown
Member

@javiercn javiercn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks great!

I think we can tweak it a bit. I don't love the part where the IServiceProvider is spilled across the application and it feels that we are missing an abstraction.

I would consider creating an ElementReferenceFactory with an implementation on M.A.C.Web that plugs in the JS runtime. The JsonConverter and the Renderer only receive a ElementReferenceFactory and the ElementReference only holds on to whatever it needs on a given platform.

We would do something like

Renderer.ElementFactory.CreateWithUniqueId()

and the remote RemoteJSRuntime would simply instantiate its own ElementReferenceFactory passing itself in the constructor.

@SteveSandersonMS
Copy link
Copy Markdown
Member

SteveSandersonMS commented Jun 25, 2020

I would consider creating an ElementReferenceFactory with an implementation on M.A.C.Web that plugs in the JS runtime

That's a bit awkward because ElementReference is a struct (and needs to be), so there's nowhere to even hold additional information. We could add some kind of object-valued ExtraInfo field onto it, but that's pretty weird and doesn't compose well if we end up needing to get access to more stuff from an ElementReference in the future.

Additionally, although for high-level features it's generally fine to push code through factories and more abstractions, the perf costs are prohibitive for code in the deep guts of rendering. There are very legit cases for building many thousands of ElementReferences per second (e.g., wanting to be able to reference each td in a grid).

I also don't really have any particular objection to referencing the IServiceProvider from ElementReference.

@javiercn
Copy link
Copy Markdown
Member

That's a bit awkward because ElementReference is a struct (and needs to be), so there's nowhere to even hold additional information. We could add some kind of object-valued ExtraInfo field onto it, but that's pretty weird and doesn't compose well if we end up needing to get access to more stuff from an ElementReference in the future.

It's already holding on to the IServiceProvider, so I don't see the difference here. I'm saying, initialize it with the JSRuntime only and through a Factory instead of giving it the IServiceProvider. I think having the IServiceProvider there encourages customers to do ElementReference.ServiceProvider.GetRequiredService<PartyOn>() which can easily get out of hand.

We designed element references to be an opaque abstraction, and we should keep treating them like that. For that reason I would have the factory live on M.A.C.Web, make it responsible for populating the state, and using an internal type that only M.A.C.Web can understand (like an internal class WebElementState) that contains the JSRuntime.

That way, the ElementReference is still opaque and we are still able to implement focus without any details leaking out.

Additionally, although for high-level features it's generally fine to push code through factories and more abstractions, the perf costs are prohibitive for code in the deep guts of rendering. There are very legit cases for building many thousands of ElementReferences per second (e.g., wanting to be able to reference each td in a grid).

I don't think what I'm proposing would have a negative impact on perf, it would be the opposite if anything. The factory is accessed from the renderer or is part of the converter and gives clear and scoped functionality.

Then on the ElementReference we just have state as an internal property (object) that is populated with the IJSRuntime. The helpers then just do a if(state is IJSRuntime jsRuntime){ jsRuntime.InvokeAsync("focus",this)) which is cheaper than asking the container to resolve the jsruntime.

In conclusion:

  • The factory makes the intent clear and the functionality scoped.
  • Doesn't incurs into any additional performance cost, if anything makes it faster on repeated uses.
  • Prevents against pit of failure using the service locator pattern with the given service provider.

Comment thread src/Components/Components/src/ElementReference.cs Outdated
@pranavkm
Copy link
Copy Markdown
Contributor

Thanks @javiercn. Having a factory with a default that just news up the ElementReference seems fine. It would also clean up
ElementReference.CreateWithUniqueId inverting the responsibility to the caller.

a) It would be convenient if there was a default factory that constructed the type without state.

interface IElementReferenceFactory
{
   ElementReference Create();
}

ElementReference
{
  public ElementReference(string id);
  public ElementReference(string id, object? state);
}

Renderer.ElementReferenceFactory { get; } = ServiceProvider.GetService<ElementReferenceFactory> ?? DefaultElementReferenceFactory.Instance;

var newElementReference = Renderer.ElementReferenceFactory.Create();

It might also be useful to also add an extension to Components.Web that does the cast for you:

public static IJSRuntime GetJSRuntime(this ElementReference reference)
{
   return reference.State as IJSRuntime ?? throw "This hasn't been correctly configured.";
}

@SteveSandersonMS
Copy link
Copy Markdown
Member

@pranavkm Let discuss all this at the sync later today! Obviously with @MackinnonBuck too.

Comment thread src/Components/Components/src/ElementReference.cs Outdated
Comment thread src/Components/Components/src/ElementReferenceContext.cs
Comment thread src/Components/Components/src/RenderTree/Renderer.cs Outdated
Comment thread src/Components/test/E2ETest/Tests/ComponentRenderingTest.cs
@MackinnonBuck MackinnonBuck requested a review from pranavkm June 26, 2020 16:04
@pranavkm pranavkm added the api-ready-for-review API is ready for formal API review - https://github.com/dotnet/apireviews label Jun 26, 2020
@ghost
Copy link
Copy Markdown

ghost commented Jun 26, 2020

Thank you for submitting this for API review. This will be reviewed by @dotnet/aspnet-api-review at the next meeting of the ASP.NET Core API Review group. Please ensure you take a look at the API review process documentation and ensure that:

  • The PR contains changes to the reference-assembly that describe the API change. Or, you have included a snippet of reference-assembly-style code that illustrates the API change.
  • The PR describes the impact to users, both positive (useful new APIs) and negative (breaking changes).
  • Someone is assigned to "champion" this change in the meeting, and they understand the impact and design of the change.

Copy link
Copy Markdown
Contributor

@pranavkm pranavkm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll bring this up during API review on Monday and address any feedback separately. Let's get this in to avoid further merge conflicts

@MackinnonBuck MackinnonBuck merged commit 36c6c2c into master Jun 26, 2020
@MackinnonBuck MackinnonBuck deleted the t-mabuc/element-focus branch June 26, 2020 16:19
@pranavkm pranavkm added api-approved API was approved in API review, it can be implemented and removed api-ready-for-review API is ready for formal API review - https://github.com/dotnet/apireviews labels Jun 29, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api-approved API was approved in API review, it can be implemented area-blazor Includes: Blazor, Razor Components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants