Open toddburnside opened 3 years ago
Hey! Yes, the way to do that is to create a standard Scala.JS facade that includes getValue()
etc, and then call .addFacade
when you're building up your component. It's described in the interop page but I see now that there's no example (hey PR welcome btw :grinning:).
Here's an example in the unit tests that should help:
Great! Thanks for the quick response. I figured there had to be a way. :) Thanks for the awesome library. Once I get it working in my code, I'll create an example for the docs and submit a PR.
No worries at all.
Once I get it working in my code, I'll create an example for the docs and submit a PR.
Woah thank you very much. I like to throw those statements out there just in case but I never really expect a response. It's very much appreciated so thanks a lot!
I created PR #838 for the addFacade
example.
I see how .addFacade
makes it possible to access the facade methods of a JS defined component from Scala, but I can't see how it can be used to make extra methods on a Scala defined component available from JS.
In this case we need to pass a Scala component to a JS component (ag-grid-react in this case), that expects to be able to call certain methods on our component.
We do have components where we have methods defined in the backend of a Scala component that we call from elsewhere, but that requires something like aladinRef.get.flatMapCB(_.backend.gotoRaDec(xxx))
, which needs to explicitly reference the backend
. Is this extra level of indirection what makes it so that these methods are not visible from JS - at least not where they are expected to be?
Hi there! I've been trying to solve this issue too.
Just for reference: we need to create a component like this one, which has a getValue
method that ag-grid
needs to access: https://gist.github.com/maxkoretskyi/fb8c630b86a772622bf8ac55dd623b76 (this is so that we use a custom editor in ag-grid
).
Passing component.toJsComponent
doesn't work, since that wraps our Scala component in a JS functional component which can't have additional methods.
Since we need to pass a JS class component, I tried passing component.raw
which would seem to be what we want, but this crashes at runtime with a TypeError
when ag-grid
tries to create our component since this
is undefined
in https://github.com/japgolly/scalajs-react/blob/master/core/src/main/scala/japgolly/scalajs/react/component/builder/ViaReactComponent.scala#L258.
And that's as far as I got for now, I haven't figured out what this
is supposed to be in this context... @japgolly can you think of a way to work around this? We would like to access the JS class component, or alternatively be able to wrap the Scala component in a JS class component.
Just a word of warning. I use JS components from within Scala often but I've never really called Scala components from JS. I've made all the raw JS representations accessible via .raw
but I haven't considered usage any more advanced than that.
@toddburnside Yeah, all the custom Scala methods go in the backend so to access from JS you'd have to access the backend first. If you want to execute a Callback
from JS I have no idea. It's a value class over Trampoline[A]
so probably the Trampoline[A]
is all you'd see from JS, in which case maybe we should expose a JS method called runNow()
that you could call from JS and matches Callback
? If React JS will actually stick to JS classes for components and not change again, we should probably support doing the same in Scala too, like Slinky does and without the "backend" concept. In which case when using it from JS you wouldn't need to call .backend
first. Problem is I have no income, working at a loss this year so I don't see myself sitting down for a two weeks (?) to implement this so unless I get funding or someone contributes it it won't happen for a while (sorry). Going via .backend
or providing your own JS wrapper to avoid users doing so would be the short-term solution to making it feel 100% like JS.
@rpiaggio Sorry man I don't quite understand what you mean. That JS component is calling this
because it's accessing it's a class-level field set in the constructor. I don't see where Scala <-> JS comes in to it. To simulate that in Scala you could have private val textInput = ...
in your backend and you could also create methods like getValue()
in your backend too, but like above, to use it from JS you'd have to call .backend
first or create your own JS wrapper :(
Thanks for the response @japgolly . At the moment we are looking into an option that does not require the above. If that doesn't work out, I may try the JS wrapper for the Scala component.
I believe I've come up with a solution to this.
For starters, users must annotate members in their backend with @JSExport
. Eg:
class Backend($: BackendScope[Unit, State]) {
@JSExport
def welcome1() = "why hello there!"
@JSExport
def welcome2 = "hey mate!"
@JSExport
var v = 123
@JSExport
val field = "i'm a field"
We could make scalajs-react add code to component constructors (either for all components with backends, or maybe if a directive is specified in Scala) so that when a component and backend is first created, the JS uses runtime reflection to automatically generate forwarders on itself to the exposed methods in the backend.
Say m
is a raw instance of a Scala component with a backend, m.backend
gives you the backend class and Object.getOwnPropertyNames(m.backend.__proto__)
returns a superset of all exposed methods. We can then iterate over them, filter out conflicts, and generate forwarders. Each forwarder would depend on the member type (fn, fn(), var, val, anything else?).
Extra points: maybe these should be added to m.__proto__
once rather than every instance of m
? It's probably more efficient and might be more idiomatic for the JS world (?). Will have to investigate the nuances and details of how JS and prototypes work.
Hi,
We have created a facade for
ag-grid-react
using ScalablyTyped. The basic functionality is working fine, but the grid allows for things like custom cell renderers and cell editors to be specified as react components. I am able to pass in components using either a function as described in INTEROP.md, and withcomponent.cmapCtorProps[…](…).toJsComponent.raw
.However, the grid requires some extra “lifecycle” functions to be available on some of the components, such as
getValue(): js.Any
, which the grid uses to get the new value after editing is completed. I was hoping that if I put the functions in the Backend of the component, they would be visible to the grid. But, no matter what I try, I get this helpful message in the console: “ag-Grid: Framework component is missing the method getValue()".Is there a way to make extra functions in a component visible to the grid?
A jsx example of what I'm talking about is: https://github.com/ag-grid/ag-grid-react-example/blob/master/src-examples/richGridDeclarativeExample/NameCellEditor.jsx In the jsx, they just define the lifecycle methods right alongside the render method.
Thanks!