Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support/document usage of JSComponent within code-based DotvvmControls/CompositeControls #1802

Open
cafour opened this issue Apr 4, 2024 · 1 comment

Comments

@cafour
Copy link
Contributor

cafour commented Apr 4, 2024

JSComponents function only when used in the dothtml syntax as documented and don't work properly when used from within code-based DotvvmControls/CompositeControls. In particular, no dotvvm-with-view-modules KO binding comment gets rendered.

Scenario

Let's have a JS component wrapping a React component, for example, recharts. When I use this component like this:

@js dotvvm-recharts.js

<dot:JsComponent Name="Recharts" ></dot:JsComponent>

...all is well, and, in the browser, the component is found and initialized.

However, when I use this component from within a CompositeControl:

public class RechartsComposite : CompositeControl
{
    public IEnumerable<DotvvmControl> GetContents()
    {
        yield return new RequiredResource("dotvvm-recharts.js");
        yield return new JsComponent("Recharts");
    }
}

The dotvvm-with-view-modules binding is missing in the page, the JSComponent is rendered without the view property, and the findComponent function a can not find control Recharts error.

When I try to avoid this by adding @js dotvvm-recharts.js, despite the RequiredResource control already in the RechartsComposite, the dotvvm-with-view-modules is present, but view property is still not rendered, and thus the component is still not found.


Is there a method other than RequiredResource that would allow a control to include a JS module?

@exyi
Copy link
Member

exyi commented Apr 4, 2024

Yea, code-only components currently cannot reference and initialize view modules. It is a fundamental issue that view module instances are identified using the JavaScript HTMLElement and found by walking the ancestor chain. Initializing a new view module in the code control would thus break all references to the page view module from inside of any templates the control has. Our current solution is to not support ITemplate in markup controls, and not support view modules in code-only controls.

That being said, you can use JsComponent without view modules. You can register the component globally in any script using dotvvm.registerGlobalComponent. Then reference the script using the RequiredResource

dotvvm.registerGlobalComponent("your-component-name", registerReactControl(... same as in view module $controls))

This will make the component available to all pages and, crucially, to the JsComponent initialized from code. When using a global component in a page, you can add Global property to the component to make sure it isn't overwritten by the module. I'll put this into the docs.


In case that doesn't suit your needs, if you require the view module for some reason, there are two other ways to work around it:

  1. Make your control a markup control. You can put into a library using embedded resources. This will make it impossible a bad idea to add ITemplate/any content properties to the control.
  2. Explicitly use the module from the page. Option A is to simply find the nearest parent with the Internal.ReferencedViewModuleInfoProperty and copy it to the JsComponent:
jsComponent.SetValue(Internal.ReferencedViewModuleInfoProperty,
    this.GetAllAncestors().Select(c => c.GetValue(Internal.ReferencedViewModuleInfoProperty)).First(i => i != null))

Or, if you want to make the code future-proof against us hopefully resolving the core issue, copy the Internal.ReferencedViewModuleInfoProperty from the TreeRoot of the current compilation unit using this (as JsComponent does it):

  [ApplyControlStyle]
  public static void AddReferencedViewModuleInfoProperty(ResolvedControl control)
  {
      if (control.TreeRoot.TryGetProperty(Internal.ReferencedViewModuleInfoProperty, out var x))
      {
          var value = ((ResolvedPropertyValue)x).Value;
          control.SetProperty(new ResolvedPropertyValue(Internal.ReferencedViewModuleInfoProperty, value));
      }
  }

and then access the property by calling this.GetValue(Internal.ReferencedViewModuleInfoProperty) instead of the LINQ.

Same trick by the way applies to using NamedCommands from a code-only control (it's probably more useful for that given there is no global option)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants