-
Notifications
You must be signed in to change notification settings - Fork 55
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
JS/React interop? #32
Comments
For 1, check out https://docs.microsoft.com/en-us/aspnet/core/razor-components/javascript-interop?view=aspnetcore-3.0 Calling vanilla JS libraries is fairly straightforward. You include the javascript in your project, add it to the index.html, and you'll be able to call functions from .NET by obtaining an IJSRuntime. It's been a bit since I've done work with JS in Boleo/Blazor land, but my only gripes were:
The interop is quite flexible though, and you can do clever things with In my experience interop with JS wasn't painful once I figured out how to approach things, but it does take time. I imagine on a bigger project I'd find myself rewriting JS components rather than importing them if they were a hassle and not terribly complex On that note though, I would really love to see Bolero create some sort of Plugin / Component import system so we can easily share work we've done to create wrappers on the JS ecosystem. Something as simple as an interface to implement where you can import JavaScript / CSS into the page before startup would go a long way. |
Sounds like it's impossible to create and use bindings for React then, since AFAIK the Elmish view function would use synchronous React calls. Is that correct? |
You can make calls into synchronous JavaScript, but you need to call those from an asynchronous context, such as a task / async computation expression. I'm not sure if it'd be realistic to host React within a Blazor/Bolero application though since they're already fully fledged SPA frameworks. I haven't used React but I imagine you'd have to isolate it inside a Blazor/Elmish component for each React component you'd want to use, and the work / overhead might not be worth it |
Thanks for the clarification. As I understand it, if using Bolero/Blazor, I'd have to forego UI frameworks like React. But what about Bootstrap, Bulma, MDC-Web, or Materialize? I suppose they would be fairly simple to use, being mostly CSS where AFAIK the JS for the most part (I may be wrong) enables interactivity and doesn't necessarily have to be called? |
I believe so, at least for non-JS frameworks. I'm not sure how things would work with some of Materialize's JS based components like dropdowns / modals. Looking at their init sample: document.addEventListener('DOMContentLoaded', function() {
var elems = document.querySelectorAll('.modal');
var instances = M.Modal.init(elems, options);
}); I imagine this means any DOM modal needs to be initialized, which means if Blazor removes a modal element from the DOM it would need to be re-initialized (although this is probably true of any JS framework right?). Or does this event listener fire whenever new DOM content is added to the page? |
Agree this would be very useful (at least until various JS libs are reimplemented in .NET). We discussed full-blown WebSharper-style extensions that plug into the Blazor component system, but we might as well start with something easier. |
@cmeeren I came across a Blazor library for Bootstrap / Material you might find interesting: https://github.com/stsrki/Blazorise As far as I can see these are standard Blazor components so you should be able to use them within Bolero Ex: comp<TextEdit> [ "Placeholder" => "Some text value..." ] [] |
I'm struggling with JS interop right now, which is why I googled my way to this issue. I am trying to use Bulma Extensions, which unlike Bulma itself contains some features with Javascript files and need for scripting. I consider using the very goodlooking Bulma Extensions DatePicker instead of the Blazor component I'm currently using, if only I can figure out how to do the JS stuff needed. To keep things simple, I started experimenting with Bulma Extensions TagsInput, and actually got it to display as expected with a "manual" workaround - running JS with a button clickevent on the page. So I push on. I am thinking the next step would be to somehow be able to run a Javascript snippet from the update function of the Elmish model. Perhaps what @weebs expains about an IJSRuntime is the proper way? If anybody is interested, this is where I'm now : BentTranberg/ExploreBolero#2 PS: If there's no easy proper solution to my problem at this time, I'll just leave this issue for the future. I'm doing just fine with what I have. |
I have just gone over the JS interop scenario over the last week or so, specifically I wanted to interop by calling an .NET instance method from JS. After some head scratching, reading the blazor docs and following the approach used in the TryFSharpOnWasm application I have come up with the code below. @BentTranberg I'd be interested to know if the approach below would work for your bulma date picker scenario? @Tarmil The bolero docs are fantastic, but they could benefit from showing the interop from JS back to .NET. Would you accept a PR showing something like the code below? I tried searching across github for F# samples which used Hooking into window resize event requires calling into .NET from JSFirst we define some Javascript, notice the callback will be provided to the JS environment. We see that the .NET framework will create the machinery for us. window.generalFunctions = {
env: {
hamburgerVisible: false
},
getSize: function(){
var size = { "height": window.innerHeight, "width" : window.innerWidth };
return size;
},
initResizeCallback: function(onResize) {
window.addEventListener('resize', (ev) => {
this.resizeCallbackJS(onResize);
});
},
resizeCallbackJS: function(callback) {
var size = this.getSize();
if(size.width < 450 && !this.env.hamburgerVisible)
{
this.env.hamburgerVisible = true;
callback.invokeMethodAsync('Invoke', size.height, size.width);
}
if(size.width > 450 && this.env.hamburgerVisible)
{
this.env.hamburgerVisible = false;
callback.invokeMethodAsync('Invoke', size.height, size.width);
}
}
}; This should be loaded after the blazor WASM framework initialization. <script src="_framework/blazor.webassembly.js"></script>
<script src="/js/windowResize.js"></script> We then use type Size(h:int, w:int) =
member this.Height with get() = h
member this.Width with get() = w
new() = Size(0,0)
type Callback =
static member OfSize(f) =
DotNetObjectReference.Create(SizeCallback(f))
and SizeCallback(f: Size -> unit) =
[<JSInvokable>]
member this.Invoke(arg1, arg2) =
f (Size(arg1, arg2))
type Message =
| Initialize
| WindowResize of Size
let update (jsRuntime:IJSRuntime) message model =
let setupJSCallback =
Cmd.ofSub (fun dispatch ->
// given a size, dispatch a message
let onResize = dispatch << WindowResize
jsRuntime.InvokeVoidAsync("generalFunctions.initResizeCallback", Callback.OfSize onResize).AsTask() |> ignore
)
match message with
| Initialize -> model, setupJSCallback
| WindowResize size ->
// handle window resize message
model, Cmd.none |
@BentTranberg Bulma calendar sample https://github.com/wilsoncg/BoleroBulmaCalendar |
@wilsoncg: Your code helped me a lot to understand this. Thx! I too would wish this was part of the Bolero documentation. I generalized the JSInvokables, so I don't have to declare a new wrapper class for each: type FunWrapper<'a> (f: 'a -> unit) =
[<JSInvokable>]
member this.Invoke(arg) =
f(arg)
type FunWrapper2<'a, 'b> (f: 'a -> 'b -> unit) =
[<JSInvokable>]
member this.Invoke(arg1, arg2) =
f(arg1, arg2)
let funWrapper (f) =
DotNetObjectReference.Create(FunWrapper(f))
let funWrapper2 (f) =
DotNetObjectReference.Create(FunWrapper2(f)) |
@MichaelMay81, @Tarmil I created a pull request some time ago |
I think that the example is a bit to complicated for the documentation. initResizeCallback: function(onResize) {
window.addEventListener('resize', (ev) => {
onResize.invokeMethodAsync('Invoke', window.innerHeight, window.innerWidth);
});
} type SizeCallback(f: int -> int -> unit) =
[<JSInvokable>]
member this.Invoke(arg1, arg2) =
f (Size(arg1, arg2))
let ofSize f = DotNetObjectReference.Create(SizeCallback(f))
let initResizeCallback dispatch jsRuntime =
let onResize h w = dispatch (WindowResize (h,w))
jsRuntime.InvokeVoidAsync("initResizeCallback", ofSize onResize).AsTask() |> ignore |
I have previously experimented a bit with Fable and React and really like the experience, but compiling to JS is a significant drawback for a .NET dev like me since 1) it's a different runtime environment than I'm used to and I have to constantly think about that while coding, and 2) I can't use any nugets except those few that have been specifically designed to work with Fable (i.e., includes sources in the .nupkg).
I came across Bolero in a recent F# Weekly, and was thrilled, to say the least. I know next to nothing about Blazor or WebAssembly, but AFAIK this project makes it possible to run .NET code directly in the browser, using nugets and a runtime environment that is familiar to .NET devs (please correct me if I'm wrong).
However, having delved into Fable/React/Material-UI a lot recently, I have two questions:
JS interop. The Writing HTML part of the docs shows plain old HTML elements. In order to create the rich/complex UIs often needed today, one might need packages/frameworks like Bootstrap, Bulma, React, or Material-UI (which requires React). The JS ecosystem is incredibly rich. Is there a story here for Bolero? Is it possible? Simple? Are there similar/better alternatives?
Elmish performance. Fable/React/Elmish uses React for rendering, which is lightning quick. Can I expect the Bolero/Elmish combo to scale similarly (using
ElmishComponent
andShouldRender
similarly tolazy
in Elmish.React)?The text was updated successfully, but these errors were encountered: