Blog: JS

TinyMCE 5: URL Dialog Component and Window Messaging

Published

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.

What’s in this article?

  1. Opening an external URL in a Dialog
  2. Handling Multiple URL Dialog Component Buttons
  3. Blocking (and unblocking) the URL Dialog
  4. Sending messages to TinyMCE
  5. iframe telling TinyMCE to do something unique
  6. Putting it all together

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.

var _urlDialogConfig = {
    title: 'Simple URL Dialog Demo',
    url: 'iframe-simple.html',
    buttons: [
        {
            type: 'custom',
            name: 'action',
            text: 'Submit',
            primary: true,
        },
        {
            type: 'cancel',
            name: 'cancel',
            text: 'Close Dialog'
        }
    ],
    onAction: function (instance, trigger) {
        // do something
        editor.windowManager.alert('onAction is running.

You can code your own onAction handler within the plugin.'); // close the dialog instance.close(); }, width: 600, height: 300 };

title and url are pretty self explanatory.

buttons is looking for an array of button configuration objects – setting a type, name, text (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:

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

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.

/**
 * Very simple example Plugin utilising URL Dialog
 *
 * @author Marty Friedel
 */
(function () {
    var iframe = (function () {
        'use strict';

        tinymce.PluginManager.add("iframe-simple", function (editor, url) {

            /*
            Add a custom icon to TinyMCE
             */
            editor.ui.registry.addIcon('frame', '');

            /*
            Used to store a reference to the dialog when we have opened it
             */
            var _api = false;

            /*
            Define configuration for the iframe
             */
            var _urlDialogConfig = {
                title: 'Simple URL Dialog Demo',
                url: 'iframe-simple.html',
                buttons: [
                    {
                        type: 'custom',
                        name: 'action',
                        text: 'Submit',
                        primary: true,
                    },
                    {
                        type: 'cancel',
                        name: 'cancel',
                        text: 'Close Dialog'
                    }
                ],
                onAction: function (instance, trigger) {
                    // do something
                    editor.windowManager.alert('onAction is running.<br /><br />You can code your own onAction handler within the plugin.');

                    // close the dialog
                    instance.close();
                },
                width: 600,
                height: 300
            };

            // Define the Toolbar button
            editor.ui.registry.addButton('iframe-simple', {
                text: "Open Simple URL Dialog",
                icon: 'frame',
                onAction: () => {
                    _api = editor.windowManager.openUrl(_urlDialogConfig)
                }
            });

        });
    }());
})();

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.

{
    buttons: [
        {
            type: 'custom',
            name: 'action1',
            text: 'Action Button 1',
            primary: true,
            align: 'end'
        },
        {
            type: 'custom',
            name: 'action2',
            text: 'Action Button 2',
            primary: false,
            align: 'end'
        },
        {
            type: 'cancel',
            name: 'cancel',
            text: 'Close Dialog',
            primary: false,
            align: 'end'
        }
    ],
    onAction: function (instance, trigger) {
        // alert to say the button we clicked.
        // it will be either "action1" or "action2"
        alert('You clicked ' + trigger.name);
    }
}

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.

// send the "block" mceAction
window.parent.postMessage({
    mceAction: 'block',
    message: 'Blocking from iframe'
}, origin);

// wait 2 seconds
setTimeout(() => {
    // send the "unblock" mceAction
    window.parent.postMessage({
        mceAction: 'unblock'
    }, origin);
}, 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.

// send the "execCommand" mceAction to call the "selectAll" command
window.parent.postMessage({
    mceAction: 'execCommand',
    cmd: 'selectAll'
});

// send the "Bold" command
window.parent.postMessage({
    mceAction: 'execCommand',
    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.

editor.addCommand('myCommandName', function (ui, value) {
    // write your code to do something when 
    // the "myCommandName" execCommand is triggered.
    //
    // remember, any commands you create are also
    // 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:

{
    title: 'URL Dialog Demo',
    url: 'iframe.html',
    buttons: [
        ...
    ],
    onAction: function (instance, trigger) {
        // code for when "custom" buttons are triggered
    },
    onMessage: function (instance, data) {
        // we can do something here with the
        // instance and the data of the message
        switch(data.mceAction)
        {
            case 'insertContent':
                // run code for inserting content
                break;
            case 'replaceContent':
                // run code for replacing the content
                break;
            //
            // etc...
            //
        }
    }
}

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.

window.parent.postMessage({
    mceAction: 'sayName',
    data: {
        name: document.getElementById('dialog-name').value
    }
});

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.

onMessage: function (instance, data) {
    // what action should we perform?
    switch (data.mceAction)
    {
        case 'sayName':
            if (data.data.name == '')
            {
                // display an error
                editor.windowManager.alert('You need to enter your name.');
            }
            else
            {
                // say hello
                editor.windowManager.alert('Hi there ' + data.data.name + ' - nice to meet you!');

                // close the window
                instance.close();
            }
            break;
        case 'anotherAction':
            break;
        // ...
        // etc
    }
}

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

Blog

View all
JS

Integrating Tiny with Vue in a real world application

TinyMCE 5 has made the setup process more straightforward - and even easier with the Cloud version. And as there are so many JS frameworks out there, Tiny have...

Continue reading...

PHP

Extending the ItemHelper for Joomla

There are times in Joomla where you want a bit of control over your content at the template override level – such as showing the first X characters of a string. PHP...

Continue reading...

JS

How Tiny helps me deliver the best content authoring experience

At Joomla Day Australia 2019 in Brisbane, I spoke about how TinyMCE helps deliver the best content authoring experience. And for those who couldn’t make it on...

Continue reading...

Photo

How to show real-time highlights and shadows clipping in Photoshop

I’ve used Photoshop for years. Decades even. Yikes, showing age there. But for my photography, I tended to use Photoshop for specific things – such as cleaning...

Continue reading...

I am the Development Director (and co-owner) at Mity Digital, a Melbourne-based digital agency specialising in responsive web design, custom web development and graphic design.
Mity Digital