Index: modules/user/user.admin.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/user/user.admin.inc,v retrieving revision 1.19 diff -u -r1.19 user.admin.inc --- modules/user/user.admin.inc 20 Feb 2008 13:46:43 -0000 1.19 +++ modules/user/user.admin.inc 20 Mar 2008 01:11:24 -0000 @@ -901,9 +901,16 @@ $header = array(array('data' => t('Access type'), 'field' => 'status'), array('data' => t('Rule type'), 'field' => 'type'), array('data' => t('Mask'), 'field' => 'mask'), array('data' => t('Operations'), 'colspan' => 2)); $result = db_query("SELECT aid, type, status, mask FROM {access}". tablesort_sql($header)); $access_types = array('user' => t('username'), 'mail' => t('e-mail'), 'host' => t('host')); + drupal_add_popups(); $rows = array(); while ($rule = db_fetch_object($result)) { - $rows[] = array($rule->status ? t('allow') : t('deny'), $access_types[$rule->type], $rule->mask, l(t('edit'), 'admin/user/rules/edit/'. $rule->aid), l(t('delete'), 'admin/user/rules/delete/'. $rule->aid)); + $rows[] = array( + $rule->status ? t('allow') : t('deny'), + $access_types[$rule->type], + $rule->mask, + l(t('edit'), 'admin/user/rules/edit/'. $rule->aid), + l(t('delete'), 'admin/user/rules/delete/'. $rule->aid, array('attributes'=>array('class'=>'popup-form'))) + ); } if (empty($rows)) { $rows[] = array(array('data' => ''. t('There are currently no access rules.') .'', 'colspan' => 5)); Index: includes/theme.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/theme.inc,v retrieving revision 1.417 diff -u -r1.417 theme.inc --- includes/theme.inc 20 Feb 2008 13:39:29 -0000 1.417 +++ includes/theme.inc 20 Mar 2008 01:11:22 -0000 @@ -1449,7 +1449,7 @@ * Returns code that emits the 'more help'-link. */ function theme_more_help_link($url) { - return ''; + return ''; } /** Index: includes/menu.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/menu.inc,v retrieving revision 1.266 diff -u -r1.266 menu.inc --- includes/menu.inc 19 Mar 2008 07:38:29 -0000 1.266 +++ includes/menu.inc 20 Mar 2008 01:11:21 -0000 @@ -1167,6 +1167,7 @@ // Add "more help" link on admin pages if the module provides a // standalone help page. if ($arg[0] == "admin" && module_exists('help') && module_invoke($name, 'help', 'admin/help#'. $arg[2], $empty_arg) && $help) { + drupal_add_popups(); $output .= theme("more_help_link", url('admin/help/'. $arg[2])); } } Index: includes/bootstrap.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/bootstrap.inc,v retrieving revision 1.206 diff -u -r1.206 bootstrap.inc --- includes/bootstrap.inc 10 Jan 2008 22:47:17 -0000 1.206 +++ includes/bootstrap.inc 20 Mar 2008 01:11:20 -0000 @@ -271,7 +271,7 @@ * session name correctly. */ function conf_init() { - global $base_url, $base_path, $base_root; + global $base_url, $base_path, $base_root, $render_mode, $request_mode; // Export the following settings.php variables to the global namespace global $db_url, $db_prefix, $cookie_domain, $conf, $installed_profile, $update_free_access; @@ -337,6 +337,10 @@ ini_set('session.cookie_domain', $cookie_domain); } session_name('SESS'. md5($session_name)); + + // TODO - add comments to describe what these globals control. + $render_mode = isset($_SERVER['HTTP_X_DRUPAL_RENDER_MODE']) ? $_SERVER['HTTP_X_DRUPAL_RENDER_MODE'] : 'xhtml/plain'; + $request_mode = isset($_SERVER['HTTP_X_REQUESTED_WITH']) ? $_SERVER['HTTP_X_REQUESTED_WITH'] : 'standard'; } /** Index: includes/form.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/form.inc,v retrieving revision 1.268 diff -u -r1.268 form.inc --- includes/form.inc 15 Mar 2008 11:02:47 -0000 1.268 +++ includes/form.inc 20 Mar 2008 01:11:21 -0000 @@ -433,7 +433,9 @@ // however, we'll skip this and let the calling function examine // the resulting $form_state bundle itself. if (!$form['#programmed'] && empty($form_state['rebuild']) && empty($form_state['storage'])) { - drupal_redirect_form($form, $form_state['redirect']); + global $request_mode; + // TODO - more comments here, and update the above comments. + module_invoke_all('after_process_form', $request_mode, $form, $form_state); } } } @@ -606,8 +608,13 @@ * @param $redirect * An optional value containing the destination path to redirect * to if none is specified by the form. + * @param $do_goto + * Boolean flag. + * If true, call to drupal_goto to do the redirect. + * If false, return the url of the redirect, but don't go there. This allows + * for alternal flows of control, such as AJAX or webservices. */ -function drupal_redirect_form($form, $redirect = NULL) { +function drupal_redirect_form($form, $redirect = NULL, $do_goto = TRUE) { $goto = NULL; if (isset($redirect)) { $goto = $redirect; @@ -615,17 +622,31 @@ if ($goto !== FALSE && isset($form['#redirect'])) { $goto = $form['#redirect']; } - if (!isset($goto) || ($goto !== FALSE)) { - if (isset($goto)) { - if (is_array($goto)) { - call_user_func_array('drupal_goto', $goto); + if (!isset($goto) || ($goto !== FALSE)) { + // If $do_goto is set, call drupal_goto and do the redirect & exit. + if ($do_goto) { + if (isset($goto)) { + if (is_array($goto)) { + call_user_func_array('drupal_goto', $goto); + } + else { + drupal_goto($goto); + } } - else { - drupal_goto($goto); + drupal_goto($_GET['q']); + } + else { // If $do_goto is false, just calculate and return the next url. + if (isset($goto)) { + if (is_array($goto)) { + return call_user_func_array('drupal_get_goto_url', $goto); + } + else { + return drupal_get_goto_url($goto); + } } + return drupal_get_goto_url($_GET['q']); } - drupal_goto($_GET['q']); - } + } } /** Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.760 diff -u -r1.760 common.inc --- includes/common.inc 17 Mar 2008 17:01:05 -0000 1.760 +++ includes/common.inc 20 Mar 2008 01:11:20 -0000 @@ -293,14 +293,14 @@ * supported. * @see drupal_get_destination() */ -function drupal_goto($path = '', $query = NULL, $fragment = NULL, $http_response_code = 302) { - +// TODO - update comments to reflect breaking drupal_goto into two functions. +function drupal_get_goto_url($path = '', $query = NULL, $fragment = NULL) { if (isset($_REQUEST['destination'])) { extract(parse_url(urldecode($_REQUEST['destination']))); } else if (isset($_REQUEST['edit']['destination'])) { extract(parse_url(urldecode($_REQUEST['edit']['destination']))); - } + } $url = url($path, array('query' => $query, 'fragment' => $fragment, 'absolute' => TRUE)); // Remove newlines from the URL to avoid header injection attacks. @@ -311,19 +311,25 @@ if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') { module_invoke_all('exit', $url); } + return $url; +} +function drupal_goto($path = '', $query = NULL, $fragment = NULL, $http_response_code = 302) { + $url = drupal_get_goto_url($path, $query, $fragment); + // Even though session_write_close() is registered as a shutdown function, we // need all session data written to the database before redirecting. session_write_close(); - + header('Location: '. $url, TRUE, $http_response_code); - + // The "Location" header sends a redirect status code to the HTTP daemon. In // some cases this can be wrong, so we make sure none of the code below the // drupal_goto() call gets executed upon redirection. exit(); } + /** * Generates a site off-line message. */ @@ -2209,6 +2215,24 @@ } /** + * Attach the popup behavior to the page. + */ +function drupal_add_popups() { + static $added = FALSE; + if (!$added) { + drupal_add_js('misc/popupapi.js', 'core'); + drupal_add_js('misc/jquery.form.js'); + $settings = array( 'popups' => array( + 'defaultTargetSelector' => variable_get('popups_content_selector', 'div.left-corner > div.clear-block:last'), + 'defaultTitleSelector' => variable_get('popups_title_selector', 'div.left-corner > h2:eq(0)'), + 'template' => theme('popup_template'), + )); + drupal_add_js( $settings, 'setting' ); + $added = TRUE; + } +} + +/** * Aggregate JS files, putting them in the files directory. * * @param $files @@ -2629,6 +2653,44 @@ } } +/** + * Abstract the Drupal rendering system. + * Allow renderers other than the theme system to produce content output. + * Example of use: render('page', $content); + * + * @param + * Array starting with the element being rendered (ex: 'page'), + * followed by the content arguments. + * + * @return + * Rendered content. + */ +function render() { + global $render_mode; + $args = func_get_args(); + +// TODO - cache the renderer dispatch table. +// static $renderers; +// if (!$renderers) { +// // look in the cache +// //$renderers = cache_get('renderers'); +// if (!$renderers) { +// $renderers = module_invoke_all('renderers'); // Build the renderer dispatch table +// cache_set('renderers', $renderers); +// } +// } + + $renderers = module_invoke_all('renderers'); // Build the renderer dispatch table + $renderer = $renderers[$render_mode]; + if ($renderer) { + return call_user_func_array($renderer, $args); + } + else { + drupal_set_message(t('Attempting to use an unsupported render mode %render_mode.', array('%render_mode' => $render_mode)), 'error'); + $args = func_get_args(); + return call_user_func_array('theme', $args); + } +} /** * Renders HTML given a structured array tree. Index: modules/block/block.admin.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/block/block.admin.inc,v retrieving revision 1.14 diff -u -r1.14 block.admin.inc --- modules/block/block.admin.inc 22 Dec 2007 23:24:24 -0000 1.14 +++ modules/block/block.admin.inc 20 Mar 2008 01:11:22 -0000 @@ -30,6 +30,7 @@ // Add CSS drupal_add_css(drupal_get_path('module', 'block') .'/block.css', 'module', 'all', FALSE); + drupal_add_popups(); // If non-default theme configuration has been selected, set the custom theme. $custom_theme = isset($theme) ? $theme : variable_get('theme_default', 'garland'); @@ -76,7 +77,7 @@ } $form[$key]['configure'] = array('#value' => l(t('configure'), 'admin/build/block/configure/'. $block['module'] .'/'. $block['delta'])); if ($block['module'] == 'block') { - $form[$key]['delete'] = array('#value' => l(t('delete'), 'admin/build/block/delete/'. $block['delta'])); + $form[$key]['delete'] = array('#value' => l(t('delete'), 'admin/build/block/delete/'. $block['delta'], array('attributes'=>array('class'=>'popup-form')))); } } Index: modules/system/system.admin.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.admin.inc,v retrieving revision 1.65 diff -u -r1.65 system.admin.inc --- modules/system/system.admin.inc 13 Mar 2008 21:26:09 -0000 1.65 +++ modules/system/system.admin.inc 20 Mar 2008 01:11:23 -0000 @@ -116,6 +116,31 @@ } /** + * Todo + * + * @return unknown + */ +function system_popup_settings() { + drupal_set_title("Popups Settings"); + $form = array(); + + $form['popuppage_title_selector'] = array( + '#type' => 'textfield', + '#title' => t('Title Selector'), + '#default_value' => variable_get('popuppage_title_selector', 'div.left-corner > h2:eq(0)'), + '#description' => t("jQuery selector to define the page's title on your Admin theme."), + ); + $form['popuppage_content_selector'] = array( + '#type' => 'textfield', + '#title' => t('Content Selector'), + '#default_value' => variable_get('popuppage_content_selector', 'div.left-corner > div.clear-block:last'), + '#description' => t('jQuery selector to define the page\'s content area on your Admin theme.'), + ); + + return system_settings_form($form); +} + +/** * Menu callback; displays a module's settings page. */ function system_settings_overview() { Index: modules/system/system.module =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.module,v retrieving revision 1.593 diff -u -r1.593 system.module --- modules/system/system.module 11 Mar 2008 08:13:14 -0000 1.593 +++ modules/system/system.module 20 Mar 2008 01:11:23 -0000 @@ -149,6 +149,11 @@ 'arguments' => array('image_path' => NULL), ), 'system_compact_link' => array(), + 'popup_template' => array( + 'template' => 'popup-template', + ), + 'popup_save_dialog' => array(), + )); } @@ -515,6 +520,20 @@ 'type' => MENU_CALLBACK, 'file' => 'system.admin.inc', ); + + // Popups: + $items['admin/settings/popuppage'] = array( + 'page callback' => 'drupal_get_form', + 'page arguments' => array('system_popup_settings'), + 'title' => 'AJAX Popups', + 'description' => 'Configure the AJAX Popup behavior.', + 'file' => 'system.admin.inc', + ); + $items['popuppage/save_dialog'] = array( + 'page callback' => 'theme', + 'page arguments' => array('popup_save_dialog'), + 'access callback' => TRUE, + ); return $items; } @@ -1887,6 +1906,77 @@ } /** + * Implements hook_renderers. + * Provide default system renderers. + */ +function system_renderers() { + return array( + 'xhtml/plain' => 'system_render_default', + 'json/popup' => 'system_render_json_popup' + ); +} + +/** + * Defualt Drupal renderer. + * Use the theme system to render to xhtml, for consumption by browsers. + * + * @return + * Themed content. + */ +function system_render_default() { + $args = func_get_args(); + return call_user_func_array('theme', $args); +} + +/** + * Render the page as JSON, for consumption by AJAX calls. + * Optimized for AJAX popups. + * + * @return + * JSON object with metadata and themed page content. + */ +function system_render_json_popup() { + $args = func_get_args(); + $hook = array_shift($args); + switch ($hook) { + case 'page': // render('page', $content); + return drupal_json(array( + 'status' => 'ok', + 'title' => drupal_get_title(), + 'messages' => theme('status_messages'), + 'path' => $_GET['q'], + 'content' => $args[0], + )); + default: + return drupal_json(array( + 'status' => 'error', + 'messages' => "No way to render '$hook' in mode 'json/popup'", + )); + } +} + +/** + * Implements hook_after_process_form + */ +function system_after_process_form($request_mode, $form, $form_state) { + switch ($request_mode) { + case 'XMLHttpRequest': + // Request comes from jQuery AJAX call. + // Return status, next page url and form state as JSON. + $url = drupal_redirect_form($form, $form_state['redirect'], FALSE); + print drupal_json(array( + 'status' => 'redirect', + 'path' => $url, + 'form_state' => $form_state, + )); + exit; + case 'standard': + // Default Drupal behavior, redirect to the next page. + drupal_redirect_form($form, $form_state['redirect']); + } +} + +/** * Format the Powered by Drupal text. * * @ingroup themeable Index: modules/system/system.css =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.css,v retrieving revision 1.48 diff -u -r1.48 system.css --- modules/system/system.css 9 Jan 2008 09:56:39 -0000 1.48 +++ modules/system/system.css 20 Mar 2008 01:11:23 -0000 @@ -542,3 +542,80 @@ span.password-confirm span { font-weight: normal; } + +/* +** Popups Dialog box styles +*/ + +#popups-overlay { + position: absolute; +/* width: 100%; + height: 100%; */ + background: black; + z-index: 9; + top: 0; +} +#popups-loading { + z-index: 10; + opacity: 0.75; + position: relative; +} +#popups-loading div{ + position: absolute; +} +#popups { + border: 2px solid #EDF5FA; + background: white; + position: absolute; + opacity: 1.0; + z-index: 10; + color: black; + padding: 0.5em; + width: 600px; + overflow: auto; +} +#popups-title { + /*background: #222; + color: white; + */ + border-bottom: 1px solid #b4d7f0; + background-color: #d4e7f3; + color: #455067; + font-weight: bold; + padding: 0.25em; + margin-bottom: 0.25em; +} +#popups-title div.title { + float:left; +} +#popups-title #popups-close { + float:right; + line-height: 0pt; + margin-top: 2px; /* Trying for vertical centering */ +} +#popups-title #popups-close a { + color: red; +} +#popups-title div.clear { + clear: both; +} +#popups input { + margin: 0.1em; +} +#popups input[type="text"]:focus, #popups input[type="password"]:focus, #popups textarea:focus { + background-color: #FFA; + outline: thin solid grey; +} +#popups div.messages { + background: transparent; + border: none; + padding: 0; + margin: 0; +} + +a.popups-processed:after { + content: "[\2020]"; + vertical-align: super; + font-size: smaller; +} + Index: modules/path/path.admin.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/path/path.admin.inc,v retrieving revision 1.7 diff -u -r1.7 path.admin.inc --- modules/path/path.admin.inc 8 Jan 2008 10:35:42 -0000 1.7 +++ modules/path/path.admin.inc 20 Mar 2008 01:11:22 -0000 @@ -41,7 +41,12 @@ $rows = array(); $destination = drupal_get_destination(); while ($data = db_fetch_object($result)) { - $row = array(check_plain($data->dst), check_plain($data->src), l(t('edit'), "admin/build/path/edit/$data->pid", array('query' => $destination)), l(t('delete'), "admin/build/path/delete/$data->pid", array('query' => $destination))); + $row = array( + check_plain($data->dst), + check_plain($data->src), + l(t('edit'), "admin/build/path/edit/$data->pid", array('query' => $destination)), + l(t('delete'), "admin/build/path/delete/$data->pid", array('query' => $destination, 'attributes'=>array('class'=>'popup-form'))) + ); if ($multilanguage) { $row[4] = $row[3]; $row[3] = $row[2]; Index: misc/tableheader.js =================================================================== RCS file: /cvs/drupal/drupal/misc/tableheader.js,v retrieving revision 1.16 diff -u -r1.16 tableheader.js --- misc/tableheader.js 30 Jan 2008 10:17:39 -0000 1.16 +++ misc/tableheader.js 20 Mar 2008 01:11:22 -0000 @@ -12,6 +12,11 @@ return; } + // If there are no sticky tables in this context, just return. + if (!$('table.sticky-enabled', context).size()) { + return; + } + // Keep track of all cloned table headers. var headers = []; @@ -37,6 +42,7 @@ // Track positioning and visibility. function tracker(e) { + console.log("tracking"); // Save positioning data. var viewHeight = document.documentElement.scrollHeight || document.body.scrollHeight; if (e.viewHeight != viewHeight) { @@ -74,6 +80,7 @@ // Track scrolling. Drupal.tableHeaderOnScroll = function() { + console.log("scrolling: " + headers); $(headers).each(function () { tracker(this); }); Index: modules/filter/filter.module =================================================================== RCS file: /cvs/drupal/drupal/modules/filter/filter.module,v retrieving revision 1.207 diff -u -r1.207 filter.module --- modules/filter/filter.module 13 Mar 2008 21:26:08 -0000 1.207 +++ modules/filter/filter.module 20 Mar 2008 01:11:22 -0000 @@ -477,6 +477,7 @@ $value = filter_resolve_format($value); $formats = filter_formats(); + drupal_add_popups(); $extra = theme('filter_tips_more_info'); if (count($formats) > 1) { @@ -581,7 +582,7 @@ * @ingroup themeable */ function theme_filter_tips_more_info() { - return '

'. l(t('More information about formatting options'), 'filter/tips') .'

'; + return '

'. l(t('More information about formatting options'), 'filter/tips', array('attributes'=>array('class'=>'popup'))) .'

'; } /** Index: modules/filter/filter.admin.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/filter/filter.admin.inc,v retrieving revision 1.10 diff -u -r1.10 filter.admin.inc --- modules/filter/filter.admin.inc 19 Feb 2008 14:07:21 -0000 1.10 +++ modules/filter/filter.admin.inc 20 Mar 2008 01:11:22 -0000 @@ -159,7 +159,8 @@ // Composition tips (guidelines) $tips = _filter_tips($format->format, FALSE); - $extra = '

'. l(t('More information about formatting options'), 'filter/tips') .'

'; + drupal_add_popups(); + $extra = '

'. l(t('More information about formatting options'), 'filter/tips', array('attributes'=>array('class'=>'popup'))) .'

'; $tiplist = theme('filter_tips', $tips, FALSE, $extra); if (!$tiplist) { $tiplist = '

'. t('No guidelines available.') .'

'; Index: index.php =================================================================== RCS file: /cvs/drupal/drupal/index.php,v retrieving revision 1.94 diff -u -r1.94 index.php --- index.php 26 Dec 2007 08:46:48 -0000 1.94 +++ index.php 20 Mar 2008 01:11:19 -0000 @@ -32,8 +32,7 @@ } } elseif (isset($return)) { - // Print any value (including an empty string) except NULL or undefined: - print theme('page', $return); + print render('page', $return); } drupal_page_footer(); Index: modules/help/help.admin.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/help/help.admin.inc,v retrieving revision 1.5 diff -u -r1.5 help.admin.inc --- modules/help/help.admin.inc 25 Nov 2007 11:11:17 -0000 1.5 +++ modules/help/help.admin.inc 20 Mar 2008 01:11:22 -0000 @@ -23,7 +23,7 @@ $output = ''; if (module_hook($name, 'help')) { $module = drupal_parse_info_file(drupal_get_path('module', $name) .'/'. $name .'.info'); - drupal_set_title($module['name']); + drupal_set_title(t('Help: '). $module['name']); $temp = module_invoke($name, 'help', "admin/help#$name", drupal_help_arg()); if (empty($temp)) { Index: modules/node/content_types.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/node/content_types.inc,v retrieving revision 1.50 diff -u -r1.50 content_types.inc --- modules/node/content_types.inc 27 Jan 2008 18:03:05 -0000 1.50 +++ modules/node/content_types.inc 20 Mar 2008 01:11:22 -0000 @@ -10,6 +10,7 @@ * Displays the content type admin overview page. */ function node_overview_types() { + drupal_add_popups(); $types = node_get_types(); $names = node_get_types('names'); $header = array(t('Name'), t('Type'), t('Description'), array('data' => t('Operations'), 'colspan' => '2')); @@ -29,7 +30,7 @@ // Set the delete column. if ($type->custom) { - $row[] = array('data' => l(t('delete'), 'admin/content/node-type/'. $type_url_str .'/delete')); + $row[] = array('data' => l(t('delete'), 'admin/content/node-type/'. $type_url_str .'/delete', array('attributes'=>array('class'=>'popup-form')))); } else { $row[] = array('data' => ''); Index: modules/menu/menu.admin.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/menu/menu.admin.inc,v retrieving revision 1.29 diff -u -r1.29 menu.admin.inc --- modules/menu/menu.admin.inc 23 Feb 2008 08:13:09 -0000 1.29 +++ modules/menu/menu.admin.inc 20 Mar 2008 01:11:22 -0000 @@ -61,6 +61,7 @@ */ function _menu_overview_tree_form($tree) { static $form = array('#tree' => TRUE); + drupal_add_popups(); foreach ($tree as $data) { $title = ''; $item = $data['link']; @@ -97,11 +98,11 @@ $operations['edit'] = l(t('edit'), 'admin/build/menu/item/'. $item['mlid'] .'/edit'); // Only items created by the menu module can be deleted. if ($item['module'] == 'menu' || $item['updated'] == 1) { - $operations['delete'] = l(t('delete'), 'admin/build/menu/item/'. $item['mlid'] .'/delete'); + $operations['delete'] = l(t('delete'), 'admin/build/menu/item/'. $item['mlid'] .'/delete', array('attributes'=>array('class'=>'popup-form'))); } // Set the reset column. elseif ($item['module'] == 'system' && $item['customized']) { - $operations['reset'] = l(t('reset'), 'admin/build/menu/item/'. $item['mlid'] .'/reset'); + $operations['reset'] = l(t('reset'), 'admin/build/menu/item/'. $item['mlid'] .'/reset', array('attributes'=>array('class'=>'popup-form'))); } $form[$mlid]['operations'] = array(); Index: modules/popuppage/popuppage.js =================================================================== RCS file: modules/popuppage/popuppage.js diff -N modules/popuppage/popuppage.js --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/popuppage/popuppage.js 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,247 @@ +// $Id:$ + +/** + * This module uses the Popup API to build dialogs that show the content from a Drupal page. + * + * It is assumed that the pages being returned from the Drupal server will be JSON + * and have the following format: + * + * response.title = The page title. + * response.messages = Error, warning and status messages, as HTML. + * response.path = The page URL. + * response.content = The content of the page, as HTML. + */ + +/** + * Attach the popup bevior to the all the requested links on the page. + * + * @param context: The jQuery object to apply the behaviors to. + */ +Drupal.behaviors.popuppage = function(context) { + var popups = new Drupal.popups(); + + // Add the popup-link-in-dialog behavior to links defined in Drupal.settings.popups.links array. + // TODO: how to handle popups-in-popups? + for (var link in Drupal.settings.popups.links) { + var options = Drupal.settings.popups.links[link]; + popups.attach(context, link, options); // Needs to be seperate function for closure. + } + popups.attach(context, 'a.popup', {}); +}; + +/** + * Attach the popup behavior to a particular link. + * + * @param link - link that was clicked. + * @param options - options associated with the link. + */ +Drupal.popups.prototype.attach = function(context, link, options) { + var popups = this; + $(link, context).not('.popups-processed').each( function() { + $(this).click( function(e){ + var a = this; + // If the option is distructive, check if the page is already modified, and offer to save. + var page_is_dirty = $('span.tabledrag-changed').size() > 0; + var will_modify_original = !options.noReload && !options.singleRow; + if( page_is_dirty && will_modify_original ) { + // The user will lose modifications, so popup dialog offering to save current state. +/* + var body = Drupal.t("There are unsaved changes on this page, which you will lose if you continue."); + var buttons = { + 'popups_save': {title: Drupal.t('Save Changes'), func: function(){popups.save_page(a, options)}}, + 'popups_submit': {title: Drupal.t('Continue'), func: function(){Drupal.popups.close(); popups.open_path(a, options)}}, + 'popups_cancel': {title: Drupal.t('Cancel'), func: Drupal.popups.close} + }; + return popups.open( Drupal.t('Warning: Please Confirm'), body, buttons ); +*/ + $.getJSON(Drupal.settings.basePath + 'popuppage/save_dialog', function(json) { + // Attach behaviors to the buttons in the save-if-modified dialog. + var buttons = { + 'popups_save': {title: json.buttons.save, func: function(){popups.save_page(a, options)}}, + 'popups_submit': {title: json.buttons.submit, func: function(){Drupal.popups.close(); popups.open_path(a, options)}}, + 'popups_cancel': {title: json.buttons.cancel, func: Drupal.popups.close} + }; + popups.open(json.title, json.content, buttons); + }); + return false; +// // Attach the save/continue/cancel behaviors +// $('#popups_save').click(function(){popups.save_page(a, options)}), +// $('#popups_submit').click(function(){Drupal.popups.close(); popups.open_path(a, options)}), +// $('#popups_cancel').click(Drupal.popups.close); +/* + var buttons = { + 'popups_save': {title: Drupal.settings.popups.save.buttons.save, func: function(){popups.save_page(a, options)}}, + 'popups_submit': {title: Drupal.settings.popups.save.buttons.submit, func: function(){Drupal.popups.close(); popups.open_path(a, options)}}, + 'popups_cancel': {title: Drupal.settings.popups.save.buttons.cancel, func: Drupal.popups.close} + }; + return popups.open( Drupal.settings.popups.save.title, Drupal.settings.popups.save.message, buttons ); +*/ + } + else { + return popups.open_path(a, options); + } + }); + $(this).addClass('popups-processed'); + }); +}; + +/** + * Use Ajax to open the link in a popup window. + * + * @param a - link that was clicked. + * @param options - options associated with the link. + */ +Drupal.popups.prototype.open_path = function( a, options ) { + var popup = this; + // let the user know something is happening + $('body').css("cursor", "wait"); + var $overlay = Drupal.popups.add_overlay(); + Drupal.popups.add_loading(); + + // Set custom headers for all following requests. + $.ajaxSetup({ + dataType: 'json', + beforeSend: function(xhr) { +// xhr.setRequestHeader('Accept', 'application/json'); // this might be a good idea? + xhr.setRequestHeader("X-Drupal-Render-Mode", 'json/popup'); + } + }); + + $.getJSON(a.href, function(json) { + popup.open_content(json.title, json.messages + json.content, options, a); + $('body').css("cursor", "auto"); // Return the cursor to normal state. + }); + + return false; +}; + + +Drupal.popups.prototype.open_content = function(title, content, options, a) { + this.open(title, content); + // Add behaviors to content in popup. + // TODO: d-n-d: need to click to let go of selection. + Drupal.attachBehaviors($('#popups-body')); + // Adding collapse moves focus. + this.refocus(); + + // If the popup contains a form, capture submits. + var $form = $('form', '#popups-body'); + $form.ajaxForm({ + dataType: 'json', + beforeSubmit: Drupal.popups.beforeSubmit, + success: function(response, status) { Drupal.popups.formSuccess(response, options, a) }, + }); +} + +/** + * Do before the form in the popup is submitted. + * + */ +Drupal.popups.beforeSubmit = function(form_data, $form, options) { + Drupal.popups.remove_popup(); // Remove just the dialog, but not the overlay. + Drupal.popups.add_loading(); +}; + +/** + * The form in the popup was successfully submitted + * Update the originating page. + * Show any messages in a popup (TODO - make this a configurable option). + * + * @param data - JSON object from server with status of form submission. + * @param options - hash of per link options. + * @param a - the link that was clicked. + */ +Drupal.popups.formSuccess = function (response, options, a) { + if (response.status != 'redirect') { // something went wrong + Drupal.popups.message( "Error: " + response.messages + response.content); + } + else { // Got a good response back from the server. + $.getJSON(response.path, function(data) { + // Are we at an end point, or just moving from one popup to another? + if (!location.href.match(data.path)) { // Not done yet, so show results in new popup. + Drupal.popups.remove_loading(); + var popups = new Drupal.popups(); + popups.open_content(data.title, data.messages + data.content, options, a); + } + else { // Done, so show messages in dialog and embed the results in the original page. + if (data.messages) { + Drupal.popups.message(data.messages); + // Also insert the message into the page above the content. + // Might not be the standard spot, but it is the easiest to find. + var $next = $(Drupal.settings.popups.defaultTargetSelector); + $next.parent().find('div.messages').remove(); // Remove the current messages. + $next.before(data.messages); + } + + // Just update a single row out of a table (still expiremental). + // Loop through, with special case for first element. + if (options.singleRow) { + var href = $(a).attr('href'); + var selector = 'table a[href=' + href + ']'; + var $new_row = $data.find(selector).parents('tr'); // new tr + var $target_row = $(selector).parents('tr'); // target tr. + for (var i in options.singleRow) { + var col = options.singleRow[i]; + $new_row.find(col).contents().not('div.indentation').wrapAll('
'); + $target_row.find(col).contents() + .not('a.tabledrag-handle').not('span.warning').not('div.indentation') + .wrapAll('
'); + $('#killme').replaceWith( $new_row.find('#newvalue').html() ); + } + } + // Update the entire content area (defined by 'target selector'). + else if (!options.noReload) { + var target = options.targetSelector; + if (!target) { + target = Drupal.settings.popups.defaultTargetSelector; + } + + // Update the original page. + var $c = $(target).html(data.content); // Inject the new content into the page. + Drupal.attachBehaviors($c); + } + + // Update the title of the page. + if (options.updateTitle) { + $(Drupal.settings.popups.defaultTitleSelector).html(data.title); + document.title = data.title; // Also update the browser page title (TODO: include site name?). + } + + // Done with changes to the original page, remove effects. + Drupal.popups.remove_loading(); + if (!data.messages) { + // If there is not a messages popup remove the overlay. + Drupal.popups.remove_overlay(); + } + } // End of updating original page. + }); // End of good response. + } +}; + +/** + * Submit the page and reload the results, before popping up the real dialog. + * + * @param a - link that was clicked. + * @param options - options associated with the link. + */ +Drupal.popups.prototype.save_page = function(a, options) { + var popups = this; + // TODO - what if clicking on link with option['targetSelector']? + var target = Drupal.settings.popups.defaultTargetSelector; + var $form = $('form', target); + var ajaxOptions = { + dataType: 'json', + beforeSubmit: Drupal.popups.beforeSubmit, + success: function(response, status) { // Sync up the current page contents with the submit. + var $data = $(response); + var content = $data.find('content').text(); + var $c = $(target).html(content); // Inject the new content into the page. + Drupal.attachBehaviors($c); + Drupal.popups.close(); + // The form has been saved, the page reloaded, now safe to show the link in a popup. + popups.open_path(a, options); + } + }; + $form.ajaxSubmit( ajaxOptions ); // Submit the form. +}; + Index: modules/system/popup-template.tpl.php =================================================================== RCS file: modules/system/popup-template.tpl.php diff -N modules/system/popup-template.tpl.php --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/system/popup-template.tpl.php 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,13 @@ + +
+
+
close popup
+
%title
+
+
+
%body
+
%buttons
+
+ Index: modules/popuppage/popuppage.module =================================================================== RCS file: modules/popuppage/popuppage.module diff -N modules/popuppage/popuppage.module --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/popuppage/popuppage.module 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,360 @@ + Add vocab. Adding second item to page does not trigger d-n-d transformation. + * Might be a problem with Taxonomy. Menus doesn't have problem (adds d-n-d on first item). + */ + + +// ************************************************************************** +// CORE HOOK FUNCTIONS **************************************************** +// ************************************************************************** + +/** + * hook_menu + * + * @return array of new menu items. + */ +function popuppage_menu() { + $items['admin/settings/popuppage'] = array( + 'page callback' => 'drupal_get_form', + 'page arguments' => array('popuppage_admin_settings'), + 'title' => 'Popups', + 'description' => 'Configure the page-in-a-dialog behavior.', + ); + $items['popuppage/save_dialog'] = array( +// 'page callback' => 'popuppage_save_dialog', + 'page callback' => 'theme', + 'page arguments' => array('popup_save_dialog'), + 'access callback' => true, + ); + + return $items; +} + +function popuppage_theme() { + return array( + 'popup_template' => array( + 'template' => 'popup-template', +// 'arguments' => array('title' => '%title', 'body' => '%body', 'buttons' => '%buttons'), + ), + 'popup_save_dialog' => array(), + ); +} + +/** + * An unusual type of theme, this function returns a block of text for the + * save-on-changed ajax popup dialog. + */ +/* +function theme_popup_save() { + $body = t("There are unsaved changes on this page, which you will lose if you continue."); + $body .= ' array( +// 'save' => t('Save Changes'), +// 'submit' => t('Continue'), +// 'cancel' => t('Cancel'), +// ), +// ); +} +*/ + +/** + * An unusual type of theme, this function returns json version of content for + * the save-on-changed ajax popup dialog. + */ +function theme_popup_save_dialog() { + print drupal_json(array( + 'title' => t('Warning: Please Confirm'), + 'content' => t("There are unsaved changes on this page, which you will lose if you continue."), + 'buttons' => array( + 'save' => t('Save Changes'), // + 'submit' => t('Continue'), + 'cancel' => t('Cancel'), + ), + )); +} + +// ??? + +/** + * hook_init + * + * Look at the page path and see if popup behavior has been requested for any links in this page. + */ +function popuppage_init() { + $popups = popuppage_get_popups(); + if (isset($popups[$_GET['q']])) { + popuppage_add_popups( $popups[$_GET['q']] ); + } +// watchdog( "server", print_r($_SERVER['HTTP_X_DRUPAL_RENDERER'], TRUE) ); + +} + +/** + * hook_form_alter + * + * Look at the form_id and see if popup behavior has been requested for any links in this form. + * + * @param form_array $form + * @param array $form_state + * @param str $form_id: + */ +function popuppage_form_alter(&$form, $form_state, $form_id) { +// print $form_id; + // Add popup behavior to the form if requested. + $popups = popuppage_get_popups(); + if (isset($popups[$form_id])) { + popuppage_add_popups( $popups[$form_id] ); + } +} + +// ************************************************************************** +// UTILITY FUNCTIONS ****************************************************** +// ************************************************************************** + +/** + * Build the list of popup rules from all modules that implement hook_popups. + * + * @todo - Add some caching so we don't rebuild everytime. + */ +function popuppage_get_popups() { + static $popups = NULL; + if (!isset($popups)) { + $popups = module_invoke_all('popups'); + } + return $popups; +} + +/** + * Attach the popup behavior to the page. + * + * The default behavoir of a popup is to open a form that will modify the original page. The popup submits + * the form and reloads the original page with the resulting new content. The popup then replaces + * the original page's content area with the new copy of that content area. + * + * @param array $rule: Array of rules to apply to the page or form, keyed by jQuery link selector. + * Options: + * noReload: Does the popup NOT modify the original page (Default: false). + * updateTitle: Does the popup modify the title of the current page (Default: false). + * surpressMessages: Don't show the messages the form returns in a popup (Default: false). + * targetSelector: Defines the area on the original page that will be updated (Default: system-wide setting) + * resultsSubselector: Defines the resulting new content to put into the targetSelector (Default: use the entire new results) + * TODO - come up with a good use cases for resultsSubselector and targetSelector. + * singleRow: Array of selectors descripting the elements inside a row to be replaced (Default: replace entire targetSelector) + * additionalJavascript: Array of JavaScript files that must be included to correctly run the page in the popup. + * additionalCss: Array of CSS files that must be included to correctly style the page in the popup. + * + * Rule Format Example: + * 'admin/content/taxonomy' => array( // Act only on the links on this page. + * 'div#tabs-wrapper a:eq(1)', // No options, so use defaults. Note: Selector must select element(s). + * 'table td:nth-child(2) a' => array( + * 'noReload' => true, // Popup will not modify original page. + * ), + * ) + * + */ +function popuppage_add_popups($rules=null) { + static $added = FALSE; + if (!$added) { + drupal_add_popups(); +// drupal_add_js('misc/popupapi.js', 'core'); +// drupal_add_js(drupal_get_path('module', 'popuppage') .'/popuppage.js'); +// drupal_add_js('misc/jquery.form.js'); + if (is_array($rules)) { + $settings = array( 'popups' => array( + 'defaultTargetSelector' => variable_get('popups_content_selector', 'div.left-corner > div.clear-block:last'), + 'defaultTitleSelector' => variable_get('popups_title_selector', 'div.left-corner > h2:eq(0)'), +// 'template' => theme('popup_template'), + 'links' => array(), // ??? + )); + foreach ($rules as $popup_selector => $options) { + if (is_array($options)) { + $settings['popups']['links'][$popup_selector] = $options; + if (isset($options['additionalJavascript']) && is_array($options['additionalJavascript'])) { + foreach ($options['additionalJavascript'] as $file) { + drupal_add_js($file); + } + } + if (isset($options['additionalCss']) && is_array($options['additionalCss'])) { + foreach ($options['additionalCss'] as $file) { + drupal_add_css($file); + } + } + } + else { + $settings['popups']['links'][$options] = array(); + } + } + drupal_add_js( $settings, 'setting' ); + } + + $added = TRUE; + } +} + +/** + * hook_popups + * + * This implements hook_popups, defined in popuppage_get_popups. + * It adds page-in-popup behavior to the core admin pages. + * See the comments in popuppage_add_popups for explination of the options. + * + * @return: Array of link selectors to apply popup behavior to. + * Keyed by path or form_id. + */ +function popuppage_popups() { +// $operations = preg_replace('/[\W]+/', '-', strtolower(t('Operations'))); + + return array( + 'admin/build/block' => array( // Blocks admin page. + '#tabs-wrapper a[href$=admin/build/block/add]', // Add Block + '#blocks a[href~=admin/build/block/configure]', // configure + '#blocks a[href~=admin/build/block/delete]', // delete + ), + 'admin/build/path' => array( // URL aliases admin page. + '#tabs-wrapper a[href$=admin/build/path/add]', // Add alias + 'td:nth-child(3) a[href~=admin/build/path/edit]', // edit alias + 'td:nth-child(4) a[href~=admin/build/path/delete]', // delete alias + ), + 'admin/content/taxonomy' => array( // Taxonomy admin page. + // TODO: If there are not more than one items to start with, d-n-d files aren't loaded into page. + // This causes trouble when the 2nd item is added, no d-n-d. + // Might be bug in taxonomy table building (or at least inconsistancy). + '#tabs-wrapper a[href$=admin/content/taxonomy/add/vocabulary]' => array( // Add vocabulary + 'additionalJavascript' => array('misc/tabledrag.js'), + ), + '#taxonomy-overview-vocabularies td a:contains('. t('edit vocabulary') .')', // edit vocabulary + '#taxonomy-overview-vocabularies td a:contains('. t('list terms') .')' => array( // list terms + 'noReload' => true, + 'additionalJavascript' => array('misc/tabledrag.js'), + ), + '#taxonomy-overview-vocabularies td a:contains('. t('add terms') .')' => array( // add terms + 'noReload' => true, + 'additionalJavascript' => array('misc/collapse.js'), + ), + ), + 'admin/content/types' => array( // Content Type admin page + '#tabs-wrapper a[href$=admin/content/types/add]' => array( // Add content type + 'additionalJavascript' => array('misc/collapse.js'), + ), + 'table td:nth-child(4) a, table td:nth-child(5) a' // edit, delete + ), + 'admin/content/node' => array( // Existing Content admin page + '#node-admin-content td a:contains('. t('edit') .')' => array( // edit + 'additionalJavascript' => array('misc/collapse.js'), + // TODO: teaser.js not working: Drupal.settings.teaser has no properties + // 'additionalJavascript' => array('misc/collapse.js', 'misc/teaser.js'), + ) + ), + 'page_node_form' => array( // Node edit form + 'a[href$=filter/tips]' => array( // Fixes insane "More information..." link + 'noReload' => true, + //TODO 'addCloseButton' => true - just an idea. + ) + ), + 'admin/content/comment' => array( // Comments admin page. + 'table td:nth-child(2) a' => array( // view (TODO: popup too small) + 'noReload' => true, + 'additionalCss' => array(), //TODO: needs some custom css to get #comments to stay in the popup (no -25px margin). + ), + '#comment-admin-overview td a:contains('. t('edit') .')' => array( // edit + 'additionalJavascript' => array('misc/collapse.js'), + ), + ), + 'admin/user/rules' => array( // Access rules admin page. + '#tabs-wrapper a[href$=admin/user/rules/add]', // Add rule + 'table td:nth-child(4) a, table td:nth-child(5) a', // edit, delete + '#tabs-wrapper a[href$=/admin/user/rules/check]' => array( // Check rule + 'noReload' => true, + ), + ), + 'admin/user/user' => array( // Manage all users admin page. + //Add user (TODO: Can't test, keeps crashing apache!) + '#tabs-wrapper a[href$=admin/user/user/create]' => array( + // TODO: "translate has no properties" user.js line 16. + 'additionalJavascript' => array(drupal_get_path('module', 'user') .'/user.js'), + ), + '#user-admin-account td:nth-child(2) a' => array( // View the user + 'noReload' => true, + ), + + ), + 'menu_overview_form' => array( // Menu admin form. + // Add Item, , edit, delete + '#tabs-wrapper a:eq(1), table#menu-overview td:nth-child(5) a, table#menu-overview td:nth-child(6) a', + '#tabs-wrapper a:eq(2)' => array( // Edit menu: update just page title. + 'updateTitle' => true, + 'noReload' => true, + ), + ), + + // CCK - Manage fields page. + 'content_admin_field_overview_form' => array( + 'div#tabs-wrapper a:eq(0)' => array( // Edit + 'updateTitle' => true, + 'noReload' => true, + ), + 'div#tabs-wrapper a:eq(2)' => array( // Display fields + 'noReload' => true, + ), + 'div#tabs-wrapper a:eq(3), div#tabs-wrapper a:eq(4)', // Add field, Add group + 'table td:nth-child(5) a' => array( // configure + 'singleRow' => array( 'td:eq(0)' ), // Just update the row (still expiremental). + ), + 'table td:nth-child(6) a', // remove + ), + ); +} + +// ************************************************************************** +// ADMIN SETTINGS ********************************************************* +// ************************************************************************** + +function popuppage_admin_settings() { + drupal_set_title("Popups Settings"); + $form = array(); + + $form['popuppage_title_selector'] = array( + '#type' => 'textfield', + '#title' => t('Title Selector'), + '#default_value' => variable_get('popuppage_title_selector', 'div.left-corner > h2:eq(0)'), + '#description' => t("jQuery selector to define the page's title on your Admin theme."), + ); + $form['popuppage_content_selector'] = array( + '#type' => 'textfield', + '#title' => t('Content Selector'), + '#default_value' => variable_get('popuppage_content_selector', 'div.left-corner > div.clear-block:last'), + '#description' => t('jQuery selector to define the page\'s content area on your Admin theme.'), + ); + + return system_settings_form($form); +} Index: misc/popupapi.js =================================================================== RCS file: misc/popupapi.js diff -N misc/popupapi.js --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ misc/popupapi.js 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,462 @@ +// $Id:$ + +/** + * Popup Modal Dialog API + * + * Provide an API for building and displaying JavaScript, in-page, popup modal dialogs. + * Modality is provided by a fixed, semi-opaque div, positioned in front of the page contents. + * + */ + +/** + * Attach the popup bevior to the all the requested links on the page. + * + * @param context + * The jQuery object to apply the behaviors to. + */ +Drupal.behaviors.popups = function(context) { + $body = $('body'); + if(!$body.hasClass('popups-processed')) { + $(document).bind('keydown', Drupal.popups.keyHandle); + $body.addClass('popups-processed'); + } + Drupal.popups.attach(context, 'a.popup', {}); + Drupal.popups.attach(context, 'a.popup-form', {updatePage: true}); + Drupal.popups.attach(context, 'a.popup-nonmodal', {nonModal: true}); +}; + +/** + * Create the popups object/namespace. + */ +Drupal.popups = function() {}; + +/** + * Attach the popup behavior to a particular link. + * + * @param selector + * jQuery selector for links to attach popup behavior to. + * @param options + * Hash of options associated with these links. + */ +Drupal.popups.attach = function(context, selector, options) { + $(selector, context).not('.popups-processed').each( function() { + $(this).click( function(e){ + var a = this; + // If the option is distructive, check if the page is already modified, and offer to save. + var pageIsDirty = $('span.tabledrag-changed').size() > 0; + var willModifyOriginal = options.updatePage; + if( pageIsDirty && willModifyOriginal ) { + // The user will lose modifications, so popup dialog offering to save current state. + $.getJSON(Drupal.settings.basePath + 'popuppage/save_dialog', function(json) { + // Attach behaviors to the buttons in the save-if-modified dialog. + var buttons = { + 'popups_save': {title: json.buttons.save, func: function(){Drupal.popups.savePage(a, options)}}, + 'popups_submit': {title: json.buttons.submit, func: function(){Drupal.popups.removePopup(); Drupal.popups.openPath(a, options)}}, + 'popups_cancel': {title: json.buttons.cancel, func: Drupal.popups.close} + }; + Drupal.popups.open(json.title, json.content, buttons); + }); + return false; + } + else { + return Drupal.popups.openPath(a, options); + } + }); + $(this).addClass('popups-processed'); + }); +}; + + +/** + * Generic dialog builder. + */ +Drupal.popups.open = function(title, body, buttons) { + + Drupal.popups.addOverlay(); // TODO - nonModal option. + var $popup = $(Drupal.theme('popupsDialog', title, body, buttons)); + // Start with dialog off the side. Making it invisible causes flash in FF2. + $popup.css('left', -9999); +// $overlay.before( $popup ); // Add the popup to the DOM. + $('body').append( $popup ); // Add the popup to the DOM. + + // Adding button functions + if (buttons) { + for (var id in buttons) { + if (buttons[id]) { // to make jslint happy. + var func = buttons[id].func; + $('#'+id).click( func ); + } + } + } + $('#popups-close').click( Drupal.popups.close ); + + // center on the screen, adding in offsets if the window has been scrolled + var popupWidth = $popup.width(); + var windowWidth = $(window).width(); + var left = (windowWidth / 2) - (popupWidth / 2) + Drupal.popups.scrollLeft(); + +// var top; + // Get popups's height on the page. + // Causes flash in FF2 if popup is not visible! + var popupHeight = $popup.height(); + var windowHeight = $(window).height(); + if (popupHeight > (0.9 * windowHeight) ) { // Must fit in 90% of window. + popupHeight = 0.9 * windowHeight; + $popup.height(popupHeight); + } + var top = (windowHeight / 2) - (popupHeight / 2) + Drupal.popups.scrollTop(); + + $popup.css('top', top).css('left', left); // Position the popup to be visible. + + this.refocus(); // TODO: capture the focus when it leaves the dialog. + Drupal.popups.removeLoading(); // Remove the loading img. + + return false; +}; + +/** + * Simple popup that functions like the browser's alert box. + */ +Drupal.popups.message = function(message, body) { + body = body || ''; + var buttons = { + 'popups_ok': { title: Drupal.t('OK'), func: Drupal.popups.close } + }; + Drupal.popups.open( message, body, buttons ); +}; + +/** + * Handle any special keys when popup is active. + */ +Drupal.popups.keyHandle = function(e) { + if (!e) { + e = window.event; + } + switch (e.keyCode) { + case 27: // esc + Drupal.popups.close(); + return false; + case 191: // '?' key, show help. + if (e.shiftKey && e.ctrlKey) { +// console.log("Show Help"); + var $help = $('a.popup.more-help'); + if ($help.size()) { + $help.click(); + } + else { + Drupal.popups.message(Drupal.t("Sorry, there is no additional help for this page")); + } + } + default: // all other keys +// console.log( e.keyCode ); +// return true; + } +}; + +/***************************************************************************** + * Appearence Functions (overlay, loading graphic, remove popup) ********* + *****************************************************************************/ + +Drupal.popups.removePopup = function() { + $('#popups').fadeOut('fast', function() { + $('#popups').remove(); + }); +}; + +Drupal.popups.addOverlay = function() { + var $overlay = $('#popups-overlay'); + if (!$overlay.size()) { // Overlay does not already exist, so create it. + $overlay = $(Drupal.theme('popupsOverlay')); + $overlay.css('opacity', '0.4'); // for ie6(?) + $body = $('body'); + // Doing absolute positioning, so make overlay's size equal the entire body. + $overlay.width($body.width()).height($body.height()); + $overlay.click(Drupal.popups.close); + $body.prepend($overlay); + } +// return $overlay; +}; + +//Drupal.popups.getOverlay = function() { +// return $('#popups-overlay'); +//}; + +Drupal.popups.removeOverlay = function() { + $('#popups-overlay').fadeOut('fast', function() { + $('#popups-overlay').remove(); + }); +}; + +Drupal.popups.addLoading = function() { + var $loading = $('#popups-loading'); + if (!$loading.size()) { // Overlay does not already exist, so create it. + var waitImageSize = 100; + var left = ($(window).width() / 2) - (waitImageSize / 2) + Drupal.popups.scrollLeft();; + var top = ($(window).height() / 2) - (waitImageSize / 2) + Drupal.popups.scrollTop();; + $loading = $( Drupal.theme('popupsLoading', left, top) ); + $('body').prepend($loading); + } +}; + +Drupal.popups.removeLoading = function() { + $('#popups-loading').remove(); +}; + +/** + * Remove everything. + */ +Drupal.popups.close = function() { + Drupal.popups.removePopup(); + Drupal.popups.removeLoading(); + Drupal.popups.removeOverlay(); +}; + +/** + * Set the focus on the popup to the first visible form element, or the first button, or the close link. + */ +Drupal.popups.refocus = function() { + $focus = $('#popups input:visible:eq(0)') + if (!focus) { + $focus = $('#popups-close'); // Doesn't seem to work. + } + $focus.focus() +}; + +/**************************************************************************** + * Theme Functions ******************************************************** + ****************************************************************************/ + +Drupal.theme.prototype.popupsLoading = function(left, top) { + var loading = '
'; + loading += '
'; + loading += ''; + loading += '
'; + return loading; +}; + +Drupal.theme.prototype.popupsOverlay = function() { + return '
'; +}; + +Drupal.theme.prototype.popupsButton = function(title, id) { + return '' +}; + +Drupal.theme.prototype.popupsDialog = function(title, body, buttons) { + var template = Drupal.settings.popups.template; + var popup = template.replace('%title', title).replace('%body', body); + + var themedButtons = ''; + for ( var id in buttons) { + if (buttons[id]) { + themedButtons += Drupal.theme('popupsButton', buttons[id].title, id); + } + } + popup = popup.replace('%buttons', themedButtons); + return popup; +}; + +/**************************************************************************** + * Utility scroll functions taken from: *** + * http://www.softcomplex.com/docs/get_window_size_and_scrollbar_position.html + ****************************************************************************/ + +Drupal.popups.scrollLeft = function() { + return Drupal.popups.filterResults ( + window.pageXOffset ? window.pageXOffset : 0, + document.documentElement ? document.documentElement.scrollLeft : 0, + document.body ? document.body.scrollLeft : 0 ); +}; + +Drupal.popups.scrollTop = function() { + return Drupal.popups.filterResults ( + window.pageYOffset ? window.pageYOffset : 0, + document.documentElement ? document.documentElement.scrollTop : 0, + document.body ? document.body.scrollTop : 0 ); +}; + +Drupal.popups.filterResults = function(win, docel, body) { + var result = win ? win : 0; + if (docel && (!result || (result > docel))) { + result = docel; + } + return body && (!result || (result > body)) ? body : result; +}; + + +/**************************************************************************** + * Page & Form in popup functions *** + ****************************************************************************/ + +/** + * Use Ajax to open the link in a popup window. + * + * @param a + * Link that was clicked to open the popup. + * @param options + * Hash of options controlling how the popup interacts with the underlying page. + */ +Drupal.popups.openPath = function(a, options) { + // let the user know something is happening + $('body').css("cursor", "wait"); + + // TODO - get nonmodal working. + if (!options.nonModal) { + Drupal.popups.addOverlay(); + } + Drupal.popups.addLoading(); + + // Set custom headers for all following requests. + $.ajaxSetup({ +// dataType: 'json', + beforeSend: function(xhr) { + xhr.setRequestHeader("X-Drupal-Render-Mode", 'json/popup'); + } + }); + + $.getJSON(a.href, function(json) { + Drupal.popups.openContent(json.title, json.messages + json.content, options); + $('body').css("cursor", "auto"); // Return the cursor to normal state. + }); + + return false; +}; + +/** + * Open content in an ajax popup. + * + * @param title + * String title of the popup. + * @param content + * HTML to show in the popup. + * @param options + * Hash of options controlling how the popup interacts with the underlying page. + */ +Drupal.popups.openContent = function(title, content, options) { + Drupal.popups.open(title, content); + // Add behaviors to content in popup. + // TODO: d-n-d: need to click to let go of selection. + Drupal.attachBehaviors($('#popups-body')); + // Adding collapse moves focus. + Drupal.popups.refocus(); + + // If the popup contains a form, capture submits. + var $form = $('form', '#popups-body'); + $form.ajaxForm({ + dataType: 'json', + beforeSubmit: Drupal.popups.beforeSubmit, + success: function(response, status) { + Drupal.popups.formSuccess(response, options) + }, + }); +}; + +/** + * Do before the form in the popup is submitted. + */ +Drupal.popups.beforeSubmit = function(formData, $form, options) { + Drupal.popups.removePopup(); // Remove just the dialog, but not the overlay. + Drupal.popups.addLoading(); +}; + +/** + * The form in the popup was successfully submitted + * Update the originating page. + * Show any messages in a popup (TODO - make this a configurable option). + * + * @param response + * JSON object from server with status of form submission. + * @param options + * Hash of options controlling how the popup interacts with the underlying page. + * //noReload: bool, does the popup effect the underlying page. + * updatePage: bool, does the popup effect the underlying page. + * updateTitle: bool, does the popup change the title of the page. + * nonModal: bool, does the popup block access to the underlying page. + * targetSelector: jQuery selector, overrides defaultTargetSelector. + */ +Drupal.popups.formSuccess = function(response, options) { + if (response.status != 'redirect') { // something went wrong + Drupal.popups.message( "Error: " + response.messages + response.content); + } + else { // Got a good response back from the server. + $.getJSON(response.path, function(data) { + // Determine if we are at an end point, or just moving from one popup to another. + if (!location.href.match(data.path)) { // Not done yet, so show results in new popup. + Drupal.popups.removeLoading(); + Drupal.popups.openContent(data.title, data.messages + data.content, options); + } + else { // Done, so show messages in dialog and embed the results in the original page. + if (data.messages) { + Drupal.popups.message(data.messages); + // Also insert the message into the page above the content. + // Might not be the standard spot, but it is the easiest to find. + var $next = $(Drupal.settings.popups.defaultTargetSelector); + $next.parent().find('div.messages').remove(); // Remove the current messages. + $next.before(data.messages); + } + + // Update the entire content area (defined by 'target selector'). +// if (!options.noReload) { + if (options.updatePage) { + var target = options.targetSelector; + if (!target) { + target = Drupal.settings.popups.defaultTargetSelector; + } + // Update the original page. + var $c = $(target).html(data.content); // Inject the new content into the page. + Drupal.attachBehaviors($c); + } + + // Update the title of the page. + if (options.updateTitle) { + $(Drupal.settings.popups.defaultTitleSelector).html(data.title); + document.title = data.title; // Also update the browser page title (TODO: include site name?). + } + + // Done with changes to the original page, remove effects. + Drupal.popups.removeLoading(); + if (!data.messages) { + // If there is not a messages popup remove the overlay. + Drupal.popups.removeOverlay(); + } + } // End of updating original page. + }); // End of good response. + } +}; + +/** + * Submit the page and reload the results, before popping up the real dialog. + * + * @param a + * Link that was clicked to open the popup. + * @param options + * Hash of options controlling how the popup interacts with the underlying page. + */ +Drupal.popups.savePage = function(a, options) { + var target = Drupal.settings.popups.defaultTargetSelector; + var $form = $('form', target); + var ajaxOptions = { + dataType: 'json', + beforeSubmit: Drupal.popups.beforeSubmit, + success: function(response, status) { + // Sync up the current page contents with the submit. + $.ajax({ + url: response.path, + dataType: 'json', + beforeSend: function(xhr) { + xhr.setRequestHeader("X-Drupal-Render-Mode", 'json/popup'); + }, + success: function(json) { + // Update the original page. + var $c = $(target).html(json.content); // Inject the new content into the page. + Drupal.attachBehaviors($c); + // The form has been saved and the page reloaded, now safe to open the clicked link in a popup. + Drupal.popups.openPath(a, options); + }, + }); + } + }; + $form.ajaxSubmit(ajaxOptions); // Submit the form. +}; + + Index: modules/popuppage/popuppage.info =================================================================== RCS file: modules/popuppage/popuppage.info diff -N modules/popuppage/popuppage.info --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/popuppage/popuppage.info 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,6 @@ +; $Id:$ +name = Popups +description = General dialog creation utilities +package = User interface +core = 7.x +