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.
Thank you, Ines. This is helpful.
My javascript abilities are really rudimentary, as I usually only use Meteor and React 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
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_script.js
function doSomething() {
console.log('The current task is:', window.prodigy.content)
}
This approach should let you write pretty straightforward Vanilla JavaScript 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
})
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?
Ahh, nice! 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.