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:
Configuration of the URL Dialog
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 something19 editor.windowManager.alert('onAction is running.20 21You can code your own onAction handler within the plugin.');22 23 // close the dialog24 instance.close();25 },26 width: 600,27 height: 30028};
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:
1editor.ui.registry.addButton('iframe', {2 text: "Open Advanced URL Dialog",3 icon: 'frame',4 onAction: () => {5 _api = editor.windowManager.openUrl(_urlDialogConfig)6 }7});
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.
1/** 2 * Very simple example Plugin utilising URL Dialog 3 * 4 * @author Marty Friedel 5 */ 6(function () { 7 var iframe = (function () { 8 'use strict'; 9 10 tinymce.PluginManager.add("iframe-simple", function (editor, url) {11 12 /*13 Add a custom icon to TinyMCE14 */15 editor.ui.registry.addIcon('frame', '');16 17 /*18 Used to store a reference to the dialog when we have opened it19 */20 var _api = false;21 22 /*23 Define configuration for the iframe24 */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 something43 editor.windowManager.alert('onAction is running.<br /><br />You can code your own onAction handler within the plugin.');44 45 // close the dialog46 instance.close();47 },48 width: 600,49 height: 30050 };51 52 // Define the Toolbar button53 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 });60 61 });62 }());63})();
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.
1{ 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 }30}
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:
The Plugin code
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:
A message to do something to the URL Dialog Component
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 2window.parent.postMessage({ 3 mceAction: 'block', 4 message: 'Blocking from iframe' 5}, origin); 6 7// wait 2 seconds 8setTimeout(() => { 9 // send the "unblock" mceAction10 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 2window.parent.postMessage({ 3 mceAction: 'execCommand', 4 cmd: 'selectAll' 5}); 6 7// send the "Bold" command 8window.parent.postMessage({ 9 mceAction: 'execCommand',10 cmd: 'Bold'11});
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:
Create a new Command in your plugin
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 when3 // the "myCommandName" execCommand is triggered.4 //5 // remember, any commands you create are also6 // available in your other plugins too7});
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:
1{ 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 the12 // instance and the data of the message13 switch(data.mceAction)14 {15 case 'insertContent':16 // run code for inserting content17 break;18 case 'replaceContent':19 // run code for replacing the content20 break;21 //22 // etc...23 //24 }25 }26}
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.
1window.parent.postMessage({2 mceAction: 'sayName',3 data: {4 name: document.getElementById('dialog-name').value5 }6});
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 else12 {13 // say hello14 editor.windowManager.alert('Hi there ' + data.data.name + ' - nice to meet you!');15 16 // close the window17 instance.close();18 }19 break;20 case 'anotherAction':21 break;22 // ...23 // etc24 }25}
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