Custom elements
editA custom element is an element whose behavior you define, as customElements.define() does in a browser. In .NET, a CustomElement subclass is attached to each element, and its virtual methods are the lifecycle callbacks:
/// <summary>The <c>x-greeting</c> element: its text greets the person named by its <c>name</c> attribute.</summary>
internal sealed class GreetingElement(HtmlElement element) : CustomElement(element)
{
// As in a JavaScript constructor, the element must not get attributes or children in the constructor
protected override void ConnectedCallback() => Update();
protected override void AttributeChangedCallback(string name, string? oldValue, string? newValue, string? @namespace) => Update();
private void Update() => Element.TextContent = "Hello " + Element.GetAttribute("name");
}Define the element in the registry of the window. The elements already in the document are upgraded, and the new ones are created with the definition:
using var context = new BrowsingContext();
var document = context.Navigate("https://example.com/", _ => "<x-greeting name=Ada></x-greeting>");
// The elements already in the document are upgraded when the element is defined
context.Window.CustomElements.Define("x-greeting", new CustomElementConstructor(element => new GreetingElement(element))
{
ObservedAttributes = ["name"],
});
var greeting = (HtmlElement)document.QuerySelector("x-greeting")!;
Console.WriteLine(greeting.TextContent); // Hello Ada
greeting.SetAttribute("name", "Grace");
Console.WriteLine(greeting.TextContent); // Hello GraceFrom JavaScript to .NET
| JavaScript | .NET |
|---|---|
class X extends HTMLElement | A CustomElement created by the factory of new CustomElementConstructor(element => ...) |
this | CustomElement.Element; HtmlElement.CustomElement returns the CustomElement of an element |
connectedCallback(), disconnectedCallback(), adoptedCallback(), attributeChangedCallback(), connectedMoveCallback() | The virtual methods of CustomElement |
formAssociatedCallback(), formResetCallback(), formDisabledCallback(), formStateRestoreCallback() | The virtual methods of CustomElement, called when FormAssociated is true |
static observedAttributes, static formAssociated, static disabledFeatures | CustomElementConstructor.ObservedAttributes, FormAssociated, DisabledFeatures |
customElements.define(name, C, { extends: "button" }) | registry.Define(name, constructor, new ElementDefinitionOptions { Extends = "button" }) |
customElements.whenDefined(name) | registry.WhenDefined(name), a Task |
The element is not a subclass of HtmlElement: it is created by the library (an HtmlElement, or the interface of the extended element for a customized built-in element such as HtmlButtonElement), and the CustomElement is attached to it. This keeps the identity of the elements that are upgraded after they were parsed.
Rules of the constructor
The factory runs as the body of a JavaScript constructor after super(). As in JavaScript, the element must not get attributes or children there: do this work in ConnectedCallback. Element.AttachInternals() returns the ElementInternals of the element: its form value (SetFormValue), its validity, its default accessibility semantics, and its custom states (States, matched by the :state() pseudo-class).
Registries
Window.CustomElements is the global registry of a window: custom elements need a browsing context, or a registry given to the parser with HtmlParserOptions.CustomElementRegistry. Scoped registries (new CustomElementRegistry()) define elements for a part of the document only. The scripts of a page can define custom elements too, with customElements.define(), when the browsing context has a script engine.