TinyMCE 5: URL Dialog Component and Window Messaging

Published May 30th, 2019

Important: The information in this article is over 12 months old, and may be out of date or no longer relevant.

But hey, you're here anyway, so give it a read see if it still applies to you.

When TinyMCE 5 launched, there was a bit of an outcry that iframe support for dialogs had been removed – so much so that it stopped many developers from upgrading. The Tiny team heard the outcry, and have implemented a new URL Dialog UI Component.

And it’s a brilliant implementation.

So let’s start with the basics: what exactly is it? It’s a new UI Component that allows you to open an external URL in an iframe, and have easy two-way messaging between TinyMCE (and any plugins you create) and the iframe itself.

It’s that last part that gets me excited.

The way the team have implemented this makes it incredibly easy to:

  • Have an iframe in a Dialog

  • Send messages back to TinyMCE

  • Have TinyMCE send messages back to your iframe

This opens up so much potential for developing complicated interfaces in an iframe but still have rich and user-friendly interactions with TinyMCE.

Before you go on, this is an advanced topic – if you’re new to working with TinyMCE 5 plugins, check out my “Hello World” blog post to get you started with writing a plugin.

As always, refer to the Tiny URL Dialog documentation for full details.

OK, on to the good stuff.

But... I’ll just start with this point. Just because you can, does that mean you should? Short answer, of course, longer answer… if you can make it look good.

The UI Component library that launched with TinyMCE 5 gives users a brilliant and consistent experience. But developing complex interfaces with the Tiny UI Components is a bit restrictive – so the ability to use an iframe does allow for complex interface design.

But now the onus is on the developer to create a good user experience, and one that is on par with that provided by the Tiny UI Components. Sure, it may not be identical, but some developers do have the ability to create… uh… questionable interfaces. I know for any implementations that I make, the focus will be on creating a quality (and attractive) user experience.

Where would I use this?

  • external file managers 

  • external link browsers (that hook in to your CMS and can help insert valid links for example)

  • interfaces that require more complex layouts that the v5 UI Components can't replicate

  • where interfaces fall outside the UI library altogether

Ultimately there is so much potential for the URL Dialog components - and I have so many ideas for how to integrate smart-looking iframes in to my projects.

Opening an external URL in a Dialog

Start with the basics – let’s make a plugin that opens an external URL in an iframe.

To get started, make a new HTML file, and put some content in there. Heck, a “Hello World” paragraph would do. Mine is called “iframe.html”, and is in the same folder as my main TinyMCE page. This is what we’ll open in the URL Dialog.

Opening the dialog requires two things:

  1. Configuration of the URL Dialog

  2. A trigger to open it

The configuration Is a simple object – at a minimum, you need to provide a title and url for your configuration. We’ll also add two buttons, and an “onAction” handler for when our “action” button is pressed, plus a width and height.

1var _urlDialogConfig = {
2 title: 'Simple URL Dialog Demo',
3 url: 'iframe-simple.html',
4 buttons: [
5 {
6 type: 'custom',
7 name: 'action',
8 text: 'Submit',
9 primary: true,
10 },
11 {
12 type: 'cancel',
13 name: 'cancel',
14 text: 'Close Dialog'
15 }
16 ],
17 onAction: function (instance, trigger) {
18 // do something
19 editor.windowManager.alert('onAction is running.
21You can code your own onAction handler within the plugin.');
23 // close the dialog
24 instance.close();
25 },
26 width: 600,
27 height: 300

title and url are pretty self explanatory.

buttons is looking for an array of button configuration objects – setting a typenametext (label) or icon. We can also set the primary button, and whether they should be at the left or right of the dialog window (right by default, FYI). Check out the Button configuration docs for full details.

onAction is a handler, with the instance, and the triggering button. Any button of type “custom” will trigger this handler – and we can do whatever processing we need. This will accept an instance (the URL Dialog component) and trigger (the details of the button triggering the action).

There are also onClose and onCancel handlers you could add too for when the window is closed or the cancel operation is triggered – maybe you need to do some extra cleanup before letting the user get back to it – the documentation lists all the configuration options.

We also need to add a button (and maybe a menu item too if you need) to trigger the URL Dialog. For a button, simply call the Window Manager’s openUrl method with your dialog configuration:

1editor.ui.registry.addButton('iframe', {
2 text: "Open Advanced URL Dialog",
3 icon: 'frame',
4 onAction: () => {
5 _api = editor.windowManager.openUrl(_urlDialogConfig)
6 }

At the very bare basics, this plugin will open “iframe.html” in a URL Dialog Component. In the source code repository, you can find the “iframe-simple” plugin to see the full working source code.

2 * Very simple example Plugin utilising URL Dialog
3 *
4 * @author Marty Friedel
5 */
6(function () {
7 var iframe = (function () {
8 'use strict';
10 tinymce.PluginManager.add("iframe-simple", function (editor, url) {
12 /*
13 Add a custom icon to TinyMCE
14 */
15 editor.ui.registry.addIcon('frame', '');
17 /*
18 Used to store a reference to the dialog when we have opened it
19 */
20 var _api = false;
22 /*
23 Define configuration for the iframe
24 */
25 var _urlDialogConfig = {
26 title: 'Simple URL Dialog Demo',
27 url: 'iframe-simple.html',
28 buttons: [
29 {
30 type: 'custom',
31 name: 'action',
32 text: 'Submit',
33 primary: true,
34 },
35 {
36 type: 'cancel',
37 name: 'cancel',
38 text: 'Close Dialog'
39 }
40 ],
41 onAction: function (instance, trigger) {
42 // do something
43 editor.windowManager.alert('onAction is running.<br /><br />You can code your own onAction handler within the plugin.');
45 // close the dialog
46 instance.close();
47 },
48 width: 600,
49 height: 300
50 };
52 // Define the Toolbar button
53 editor.ui.registry.addButton('iframe-simple', {
54 text: "Open Simple URL Dialog",
55 icon: 'frame',
56 onAction: () => {
57 _api = editor.windowManager.openUrl(_urlDialogConfig)
58 }
59 });
61 });
62 }());

Handling Multiple URL Dialog Component Buttons

You may need to have multiple “custom” buttons defined – maybe one might “insert” content while another may “replace”.

When your onAction handler is running, the second parameter, trigger, will let us know which button was used by looking at the “name”. We can look at that name and then determine what action we need to be running.

In this configuration, we’re simply going to insert a new paragraph telling us which button was used – basically a proof of concept to see that within our onAction handler we do know where the trigger came from.

2 buttons: [
3 {
4 type: 'custom',
5 name: 'action1',
6 text: 'Action Button 1',
7 primary: true,
8 align: 'end'
9 },
10 {
11 type: 'custom',
12 name: 'action2',
13 text: 'Action Button 2',
14 primary: false,
15 align: 'end'
16 },
17 {
18 type: 'cancel',
19 name: 'cancel',
20 text: 'Close Dialog',
21 primary: false,
22 align: 'end'
23 }
24 ],
25 onAction: function (instance, trigger) {
26 // alert to say the button we clicked.
27 // it will be either "action1" or "action2"
28 alert('You clicked ' + trigger.name);
29 }

Blocking (and unblocking) the URL Dialog

There may be times when you need to do some processing on within your iframe content. Maybe loading from the server, maybe something just takes time to process – but just like the “Hello World” plugin example, it is easy to block and unblock a URL Dialog.

We have access to block and unblock functions that help us out. Calling block on your instance stops users from interacting with your URL Dialog, and then unblock releases it.

We can do this in two places:

  1. The Plugin code

  2. The iframe javascript code

In the Plugin, block and unblock work just like the “Hello World” example – so you should be all set to go there (or check out the Creating a Custom Dialog Plugin blog post)

But we can also do that by sending a message to TinyMCE to handle this for us. We’ll look at that in the next section.

Sending messages to TinyMCE

Sending a message from our iframe back to our TinyMCE is incredibly easy using the browser’s postMessage API.

There are two types of messages we may want to send:

  1. A message to do something to the URL Dialog Component

  2. A message to do something else within TinyMCE

Either way, we send our messages in the same simple way.

TinyMCE requires we send an object with a mceAction property – and that’s the basic gist of it all. Without a mceAction, TinyMCE will ignore any message – so make sure you add something – what it is, well, that depends on what you need to do… more on that as we go.

So we talked about block and unblock at the Plugin level – but using the postMessage API we can easily call them from within the iframe too.

1// send the "block" mceAction
3 mceAction: 'block',
4 message: 'Blocking from iframe'
5}, origin);
7// wait 2 seconds
8setTimeout(() => {
9 // send the "unblock" mceAction
10 window.parent.postMessage({
11 mceAction: 'unblock'
12 }, origin);
13}, 2000);

This is a simple example that will block the URL Dialog Component, and then 2 seconds later, unblock it again. Purely for demonstration fun – a real-world use would be to block before you start an AJAX call, perform your call, then call unblock when your processing is complete. I’ve just used “setInterval” to simulate that processing time.

But how easy is that? TinyMCE just knows what to do.

We can go one step further and also call additional commands within TinyMCE – looking through the source code shows a number of commands exposed in the core and in a various core plugins. Maybe you want to select all of your content and make it bold – we can run the execCommand and trigger the ‘selectAll’ and ‘Bold’ internal Commands.

1// send the "execCommand" mceAction to call the "selectAll" command
3 mceAction: 'execCommand',
4 cmd: 'selectAll'
7// send the "Bold" command
9 mceAction: 'execCommand',
10 cmd: 'Bold'

The official Tiny documentation does expose the commands for the core plugins – so there may be something in there for you: https://www.tiny.cloud/docs/

iframe telling TinyMCE to do something unique

Sending a message from your iframe to TinyMCE is easy – but what about when you want to do more than just something on the URL Dialog Component or a core TinyMCE Command? We need to also be able to perform our own actions.

There are two ways you could accomplish this:

  1. Create a new Command in your plugin

  2. Use the URL Dialog’s onMessage handler

Creating a Command can be done within your Plugin file, and you can write your logic. The really cool part of adding your own Commands is that they become global – if you have multiple plugins, they can call Commands defined by other Plugins.

1editor.addCommand('myCommandName', function (ui, value) {
2 // write your code to do something when
3 // the "myCommandName" execCommand is triggered.
4 //
5 // remember, any commands you create are also
6 // available in your other plugins too

You can also use the onMessage handler in the URL Dialog configuration to receive messages back in TinyMCE.

Just like onAction, you define a method to handle the message coming back to the plugin:

2 title: 'URL Dialog Demo',
3 url: 'iframe.html',
4 buttons: [
5 ...
6 ],
7 onAction: function (instance, trigger) {
8 // code for when "custom" buttons are triggered
9 },
10 onMessage: function (instance, data) {
11 // we can do something here with the
12 // instance and the data of the message
13 switch(data.mceAction)
14 {
15 case 'insertContent':
16 // run code for inserting content
17 break;
18 case 'replaceContent':
19 // run code for replacing the content
20 break;
21 //
22 // etc...
23 //
24 }
25 }

To trigger onMessage, we send a postMessage with a string that isn’t a TinyMCE Command as the “mceAction”, and a data object.

Given you may need your iframe to send different types of messages to do different things, it is a good idea to set “mceAction” as a faux-function name as to what the message needs to do. Within your onMessage handler, you can then do something based on this faux-function name that gets received.

2 mceAction: 'sayName',
3 data: {
4 name: document.getElementById('dialog-name').value
5 }

So in this example, my onMessage handler will run specific code for the “sayName” action, and use the “name” from the data attribute to do so.

1onMessage: function (instance, data) {
2 // what action should we perform?
3 switch (data.mceAction)
4 {
5 case 'sayName':
6 if (data.data.name == '')
7 {
8 // display an error
9 editor.windowManager.alert('You need to enter your name.');
10 }
11 else
12 {
13 // say hello
14 editor.windowManager.alert('Hi there ' + data.data.name + ' - nice to meet you!');
16 // close the window
17 instance.close();
18 }
19 break;
20 case 'anotherAction':
21 break;
22 // ...
23 // etc
24 }

So which to choose? Command or onMessage?

For me, if my URL Dialog was running actions that are solely for that Plugin, onMessage may be a better way to go as it keeps everything within the plugin (and not exposed to other plugins).

If my URL Dialog was needing to run actions that are added through core TinyMCE functionality or other plugins, adding new Commands may be better suited.

I honestly don’t see there being one way better than the other – they both have totally valid use cases, so it’s great that the Tiny developers have given us both options.

Putting it all together

This is what you’re here for, right? A full working example of a URL Dialog with:

  • An external file being displayed

  • Dialog button actions

  • Messaging to TinyMCE using commands

  • Messaging to TinyMCE using onMessage

  • Messaging from TinyMCE back to the external file

Yeah, lots in here. Take a look at it all working. Note that window positioning is a little kooky given how long this page is.

You can also see all of the plugins working at https://tinymce.martyfriedel.com/

It’s not really the most exciting demo to visually see, but my repository on Github has all the source code for this (and all the plugin demonstrations) which may be really useful and helpful for you. Check it out! https://github.com/martyf/tinymce-5-plugin-playground

You may be interested in...