fsprojects / IfSharp

F# for Jupyter Notebooks
Other
442 stars 71 forks source link

Loading Javascript results in "undefined" functions #192

Closed secana closed 5 years ago

secana commented 5 years ago

Description

When a JavaScript library is loaded, the exposed functions cannot be called because the are undefined.

Repro steps

  1. Step A Load some arbitrary JS library: @"<script src=""https://unpkg.com/sweetalert/dist/sweetalert.min.js""></script>" |> Util.Html |> Display (Lib to show "nice" alerts)

  2. Step B Try to execute some function from the JS library: """<script type="text/javascript">swal("Hello World!")</script>""" |> Util.Html |> Display

Expected behavior

Alert popup with the text "Hello World".

Actual behavior

Uncaught ReferenceError: swal is not defined
    at <anonymous>:1:1
    at m (jquery.min.js:2)
    at Re (jquery.min.js:2)
    at w.fn.init.append (jquery.min.js:2)
    at OutputArea._safe_append (outputarea.js:457)
    at OutputArea.append_display_data (outputarea.js:660)
    at OutputArea.append_output (outputarea.js:346)
    at OutputArea.handle_output (outputarea.js:257)
    at output (codecell.js:391)
    at Kernel._handle_output_message (kernel.js:1196)

I can see in the browser debug view that the library itself was downloaded but the function is not callable.

Related information

I've used a Docker image to start a local Jupyter Notebook: docker run --rm --name jupyter -it -v /home/user/notebookfolder:/notebooks -p 8888:8888 godu/ifsharp

cgravill commented 5 years ago

So what's happening here is that the Jupyter front-end uses RequireJS and the library Sweet Alert library is configured to adapt to this. There are two approaches to dealing with this. One is that you can make use of RequireJS more like this:

https://www.stefaanlippens.net/jupyter-custom-d3-visualization.html

Sketched implementation:

"""<script>
require.config({
    paths: {
        sweetalert: 'https://unpkg.com/sweetalert/dist/sweetalert.min'
    }
});
</script>""" |> Util.Html |> Display
"""
<script>
    (function() {
        require(['sweetalert'], function(swal) {
            swal('Hi Stefan');
        });
    })();
</script>""" |> Util.Html |> Display

image

The other way is if you don't want the module system, you can hide it and stick JavaScript in global scope (which has issues but convenient in a notebook context):

let wc = new System.Net.WebClient()

wc.DownloadString("https://unpkg.com/sweetalert/dist/sweetalert.min")
|> sprintf
    """
<script type="text/javascript">
var require_save = require;
var requirejs_save = requirejs;
var define_save = define;
require = requirejs = define = undefined;
%s
require = require_save;
requirejs = requirejs_save;
define = define_save;
"""
|> Util.Html
|> Display
"""<script type="text/javascript">swal("Hello World!")</script>""" |> Util.Html |> Display

image

This second approach is what we use in: https://github.com/fsprojects/IfSharp/blob/f1d6c88e834df4208edfca863d9f0f9d93bb59fa/src/IfSharp/XPlot.GoogleCharts.fsx#L11 https://github.com/fsprojects/IfSharp/blob/f1d6c88e834df4208edfca863d9f0f9d93bb59fa/src/IfSharp/XPlot.Plotly.fsx#L18

where we decided to just make the functions global and inline the scripts.

We've discussed potentially making general helper scripts for this but I'm not quite sure of the right API and modules or not.

Hope this helps.

secana commented 5 years ago

Hi @cgravill,

I got it working with your second approach (there is a closing missing). Thanks a lot for the answer. Without it, my project idea would have been dead.

Regards, Stefan

cgravill commented 5 years ago

Great, glad it was useful. Funny my test worked without closing in my browser!

I had a few thoughts of ways we could make this more usable (note they're all proposals, not implemented): sweetalert

The wrapped functions is the easiest to implement but would be standardising use of global scope which perhaps isn't ideal. That would fit in with what's been done elsewhere.

@dsyme and @tpetricek do either of you have a view on what would makes sense together with other interactive and notebook-variant uses of F#?

tpetricek commented 5 years ago

All my IFSharp experiments involving JavaScript were somewhat hacky - so I guess that's what the standard situation is :-). I think having helpers like the ones in your first example would be a good starting point - it should help with getting some common scenarios to work (and we could see if people actually use it, how and whether it works for them before trying to do something more clever...)

cgravill commented 5 years ago

Great, I've merged that syntax and I'll go ahead and close this issue. We can do clever things later if they seem needed.