Embedding Existing JavaScript Components Into Uno-WASM - Part 1

HTML5 is a Rich and Powerful Platform

Uno Platform fully embraces HTML5 as its display backend when targeting WebAssembly, for both Native and Skia renderers. As a result, it is possible to integrate with almost any existing JavaScript library to extend the behavior of an app.

Embedding assets

In the HTML world, everything running in the browser is assets that must be downloaded from a server. To integrate existing JavaScript frameworks, they can be either downloaded from another location on the Internet (usually from a CDN service) or embedded and deployed with the app.

The Uno Bootstrapper can automatically embed any asset and deploy it with the app. Some of them (CSS & JavaScript) can also be loaded with the app. Here's how to declare them in a Uno Wasm project:

  1. JavaScript files should be in the Platforms/WebAssembly/WasmScripts folder. They will be copied to the output folder and loaded automatically by the bootstrapper when the page loads.

  2. CSS Style files should be in the Platforms/WebAssembly/WasmCSS folder. They will be copied to the output folder and referenced in the HTML head of the application.

  3. Asset files can be placed in the Assets folder. These files will be copied to the output folder and will preserve the same relative path to the Assets folder.

  4. Alternatively, any kind of asset file can be placed directly in the wwwroot folder as with any standard ASP.NET Core project. They will be deployed with the app, but the application code is responsible for fetching and using them.

    Is it an ASP.NET Core "web" project? No, but it shares a common structure. Some of the deployment features, like the wwwroot folder, and the Visual Studio integration for running/debugging are reused in a similar way to an ASP.NET Core project. The C# code put in the project will run in the browser, using the .NET runtime. There is no need for a server side component in Uno-Wasm projects.

Embedding Native Elements

Embedding native JavaScript elements is done through the Uno.UI.NativeElementHosting.BrowserHtmlElement class, which serves as an entry point to interact with your native element.

[!IMPORTANT] The BrowserHtmlElement class is available on all target frameworks, avoid the need for condition #if code, yet it is only useable on WebAssembly. You'll need to guard its use by validating the platform with the OperatingSystem class.

using Uno.UI.NativeElementHosting.BrowserHtmlElement;

public sealed partial class MyControlHost : ContentControl
{
  private BrowserHtmlElement? _element;

  public MyControlHost()
  {
    if (OperatingSystem.IsBrowser())
    {
      _element = BrowserHtmlElement.CreateHtmlElement("div");
      Content = _element;
    }
    else
    {
      Content = "This control only supported on WebAssembly on the Browser";
    }
  }
}

This way, your native element is hosted inside a content control of your choosing. You can replace it with any available HTML tag.

Once created, it is possible to interact directly with this element by calling BrowserHtmlElement methods.

Here is a list of helper methods used to facilitate the integration with the HTML DOM:

  • The method element.SetCssStyle() can be used to set a CSS Style on the HTML element. Example:

    // Setting only one CSS style
    _element.SetCssStyle("text-shadow", "2px 2px red");
    
    // Setting many CSS styles at once using C# tuples
    _element.SetCssStyle(("text-shadow", "2px 2px blue"), ("color", "var(--app-fg-color1)"));
    
  • The element.ClearCssStyle() method can be used to set CSS styles to their default values. Example:

    // Reset text-shadow style to its default value
    _element.ClearCssStyle("text-shadow");
    
    // Reset both text-shadow and color to their default values
    _element.ClearCssStyle("text-shadow", "color");
    
  • The element.SetHtmlAttribute() and element.ClearHtmlAttribute() methods can be used to set HTML attributes on the element:

    // Set the "href" attribute of an <a> element
    _element.SetHtmlAttribute("href", "#section2");
    
    // Set many attributes at once (less interop)
    _element.SetHtmlAttribute(("target", "_blank"), ("referrerpolicy", "no-referrer"));
    
    // Remove attribute from DOM element
    _element.ClearHtmlAttribute("href");
    
    // Get the value of an attribute of a DOM element
    var href = _element.GetHtmlAttribute("href");
    
  • The element.SetCssClass() and element.UnsetCssClass() methods can be used to add or remove CSS classes to the HTML Element:

    // Add the class to element
    _element.SetCssClass("warning");
    
    // Add many classes at once (less interop)
    _element.SetCssClass("warning", "level2");
    
    // Remove class from element
    _element.UnsetCssClass("paused");
    
    // You can also set one class from a list of possible values.
    // Like a radio-button, like non-selected values will be unset
    var allClasses = new [] { "Small", "Medium", "Large"};
    _element.SetCssClass(allClasses, 2); // set to "Large"
    
  • The element.SetHtmlContent() method can be used to set arbitrary HTML content as child of the control.

    _element.SetHtmlContent("<h2>Welcome to Uno Platform!</h2>");
    
  • Finally, it is possible to make calls from and to JavaScript code by using JSImport/JSExport. The javascript code is directly executed in the context of the browser, giving the ability to perform anything that JavaScript can do. See next section for more details.

Raising custom Javascript events

It's also possible to have Javscript components raise events to be handled by C# code.

From your Javascript or TypeScript code, you can raise events:

  // Generate a custom generic event from JavaScript/Typescript
  htmlElement.dispatchEvent(new Event("simpleEvent"));

  // Generate a custom event with a string payload
  const payload = "this is the payload of the event";
  htmlElement.dispatchEvent(new CustomEvent("stringEvent", { detail: payload }));

  // Generate a custom event with a complex payload
  const payload = { property:"value", property2: 1234 };
  htmlElement.dispatchEvent(new CustomEvent("complexEvent", { detail: payload }));

Then from your C# code, add the following:

  protected override void OnLoaded()
  {
      // Note: following extensions are in the namespace "Uno.Extensions"
      this.RegisterHtmlEventHandler("simpleEvent", OnSimpleEvent);
      this.RegisterHtmlEventHandler("stringEvent", OnStringEvent);
      this.RegisterHtmlEventHandler("complexEvent", OnComplexEvent);
  }

  private void OnSimpleEvent(object sender, JSObject args)
  {
      // You can react on "simpleEvent" here
  }

  private void OnStringEvent(object sender, JSObject args)
  {
      // You can react on "stringEvent" here
      var detail = args.GetPropertyAsString("detail");
  }

  private void OnComplexEvent(object sender, JSObject args)
  {
      // You can react on "complexEvent" here
      var detail = args.GetPropertyAsJSObject("detail");
      var property = detail?.GetPropertyAsString("property");
      var property2 = detail?.GetPropertyAsInt32("property2");
  }

🔬 Going further