modalframe.module in Modal Frame API 7
Same filename and directory in other branches
Provides an API to render an iframe within a modal dialog based on the jQuery UI Dialog plugin.
File
modalframe.moduleView source
<?php
/**
* @file
* Provides an API to render an iframe within a modal dialog based
* on the jQuery UI Dialog plugin.
*/
/**
* Implementation of hook_theme_registry_alter().
*
* Prepend our module path to the theme registry entry for theme('page').
*
* When we need to render a modal frame, modalframe_child_js() defines the
* variable $GLOBALS['modalframe_page_template']. Then, when theme('page') is
* executed, Drupal invokes all template preprocess functions related to the
* page template. When modalframe_preprocess_page() is invoked, it looks for
* this global variable, and if it exists, then the name of the template file
* 'page' is replaced by 'modalframe-page'.
*
* This technique allows us to use a particular version of the template used
* to generate the output of theme('page') with a different layout (ie. no
* header, no left/right blocks, etc.), while still keeping all the features
* related to theme('page') implemented by core, themes and contrib modules
* such us jquery_update at no additional cost. Also, themers can provide their
* own custom versions of page.tpl.php and modalframe-page.tpl.php.
*
* There is no additional performance impact during normal site operation,
* when using theme('page') without further alterations. No additional lookup
* will be made for page.tpl.php. This is it:
*
* 1) Active theme directory (it may or may not exist, often will).
* 2) Drupal's modules/system/page.tpl.php (default).
* 3) modalframe module directory (this lookup will be never reached).
*
* On the other hand, when the file name for page template is altered, the
* lookups for file modalframe-page.tpl.php will proceed like this:
*
* 1) Active theme directory (it may or may not exist, often won't).
* 2) modules/system/modalframe-page.tpl.php (this lookup will always fail).
* 3) modalframe module directory (default).
*
* @see modalframe_child_js()
* @see modalframe_preprocess_page()
* @see _theme_build_registry()
* @see theme_get_registry()
* @see theme()
*
* @ingroup themeable
*/
function modalframe_theme_registry_alter(&$theme_registry) {
if (isset($theme_registry['page'])) {
$theme_registry['page']['theme paths'] = array();
$module_path = drupal_get_path('module', 'modalframe');
$theme = list_themes();
$theme_registry_copy = $theme_registry;
// munge on a copy
_theme_process_registry($theme_registry_copy, 'phptemplate', 'theme_engine', 'pow', $module_path);
$theme_registry += array_diff_key($theme_registry_copy, $theme_registry);
$hooks = array(
'page',
'html',
);
foreach ($hooks as $h) {
_modalframe_insert_after_first_element($theme_registry[$h]['theme paths'], $module_path);
}
// Add pre-preprocess function for page template.
array_unshift($theme_registry['page']['preprocess functions'], 'modalframe_pre_preprocess_page');
}
}
/**
* Helper function for modalframe_theme_registry_alter()
*
* Alters order of elements in theme paths array.
*
* @param array $a
* @param array $element
*/
function _modalframe_insert_after_first_element(&$a, $element) {
if (is_array($a)) {
$first_element = array_shift($a);
array_unshift($a, $first_element, $element);
}
}
/**
* Preprocess template variables for page.tpl.php - step 1.
*
* Performance enhancement: prevent template_preprocess_page() from generating
* sidebar blocks when a modal frame has been requested.
*/
function modalframe_pre_preprocess_page(&$variables) {
if (!empty($GLOBALS['modalframe_page_template'])) {
$variables['show_blocks'] = FALSE;
}
}
/**
* Preprocess template variables for html.tpl.php
*/
function modalframe_preprocess_html(&$variables) {
if (!empty($GLOBALS['modalframe_page_template'])) {
$variables['theme_hook_suggestions'][] = 'html__modalframe';
}
}
/**
* Preprocess template variables for page.tpl.php - step 2.
*
* Now that we have altered the registry entry for theme('page'), we can tell
* theme() to use a different template file name when we need to render a child
* window.
*
* @see modalframe_child_js()
* @see modalframe_theme_registry_alter()
* @see template_preprocess_page()
* @see template_preprocess()
* @see theme()
*
* @ingroup themeable
*/
function modalframe_preprocess_page(&$variables) {
if (!empty($GLOBALS['modalframe_page_template'])) {
$variables['theme_hook_suggestions'][] = 'page__modalframe';
}
}
/**
* Implements hook_page_alter().
*
* Remove the 'toolbar' when in the child frame.
*/
function modalframe_page_alter(&$page) {
if (!empty($GLOBALS['modalframe_page_template']) && isset($page['page_top']['toolbar'])) {
unset($page['page_top']['toolbar']);
}
}
/**
* Implementation of hook_form_alter().
*
* The main goal of this function is to set $form_state['redirect'] to FALSE
* when a submitted form performs a request to close the modal dialog where
* it's been processed.
*
* A few notes on the workflow we're using here:
*
* - Form API is requested to build a form. Then invokes all hook_form_alter()
* implementations.
* - Here we install an after_build callback because still, there may be other
* modules that can add submit handlers from their own hook_form_alter().
* - Ok, form API processes all hook_form_alter() implementations, then invokes
* our after_build callback where we can scan the whole form structure in
* search for elements that have submit handlers. We want to append our own
* submit handler so that we can catch requests to close the active child
* dialog performed by other modules using the API modalframe_close_dialog()
* from their submit handlers.
* - Our submit handler has been installed using an after_build callback because
* this is kind of low level technique, which is not often used by contributed
* modules, and chances are that our submit handler is executed after all
* potential places from where modalframe_close_dialog() can be used by a
* external module using the "Modal Frame API". Also, this way our module does
* not depend on the order module hooks are being invoked by Drupal.
*
* @see modalframe_close_dialog()
* @see drupal_process_form()
* @see drupal_redirect_form()
* @see drupal_get_form()
*
* @ingroup forms
*/
function modalframe_form_alter(&$form, &$form_state, $form_id) {
// Here we simply want to install a form after_build callback.
if (!isset($form['#after_build'])) {
$form['#after_build'] = array();
}
$form['#after_build'][] = 'modalframe_form_after_build';
}
/**
* Form after build callback.
*
* Ok, all hook_form_alter() have been processed. Now, if someone has enabled
* the global variable $GLOBALS['modalframe_page_template'], then we want to
* scan the form structure in search of elements with submit handlers.
*
* @see _form_builder_handle_input_element()
* @see _form_builder_ie_cleanup()
* @see form_execute_handlers()
* @see form_builder()
*
* @ingroup forms
*/
function modalframe_form_after_build($form, &$form_state) {
if (!empty($GLOBALS['modalframe_page_template'])) {
// Form API may have already captured submit handlers from the submitted
// button before after_build callback is invoked. This may have been done
// by _form_builder_handle_input_element().
// If so, the list of submit handlers is stored in the $form_state array
// which is something we can also alter from here, luckily. :)
// Remember: our goal here is to make sure $form_state['redirect'] is set
// to FALSE when the modalframe_close_dialog() API is invoked, and that's
// because we want to tell the parent window to close the modal frame.
if (!empty($form_state['submit_handlers']) && !in_array('modalframe_form_submit', $form_state['submit_handlers'])) {
$form_state['submit_handlers'][] = 'modalframe_form_submit';
}
// Find form elements with submit handlers recursively.
modalframe_form_after_build_recursive($form, $form_state);
}
return $form;
}
/**
* Find form elements with submit handlers recursively.
*
* @ingroup forms
*/
function modalframe_form_after_build_recursive(&$elements, &$form_state) {
// Recurse through all children elements.
foreach (element_children($elements) as $key) {
if (isset($elements[$key]) && $elements[$key]) {
modalframe_form_after_build_recursive($elements[$key], $form_state);
}
}
// If this element has submit handlers, then append our own.
if (isset($elements['#submit'])) {
$elements['#submit'][] = 'modalframe_form_submit';
}
}
/**
* Generic form submit handler.
*
* When we are requested to close a modal dialog, we don't want Form API to
* perform any redirection once the submitted form has been processed.
*
* When $form_state['redirect'] is set to FALSE, then Form API will simply
* re-render the form with the values still in its fields. And this is all
* we need to output the javascript that will tell the parent window to close
* the child dialog.
*
* @ingroup forms
*/
function modalframe_form_submit($form, &$form_state) {
if (!empty($GLOBALS['modalframe_close_dialog'])) {
$form_state['redirect'] = FALSE;
}
}
/**
* API: Add javascript and stylesheets to the parent page.
*
* @ingroup modalframe_api
*/
function modalframe_parent_js() {
static $processed;
// Make sure external resources are not included more than once.
if (isset($processed)) {
return;
}
$processed = TRUE;
drupal_add_library('system', 'ui.dialog');
drupal_add_library('system', 'ui.draggable');
$module_path = drupal_get_path('module', 'modalframe');
drupal_add_css($module_path . '/css/modalframe.parent.css');
drupal_add_js($module_path . '/js/parent.js');
}
/**
* API: Add javascript and stylesheets to the child page.
*
* @ingroup modalframe_api
*/
function modalframe_child_js() {
static $processed;
// Make sure external resources are not included more than once.
if (isset($processed)) {
return;
}
$processed = TRUE;
// Disable admin_menu, admin module output and similar modules, which
// is something child windows don't need.
module_invoke_all('suppress');
// Add javascript and stylesheets to the child page.
$module_path = drupal_get_path('module', 'modalframe');
drupal_add_css($module_path . '/css/modalframe.child.css');
drupal_add_js($module_path . '/js/child.js');
if (module_exists('onbeforeunload')) {
onbeforeunload_add_js();
}
drupal_add_library('system', 'ui');
// Tell Drupal's theme system to use the Modal Frame template.
$GLOBALS['modalframe_page_template'] = TRUE;
}
/**
* API: Close a client side dialog.
*
* This function should be used on form submit handlers to trigger the
* action that will close the modal frame on the client side.
*
* @param $args
* An optional array of arguments that will be forwarded to the client
* side onSubmit callback.
*
* @ingroup modalframe_api
*/
function modalframe_close_dialog($args = NULL) {
// Make sure this action is not processed more than once.
if (isset($GLOBALS['modalframe_close_dialog'])) {
return;
}
// Build the javascript settings that will close the modal frame on the
// client side.
$child_js_settings = array(
'closeModal' => 1,
'statusMessages' => theme('status_messages'),
'args' => isset($args) && is_array($args) ? $args : array(),
);
drupal_add_js(array(
'modalFrameChild' => $child_js_settings,
), 'setting');
// Tell Drupal's Form API that we are requested to close the modal dialog,
// so we do not wish to perform redirections after submitted form has been
// processed.
$GLOBALS['modalframe_close_dialog'] = TRUE;
}
Functions
Name | Description |
---|---|
modalframe_child_js | API: Add javascript and stylesheets to the child page. |
modalframe_close_dialog | API: Close a client side dialog. |
modalframe_form_after_build | Form after build callback. |
modalframe_form_after_build_recursive | Find form elements with submit handlers recursively. |
modalframe_form_alter | Implementation of hook_form_alter(). |
modalframe_form_submit | Generic form submit handler. |
modalframe_page_alter | Implements hook_page_alter(). |
modalframe_parent_js | API: Add javascript and stylesheets to the parent page. |
modalframe_preprocess_html | Preprocess template variables for html.tpl.php |
modalframe_preprocess_page | Preprocess template variables for page.tpl.php - step 2. |
modalframe_pre_preprocess_page | Preprocess template variables for page.tpl.php - step 1. |
modalframe_theme_registry_alter | Implementation of hook_theme_registry_alter(). |
_modalframe_insert_after_first_element | Helper function for modalframe_theme_registry_alter() |