Using jQuery and jQuery UI in TinyMCE Dialog Iframe

WordPress is a wonderful tool.  But some things are very difficult to do.  A WordPress plugin I recently created (called Tweet This) adds a button to the WordPress editor toolbar.  This button, when clicked, opens a sizable dialog window with lots of items and a fair amount of jQuery and jQuery UI code.

 

My Toolbar Button:

tinymce-button

 

My Dialog:

gouda-example-0-1024x778

 

 

Getting  jQuery and jQuery UI to work inside the dialog box was challenging because it is poorly documented and not a commonly executed task.  The dialog box was by far the most time and labor intensive aspect of the plugin.  And was the source of many headaches and frustration.  So, now that I’ve figured it out, I want to write it down so I don’t forget!  And, hopefully, it will help you out as well.

 

The Problem

WordPress makes it easy to include jQuery and jQuery UI in admin pages using the wp_enqueue_script function.  However, TinyMCE dialog boxes are opened dynamically as iframes.  They are not loaded in advance, and the source code for the dialog box is independent of the WordPress admin page.

So jQuery isn’t present, and WordPress doesn’t have the capability to load jQuery in the dialog box.  It needs to be included another way.

 

 

The Easy Solution

The easy solution is to simply include jQuery (and jQuery UI if needed) in your dialog box like you would on any HTML page.  You can download it yourself or use Google’s hosted versions.

 

But this is a very inelegant solution.  It slows down the loading of your dialog box dramatically.  And will cause the WordPress plugin folks to reject your plugin because you are creating a dependency on an external source and/or you are re-including a different version of something that WordPress already has, has tested, and approves of.

In other words, this works, but is bad form and unacceptable.

 

The Better Solution

The better solution isn’t terribly difficult in theory, but was awful to figure out how to do.  The documentation wasn’t helpful, and it seemed I was the only human being on the planet not using the easy solution.  But, with information cobbled together from lots of other sources, I figured it out.  And it’s pretty simple to do.

 

In words, what you need to do, is pass jQuery as an argument to your dialog, and pass the dialog box context to jQuery when using it.

Okay, now let’s see what that actually means.  A simplified version of my TinyMCE plugin code is included below.

 

 

The entire plugin initialization code is wrapped in an immediately-invoked function expression (IIFE) since it is all self-contained.  This IIFE takes the jQuery variable from the global scope and uses it inside as the infamous dollar-sign variable ($).  You will need your WordPress plugin to include jQuery in the admin section using the previously mentioned wp_enqueue_script function.

 

The above code register a button and a command with TinyMCE.  The command is tied to the button and it opens a dialog box which loads content from a file called tinymce-dialog.html.  Any arguments you want passed to this dialog are put into an object and passed as the second arguments to windowManager.open().  You can add as many arguments as needed, but you will need to pass at least the jQuery variable which is the last argument in the example above.

 

Then, the contents of tinymce-dialog.html is simply any HTML, CSS, and/or JavaScript you want in your dialog box.  The JavaScript at the bottom shows how to use jQuery.  An explanation is included underneath the code.

 

By passing jQuery as an argument in the plugin initialization code, it is now included in the dialog box document.  You can fetch passed arguments using the following:

Now, the variable args is the object passed in the initialization code:

 

And jQuery is args[‘jquery’].  To make life simple, I assigned jQuery to the $ variable.

 

Now, using jQuery should work the same as normal, with one crucial exception.

jQuery’s context is the parent window, not the dialog box.  Any selectors you use will search the parent document, not the dialog unless you specify that the dialog is the context you want.  This is done by passing the body tag of the dialog element as the second argument in  jQuery’s $ function.  There are multiple ways of getting and using this body tag, but in my opinion, this is the simplest:

Use plain JavaScript to select the first body tag it encounters in the DOM and store it in a variable.  If you have multiple body tags on your page (multiple iframes?), this won’t work, but when using the WordPress post/page editor, the first and only iframe should be your TinyMCE dialog and it works beautifully.

 

 

And now, as long as you remember the context argument, jQuery will work as expected within the dialog.

 

 

Not too difficult once you’ve figured it out.  But I’ve lost many hours figuring it out.  Hopefully, you won’t have the same problem!

 

Comments & Discussions

  1. David McCan

    Hi John,

    I’ve been working on a plugin that needs this functionality. In addition to including external files via a CDN, I have also seen plugins that include ‘wp-load.php’ and ‘admin.php’ in order to access the built-in WP functionality. Additionally, I’ve hacked together yet another option of loading the WP versions via JavaScript, writing into the header.

    The title of your post includes mention of jquery-ui. How did you include that? I was also wondering how you handled security, since we end up with a popup that is outside of the WP authorization schema?

    Thanks,

    David

    Reply
    • John Morris

      I haven’t seen the wp-load.php and admin.php method before. That’s an interesting idea, but I cringe at the inclusion of random parts from WordPress. I’m militant about minimizing potential for plugin breakage with WordPress updates, and I would be worried.

      —–

      jQuery UI was included as described in the WordPress documentation.


      add_action( 'admin_enqueue_scripts', 'prefix_enqueue_admin_scripts' );
      function prefix_enqueue_admin_scripts() {
      wp_enqueue_script( 'jquery' );
      wp_enqueue_script( 'jquery-ui' );
      }

      All the jQuery UI features are then available through the jQuery brought into the dialog from WordPress.

      It’s important to note, however, that WordPress doesn’t have themes built-in for jQuery UI, so you have to enqueue those by including the theme files in your plugin or using a CDN.

      —–

      Security I handled when registering the TinyMCE plugin and button with WordPress. if(foo && bar) { // register TinyMCE plugin }

      The things I have done with these popups doesn’t expose any of WordPress’ soft underbelly. I have only ever used them to construct and insert shortcodes for plugins in the editor. So, my security checks are quite simple. I check if the user is in the admin backend, and if they are admin users (see example on GitHub). If so, I register the TinyMCE plugin.

      More dangerous operations would probably be simplest to secure using exclusively AJAX to retrieve, insert, and update data. The AJAX handler would be within WordPress’ authorization schema, so it can do the security checking. The popup would only then be responsible for displaying and collecting data.

      —–

      I’m in the process of moving to a new website. It’s stalled mid-transition because I’ve been extremely busy, but the new website is up and running. On that website, there is a newer post that documents step-by-step how to create a plugin with a TinyMCE plugin. It shows the security checks, and jQuery/jQuery UI inclusion as well as much more detail than is available here. Here’s a link: http://johnmorris.me/computers/software/how-to-create-a-tinymce-editor-dialog-window-in-a-wordpress-plugin/. There’s also a working sample of the plugin that tutorial describes on GitHub: https://github.com/jtmorris/WP-TinyMCE-Dev-Starter-Kit

      —–

      With one of my WordPress plugins utilizing this technique, a user encountered a problem where WordPress would not pass arguments correctly. Technically, the problem was attempting to bust out of the iframe with the line of code below would fail saying ‘tinymce’ did not exist.


      var args = top.tinymce.activeEditor.windowManager.getParams();

      I believe the problem was browser security preventing busting out of the frame for some reason. After weeks of back and forth troubleshooting and dozens of attempted resolutions, I eventually gave up and trashed this whole method. Instead of using TinyMCE’s windowManager to create a popup, I simply made a popup using jQuery UI that was hidden by default. When the user clicks on the TinyMCE toolbar button, it opens the dialog. I just got this working and released the update about a week ago.

      This method was MUCH simpler and much more robust. TinyMCE opens an iframe in the popup, which is the source of the biggest headaches. It’s why jQuery has to be passed in in a strange way, and why my plugin’s user was having problems. The jQuery dialog is simply a div on the page. It is kept inside WordPress’ authorization schema. It has direct access to everything.

      I eventually intend to write a new blog post detailing this method because I vastly prefer it, and it’s so much simpler than dealing with TinyMCE’s idiosynchrocies. I’ve lost hundreds of manhours to this seemingly simple idea. The blog post is at the bottom of my very long to-do list however, so it will be a while before it’s done.

      If you’re willing to sort through the code, the source for the plugin that uses this new method is here: https://github.com/jtmorris/tweet-this. The code is a bit messy. I was in a rush to get the update with this new dialog box method out, so it’s a bit haphazard and inelegant at the moment. Once I finish putting out any fires this massive change created, I’ll be cleaning things up. The main work for the dialog is done in includes/setup.php, includes/tinymce-dialog.html, and /assets/js/tinymce-plugin.js.

      Reply
  2. David McCan

    All good information. Thank you. I’ve been playing with a couple of things:

    * What I got working was to write the settings needed using the admin_head() method. Then getting those in the tinyMCE js plugin file to pass to the popup file. Basically I’m just passing in the path to the wp-includes folder and then loading all of the JavaScript I need … jQuery and the UI scripts manually. See: http://www.nczonline.net/blog/2009/07/28/the-best-way-to-load-external-javascript/. That actually works pretty well.

    * I’m working now on a tweak by adding to the tinyMCE js plugin file an ajax call to get the settings needed and then passing them into the popup. I think that would be more secure.

    One of the things I meant by ‘security’ was if it were possible to hit your tinymce-dialog.html directly if you typed in the URL. Even though it would not be functional, it might be something someone could access.

    Of course, your idea of sidestepping the entire issue with a jquery UI popup sounds really good. I’ll take a loot at your tweet this plugin.

    I suspect that including WP core and admin is discouraged, though I see it in current plugins.

    Cheers.

    Reply
  3. David McCan

    Hi John,

    I took a look at your tweet-this plug-in. It is a good example. I like the jQuery UI method much better than any of the other ways I got AJAX to work in an editor pop-up. I agree that it seem more secure also. Thank you. Really. I spent three weekends getting three different methods to work and I am glad that I finally have a method that I feel good about.

    I did a couple of small things differently, which seem to work:

    * WordPress includes / injects a variable, ajaxurl, into the header on the admin side. Because of that I did not need to inject that myself. Using the WordPress supplied one worked fine.

    * Also, I was able to include a file with all of the JS for the admin and did not need to write the ‘ready’ or ‘dialog’ code into the header.

    I’ll watch for your next article.

    Regards,

    David

    Reply
    • John Morris

      Hi David,

      Yes, those two differences are holdovers from the previous way of doing things. I kept them because of the time constraint present with dealing with bugs, and as a fallback because there was a transition period where both methods were included with the plugin, and a simple change to a few lines would re-enable the TinyMCE popup.

      They are among several cleanups and improvements I want to enact at some point. Less is more, and isolating content into self-contained modules is divine. At the moment, a different plugin of mine has yielded an unexpected flurry of support requests that I’m scrambling to keep up with.

      Reply
  4. Joel

    You are not the only person to use this version to solve. Just rejected by wordpress folk by using script tag.

    Reply

Leave a Reply

  • (will not be published)

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

CAPTCHA Image
*