toolness / p5.js-widget

A reusable widget for embedding editable p5 sketches in web pages.
https://toolness.github.io/p5.js-widget/
GNU Lesser General Public License v2.1
162 stars 44 forks source link

Allow the embedder to specify p5 addon libraries to load #53

Open toolness opened 8 years ago

toolness commented 8 years ago

Might be nice for tutorials that want to use p5.sound, p5.dom, et cetera.

toolness commented 8 years ago

Hmm, so this might actually be a bit trickier than I thought... In preview-frame.ts, we're loading the p5.js script asynchronously, but we're also counting on it to notice the setup/draw functions in an inline <script> tag, and then immediately activate global mode and start the sketch itself.

The challenge with adding p5 add-on libraries is that they need to be added after the main p5 script is loaded (or else their calls to e.g. p5.prototype.registerPreloadMethod() will fail), but before the sketch executes.

Normally this is just done by adding another <script> tag to one's HTML file, but because the preview widget is doing things dynamically and loading things asynchronously, that's hard. By the time the main p5 library has loaded, document.readyState will likely be complete, so it'll immediately check for the existence of draw/render to start up a sketch in global mode. If it finds them, it'll immediately start the sketch--giving us no time to load more add-on libraries; if it doesn't find them, our "chance" to use global mode has passed us by, as there's no documented way for us to start p5 global mode explicitly.

There might be a few ways to work around this:

  1. We could have the preview frame create yet another iframe within it, whose HTML document it dynamically creates via document.write(). This way it could write out all the script tags at once and loading/execution would proceed as expected.
  2. We could write a bunch of mangly code to convert the user's sketch to use p5 instance mode, which we then activate manually once we've loaded all the add-on libraries. This code would probably crib from the mangly code in the p5 website's render.js... However, we'd have to be very careful to preserve line numbers so that errors would still be helpful. The mangly code would also be tightly coupled to p5, so as e.g. new global event-handling methods like mouseClicked are added--or new ones are introduced by add-on libraries--the mangly code would become out-of-date. I really don't like this option.
  3. We could add a documented/supported mechanism to p5 that allows environments like the widget to instantiate p5 global mode explicitly, after all dependencies have been loaded and such. I think there's actually a "hacky" way to go about doing this--iirc, just calling new p5() without any arguments will actually do it--but we'd want to make sure it's actually documented, so that it continues to be supported going forward.

I think I like options 1 and 3. Option 2 is right out.

@kaganjd does any of this make sense? It's a lot more complicated than I thought it'd be, but I think we can still do it! (Sorry I said it'd be easy, though... :persevere:)

kaganjd commented 8 years ago

@toolness I've been thinking about option 1. Just to make sure I'm on the right track, are you saying that instead of the loadP5 function in preview-frame.js [1], we'd have something like a writeToIframe() function that writes preview-frame.html? [2]

[1] https://github.com/toolness/p5.js-widget/blob/7670852f7baf1faf7619c82ebfebd00fd29a8822/lib/preview-frame.ts#L12 [2] https://gist.github.com/kaganjd/fef5209591233473c2cd7a8e796997be

toolness commented 8 years ago

Yep! Or slightly more accurately, creates an iframe within preview-frame.html that it writes to. I think that there will ultimately be a ridiculous number of iframes here:

It's important to note that in an ideal scenario, we'd actually host preview-frame.html on a separate domain to prevent cross-site scripting attacks (see #57). We'd like to do that eventually, which is why we want preview-frame.html to actually exist (otherwise one could argue that we should simplify the situation and remove preview-frame.html from this picture entirely).

Ummm I think that's one way forward, at least. Wanna make a protoype? :grin:

toolness commented 8 years ago

Blergh now I am starting to wonder if there are other ways to do this without making yet another iframe. I think we actually re-create the preview frame every time the user changes their code, in which case we could potentially just deliver all the required information through querystring arguments... Arg, why is this so hard.

Sorry about this, I really thought it'd be a lot easier!

kaganjd commented 8 years ago

Maybe Javascript promises could be useful here? I'll start looking into that. And qs arguments :)

jaredhirsch commented 8 years ago

I don't have a lot of p5 experience, but just in straight front-end programming terms, option 3 sounds like the least amount of work :+1:

Changing the semantics of new p5() seems unlikely, which is good. And, if that change does happen, I guess p5.js-widget could hang back with the last working p5 version until option 1 or 2 gets implemented.

toolness commented 8 years ago

Good point @6a68 ! Thanks for the feedback.

JunShern commented 6 years ago

Hi @toolness, did you get anywhere on this? I'm working on a tutorial for p5.sound right now and would love to use your widget for this (it would be very helpful to have p5.dom as well).

I suppose that based on your answer about the script loading order, something simple like adding the add-on library urls to the loadScripts part of the code won't work?

ghost commented 6 years ago

Hi @toolness, Just LOVE your p5.js-widget! I wonder if anyone found an answer to how to add on p5.dom to it? I would like to use the createSlider on some tutorials.