Import HTML Template And React

custom
front-end

(Neal Lewis) #1

Hi. I apologize if this is a duplicate. But I noticed that after version 1.5 there is support for external javascript.

So three questions:

  1. Can I import an html file for the template instead of using a string?
{'html_template': 'file:///path/to/template.html'}
  1. Is it still recommended to import JS libs in static/index.html ?

  2. Since this is a React App, is it possible to us a simple react component, like a chart or graph?

Thank you!


(Ines Montani) #2

Hi! Support for JavaScript is still slightly experimental. If you haven’t seen it already, you can find some examples and background on how it all works in this thread:

At the moment, the html_template expects a string (to make it easier to generate it programmaticaly in Python etc.). So you’d have to do something like:

with open('your_file.html') as f:
    html_template = f.read()

That’s probably the most straightforward solution, yes.

If it’s a small dependency, you could also copy-paste it into the main JS code you load into Prodigy. Alternatively, you could also import / require your JS dependencies your .js file, then use a bundler like Parcel or Webpack to bundle it all together and finally load the contents of that bundle and pass it forward to Prodigy as the 'javascript' config option. You could even orchestrate all of these processes from Python. But this can easily be overkill and I’d always advocate for simpler solutions with fewer build tools wherever possible.

The Prodigy app uses React under the hood, but it’s a compiled app – so there’s no easy way to insert React components via external JavaScript. Even if we did ship all the source with it, you’d still need to add your components and compile the whole thing from scratch. (You could maybe render a React app within the compiled React app, but I haven’t tried this myself, so not sure if this works as expected.)

We’ve been thinking about ways to make this easier – e.g. to allow importing Prodigy as a component, passing in your own interfaces (which are also React components that all receive the same props as the built-in interfaces) and rebuilding the app to create a custom bundle. If we wrap this using something like create-react-app, we could even support something close to a zero-config setup for users who are not so experienced with JavaScript build tooling.


(Neal Lewis) #3

Thank you, Ines. This is helpful.
My javascript abilities are really rudimentary, as I usually only use Meteor and React :slight_smile: so, I have a simple question: Do you add the .js file in the static directory? In this case I mean your Uppercase Button .js


(Ines Montani) #4

No worries and sorry if I phrased this in a confusing way!

The latest version of Prodigy now supports passing in a 'javascript' option that also accepts a string of JavaScript code, just like the html_template does for HTML. The JavaScript will be added as the last child of the page’s <body>, so after everything else. Here’s some example recipe code:

@prodigy.recipe('custom-recipe')
def custom_recipe():
    # some recipe code here...

    with open('/path/to/your_html.html') as f:
        html_template = f.read()

    with open('/path/to/your_script.js') as f:
        javascript = f.read()

    return {
        'view_id': 'html',
        'config': {
            'html_template': html_template,
            'javascript': javascript
        }
        # etc.
    }

Your two files could then look like this:

<!--- your_html.html -->
<button onClick="doSomething()">Click me!</button>
// your_script.js
function doSomething() {
    console.log('The current task is:', window.prodigy.content)
}

This approach should let you write pretty straightforward Vanilla JavaScript :smiley: Just keep in mind that you need to wait for the app to be rendered if you want your JavaScript to reference or select elements in your HTML template. Otherwise, the element might not exist yet and you’ll see an error (also see this comment). onClick handlers are fine, because they’re only executed once the user interacts with the element.

Prodigy fires a few custom events, including prodigymount when the app is mounted. So you can listen to this event to only execute your code once the app is ready:

<div class="your-div">Hello world</div>
document.addEventListener('prodigymount', event => {
    var div = document.querySelector('.your-div');
    // do something here
})

(Neal Lewis) #5

Oh Awesome!!! Thank you! I didn’t really see this in the documentation, did I miss it?

I think because I wasn’t using a custom recipe, just the mark recipe that is provided. I’ll work on the custom one now.

Thanks again, Ines, this is very helpful :slight_smile:


(Neal Lewis) #6

Ines, one last question:

Is it expected that the window.prodigy.content to change with each task?

I ask because I used the addEventListener('prodigymount') example like you suggested, but it only loads the javascript on the first task, and not for the remaining (after I select accept / reject).

If I need another event listener, which one would it be?

Thank you!


(Neal Lewis) #7

Nevermind!! you had it in the link above :slight_smile:

So, here is what I did. I wanted to display a radar chart along with the a table, so here is the js incase anyone else wants to know:

function displayRadar(){
    var ctx = document.getElementById('radarChart').getContext('2d');
    var rdata = window.prodigy.content.radar
    var myRadarChart = new Chart(ctx, {
    type: 'radar',
        data: rdata
        });
}

document.addEventListener('prodigymount', event => {
        displayRadar();
});

document.addEventListener('prodigyanswer', event => {
        displayRadar();
});

html:

<canvas id="radarChart"></canvas>

And added this to the index.html:

<body>
       <div id="root"></div>
       <script src="bundle.js"></script>
       <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.4.0/Chart.min.js"></script>
</body>

(Ines Montani) #8

Ahh, nice! :+1: :tada: Glad you got it working – and it’s only a few lines of code!

Just putting it here again in case others come across this issue later: You can think of prodigymount as a hook similar to React’s componentDidMount() – in fact, the event is fired within the component’s componentDidMount lifecycle method. There’s also a prodigyupdate, which is fired in componentDidUpdate. Here’s a quick overview:

Event Name Fired when React lifecycle
prodigymount Custom HTML interface mounts componentDidMount
prodigyupdate Custom HTML interface updates (e.g. new content) componentDidUpdate
prodigyanswer User submits an answer (accept, reject, ignore) -

Sorry about the lack of proper documentation, but as I mentioned earlier, the functionality is still experimental, so it’s currently mostly documented on here. But it’s been working pretty well so far, so we’re happy to make it “official” in the next minor release version.