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	19 Mar 2008 18:33:35 -0000
@@ -1449,7 +1449,7 @@
  * Returns code that emits the 'more help'-link.
  */
 function theme_more_help_link($url) {
-  return '<div class="more-help-link">'. t('[<a href="@link">more help...</a>]', array('@link' => check_url($url))) .'</div>';
+  return '<div class="more-help-link">'. t('[<a class="popup more-help" href="@link">more help...</a>]', array('@link' => check_url($url))) .'</div>';
 }
 
 /**
@@ -1616,6 +1616,22 @@
 }
 
 /**
+ * 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'), 
+    ),
+  ));
+}
+
+/**
  * @} End of "defgroup themeable".
  */
 
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	19 Mar 2008 18:33:35 -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/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	19 Mar 2008 18:33:34 -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/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	19 Mar 2008 18:33:33 -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/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	19 Mar 2008 18:33:34 -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/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	19 Mar 2008 18:33:37 -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' => '<em>'. t('There are currently no access rules.') .'</em>', 'colspan' => 5));
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	19 Mar 2008 18:33:35 -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	19 Mar 2008 18:33:36 -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	19 Mar 2008 18:33:37 -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	19 Mar 2008 18:33:36 -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	19 Mar 2008 18:33:36 -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	19 Mar 2008 18:33:35 -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: 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	19 Mar 2008 18:33:33 -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/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	19 Mar 2008 18:33:35 -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 '<p>'. l(t('More information about formatting options'), 'filter/tips') .'</p>';
+  return '<p>'. l(t('More information about formatting options'), 'filter/tips', array('attributes'=>array('class'=>'popup'))) .'</p>';
 }
 
 /**
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	19 Mar 2008 18:33:35 -0000
@@ -159,7 +159,8 @@
 
     // Composition tips (guidelines)
     $tips = _filter_tips($format->format, FALSE);
-    $extra = '<p>'. l(t('More information about formatting options'), 'filter/tips') .'</p>';
+    drupal_add_popups();
+    $extra = '<p>'. l(t('More information about formatting options'), 'filter/tips', array('attributes'=>array('class'=>'popup'))) .'</p>';
     $tiplist = theme('filter_tips', $tips, FALSE, $extra);
     if (!$tiplist) {
       $tiplist = '<p>'. t('No guidelines available.') .'</p>';
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	19 Mar 2008 18:33:35 -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	19 Mar 2008 18:33:36 -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	19 Mar 2008 18:33:36 -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('<div id="newvalue"/>');
+  	          $target_row.find(col).contents()
+  	            .not('a.tabledrag-handle').not('span.warning').not('div.indentation')
+  	            .wrapAll('<div id="killme"/>');
+  	          $('#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 @@
+<?php
+// $Id:$
+?>
+<div id="popups">
+  <div id="popups-title">
+    <div id="popups-close"><a><img alt="close popup" src="<?php print base_path() ?>misc/popup-close.png" /></a></div>
+    <div class="title">%title</div>
+    <div class="clear"></div>
+  </div>
+  <div id="popups-body">%body</div>
+  <div id="popups-buttons">%buttons</div>
+</div>
+ 
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 = '<div id="popups-loading">';
+  loading += '<div style="left:' + left +'px; top:' + top +'px;">';
+  loading += '<img src="'+ Drupal.settings.basePath +'/misc/ajax-loader.gif" />';
+  loading += '</div></div>';
+  return loading;
+};
+
+Drupal.theme.prototype.popupsOverlay = function() {
+  return '<div id="popups-overlay"></div>';
+};
+
+Drupal.theme.prototype.popupsButton = function(title, id) {
+  return '<input type="button" value="'+ title +'" id="'+ id +'" />'
+};
+
+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.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,374 @@
+<?php
+// $Id:$
+
+/**
+ * @file popuppage.module
+ *
+ * This module uses the popup API to enhance the Administration Pages by allowing pages
+ *  to be shown inside modual dialogs.
+ * It also provides a hook_popups for other pages that want to use this functionality. 
+ *
+ * @todo 
+ * * Adding Javascript into popups doesn't always work.
+ * *   tabledrag onmouse up action. 
+ * *   user.js and teaser.js bugs.
+ * * Get cursor visible in Firefox 2: Ugly! https://bugzilla.mozilla.org/show_bug.cgi?id=167801
+ * * * Also: http://groups.google.com/group/jquery-ui/browse_thread/thread/7d448f5d1d2abd78/e7abb68dd7325952?#e7abb68dd7325952
+ * * * Might not be solvable before FF 3 comes out.
+ * * * Partial fix is :focus {background-color: #FFA}, but does not good for color-blind folk.
+ * * * Note: The popup used by Teleport does not have this problem!
+ * * * Aha! Changing #popups-overlay from "fixed" to "absolute" solves this issue (but raises others)
+ * * Cache the results of hook_popups.
+ * * Make the message-in-popup behavior configurable (?).
+ * * Taxonomy > 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 .= '<input type="button" value="'. t('Save Changes') .'" id="save" onclick="/>';
+  $body .= '<input type="button" value="'. t('Save Changes') .'" id="save" onclick="/>';
+  $body .= '<input type="button" value="'. t('Save Changes') .'" id="save" onclick="/>';
+  print $body;  
+  
+//  return theme('popup_template', t('Warning: Please Confirm'), );
+  
+//  return array( 
+//    'title' => t('Warning: Please Confirm'),
+//    'message' => 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'),
+//    ),
+//  );              
+}
+*/
+
+/**
+ * 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'),  // <input type="button" value="'. ('Save Changes') .'" id="save" />
+      'submit' => t('Continue'),
+      'cancel' => t('Cancel'), 
+    ),
+  ));
+}
+
+// ??? <input type="button" value="'+ button.title +'" id="'+ id +'" />
+
+/**
+ * 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;
+}
+
+function drupal_add_popups() {
+  static $added = FALSE;
+  if (!$added) {
+    drupal_add_js('misc/popupapi.js', 'core');    
+    drupal_add_js(drupal_get_path('module', 'popuppage') .'/popuppage.js');
+    drupal_add_js('misc/jquery.form.js');
+    $settings = array( 'popups' => array(
+        'template' => theme('popup_template'),
+    ));
+    drupal_add_js( $settings, 'setting' );
+    $added = TRUE;    
+  }
+}
+
+/**
+ * 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 <a> 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: 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
+
