Sorry for the essay, couldn't find a short way to explain this :(
Context
I am currently building a WebPart for Office 365 which is pretty much like a client side App that users can add to their page and configure through a UI.
The WebPart renders some content, and I would like users to be able to provide an external script that could run AFTER the rendering process. So far this is easy, I could simply do something like this in the WebPart code :
// Render the WebPart
this.render();
// Load external script
this.loadExternalScript(this.props.externalScriptUrl);
The issue is I would like the external script to act like a callback, that my WebPart could call in order to provide some context to it.
Solution 1
The first solution that I found, was to provide guidance for the users on how to create their external script using a specific namespace based on the file name, and a precise function so that way my WebPart could :
- Render
- Load the external script (Which would populate that specific namespace)
- Retrieve the external script's namespace (based on the file name)
- Call the external script callback using the namespace and provide the context
That works well and it looks like this :
MyExternalScript.js
MyNamespace.ExternalScripts.MyExternalScript = {
onPostRender: function(wpContext) {
console.log(wpContext);
}
}
WebPart
// Render the WebPart
this.render();
// Load external script
this.loadExternalScript(this.props.externalScriptUrl);
// Calls the script callback if available
var scriptNamespace = MyNamespace.ExternalScripts.MyExternalScript;
var scriptCallback = scriptNamespace ? scriptNamespace.onPostRender : null;
if(scriptCallback) {
scriptCallback(this.wpContext);
}
That works fine but building the namespace based on the file's name is a pretty sketchy thing to ask users to do, and I would love to find something more straight forward than this hacky solution.
Solution 2
Another solution I thought about was to do the following :
- Remove the funky namespace from external script and leave defined function signature
- Load script content as a string
- Let the WebPart dynamically create a unique namespace name for that particular script
- Prepend the script content with the namespace
- eval() the whole thing
- Call the callback
That would look along these lines :
MyExternalScript.js
onPostRender: function(wpContext) {
console.log(wpContext);
}
WebPart
// Render the WebPart
this.render();
// Load external script content
$scriptContent = this.readExternalScript(this.props.externalScriptUrl);
// Append unique namespace
$scriptContent = "MyNamespace.ExternalScripts.MyExternalScript = {" + $scriptContent + "}";
// Eval everything within that namespace
eval($scriptContent);
// Calls the script callback if available
var scriptNamespace = MyNamespace.ExternalScripts.MyExternalScript;
var scriptCallback = scriptNamespace ? scriptNamespace.onPostRender : null;
if(scriptCallback) {
scriptCallback(this.wpContext);
}
I did some quick testing and that looks like it's working, the fact that the WebPart dynamically generates the namespace is way better than asking the user to comply to a complicated namespace, however I am not sure if there is a better solution than using eval().
All I need at the end of the day is to find a way to make my WebPart "aware" of the callback it needs to call. I also have to make sure that the namespacing is WebPart-unique and script-unique as there could be 4 WebParts on the same page, loading different scripts, so I have to avoid namespace conflicts at all costs.
Anyone have a better idea?
Thanks!