0

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!

2 Answers 2

0

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)
Sign up to request clarification or add additional context in comments.

1 Comment

Actually I like this Idea with the Function, in my case it’s the opposite though : The user doesn’t have access to the WebPart code, he can only provide a URL for his external script and it’s not the external script that must be able to call a onPostRender defined in the WebPart, it’s actually the opposite, the WebPart must call a onPostRender defined in the external script. But your example is still relevant, I will answer with how I could use it as soon as I am on a computer (cellphone right now) thanks
0

So as explained in my comment, the main goal is to be able to execute an external script which has access to a "magic" variable "wpContext" provided by the WebPart.

The timing on which the external script is called/evaluated isn't really important since it's up to the WebPart to decide when he wants to call it.

Following your example, I think it would look more like this :

WebPart

// Render the WebPart
this.render();

// Load the external script
var runExternalScript = Function('wpContext', `
  // External script content
  console.log('External script is able to use the WebPart context!');
  console.log(wpContext);
`);

// Run the external script while providing the current WebPart's context
runExternalScript(this.wpcontext)

This is far more elegant than my eval() because I do not have any namespace to generate, and the external script doesn't even have to comply to a specific function signature in order for the WebPart to retrieve it, it can simply use the "wpContext" variable straight up in it's content.

So even if that looks like a better solution, am I missing another solution that wouldn't require the use of eval/Function or this is pretty much the way to go?

Thanks!

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.