Overview

The Modal Frame API provides a set of functions to render an iframe within a modal dialog based on the jQuery UI Dialog plugin. An advantage over other methods to deal with modal dialogs based on AJAX is that using an iframe, the child document lives in its own DOM namespace, therefore no DOM conflicts will occur with the parent document, and it is much easier (technically) to work with complex client-side behaviors such as Wysiwyg editors, file uploads, SWFUpload, CCK buttons to add more items, Multigroups, etc.

The Modal Frame API enhances the natural behavior of the jQuery UI Dialog plugin itself with better keyboard navigation (the focus is always on a child element or the close button; the escape key closes the dialog), and also methods to communicate and synchronize events between both the parent and child documents.

The default template used to render documents opened on modal frames is designed to render the main content area of the child page transparently, and it is also optimized to avoid generation of sidebar blocks to help load child documents a bit faster than other pages.

While there may be solutions based on the Modal Frame API, it has been implemented as a tool for developers, so you have the power to implement this kind of dialog exactly the way you need. The API itself is described in the project page, and the following sections will provide a step by step guide to use the Modal Frame API in your projects.

The basics

To follow this guide you need to:

  • Open the project page of the Modal Frame API to get documentation at hand.
  • Download the Modal Frame Contrib Package. It provides several modules that implement the Modal Frame API to enhance the usability of common day to day tasks in a Drupal installation. We'll use this package to look at how some of its modules are implemented as an introduction to the things you can do with the Modal Frame API.
  • You also need basic developer knowledge about how jQuery and Drupal work.

The basic steps to use the Modal Frame API are:

  1. The parent page should invoke modalframe_parent_js() somewhere in your module, before theme('page') is executed.
  2. The parent page should also inject a custom .js file that will invoke Drupal.modalFrame.open() based on your needs, for example from an onclick event handler.
  3. The child page should invoke modalframe_child_js() somewhere in your module to send the javascript code that child window needs to communicate with the parent.

In addition to the above minimum steps:

  • The child page may invoke modalframe_close_dialog($args) from a submit handler, so that the modal frame is closed when the form is submitted successfully.
  • You can pass arguments to modalframe_close_dialog(). These are made available in the onSubmit callback used on the call to Drupal.modalFrame.open(), so you can take action client-side when the form is processed.

Guided examples

The contrib package contains several modules. It's easier to start with those that are smaller. For example, let's look at modalframe_more_help.js:

(function ($) {

Drupal.behaviors.modalFrameMoreHelp = function(context) {
  $('.more-help-link a:not(.modalframe-more-help-processed)', context).addClass('modalframe-more-help-processed').click(function() {
    Drupal.modalFrame.open({url: $(this).attr('href'), width: 800, height: 600});
    return false;
  });
};

})(jQuery);

Step by step:

Wrapping our javascript code into an anonymous function:

(function ($) {

// Our code lives here.

})(jQuery);

Creating our Drupal behavior:

Drupal.behaviors.modalFrameMoreHelp = function(context) {

// Here's where we add code that will be executed by Drupal as soon as the DOM is ready.
// The context argument is the DOM document. However, our code here could be executed
// by an AHAH callback, and the context could be different.

};

Here's the real code, step by step:

  // Search for A elements wrapped with an element that has the class
  // "more-help-link" within the given context. Also, make sure this A element
  // does not have the CSS class "modalframe-more-help-processed".
  $('.more-help-link a:not(.modalframe-more-help-processed)', context)

    // Now that we have matched an element, we add a CSS class, so that
    // we do not process the same element more than once.
    .addClass('modalframe-more-help-processed')

    // Now, we attach an onClick event handler to the element.
    .click(function() {

    // When the A element is clicked, the URL of the same link will be opened within a modal frame.
    Drupal.modalFrame.open({
      url: $(this).attr('href'),
      width: 800,
      height: 600
    });

    // Return false so that we cancel the default link behavior.
    return false;
  });

The other modules are not so simple, true. :( ...I've been unable to provide an easier way. Well, maybe it could have been, but I think it could have been with more limitations. This way you have the whole power to do it the way you exactly need.

If you do not have a CSS class to search for elements, then you need to search for links that match a particular URL, or a similar trick. This is done using jQuery selectors and regular expressions. It may look complex, but not too much once you get used to it. It just needs a bit of time to understand how and why each step is performed.

jQuery selectors to search for links

The above example uses a pretty simple jQuery selector that's based on a known CSS class. However, sometimes things are a bit more complex because the default rendering of those links does not provide a known selector. Here's an example that searches for the URLs in links.

Let's look at modalframe_filter_tips.js:

(function ($) {

Drupal.behaviors.modalFrameFilterTips = function(context) {
  $('a[href$="filter/tips"]:not(.modalframe-filter-tips-processed)', context).addClass('modalframe-filter-tips-processed').click(function() {
    Drupal.modalFrame.open({url: $(this).attr('href'), width: 800, height: 600});
    return false;
  });
};

})(jQuery);

This is basically the same thing we've seen in modalframe_more_help.js. It just uses a different jQuery selector, and CSS classes.

  // Here we are searching for A elements where the HREF attribute ends with "filter/tips".
  $('a[href$="filter/tips"]:not(.modalframe-filter-tips-processed)', context)

We can see another method to search for links in a table in modalframe_dblog.js:

(function ($) {

Drupal.behaviors.modalFrameDBLog = function(context) {
  $('#admin-dblog td a:not(.modalframe-dblog-processed)', context).addClass('modalframe-dblog-processed').each(function() {
    var $link = $(this), url = $link.attr('href');
    var regExp = new RegExp('^'+ Drupal.settings.basePath +'(?:[-a-z]+/)?admin/reports/event/[0-9]+$');
    if (regExp.test(url)) {
      $link.click(function() {
        Drupal.modalFrame.open({url: url, width: 800, height: 600});
        return false;
      });
    }
  });
};

})(jQuery);

This one is a bit more complex because there's no easy way to search for the links we're interested in. So, we need to combine jQuery selectors with regular expressions.

The other mini-modules in the Contrib package look more complex, but that's because they use onSubmit callbacks of the Modal Frame to add code that modifies the parent page with the result of the form in the child document, and they also contain code to show a throbber to give a visual indication when something in the DOM is updated as a result of the form being submitted within the modal frame.

Theming

The following issue contains interesting information for those who are interested in theming modal frames: #672970: Theming Modal Frames. Feel free to create a child page in this handbook if you feel it will benefit others. Thanks!