javascriptnamespacesevaljavascript-namespaces

Javascript : Eval external script within defined namespace


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 :

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 :

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!


Solution

  • I don't quite understand the context here but how about passing in the post render function as a callback?

    var runExternalScript = Function('onPostRender', `
    // your external script
      console.log('rendering...');
      console.log('rendering finished');
      if (onPostRender) onPostRender();
    `)
    
    function postCallback(){ console.log('finished!') }
    
    runExternalScript(postCallback)