changeset 0:76f9b43738f2

Popups 2.0-alpha5
author Franck Deroche <franck@defr.org>
date Fri, 31 Dec 2010 13:41:08 +0100
parents
children 4215c43e74eb c076d54409cb
files .cvsignore .project API.txt CHANGELOG.txt LICENSE.txt README.txt ajax-loader.gif popups.api.php popups.css popups.info popups.install popups.js popups.module popups_admin.info popups_admin.module popups_test.info popups_test.module skins/basic/basic.css skins/basic/popup-icon.png skins/blue/blue.css skins/blue/images/sprites.png skins/blue/images/tile-v.png skins/facebook/facebook.css skins/facebook/facebook.js skins/facebook/loading-large.gif skins/facebook/loading.gif skins/facebook/popups-border.png translations/ja.po
diffstat 28 files changed, 3037 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.cvsignore	Fri Dec 31 13:41:08 2010 +0100
@@ -0,0 +1,1 @@
+.project
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.project	Fri Dec 31 13:41:08 2010 +0100
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>popups62</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+	</buildSpec>
+	<natures>
+	</natures>
+</projectDescription>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API.txt	Fri Dec 31 13:41:08 2010 +0100
@@ -0,0 +1,37 @@
+As well as attaching popup behavior to links, 
+Popups API provides javascript function for creating in-window popup messages.
+
+Popups.message(title, message)
+  Produces a simple modal box with the title, message and "OK", "Cancel" buttons.
+  
+Popups.open(title, body, buttons, width)
+  More powerful, allows you to specify what the buttons are and what they do.
+  buttons is a hash of hash, with button title and function.
+  * Example:
+  Drupal.popups.open( 
+    Drupal.t('Warning: Please Confirm'), 
+    Drupal.t("There are unsaved changes on this page, which you will lose if you continue."),
+    { 
+      'popup_save': {
+        title: Drupal.t('Save Changes'), 
+        func: function(){Drupal.popups.savePage(element, options);}
+      },
+      'popup_submit': {
+        title: Drupal.t('Continue'), 
+        func: function(){Drupal.popups.removePopup(); Drupal.popups.openPath(element, options);}
+      },
+      'popup_cancel': {
+        title: Drupal.t('Cancel'), func: Drupal.popups.close;
+      } 
+    }
+  );
+
+// TODO - make a more useful api function for opening a path.
+Popups.openPath = function(element, options, parent)
+ * @param element
+ *   Element that was clicked to open the popups.
+ * @param options
+ *   Hash of options controlling how the popups interacts with the underlying page.
+ * @param parent
+ *   If path is being opened from inside another popup, that popup is the parent.
+  
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CHANGELOG.txt	Fri Dec 31 13:41:08 2010 +0100
@@ -0,0 +1,91 @@
+6.x--2-0-ALPHA5
+Features
+ * New options and options system.
+ ** Added updateMethod, updateSource, onUpdate, doneTest, skipDirtyCheck, and hijackDestination.
+ ** Removed noUpdate, reloadOnUpdate, forceReturn, nonModal and afterSubmit.
+ ** Default options are declared in popups.js
+ ** Options in on-popups-option attribute now override default and hook options, instead of replacing them. 
+ * Popups.addedJS => Keeping track of JIT loaded JS files, so they are not reloaded. 
+Bug Fixes
+ * Fixed tests #7 & #8.
+ * Fixing :input highlight in basic.css.
+ * http://drupal.org/node/406326 (sirkitree) - fixing facebook skin resize.
+Other
+ * Broke out Popups.clickPopupElement from Popups.attach
+ * Broke out Popups.activeLayerIsEdited from Popups.attach
+ * Broke out Popups.Popup.prototype.fill from Popups.open
+ * Popups.openContent -> Popups.openContentPath
+ * New properties added to Popups.Popup object.
+ * Abstracted Popups.activeLayerIsEdited.
+ * Abstracted Popups.offerToSave.
+ * Popups.formSuccess(popup, json) - parameters changed.
+ * SavePage function changed to saveFormOnLayer.
+Todo
+ * Improve Popups.activeLayerIsEdited
+ * Recapture the focus on popup if user tabs away.
+ 
+6.x--2-0-ALPHA4
+Features  
+ * Broadcast "Popups Open Path Done" event.
+ * Huge change - Popup Stacking now works. Can have multiple Popups in the dom.
+ ** Skins change most ids to classes.
+ ** Introduced Popups.Popup object.
+Other
+ * Clean up.
+ * Using Drupal.parseJson instead of eval for encoded options.
+
+6.x--2-0-ALPHA3
+Bug Fixes
+ * Removing the jit css files on popups close.
+Other
+ * Removing additionalJavascript, additionalCSS and additionalJavascriptSettings. 
+
+6.x--2-0-ALPHA2
+New Features
+ * http://drupal.org/node/336641 - Nate's big ol' just-in-time css and js loader patch
+Issues
+ * JIT loaded css and js are not unloaded when popup is dismissed
+ * File order is not maintained. JIT files are loaded after page's original pages.
+ * Inline scripts need more testing:
+ ** Should inline scripts be called before or after popup content is loaded?
+ *** Poll wants to go after
+ ** Should already loaded inlines be executed again? 
+Todo
+ * Can I control order of css adds (modules vs themes)?
+ * What about multiple popups - can I flag the js files as already loaded?
+ ** Inlines will still want to be run again.
+ * Q: Why the heck is the poll script inlined and not a behavior?
+ * Drupal.popups.restorePage();
+ * Not working with Popup: A&R?
+
+6.x--2-0-ALPHA1
+New Features
+ * http://drupal.org/node/364712 (sirkitree) - Customize loading graphic.
+ * Using a small popup signifier icon instead of unicode char (pulled from D7 patch).
+ * http://drupal.org/node/372378 - sirkitree & Rob Loach - popup skin interface.
+ * Added Facebook skin by sirkitree
+ * Added Blue skin by Tj Holowaychuk
+ * Unskinned skin checks current theme for popups-skin.js
+ * http://drupal.org/node/373737 (Rob Loach) - Finally caching hook_popups hash (thanks Rob).
+ * http://drupal.org/node/386168 (Rob Loach) - adding hook_popups_alter and popups.api.php
+Bug Fixes
+ * http://drupal.org/node/366093 (mathiaz.sk) - Opera fix.
+ * Modified basic loading layer to work with #364712.
+ * http://drupal.org/node/361957 - breaking admin/build/block (fixed by removing popups-popup.tpl.php)
+ * Popups working on admin/build/block/list/*
+ * http://drupal.org/node/373702 (Rob Loach) - Better wording for auto close messages.
+ * Removing hook_theme and popup/save_dialog menu item.
+ * http://drupal.org/node/385732 (Rob Loach) - Bugs fixes in refocus().
+Other 
+ * Removed popups-popup.tpl.php - theming html now done in my_theme.js or in a skin.
+
+6.x--1-2
+New Features
+ * http://drupal.org/node/336063 - Adding optional afterSubmit callback.
+ * Compatability with Popups: Add and Reference
+ ** Adding broadcasting of a couple of popup lifecycle events (popups_open_path, popups_form_success).
+Other
+ * Added a couple new comments and cleaned up some obsolete code in popups.js
+
+6.x--1-1
+ * Removed a couple non-working links from popups_admin.module.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/LICENSE.txt	Fri Dec 31 13:41:08 2010 +0100
@@ -0,0 +1,274 @@
+GNU GENERAL PUBLIC LICENSE
+
+              Version 2, June 1991
+
+Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave,
+Cambridge, MA 02139, USA. Everyone is permitted to copy and distribute
+verbatim copies of this license document, but changing it is not allowed.
+
+                  Preamble
+
+The licenses for most software are designed to take away your freedom to
+share and change it. By contrast, the GNU General Public License is
+intended to guarantee your freedom to share and change free software--to
+make sure the software is free for all its users. This General Public License
+applies to most of the Free Software Foundation's software and to any other
+program whose authors commit to using it. (Some other Free Software
+Foundation software is covered by the GNU Library General Public License
+instead.) You can apply it to your programs, too.
+
+When we speak of free software, we are referring to freedom, not price. Our
+General Public Licenses are designed to make sure that you have the
+freedom to distribute copies of free software (and charge for this service if
+you wish), that you receive source code or can get it if you want it, that you
+can change the software or use pieces of it in new free programs; and that
+you know you can do these things.
+
+To protect your rights, we need to make restrictions that forbid anyone to
+deny you these rights or to ask you to surrender the rights. These restrictions
+translate to certain responsibilities for you if you distribute copies of the
+software, or if you modify it.
+
+For example, if you distribute copies of such a program, whether gratis or for
+a fee, you must give the recipients all the rights that you have. You must make
+sure that they, too, receive or can get the source code. And you must show
+them these terms so they know their rights.
+
+We protect your rights with two steps: (1) copyright the software, and (2)
+offer you this license which gives you legal permission to copy, distribute
+and/or modify the software.
+
+Also, for each author's protection and ours, we want to make certain that
+everyone understands that there is no warranty for this free software. If the
+software is modified by someone else and passed on, we want its recipients
+to know that what they have is not the original, so that any problems
+introduced by others will not reflect on the original authors' reputations.
+
+Finally, any free program is threatened constantly by software patents. We
+wish to avoid the danger that redistributors of a free program will individually
+obtain patent licenses, in effect making the program proprietary. To prevent
+this, we have made it clear that any patent must be licensed for everyone's
+free use or not licensed at all.
+
+The precise terms and conditions for copying, distribution and modification
+follow.
+
+           GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND
+               MODIFICATION
+
+0. This License applies to any program or other work which contains a notice
+placed by the copyright holder saying it may be distributed under the terms
+of this General Public License. The "Program", below, refers to any such
+program or work, and a "work based on the Program" means either the
+Program or any derivative work under copyright law: that is to say, a work
+containing the Program or a portion of it, either verbatim or with
+modifications and/or translated into another language. (Hereinafter, translation
+is included without limitation in the term "modification".) Each licensee is
+addressed as "you".
+
+Activities other than copying, distribution and modification are not covered
+by this License; they are outside its scope. The act of running the Program is
+not restricted, and the output from the Program is covered only if its contents
+constitute a work based on the Program (independent of having been made
+by running the Program). Whether that is true depends on what the Program
+does.
+
+1. You may copy and distribute verbatim copies of the Program's source
+code as you receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice and
+disclaimer of warranty; keep intact all the notices that refer to this License
+and to the absence of any warranty; and give any other recipients of the
+Program a copy of this License along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and you
+may at your option offer warranty protection in exchange for a fee.
+
+2. You may modify your copy or copies of the Program or any portion of it,
+thus forming a work based on the Program, and copy and distribute such
+modifications or work under the terms of Section 1 above, provided that you
+also meet all of these conditions:
+
+a) You must cause the modified files to carry prominent notices stating that
+you changed the files and the date of any change.
+
+b) You must cause any work that you distribute or publish, that in whole or in
+part contains or is derived from the Program or any part thereof, to be
+licensed as a whole at no charge to all third parties under the terms of this
+License.
+
+c) If the modified program normally reads commands interactively when run,
+you must cause it, when started running for such interactive use in the most
+ordinary way, to print or display an announcement including an appropriate
+copyright notice and a notice that there is no warranty (or else, saying that
+you provide a warranty) and that users may redistribute the program under
+these conditions, and telling the user how to view a copy of this License.
+(Exception: if the Program itself is interactive but does not normally print such
+an announcement, your work based on the Program is not required to print
+an announcement.)
+
+These requirements apply to the modified work as a whole. If identifiable
+sections of that work are not derived from the Program, and can be
+reasonably considered independent and separate works in themselves, then
+this License, and its terms, do not apply to those sections when you distribute
+them as separate works. But when you distribute the same sections as part
+of a whole which is a work based on the Program, the distribution of the
+whole must be on the terms of this License, whose permissions for other
+licensees extend to the entire whole, and thus to each and every part
+regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest your rights to
+work written entirely by you; rather, the intent is to exercise the right to
+control the distribution of derivative or collective works based on the
+Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of a
+storage or distribution medium does not bring the other work under the scope
+of this License.
+
+3. You may copy and distribute the Program (or a work based on it, under
+Section 2) in object code or executable form under the terms of Sections 1
+and 2 above provided that you also do one of the following:
+
+a) Accompany it with the complete corresponding machine-readable source
+code, which must be distributed under the terms of Sections 1 and 2 above
+on a medium customarily used for software interchange; or,
+
+b) Accompany it with a written offer, valid for at least three years, to give
+any third party, for a charge no more than your cost of physically performing
+source distribution, a complete machine-readable copy of the corresponding
+source code, to be distributed under the terms of Sections 1 and 2 above on
+a medium customarily used for software interchange; or,
+
+c) Accompany it with the information you received as to the offer to distribute
+corresponding source code. (This alternative is allowed only for
+noncommercial distribution and only if you received the program in object
+code or executable form with such an offer, in accord with Subsection b
+above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source code
+means all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation and
+installation of the executable. However, as a special exception, the source
+code distributed need not include anything that is normally distributed (in
+either source or binary form) with the major components (compiler, kernel,
+and so on) of the operating system on which the executable runs, unless that
+component itself accompanies the executable.
+
+If distribution of executable or object code is made by offering access to
+copy from a designated place, then offering equivalent access to copy the
+source code from the same place counts as distribution of the source code,
+even though third parties are not compelled to copy the source along with the
+object code.
+
+4. You may not copy, modify, sublicense, or distribute the Program except as
+expressly provided under this License. Any attempt otherwise to copy,
+modify, sublicense or distribute the Program is void, and will automatically
+terminate your rights under this License. However, parties who have received
+copies, or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+5. You are not required to accept this License, since you have not signed it.
+However, nothing else grants you permission to modify or distribute the
+Program or its derivative works. These actions are prohibited by law if you
+do not accept this License. Therefore, by modifying or distributing the
+Program (or any work based on the Program), you indicate your acceptance
+of this License to do so, and all its terms and conditions for copying,
+distributing or modifying the Program or works based on it.
+
+6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the original
+licensor to copy, distribute or modify the Program subject to these terms and
+conditions. You may not impose any further restrictions on the recipients'
+exercise of the rights granted herein. You are not responsible for enforcing
+compliance by third parties to this License.
+
+7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues), conditions
+are imposed on you (whether by court order, agreement or otherwise) that
+contradict the conditions of this License, they do not excuse you from the
+conditions of this License. If you cannot distribute so as to satisfy
+simultaneously your obligations under this License and any other pertinent
+obligations, then as a consequence you may not distribute the Program at all.
+For example, if a patent license would not permit royalty-free redistribution
+of the Program by all those who receive copies directly or indirectly through
+you, then the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply and
+the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any patents or
+other property right claims or to contest validity of any such claims; this
+section has the sole purpose of protecting the integrity of the free software
+distribution system, which is implemented by public license practices. Many
+people have made generous contributions to the wide range of software
+distributed through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing to
+distribute software through any other system and a licensee cannot impose
+that choice.
+
+This section is intended to make thoroughly clear what is believed to be a
+consequence of the rest of this License.
+
+8. If the distribution and/or use of the Program is restricted in certain
+countries either by patents or by copyrighted interfaces, the original copyright
+holder who places the Program under this License may add an explicit
+geographical distribution limitation excluding those countries, so that
+distribution is permitted only in or among countries not thus excluded. In such
+case, this License incorporates the limitation as if written in the body of this
+License.
+
+9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will be
+similar in spirit to the present version, but may differ in detail to address new
+problems or concerns.
+
+Each version is given a distinguishing version number. If the Program specifies
+a version number of this License which applies to it and "any later version",
+you have the option of following the terms and conditions either of that
+version or of any later version published by the Free Software Foundation. If
+the Program does not specify a version number of this License, you may
+choose any version ever published by the Free Software Foundation.
+
+10. If you wish to incorporate parts of the Program into other free programs
+whose distribution conditions are different, write to the author to ask for
+permission. For software which is copyrighted by the Free Software
+Foundation, write to the Free Software Foundation; we sometimes make
+exceptions for this. Our decision will be guided by the two goals of
+preserving the free status of all derivatives of our free software and of
+promoting the sharing and reuse of software generally.
+
+               NO WARRANTY
+
+11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE,
+THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT
+PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE
+STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT
+WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND
+PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
+NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR
+AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR
+ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE
+LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL,
+SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES
+ARISING OUT OF THE USE OR INABILITY TO USE THE
+PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA
+OR DATA BEING RENDERED INACCURATE OR LOSSES
+SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE
+PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN
+IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF
+THE POSSIBILITY OF SUCH DAMAGES.
+
+          END OF TERMS AND CONDITIONS
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README.txt	Fri Dec 31 13:41:08 2010 +0100
@@ -0,0 +1,129 @@
+  This module gives Drupal the ability to easily change links into popup dialog boxes.
+
+  IMPORTANT INSTRUCTIONS
+  ------------------------------------------------------------------------------------
+  Ajax updating only works with themes that have selectable content areas. 
+  If you are not using garland, you will need to figure out the selector for your theme, 
+  and enter it into the "Content Selector" field on the admin/build/themes/settings page
+  for your theme. Open the page.tpl.php file for your theme, and search for "print $content".
+  The $content should be surrounded by a div with an id. Ex:
+    <div id="content-content">
+      <?php print $content; ?>
+    </div> <!-- /content-content -->
+  In this case, just enter '#content-content' into the Content Selector field.
+  Unfortunately, a lot of themes do not have well defined content areas.  Just add the div yourself,
+  and then complain on the issue queue for the theme.  It is important that there are no other
+  print statements inside the div.
+
+  LIMITATIONS
+  ------------------------------------------------------------------------------------  
+  Is this still true? Does not work with tinymce. Unlikely to work with other WYSIWYG's.
+
+  HOW TO USE THE POPUPS API
+  ------------------------------------------------------------------------------------  
+  If you just want to use the built in admin links, just enable the Popups: Admin Links
+  module and you are good to go.
+  If you want to add popups behavior to new links, or incorporate popups into your module,
+  there are a couple of ways to do it.
+  
+  #1) Attach popup behavior to a link with popups_add_popups() call.
+  ----------------------------------------------------------------  
+  <!-- In html or theme -->
+  <a href="popups/test/response" id="mylink"> 
+  <a href="popups/test/response" id="mylink2"> 
+  
+  // In your module
+  popups_add_popups(array('#mylink', '#mylink2=>array('width'=>'200px')));  
+  IMPORTANT: You can only put popups_add_popups in module, NOT in a .tpl file 
+             or the template.php file.
+  This is the simplest method if you want to pass in per-link options.
+  The first key is a jQuery selector. It should select an 'a' element (unless you 
+  are using the 'href' option). See http://docs.jquery.com/Selectors to learn more 
+  about jQuery selectors.
+  The array is a set of Options. See below for the list of options.
+  No array means just use the defualts. 
+  
+  #2) Add the class="popup" to an existing link.
+  -------------------------------------------
+  And then either be sure popups_add_popups() is called sometime for the page,
+  or use the "Scan all pages for popup links" checkbox on the popups settings page. 
+  
+  Example on the theme level ("Scan all pages for popups links" must be checked):
+    <a href="popups/test/response" class="popups">
+
+  Example in code:
+    popups_add_popups();
+    $output .= l("Pop up entire local page.", 'popups/test/response', array('attributes'=>array('class' => 'popups')));
+  
+  Here are the classes that you can use:
+  class="popups" requests an informational popup (or a form that doesn't want ajax processing).
+  class="popups-form" requests a popup with a form that modifies the content of the original page.
+  class="popups-form-reload" requests a popup with a form, and reloads the entire page when done.
+  class="popups-form-noupdate" requests a popup with a form, and leaves the original page as-is.
+  
+  You can use the pseudo-attribute, "on-popups-options" to send options, if you don't mind having non-validating HTML.
+  Note: this attribute gets removed from user content by the HTML filter.
+  Example:
+    print l("Pop with options (width=200px).", 'popups/test/response', 
+             array('attributes'=>array(array('class' => 'popups', 'on-popups-options' => '{width: "200px"}'))))
+  See popups_test.module for more examples.    
+  
+  #3) Add a custom module that implements hook_popups().
+  ---------------------------------------------------------------------
+  hook_popups() returns an array of popup rules, keyed by the id of a form, 
+  or the url of a page (which can use the wildcard '*').
+  Each rule is an array of options, keyed by a jQuery selector.  
+  Leaving off the options array is equal to a link with class="popup-form".
+  This is equivent to using a series of popup_add_popups() calls.
+  
+  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.
+      'table td:nth-child(2) a' => array( 
+        'noUpdate' => true, // Popup will not modify original page.
+      ),
+    );
+  
+  #4) Make your module alter the default popup rules with hook_popups_alter().
+  ----------------------------------------------------------------------------
+  hook_popups_alter() allows you to modify how the popup rules are
+  registered. This is useful to modify the default behavior of some
+  already existing popup rules.
+
+  See hook_popups_alter() in popups.api.php for an example.
+
+
+  LIST OF POPUP OPITIONS
+  ------------------------------------------------------------------------------------ 
+  DEPRECATED OPTIONS
+//   noUpdate: Does the popup NOT modify the original page (Default: FALSE).
+//   reloadWhenDone: Force the entire page to reload after the popup form is submitted (Default: FALSE)
+//   nonModel: Not working.
+//   forceReturn: url to force a stop to work flow (Advanced. Use in conjunction with noUpdate or targetSelectors).  
+//   afterSubmit: function to call when updating after successful form submission.   
+   
+   doneTest: how do we know when the multiform flow is done?
+     null: flow is done when returned path = original path (default).
+     *path*: 
+     *regexp*: done when returned path matches regexp.
+   updateMethod:
+     none: do not update the initial page 
+     ajax: targeted replacement of parts of the initial page (default).
+     reload: full replacement of initial page with new page.
+     callback: use onUpdate(data, options, element).
+   updateSource (only used if updateMethod is not none):
+     initial: use the initial page (default).
+     final: use the path returned at the end of the multiform flow.
+   href: Override the href in the a element, or attach an href to a non-link element.
+   width: Override the width specified in the css.
+   targetSelectors: Hash of jQuery selectors that define the content to be swapped out.
+   titleSelectors: Array of jQuery selectors to place the new page title.
+   reloadOnError: Force the entire page to reload if the popup href is unaccessable (Default: FALSE)
+   noMessage: Don't show drupal_set_message messages.
+   onUpdate: function to call when updating after successful form submission.   
+   skipDirtyCheck: If true, this popup will not check for edits on the originating page.  
+                   Often used with custom target selectors. Redundant is noUpdate is true. (Default: FALSE)
+   hijackDestination: Use the destiination param to force a form submit to return to the originating page.
+                      Overwrites any destination already set one the link (Default: TRUE)
+   
+ 
\ No newline at end of file
Binary file ajax-loader.gif has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/popups.api.php	Fri Dec 31 13:41:08 2010 +0100
@@ -0,0 +1,51 @@
+<?php
+// $Id: popups.api.php,v 1.1.4.2 2009/03/05 19:52:48 starbow Exp $
+
+/**
+ * @file
+ * Provides hook documentation for the Popups API.
+ */
+
+/**
+ * Creates the rule registry for the popups.
+ */
+function hook_popups() {
+  $popups = array();
+  $popups['admin/content/taxonomy'] = array(
+    // Act on the first primary tab. 
+    'div#tabs-wrapper a:eq(1)',
+    // Act on the 2nd column link in the table.
+    'table td:nth-child(2) a' => array(
+      // Don't update the original page.
+      'noUpdate' => TRUE,
+    ),
+  );
+  return $popups;
+}
+
+/**
+ * Allows altering of the popup rule registry.
+ *
+ * @param $popups
+ *   The popup registry to be altered.
+ */
+function hook_popups_alter(&$popups) {
+  // Remove acting on the primary tabs.
+  unset($popups['admin/content/taxonomy']['div#tabs-wrapper a:eq(1)']);
+
+  // Make clicking on the link update the original page.
+  $popups['admin/content/taxonomy']['table td:nth-child(2) a']['noUpdate'] = FALSE;
+}
+
+/**
+ * Adds skins to the Popups API.
+ *
+ * Returns an associative array where the key is the skin name, along
+ * with CSS and JS values to tell where the skin can be found.
+ */
+function hook_popups_skins() {
+  $skin['My Skin'] = array(
+    'css' => drupal_get_path('module', 'myskin') .'/myskin.css',
+    'js' => drupal_get_path('module', 'myskin') .'/myskin.js',
+  );
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/popups.css	Fri Dec 31 13:41:08 2010 +0100
@@ -0,0 +1,47 @@
+/*
+** Ajax popups dialog box styles
+*/
+
+#popups-overlay {
+  position: absolute;
+  z-index: 8;  
+  background: black;
+  top: 0;
+}
+#popups-loading {
+  position: absolute;
+  z-index: 10;  
+  opacity: 0.75;
+  width: 100px;
+  height: 100px;
+  display: none;
+}
+.popups-box {
+  position: absolute;
+  z-index: 9;  
+  background: white;
+  border: 1px solid black;
+  padding: 0.5em;
+  width: 600px;
+  overflow: auto;  
+}
+.popups-title {
+  font-weight: bold;
+  margin-bottom: 0.25em;
+}
+.popups-title div.title {
+  float: left;
+}
+.popups-title .popups-close {
+  float: right;
+}
+.popups-title .popups-close a {
+  font-weight: normal;  
+}
+/* Allow messages to be used as the title of the popups */
+.popups-box div.messages {
+  background: transparent;
+  border: none;
+  padding: 0;
+  margin: 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/popups.info	Fri Dec 31 13:41:08 2010 +0100
@@ -0,0 +1,13 @@
+; $Id: popups.info,v 1.4.8.2 2009/03/05 19:52:48 starbow Exp $
+name = Popups API
+description = General dialog creation utilities
+package = User interface
+core = 6.x
+
+
+; Information added by drupal.org packaging script on 2009-03-21
+version = "6.x-2.0-alpha5"
+core = "6.x"
+project = "popups"
+datestamp = "1237597273"
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/popups.install	Fri Dec 31 13:41:08 2010 +0100
@@ -0,0 +1,15 @@
+<?php
+// $Id: popups.install,v 1.4.8.2 2009/03/05 19:52:48 starbow Exp $
+
+/**
+ * @file
+ */
+
+/**
+ * Implementation of hook_install().
+ *
+ * Ensures popups runs after everything else, since it short circuits in hook_init. 
+ */
+function popups_install() {
+  db_query("UPDATE {system} SET weight = %d WHERE name = 'popups'", 9999);
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/popups.js	Fri Dec 31 13:41:08 2010 +0100
@@ -0,0 +1,1158 @@
+// $Id: popups.js,v 1.9.8.12 2009/03/21 00:57:15 starbow Exp $
+
+/**
+ * Popup Modal Dialog API
+ *
+ * Provide an API for building and displaying JavaScript, in-page, popups modal dialogs.
+ * Modality is provided by a fixed, semi-opaque div, positioned in front of the page contents.
+ *
+ */
+
+/*
+ * TODO
+ * * Return key in add node form not working.
+ * * Tabledrag breaking after ahah reload.
+ */
+
+// ***************************************************************************
+// DRUPAL Namespace
+// ***************************************************************************
+
+/**
+ * Attach the popups 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) {
+  Popups.saveSettings();
+  
+  var $body = $('body');
+  if(!$body.hasClass('popups-processed')) {
+    $body.addClass('popups-processed');
+    $(document).bind('keydown', Popups.keyHandle);
+    var $popit = $('#popit');
+    if ($popit.length) {
+      $popit.remove();
+      Popups.message($popit.html());
+    }
+  }
+  
+  // Add the popups-link-in-dialog behavior to links defined in Drupal.settings.popups.links array.
+  // Get these from current Drupal.settings, not Popups.originalSettings, as each page has it's own hooks.
+  if (Drupal.settings.popups && Drupal.settings.popups.links) {
+    jQuery.each(Drupal.settings.popups.links, function (link, options) { 
+      Popups.attach(context, link, Popups.options(options));
+    });
+  }
+  
+  Popups.attach(context, '.popups', Popups.options({updateMethod: 'none'}));  
+  Popups.attach(context, '.popups-form', Popups.options({updateMethod: 'ajax'})); // ajax reload.
+  Popups.attach(context, '.popups-form-reload', Popups.options({updateMethod: 'reload'})); // whole page reload. 
+  Popups.attach(context, '.popups-form-noupdate', Popups.options({updateMethod: 'none'}));  // no reload at all.
+};
+
+// ***************************************************************************
+// Popups Namespace **********************************************************
+// ***************************************************************************
+/**
+ * The Popups namespace contains:
+ * * An ordered stack of Popup objects,
+ * * The state of the original page,
+ * * Functions for managing both of the above.
+ */
+Popups = function(){};
+
+/**
+ * Static variables in the Popups namespace.
+ */
+Popups.popupStack = []; 
+Popups.addedCSS = [];
+Popups.addedJS = [];
+Popups.originalSettings = null; // The initial popup options of the page.
+/**
+ * Each popup object gets it's own set of options.
+ * These are the defaults.
+ */
+Popups.defaultOptions = {
+  doneTest: null, // null, *path*, *regexp*. how do we know when a multiform flow is done?
+  updateMethod: 'ajax', // none, ajax, reload, *callback*
+  updateSource: 'initial', // initial, final. Only used if updateMethod != none.
+  href: null, 
+  width: null, // Override the width specified in the css.
+  targetSelectors: null, // Hash of jQuery selectors that define the content to be swapped out.
+  titleSelectors: null, // Array of jQuery selectors to place the new page title.
+  reloadOnError: false, // Force the entire page to reload if the popup href is unaccessable.
+  noMessage: false, // Don't show drupal_set_message messages.   
+  skipDirtyCheck: false, // If true, this popup will not check for edits on the originating page.  
+  hijackDestination: true // Use the destiination param to force a form submit to return to the originating page. 
+};
+
+// ***************************************************************************
+// Popups.Popup Object *******************************************************
+// ***************************************************************************
+/**
+ * A Popup is a single modal dialog.
+ * The popup object encapslated all the info about a single popup.
+ */
+Popups.Popup = function() {
+  this.id = 'popups-' + Popups.nextCounter();
+  
+  // These properties are needed if the popup contains a form that will be ajax submitted.
+  this.parent = null; // The popup that spawned this one. If parent is null, this popup was spawned by the original page.
+  this.path = null; // If popup is showing content from a url, this is that path.
+  this.element = null; // The DOM element that was clicked to launch this popup.
+  this.options = null; // An option array that control how the popup behaves.  See Popups.defaultOptions for explainations.
+};
+Popups.Popup.prototype.$popup = function() {
+  return $('#' + this.id);
+};
+Popups.Popup.prototype.$popupBody = function() {
+  return $('#' + this.id + ' .popups-body');
+};
+Popups.Popup.prototype.$popupClose = function() {
+  return $('#' + this.id + ' .popups-close');
+};
+Popups.Popup.prototype.$popupTitle = function() {
+  return $('#' + this.id + ' .popups-title');
+};
+Popups.Popup.prototype.$popupButtons = function() {
+  return $('#' + this.id + ' .popups-buttons');
+};
+Popups.Popup.prototype.$popupFooter = function() {
+  return $('#' + this.id + ' .popups-footer');
+};
+
+/**
+ * Create the jQuery wrapped html at the heart of the popup object.
+ * 
+ * @param title
+ *   String
+ * @param body
+ *   String/HTML
+ * @param buttons
+ *   Hash/Object
+ * @return
+ *   The $popup.
+ */
+Popups.Popup.prototype.fill = function(title, body, buttons) {
+  return $(Drupal.theme('popupDialog', this.id, title, body, buttons));
+}
+
+/**
+ * Hide the popup by pushing it off to the side. 
+ * Just making it display:none causes flash in FF2.
+ */
+Popups.Popup.prototype.hide = function() {
+  this.$popup().css('left', '-9999px');
+};
+
+Popups.Popup.prototype.show = function() {
+  Popups.resizeAndCenter(this);
+};
+
+Popups.Popup.prototype.open = function(title, body, buttons, width){
+  return Popups.open(this, title, body, buttons, width);
+};
+
+Popups.Popup.prototype.removePopup = function() { 
+  Popups.removePopup(this);
+}; 
+
+/**
+ * Remove everything.
+ */
+Popups.Popup.prototype.close = function() {
+  return Popups.close(this);
+};
+
+/**
+ * Set the focus on the popups to the first visible, enabled form element, or the close link.
+ */
+Popups.Popup.prototype.refocus = function() {
+  // Select the first visible enabled input element.
+  var $popup = this.$popup();
+  var $focus = $popup.find(':input:visible:enabled:first');
+  if (!$focus.length) {
+    // There is no visible enabled input element, so select the close link.
+    $focus = $popup.find('.popups-close a');
+  }
+  $focus.focus();
+};
+
+/**
+ * Return a selector that will find target content on the layer that spawned this popup.
+ * This is needed for the popup to do ajax updates. 
+ */
+Popups.Popup.prototype.targetLayerSelector = function() {
+  if (this.parent === null) {
+    return 'body'; // Select content in the original page.
+  }
+  else {
+    return '#' + this.parent.id; // Select content in the parent popup.
+  } 
+};
+
+/**
+ * Determine if we are at an end point of a form flow, or just moving from one popups to another.
+ * 
+ * @param path
+ *   The path of the page that the form flow has moved to.
+ *   This path is relative to the base_path.  
+ *   Ex: node/add/story, not http://localhost/drupal6/node/add/story or drupa6/node/add/story.
+ * @return bool
+ */ 
+Popups.Popup.prototype.isDone = function(path) {
+//  console.log("Doing isDone for popup: " + this.id + ", now at " + path );
+  var done;
+  if (this.options.doneTest) {
+    // Test if we are at the path specified by doneTest.
+    done = (path === this.options.doneTest || path.match(this.options.doneTest));    
+  }
+  else { 
+    if (this.parent) {
+       // Test if we are back to the parent popup's path.
+      done = (path === this.parent.path);     
+//      console.log("Lookin at parent: " + this.parent.path + ". Done = " + done); 
+    }
+    else {
+       // Test if we are back to the original page's path.
+      done = (path === Popups.originalSettings.popups.originalPath);
+//      console.log("Lookin at original page: " + Popups.originalSettings.popups.originalPath + ". Done = " + done); 
+    }
+  }; 
+  return done;  
+};
+
+
+// ***************************************************************************
+// Popups Functions **********************************************************
+// ***************************************************************************
+
+/**
+ * Test if the param has been set. 
+ * Used to distinguish between a value set to null or false and on not yet unset.
+ */
+Popups.isset = function(v) {
+  return (typeof(v) !== 'undefined');
+};
+
+/**
+ * Get the currently active popup in the page.
+ * Currently it is the only one visible, but that could change.
+ */
+Popups.activePopup = function() {
+  if (Popups.popupStack.length) {
+    return Popups.popupStack[Popups.popupStack.length - 1]; // top of stack.
+  }
+  else {
+    return null;
+  }
+};
+
+/**
+ * Manage the page wide popupStack.
+ */
+Popups.push = function(popup) {
+  Popups.popupStack.push(popup);
+};
+// Should I integrate this with popupRemove??
+Popups.pop = function(popup) {
+  return Popups.popupStack.pop();
+};
+
+/**
+ * Build an options hash from defaults.
+ * 
+ * @param overrides
+ *   Hash of values to override the defaults.
+ */
+Popups.options = function(overrides) {
+  var defaults = Popups.defaultOptions;
+  return Popups.overrideOptions(defaults, overrides);  
+}
+
+/**
+ * Build an options hash.  
+ * Also maps deprecated options to current options.
+ * 
+ * @param defaults
+ *   Hash of default values
+ * @param overrides
+ *   Hash of values to override the defaults with.
+ */
+Popups.overrideOptions = function(defaults, overrides) {
+  var options = {};
+  for(var option in defaults) {
+    var value;
+    if (Popups.isset(overrides[option])) {
+      options[option] = overrides[option];
+    }
+    else {     
+      options[option] = defaults[option];
+    }
+  }
+  // Map deprecated options.
+  if (overrides['noReload'] || overrides['noUpdate']) {
+    options['updateMethod'] = 'none';
+  } 
+  if (overrides['reloadWhenDone']) {
+    options['updateMethod'] = 'reload';
+  } 
+  if (overrides['afterSubmit']) {
+    options['updateMethod'] = 'callback';
+    options['onUpdate'] = overrides['afterSubmit'];
+  } 
+  if (overrides['forceReturn']) {
+    options['doneTest'] = overrides['forceReturn'];
+  } 
+  return options;
+}
+
+/**
+ * Attach the popups behavior to all elements inside the context that match the selector.
+ *
+ * @param context
+ *   Chunk of html to search.
+ * @param selector
+ *   jQuery selector for elements to attach popups behavior to.
+ * @param options
+ *   Hash of options associated with these links.
+ */
+Popups.attach = function(context, selector, options) {
+//  console.log(options);
+  $(selector, context).not('.popups-processed').each(function() {
+    var $element = $(this);  
+    
+    // Mark the element as processed.    
+    $element.addClass('popups-processed');
+    
+    // Append note to link title.
+    var title = '';
+    if ($element.attr('title')) {
+      title = $element.attr('title') + ' ';
+    }
+    title += Drupal.t('[Popup]');
+    $element.attr('title', title); 
+    
+    // Attach the on-click popup behavior to the element.
+    $element.click(function(event){
+      return Popups.clickPopupElement(this, options);
+    });
+  });
+};    
+
+/**
+ * Respond to click by opening a popup.
+ * 
+ * @param element
+ *   The element that was clicked.
+ * @param options
+ *   Hash of options associated with the element.
+ */
+Popups.clickPopupElement = function(element, options) {
+  Popups.saveSettings();
+  
+  // If the element contains a on-popups-options attribute, override default options param.
+  if ($(element).attr('on-popups-options')) {
+    var overrides = Drupal.parseJson($(element).attr('on-popups-options')); 
+    options = Popups.overrideOptions(options, overrides);
+  }
+	
+  // The parent of the new popup is the currently active popup.
+  var parent = Popups.activePopup();
+  
+  // If the option is distructive, check if the page is already modified, and offer to save.
+  var willModifyOriginal = !(options.updateMethod === 'none' || options.skipDirtyCheck);
+  if (willModifyOriginal && Popups.activeLayerIsEdited()) {
+    // The user will lose modifications, so show dialog offering to save current state.
+    Popups.offerToSave(element, options, parent);
+  }
+  else {
+    // Page is clean, or popup is safe, so just open it.
+    Popups.openPath(element, options, parent);
+  }
+  return false; 
+};
+
+/**
+ * Test if the active layer been edited.
+ * Active layer is either the original page, or the active Popup.
+ */
+Popups.activeLayerIsEdited = function() {
+  var layer = Popups.activePopup();
+  var $context = Popups.getLayerContext(layer);
+  // TODO: better test for edited page, maybe capture change event on :inputs.   
+  var edited = $context.find('span.tabledrag-changed').length;  
+  return edited;
+}
+
+/**
+ * Show dialog offering to save form on parent layer.
+ * 
+ * @param element
+ *   The DOM element that was clicked.
+ * @param options
+ *   The options associated with that element.
+ * @param parent
+ *   The layer that has the unsaved edits.  Null means the underlying page.
+ */
+Popups.offerToSave = function(element, options, parent) {
+  var popup = new Popups.Popup();
+  var body = Drupal.t("There are unsaved changes in the form, which you will lose if you continue.");
+  var buttons = {
+   'popup_save': {title: Drupal.t('Save Changes'), func: function(){Popups.saveFormOnLayer(element, options, parent);}},
+   'popup_submit': {title: Drupal.t('Continue'), func: function(){popup.removePopup(); Popups.openPath(element, options, parent);}},
+   'popup_cancel': {title: Drupal.t('Cancel'), func: function(){popup.close();}}
+  };
+  popup.open(Drupal.t('Warning: Please Confirm'), body, buttons);  
+};
+
+/**
+ * Generic dialog builder.
+ * Adds the newly built popup into the DOM.
+ * 
+ * TODO: capture the focus if it tabs out of the dialog.
+ *
+ * @param popup
+ *   Popups.Popup object to fill with content, place in the DOM, and show on the screen.
+ * @param String title
+ *   String: title of new dialog.
+ * @param body (optional)
+ *   String: body of new dialog.
+ * @param buttons (optional)
+ *   Hash of button parameters.
+ * @param width (optional)
+ *   Width of new dialog.
+ *   
+ * @return popup object
+ */
+Popups.open = function(popup, title, body, buttons, width){
+  Popups.addOverlay();
+  
+  if (Popups.activePopup()) {
+    // Hiding previously active popup.
+    Popups.activePopup().hide();
+  }
+  
+  if (!popup) {
+    // Popup object was not handed in, so create a new one.
+    popup = new Popups.Popup();
+  }
+  Popups.push(popup); // Put this popup at the top of the stack.
+
+  // Create the jQuery wrapped html for the new popup.
+  var $popup = popup.fill(title, body, buttons);
+  popup.hide(); // Hide the new popup until it is finished and sized.
+
+  if (width) {
+    $popup.css('width', width);
+  }
+  
+  // Add the new popup to the DOM.
+  $('body').append($popup); 
+
+  // Add button function callbacks.
+  if (buttons) {
+    jQuery.each(buttons, function(id, button){
+      $('#' + id).click(button.func);
+    });
+  }
+    
+  // Add the default click-to-close behavior.
+  popup.$popupClose().click(function(){
+    return Popups.close(popup);
+  });
+
+  Popups.resizeAndCenter(popup);
+
+  // Focus on the first input element in the popup window.
+  popup.refocus(); 
+  
+  // TODO - this isn't the place for this - should mirror addLoading calls.
+  // Remove the loading image.
+  Popups.removeLoading();
+   
+  return popup;
+};  
+
+/**
+ * Adjust the popup's height to fit it's content.
+ * Move it to be centered on the screen.
+ * This undoes the effects of popup.hide().
+ * 
+ * @param popup
+ */
+Popups.resizeAndCenter = function(popup) {
+  var $popup = popup.$popup();
+  
+  // center on the screen, adding in offsets if the window has been scrolled
+  var popupWidth = $popup.width();  
+  var windowWidth = Popups.windowWidth();
+  var left = (windowWidth / 2) - (popupWidth / 2) + Popups.scrollLeft();
+  
+  // Get popups's height on the page.
+  $popup.css('height', 'auto'); // Reset height.
+  var popupHeight = $popup.height(); 
+  $popup.height(popupHeight);
+  var windowHeight = Popups.windowHeight();
+   
+  if (popupHeight > (0.9 * windowHeight) ) { // Must fit in 90% of window.
+    popupHeight = 0.9 * windowHeight;
+    $popup.height(popupHeight);
+  }  
+  var top = (windowHeight / 2) - (popupHeight / 2) + Popups.scrollTop();
+
+  $popup.css('top', top).css('left', left); // Position the popups to be visible. 
+};
+  
+
+/**
+ *  Create and show a simple popup dialog that functions like the browser's alert box.
+ */
+Popups.message = function(title, message) {
+  message = message || '';
+  var popup = new Popups.Popup();
+  var buttons = {
+    'popup_ok': {title: Drupal.t('OK'), func: function(){popup.close();}}
+  };
+  popup.open(title, message, buttons);
+  return popup;
+};
+
+/**
+ * Handle any special keys when popups is active.
+ */
+Popups.keyHandle = function(e) {
+  if (!e) {
+    e = window.event;
+  }
+  switch (e.keyCode) {
+    case 27: // esc
+      Popups.close();
+      break;
+    case 191: // '?' key, show help.
+      if (e.shiftKey && e.ctrlKey) {
+        var $help = $('a.popups.more-help');
+        if ($help.size()) {
+          $help.click();
+        }
+        else {
+          Popups.message(Drupal.t("Sorry, there is no additional help for this page"));
+        }
+      }
+      break;
+  }
+};
+
+/*****************************************************************************
+ * Appearence Functions (overlay, loading graphic, remove popups)     *********
+ *****************************************************************************/
+ 
+/**
+ * Add full page div between the page and the dialog, to make the popup modal.
+ */
+Popups.addOverlay = function() {
+  var $overlay = $('#popups-overlay');
+  if (!$overlay.length) { // Overlay does not already exist, so create it.
+    $overlay = $(Drupal.theme('popupOverlay'));
+    $overlay.css('opacity', '0.4'); // for ie6(?)
+    // Doing absolute positioning, so make overlay's size equal the entire body.
+    var $doc = $(document);
+    $overlay.width($doc.width()).height($doc.height()); 
+    $overlay.click(function(){Popups.close();});
+    $('body').prepend($overlay);
+  }
+};
+
+/**
+ * Remove overlay if popupStack is empty.
+ */
+Popups.removeOverlay = function() {
+  if (!Popups.popupStack.length) {
+    $('#popups-overlay').remove();
+  }
+};
+
+/**
+ * Add a "Loading" message while we are waiting for the ajax response.
+ */
+Popups.addLoading = function() {
+  var $loading = $('#popups-loading');
+  if (!$loading.length) { // Loading image does not already exist, so create it.
+    $loading = $(Drupal.theme('popupLoading'));
+    $('body').prepend($loading); // Loading div is initially display:none.
+    var width = $loading.width();
+    var height = $loading.height();
+    var left = (Popups.windowWidth() / 2) - (width / 2) + Popups.scrollLeft();
+    var top = (Popups.windowHeight() / 2) - (height / 2) + Popups.scrollTop();
+    $loading.css({'top': top, 'left': left, 'display': 'block'}); // Center it and make it visible.
+  }
+};
+
+Popups.removeLoading = function() {
+  $('#popups-loading').remove();
+};
+
+// Should I fold this function into Popups.pop?
+Popups.removePopup = function(popup) {  
+//  console.log("Popups.removePopup: " + popup);
+  if (!Popups.isset(popup)) {
+    popup = Popups.activePopup();
+  }
+  if (popup) {
+//    console.log('removing '+popup.id);
+    popup.$popup().remove();
+    Popups.popupStack.splice(Popups.popupStack.indexOf(popup), 1); // Remove popup from stack.  Probably should rework into .pop()
+  }  
+//  else {
+//    console.log("Popups.removePopup - there is no popup to remove.");
+//  }
+}; 
+
+/**
+ * Remove everything.
+ */
+Popups.close = function(popup) {
+  if (!Popups.isset(popup)) {
+    popup = Popups.activePopup();
+  }
+  Popups.removePopup(popup);  // Should this be a pop??
+  Popups.removeLoading();
+  if (Popups.activePopup()) {
+    Popups.activePopup().show();
+    Popups.activePopup().refocus();
+  }
+  else {
+    Popups.removeOverlay();
+    Popups.restorePage();
+  }
+  return false;
+};
+
+/**
+ * Save the page's original Drupal.settings.
+ */
+Popups.saveSettings = function() {
+  if (!Popups.originalSettings) {
+    Popups.originalSettings = Drupal.settings;
+  }
+};
+
+/**
+ * Restore the page's original Drupal.settings.
+ */
+Popups.restoreSettings = function() {
+  Drupal.settings = Popups.originalSettings;  
+};
+
+/**
+ * Remove as much of the effects of jit loading as possible.
+ */
+Popups.restorePage = function() {
+  Popups.restoreSettings();
+  // Remove the CSS files that were jit loaded for popup.
+  for (var i in Popups.addedCSS) {
+    var link = Popups.addedCSS[i];
+    $('link[href='+ $(link).attr('href') + ']').remove();
+  }
+  Popups.addedCSS = [];
+};
+
+
+/****************************************************************************
+ * Utility Functions   ******************************************************
+ ****************************************************************************/
+
+/**
+ * Get the position of the left side of the browser window.
+ */
+Popups.scrollLeft = function() {
+  return Math.max(document.documentElement.scrollLeft, document.body.scrollLeft);
+};
+
+/**
+ * Get the position of the top of the browser window.
+ */
+Popups.scrollTop = function() {
+  return Math.max(document.documentElement.scrollTop, document.body.scrollTop);
+};
+
+/**
+ * Get the height of the browser window.
+ * Fixes jQuery & Opera bug - http://drupal.org/node/366093
+ */
+Popups.windowHeight = function() {
+  if ($.browser.opera && $.browser.version > "9.5" && $.fn.jquery <= "1.2.6") { 
+    return document.documentElement.clientHeight;
+  }
+  return $(window).height();
+};
+
+/**
+ * Get the height of the browser window.
+ * Fixes jQuery & Opera bug - http://drupal.org/node/366093
+ */
+Popups.windowWidth = function() {
+  if ($.browser.opera && $.browser.version > "9.5" && $.fn.jquery <= "1.2.6") { 
+    return document.documentElement.clientWidth;
+  }
+  return $(window).width();
+};
+
+Popups.nextCounter = function() {
+  if (this.counter === undefined) {
+    this.counter = 0;
+  }
+  else {
+    this.counter++;
+  }
+  return this.counter;
+};
+
+/****************************************************************************
+ * Ajax Functions   ******************************************************
+ ****************************************************************************/
+
+/**
+ * Add additional CSS to the page.
+ */
+Popups.addCSS = function(css) {
+  Popups.addedCSS = [];
+  for (var type in css) {
+    for (var file in css[type]) {
+      var link = css[type][file];
+      // Does the page already contain this stylesheet?
+      if (!$('link[href='+ $(link).attr('href') + ']').length) {
+        $('head').append(link);
+        Popups.addedCSS.push(link); // Keep a list, so we can remove them later.
+      }
+    }
+  }
+};
+
+/**
+ * Add additional Javascript to the page.
+ */
+Popups.addJS = function(js) {
+  // Parse the json info about the new context.
+  var scripts = [];
+  var inlines = [];
+  for (var type in js) {
+    if (type != 'setting') {
+      for (var file in js[type]) {
+        if (type == 'inline') {
+          inlines.push($(js[type][file]).text());
+        }
+        else {
+          scripts.push($(js[type][file]).attr('src'));
+        }
+      }
+    }
+  }
+
+  // Add new JS settings to the page, needed for #ahah properties to work.
+  Drupal.settings = js.setting;
+//  console.log('js.setting...');
+//  console.log(js.setting);
+
+  for (var i in scripts) {
+    var src = scripts[i];
+    if (!$('script[src='+ src + ']').length && !Popups.addedJS[src]) {
+      // Get the script from the server and execute it.
+      $.ajax({ 
+        type: 'GET',
+        url: src,
+        dataType: 'script',
+        async : false,
+        success: function(script) {
+          eval(script);
+        }
+      });
+      // Mark the js as added to the underlying page.
+      Popups.addedJS[src] = true;
+    }
+  }
+
+  return inlines;
+};
+
+/**
+ * Execute the jit loaded inline scripts.
+ * Q: Do we want to re-excute the ones already in the page?
+ * 
+ * @param inlines
+ *   Array of inline scripts.
+ */
+Popups.addInlineJS = function(inlines) {
+  // Load the inlines into the page.
+  for (var n in inlines) {
+    // If the script is not already in the page, execute it.
+    if (!$('script:not([src]):contains(' + inlines[n] + ')').length) {
+      eval(inlines[n]);
+    }
+  }
+};
+
+Popups.beforeSend = function(xhr) {
+  xhr.setRequestHeader("X-Drupal-Render-Mode", 'json/popups');
+};
+
+/**
+ * Do before the form in the popups is submitted.
+ */
+Popups.beforeSubmit = function(formData, $form, options) {
+  Popups.removePopup(); // Remove just the dialog, but not the overlay.
+  Popups.addLoading();
+};
+
+
+/****************************************************************************
+ * Page & Form in popups functions                                         ***
+ ****************************************************************************/
+
+/**
+ * Use Ajax to open a link in a popups window.
+ *
+ * @param element
+ *   Element that was clicked to open the popups.
+ * @param options
+ *   Hash of options controlling how the popups interacts with the underlying page.
+ * @param parent
+ *   If path is being opened from inside another popup, that popup is the parent.
+ */
+Popups.openPath = function(element, options, parent) {
+  Popups.saveSettings();
+
+  // Let the user know something is happening.
+  $('body').css("cursor", "wait");
+  
+  // TODO - get nonmodal working.
+  if (!options.nonModal) {
+    Popups.addOverlay(); 
+  }
+  Popups.addLoading();
+  
+  var href = options.href ? options.href : element.href;
+  $(document).trigger('popups_open_path', [element, href]); // Broadcast Popup Open Path event.
+  
+  var params = {}; 
+  // Force the popups to return back to the orignal page when forms are done, unless hijackDestination option is set to FALSE.
+  if (options.hijackDestination) { 
+    var returnPath;
+    if (parent) {
+      returnPath = parent.path;
+//      console.log('Popup parent is ...');
+//      console.log(parent);
+    }
+    else { // No parent, so bring flow back to original page.
+      returnPath = Popups.originalSettings.popups.originalPath;
+    }    
+    href = href.replace(/destination=[^;&]*[;&]?/, ''); // Strip out any existing destination param.
+//    console.log("Hijacking destination to " + returnPath);
+    params.destination = returnPath; // Set the destination to return to the parent's path.    
+  }
+
+  var ajaxOptions = {
+    url: href,
+    dataType: 'json',
+    data: params,
+    beforeSend: Popups.beforeSend,
+    success: function(json) { 
+      // Add additional CSS to the page.
+      Popups.addCSS(json.css);
+      var inlines = Popups.addJS(json.js);
+      var popup = Popups.openPathContent(json.path, json.title, json.messages + json.content, element, options, parent);
+      Popups.addInlineJS(inlines);   
+      // Broadcast an event that the path was opened.
+      $(document).trigger('popups_open_path_done', [element, href, popup]);  
+    },
+    complete: function() {
+      $('body').css("cursor", "auto"); // Return the cursor to normal state.      
+    }
+  };
+
+  var ajaxOptions;
+  if (options.reloadOnError) {
+    ajaxOptions.error = function() {
+      location.reload(); // Reload on error. Is this working?
+    };    
+  }
+  else {
+    ajaxOptions.error = function() {
+      Popups.message("Unable to open: " + href);
+    };
+  }
+  $.ajax(ajaxOptions);
+        
+  return false;         
+};
+
+/**
+ * Open path's content in an ajax popups.
+ *
+ * @param title
+ *   String title of the popups.
+ * @param content
+ *   HTML to show in the popups.
+ * @param element
+ *   A DOM object containing the element that was clicked to initiate the popup. 
+ * @param options
+ *   Hash of options controlling how the popups interacts with the underlying page.
+ * @param parent
+ *   Spawning popup, or null if spawned from original page. 
+ */
+Popups.openPathContent = function(path, title, content, element, options, parent) {  
+  var popup = new Popups.Popup();
+  Popups.open(popup, title, content, null, options.width); 
+
+  // Set properties on new popup.  
+  popup.parent = parent;
+  popup.path = path;
+//  console.log("Setting popup " + popup.id + " originalPath to " + path);
+  popup.options = options;
+  popup.element = element;
+
+  // Add behaviors to content in popups. 
+  delete Drupal.behaviors.tableHeader; // Work-around for bug in tableheader.js (http://drupal.org/node/234377)
+  delete Drupal.behaviors.teaser; // Work-around for bug in teaser.js (sigh).
+  Drupal.attachBehaviors(popup.$popupBody());
+  // Adding collapse moves focus.
+  popup.refocus();
+
+  // If the popups contains a form, capture submits.
+  var $form = $('form', popup.$popupBody());
+  if ($form.length) {
+    $form.ajaxForm({   
+      dataType: 'json',   
+      beforeSubmit: Popups.beforeSubmit,
+      beforeSend: Popups.beforeSend,
+      success: function(json, status) {
+        Popups.formSuccess(popup, json);
+      },
+      error: function() {
+        Popups.message("Bad Response form submission");
+      }
+    });
+  }
+  return popup;
+};
+
+/**
+ * The form in the popups was successfully submitted
+ * Update the originating page.
+ * Show any messages in a popups.
+ * 
+ * @param popup
+ *   The popup object that contained the form that was just submitted.
+ * @param data
+ *   JSON object from server with status of form submission.
+ */
+Popups.formSuccess = function(popup, data) {    
+  // Determine if we are at an end point, or just moving from one popups to another.
+  var done = popup.isDone(data.path);
+  if (!done) { // Not done yet, so show new page in new popups.
+    Popups.removeLoading();
+    Popups.openPathContent(data.path, data.title, data.messages + data.content, popup.element, popup.options, popup.parent);
+  }
+  else { // We are done with popup flow.
+    // Execute the onUpdate callback if available.
+    if (popup.options.updateMethod === 'callback' && popup.options.onUpdate) { 
+      var result = eval(popup.options.onUpdate +'(data, popup.options, popup.element)');
+      if (result === false) { // Give onUpdate callback a chance to skip normal processing.
+        return;
+      }
+    }
+
+    if (popup.options.updateMethod === 'reload') { // Force a complete, non-ajax reload of the page.
+      if (popup.options.updateSource === 'final') {
+        location.href = Drupal.settings.basePath + data.path; // TODO: Need to test this.
+      }
+      else { // Reload originating page.
+        location.reload(); 
+      }
+    }
+    else { // Normal, targeted ajax, reload behavior.
+      // Show messages in dialog and embed the results in the original page.
+      var showMessage = data.messages.length && !popup.options.noMessage;
+      if (showMessage) {
+        var messagePopup = Popups.message(data.messages); // Popup message.
+        if (Popups.originalSettings.popups.autoCloseFinalMessage) {
+          setTimeout(function(){Popups.close(messagePopup);}, 2500); // Autoclose the message box in 2.5 seconds.
+        }
+  
+        // Insert the message into the page above the content.
+        // Might not be the standard spot, but it is the easiest to find.
+        var $next;
+        if (popup.targetLayerSelector() === 'body') {
+          $next = $('body').find(Popups.originalSettings.popups.defaultTargetSelector);
+        }
+        else {
+          $next = $(popup.targetLayerSelector()).find('.popups-body');
+        }
+        $next.parent().find('div.messages').remove(); // Remove the existing messages.
+        $next.before(data.messages); // Insert new messages.
+      }
+          
+      // Update the content area (defined by 'targetSelectors').
+      if (popup.options.updateMethod !== 'none') { 
+        Popups.testContentSelector(); // Kick up warning message if selector is bad.
+
+        Popups.restoreSettings(); // Need to restore original Drupal.settings.popups.links before running attachBehaviors.  This probably has CSS side effects!        
+        if (popup.options.targetSelectors) { // Pick and choose what returned content goes where.
+          jQuery.each(popup.options.targetSelectors, function(t_new, t_old) {
+            if(!isNaN(t_new)) {
+              t_new = t_old; // handle case where targetSelectors is an array, not a hash.
+            }
+//            console.log("Updating target " + t_old + ' with ' + t_new);
+            var new_content = $(t_new, data.content);
+//            console.log("new content... ");
+//            console.log(new_content);
+            var $c = $(popup.targetLayerSelector()).find(t_old).html(new_content); // Inject the new content into the original page.
+
+            Drupal.attachBehaviors($c);
+          });
+        }
+        else { // Put the entire new content into default content area.
+          var $c = $(popup.targetLayerSelector()).find(Popups.originalSettings.popups.defaultTargetSelector).html(data.content);
+//          console.log("updating entire content area.")
+          Drupal.attachBehaviors($c);                    
+        }
+      }
+      
+      // Update the title of the page.
+      if (popup.options.titleSelectors) {
+        jQuery.each(popup.options.titleSelectors, function() {
+          $(''+this).html(data.title);
+        });
+      }
+              
+      // Done with changes to the original page, remove effects.
+      Popups.removeLoading();
+      if (!showMessage) { 
+        // If there is not a messages popups, close current layer.
+        Popups.close();
+      }
+    }
+    
+    // Broadcast an event that popup form was done and successful.
+    $(document).trigger('popups_form_success', [popup]);
+    
+  }  // End of updating spawning layer.
+}; 
+
+
+/**
+ * Get a jQuery object for the content of a layer.
+ * @param layer
+ *   Either a popup, or null to signify the original page.
+ */
+Popups.getLayerContext = function(layer) {  
+  var $context;
+  if (!layer) {
+    $context = $('body').find(Popups.originalSettings.popups.defaultTargetSelector);
+  }
+  else {
+    $context = layer.$popupBody();
+  }
+  return $context;
+}
+
+/**
+ * Submit the page and reload the results, before popping up the real dialog.
+ *
+ * @param element
+ *   Element that was clicked to open a new popup.
+ * @param options
+ *   Hash of options controlling how the popups interacts with the underlying page.
+ * @param layer
+ *   Popup with form to save, or null if form is on original page. 
+ */
+Popups.saveFormOnLayer = function(element, options, layer) {
+  var $context = Popups.getLayerContext(layer);
+  var $form = $context.find('form');
+  var ajaxOptions = {
+    dataType: 'json',
+    beforeSubmit: Popups.beforeSubmit,   
+    beforeSend: Popups.beforeSend,
+    success: function(response, status) { 
+      // Sync up the current page contents with the submit.
+      var $c = $context.html(response.content); // Inject the new content into the page.
+      Drupal.attachBehaviors($c);
+      // The form has been saved, the page reloaded, now safe to show the triggering link in a popup.
+      Popups.openPath(element, options, layer); 
+    } 
+  };
+  $form.ajaxSubmit(ajaxOptions); // Submit the form. 
+};
+
+/**
+ * Warn the user if ajax updates will not work
+ *   due to mismatch between the theme and the theme's popup setting.
+ */
+Popups.testContentSelector = function() {
+  var target = Popups.originalSettings.popups.defaultTargetSelector;
+  var hits = $(target).length;
+  if (hits !== 1) { // 1 is the corrent answer.
+    var msg = Drupal.t('The popup content area for this theme is misconfigured.') + '\n';
+    if (hits === 0) {
+      msg += Drupal.t('There is no element that matches ') + '"' + target + '"\n';
+    }
+    else if (hits > 1) {
+      msg += Drupal.t('There are multiple elements that match: ') + '"' + target + '"\n';
+    }
+    msg += Drupal.t('Go to admin/build/themes/settings, select your theme, and edit the "Content Selector" field'); 
+    alert(msg);
+  }
+};
+
+
+// ****************************************************************************
+// * Theme Functions   ********************************************************
+// ****************************************************************************
+
+Drupal.theme.prototype.popupLoading = function() {
+  var loading = '<div id="popups-loading">';
+  loading += '<img src="'+ Drupal.settings.basePath + Popups.originalSettings.popups.modulePath + '/ajax-loader.gif" />';
+  loading += '</div>';
+  return loading;
+};
+
+Drupal.theme.prototype.popupOverlay = function() {
+  return '<div id="popups-overlay"></div>';
+};
+
+Drupal.theme.prototype.popupButton = function(title, id) {
+  return '<input type="button" value="'+ title +'" id="'+ id +'" />';
+};
+
+Drupal.theme.prototype.popupDialog = function(popupId, title, body, buttons) {
+  var template = Drupal.theme('popupTemplate', popupId);
+  var popups = template.replace('%title', title).replace('%body', body);
+  
+  var themedButtons = '';
+  if (buttons) {
+    jQuery.each(buttons, function (id, button) { 
+      themedButtons += Drupal.theme('popupButton', button.title, id);
+    });  
+  }  
+  popups = popups.replace('%buttons', themedButtons);  
+  return popups;
+};
+
+Drupal.theme.prototype.popupTemplate = function(popupId) {
+  var template;
+  template += '<div id="'+ popupId + '" class="popups-box">';
+  template += '  <div class="popups-title">';
+  template += '    <div class="popups-close"><a href="#">' + Drupal.t('Close') + '</a></div>';
+  template += '    <div class="title">%title</div>';
+  template += '    <div class="clear-block"></div>';
+  template += '  </div>';
+  template += '  <div class="popups-body">%body</div>';
+  template += '  <div class="popups-buttons">%buttons</div>';
+  template += '  <div class="popups-footer"></div>';
+  template += '</div>';
+  return template;
+};
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/popups.module	Fri Dec 31 13:41:08 2010 +0100
@@ -0,0 +1,451 @@
+<?php
+// $Id: popups.module,v 1.11.8.10 2009/03/21 00:57:15 starbow Exp $
+
+/**
+ * @file
+ * This module provides a hook_popups for links to be openned in an Ajax Popup Modal Dialog. 
+ */
+
+
+// **************************************************************************
+// CORE HOOK FUNCTIONS   ****************************************************
+// **************************************************************************
+
+/**
+ * Implementation of hook_menu().
+ *
+ * @return array of new menu items.
+ */
+function popups_menu() { 
+  
+  // Admin Settings.
+  $items['admin/settings/popups'] = array(
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('popups_admin_settings'),
+    'title' => 'Popups',
+    'access arguments' => array('administer site configuration'),
+    'description' => 'Configure the page-in-a-dialog behavior.',
+  ); 
+  
+  return $items;
+}
+
+/**
+ * Implementation of hook_init().
+ * 
+ * Look at the page path and see if popup behavior has been requested for any links in this page.
+ */
+function popups_init() {  
+  $popups = popups_get_popups();
+
+  if (variable_get('popups_always_scan', 0)) {
+    popups_add_popups();
+  }
+
+  foreach ($popups as $path => $popup_config) {
+    if ($path == $_GET['q']) {
+      popups_add_popups($popup_config);
+    }
+    elseif (strpos($path, '*') !== FALSE && drupal_match_path($_GET['q'], $path)) {
+      popups_add_popups($popup_config);
+    }
+  }  
+  
+  $render_mode = '';
+  if (isset($_SERVER['HTTP_X_DRUPAL_RENDER_MODE'])) {
+    $render_mode = $_SERVER['HTTP_X_DRUPAL_RENDER_MODE'];
+  }
+  
+  // Check and see if the page_override param is in the URL.
+  // Note - the magic happens here.
+  // Need to cache the page_override flag in the session, so it will effect
+  // the results page that follows a form submission.
+  if ($render_mode == 'json/popups') {
+    $_SESSION['page_override'] = TRUE;
+  }
+
+  // Move the page_override flag back out of the session.
+  if (isset($_SESSION['page_override'])) {
+    // This call will not return on form submission.
+    $content = menu_execute_active_handler();
+    
+    // The call did return, so it wasn't a form request, 
+    // so we are returning a result, so clear the session flag.
+    $override = $_SESSION['page_override'];
+    unset($_SESSION['page_override']);
+       
+    // Menu status constants are integers; page content is a string.
+    if (isset($content) && !is_int($content) && isset($override)) {
+      print popups_render_as_json($content); 
+      exit;  // Do not continue processing request in index.html.
+    }    
+  }
+  
+}
+
+/**
+ * Implementation of 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 popups_form_alter(&$form, $form_state, $form_id) {
+  // Add popup behavior to the form if requested.
+//  dsm($form_id);
+  $popups = popups_get_popups();
+  if (isset($popups[$form_id])) {
+    popups_add_popups($popups[$form_id]);
+  } 
+
+  // Alter the theme configuration pages, to add a per-theme-content selector. 
+  $theme = arg(4);
+  if ($form_id == 'system_theme_settings' && $theme) {
+    $form['popups'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Popup Settings'),
+      '#weight' => -2,
+    );
+    $form['popups']['popups_content_selector'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Content Selector'),
+      '#default_value' => variable_get('popups_'. $theme .'_content_selector', _popups_default_content_selector()),
+      '#description' => t("jQuery selector to define the page's content area on this theme."),
+    ); 
+    $form['popups']['popups_theme'] = array(
+      '#type' => 'hidden',
+      '#value' => $theme,
+    ); 
+    $form['#submit'][] = 'popups_theme_settings_form_submit';
+  }
+}
+
+// **************************************************************************
+// UTILITY FUNCTIONS   ******************************************************
+// **************************************************************************
+
+/**
+ * Render the page contents in a custom json wrapper.
+ *
+ * @param $content: themed html.
+ * @return $content in a json wrapper with metadata.
+ */
+function popups_render_as_json($content) { 
+  $path = $_GET['q']; // Get current path from params.
+  return drupal_json(array(
+    'title' => drupal_get_title(),
+    'messages' => theme('status_messages'),
+    'path' => $path,
+    'content' => $content,
+    'js' => popups_get_js(),
+    'css' => popups_get_css(), 
+  ));
+}
+
+/**
+ * Get the added JS in a format that is readable by popups.js.
+ */
+function popups_get_js() {
+  $js = array_merge_recursive(drupal_add_js(), drupal_add_js(NULL, NULL, 'footer'));
+  $query_string = '?'. substr(variable_get('css_js_query_string', '0'), 0, 1);
+  
+  $popup_js = array();
+
+  foreach ($js as $type => $data) {
+    if (!$data) continue;
+    switch ($type) {
+      case 'setting':
+        // Why not just array_merge_recursive($data);
+        $popup_js['setting'] = call_user_func_array('array_merge_recursive', $data);
+        break;
+      case 'inline':
+        foreach ($data as $info) {
+          $popup_js['inline'][] = '<script type="text/javascript"' . ($info['defer'] ? ' defer="defer"' : '') . '>' . $info['code'] . "</script>\n";
+        }
+        break;
+      default:
+        foreach ($data as $path => $info) {
+          $popup_js[$type][$path] = '<script type="text/javascript"'. ($info['defer'] ? ' defer="defer"' : '') .' src="'. base_path() . $path . $query_string ."\"></script>\n";
+        }
+        break;
+    }
+  }
+
+  // A special exception, never include the popups settings in the JS array.
+  // ???
+//  unset($popup_js['setting']['popups']);
+
+  return $popup_js;
+}
+
+/**
+ * Get the added CSSS in a format that is readable by popups.js.
+ */
+function popups_get_css() {
+  $css = drupal_add_css();
+  $popup_css = array();
+
+  $query_string = '?'. substr(variable_get('css_js_query_string', '0'), 0, 1);
+
+  // Only process styles added to "all".
+  $media = 'all';
+  foreach ($css[$media] as $type => $files) {
+    if ($type == 'module') {
+      // Setup theme overrides for module styles.
+      $theme_styles = array();
+      foreach (array_keys($css[$media]['theme']) as $theme_style) {
+        $theme_styles[] = basename($theme_style);
+      }
+    }
+    foreach($css[$media][$type] as $file => $preprocess) {
+      // If the theme supplies its own style using the name of the module style, skip its inclusion.
+      // This includes any RTL styles associated with its main LTR counterpart.
+      if ($type == 'module' && in_array(str_replace('-rtl.css', '.css', basename($file)), $theme_styles)) {
+        // Unset the file to prevent its inclusion when CSS aggregation is enabled.
+        unset($css[$media][$type][$file]);
+        continue;
+      }
+      // Only include the stylesheet if it exists.
+      if (file_exists($file)) {
+        // If a CSS file is not to be preprocessed and it's a module CSS file, it needs to *always* appear at the *top*,
+        // regardless of whether preprocessing is on or off.
+        if ($type == 'module') {
+          $popup_css['module'][$file] = '<link type="text/css" rel="stylesheet" media="'. $media .'" href="'. base_path() . $file . $query_string .'" />'."\n";
+        }
+        // If a CSS file is not to be preprocessed and it's a theme CSS file, it needs to *always* appear at the *bottom*,
+        // regardless of whether preprocessing is on or off.
+        else if ($type == 'theme') {
+          $popup_css['theme'][$file] = '<link type="text/css" rel="stylesheet" media="'. $media .'" href="'. base_path() . $file . $query_string .'" />'."\n";
+        }
+        else {
+          $popup_css['unknown'][$file] = '<link type="text/css" rel="stylesheet" media="'. $media .'" href="'. base_path() . $file . $query_string .'" />'."\n";
+        }
+      }
+    }
+  }
+
+  return $popup_css;
+}
+
+
+/**
+ * Define hook_popups().
+ * Build the list of popup rules from all modules that implement hook_popups.
+ * 
+ * Retrieves the list of popup rules from all modules that implement hook_popups.
+ *
+ * @param $reset
+ *   (optional) If set to TRUE, forces the popup rule cache to reset.
+ * 
+ */
+function popups_get_popups($reset = FALSE) {
+  static $popups = NULL;
+  if (!isset($popups) || $reset) {
+    // Get popups hash out of cache.
+    if (!$reset && ($cache = cache_get('popups:popups')) && !empty($cache->data)) {
+      $popups = $cache->data;
+    }
+    else { 
+      // Call all hook_popups and cache results.
+      $popups = module_invoke_all('popups');
+      
+      // Invoke hook_popups_alter() to allow altering the default popups registry.
+      drupal_alter('popups', $popups);
+
+      // Save the popup registry in the cache.      
+      cache_set('popups:popups', $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 $rules: Array of rules to apply to the page or form, keyed by jQuery link selector.
+ *   See README.txt for a listing of the options, and popups_admin.module for examples. 
+ */
+function popups_add_popups($rules=NULL) { 
+  static $added = FALSE;
+  $settings = array('popups' => array());
+  
+  if (is_array($rules)) {
+    $settings['popups']['links'] = array();
+    foreach ($rules as $popup_selector => $options) { 
+      if (is_array($options)) {
+        $settings['popups']['links'][$popup_selector] = $options;
+      }
+      else {
+        $settings['popups']['links'][$options] = array();
+      }
+    }
+    if($added) {
+      drupal_add_js( $settings, 'setting' );
+    }
+  }
+  if (!$added) {
+    // Determing if we are showing the default theme or a custom theme.
+    global $custom_theme;
+    $theme = $custom_theme;
+    if (!$theme) {
+      $theme = variable_get('theme_default','none');
+    }
+    
+    drupal_add_js('misc/jquery.form.js');
+    drupal_add_css(drupal_get_path('module', 'popups') .'/popups.css');
+    drupal_add_js(drupal_get_path('module', 'popups') .'/popups.js');
+
+    // Allow skinning of the popup.
+    $skin = variable_get('popups_skin', 'Basic');
+    $skins = popups_skins();
+    if (!$skins[$skin]['css']) { // $skin == 'Unskinned'
+      // Look in the current theme for popups-skin.js
+      drupal_add_js(drupal_get_path('theme', $theme) . '/popups-skin.js');
+    }
+    else { // Get css and js from selected skin.
+      drupal_add_css($skins[$skin]['css']);
+      if (isset($skins[$skin]['js'])) {
+        drupal_add_js($skins[$skin]['js']);
+      }   
+    }
+        
+    $default_target_selector = variable_get('popups_'. $theme .'_content_selector', _popups_default_content_selector());
+    
+    $settings['popups']['originalPath'] = $_GET['q'];
+    $settings['popups']['defaultTargetSelector'] = $default_target_selector;
+    $settings['popups']['modulePath'] = drupal_get_path('module', 'popups');
+//    $settings['popups']['popupFinalMessage'] = variable_get('popups_popup_final_message', 1);
+    $settings['popups']['autoCloseFinalMessage'] = variable_get('popups_autoclose_final_message', 1);
+    drupal_add_js( $settings, 'setting' );
+    $added = TRUE;
+  }
+}
+
+ /**
+ * Retrieve all information from the popup skin registry.
+ *
+ * @param $reset
+ *   (optional) If TRUE, will force the the skin registry to reset.
+ * @see popups_popups_skins
+ */
+function popups_skins($reset = FALSE) {
+  static $skins = array();
+  if (empty($skins) || $reset) {
+    if (!$reset && ($cache = cache_get('popups:skins')) && !empty($cache->data)) {
+      $skins = $cache->data;
+    }
+    else {
+      // Create the popup skin registry (hook_popups_skins) and cache it.
+      $skins = module_invoke_all('popups_skins');
+      ksort($skins);  // Sort them alphabetically
+      cache_set('popups:skins', $skins, 'cache', CACHE_PERMANENT);
+    }
+  }
+  return $skins;
+}
+
+/**
+ * Implementation of hook_popups_skins.
+ * 
+ * This hook allows other modules to create additional custom skins for the
+ * popups module.
+ * 
+ * @return array
+ *   An array of key => value pairs suitable for inclusion as the #options in a
+ *   select or radios form element. Each key must be the location of at least a 
+ *   css file for a popups skin. Optionally can have a js index as well. Each 
+ *   value should be the name of the skin.
+ */
+function popups_popups_skins() {
+  $skins = array();
+  $skins_directory = drupal_get_path('module', 'popups') .'/skins';
+  $files = file_scan_directory($skins_directory, '\.css$');
+
+  foreach ($files as $file) {
+    $name = drupal_ucfirst($file->name);
+    $skins[$name]['css'] = $file->filename;
+    $js = substr_replace($file->filename, '.js', -4);
+    if (file_exists($js)) {
+      $skins[$name]['js'] = $js;
+    }
+  }
+  return $skins;
+}
+
+
+/**
+ * Returns the default jQuery content selector as a string.
+ * Currently uses the selector for Garland.  
+ * Sometime in the future I will change this to '#content' or '#content-content'.
+ */
+function _popups_default_content_selector() {
+  return 'div.left-corner > div.clear-block:last'; // Garland in Drupal 6.
+}
+
+// **************************************************************************
+// ADMIN SETTINGS   *********************************************************
+// **************************************************************************
+
+/**
+ * Form for the Popups Settings page.
+ *
+ */
+function popups_admin_settings() {
+  popups_add_popups();
+//  drupal_add_css(drupal_get_path('module', 'popups'). '/skins/blue/blue.css'); // temp
+  drupal_set_title("Popups Settings");
+  $form = array();
+
+  $form['popups_always_scan'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Scan all pages for popup links.'),
+    '#default_value' => variable_get('popups_always_scan', 0),
+  );
+  $form['popups_autoclose_final_message'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Automatically close final confirmation messages.'),
+    '#default_value' => variable_get('popups_autoclose_final_message', 1),
+  );
+
+  // Retrieve all available skins, forcing the registry to refresh.
+  $skins['Unskinned'] = array();
+  $skins += popups_skins(TRUE);
+  
+  $skin_options = drupal_map_assoc(array_keys($skins));
+  $form['popups_skins'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Skins'),
+    '#description' => t('Choose a skin from the list. After you save, click !here to test it out.', array('!here' => l('here', 'user', array('attributes' => array('class' => 'popups'))))),
+    '#collapsible' => TRUE,
+    '#collapsed' => FALSE,
+  );
+  $form['popups_skins']['popups_skin'] = array(
+    '#type' => 'radios',
+    '#title' => t('Available skins'),
+    '#default_value' => variable_get('popups_skin', 'Basic'),
+    '#options' => $skin_options,
+  );
+
+  
+  return system_settings_form($form);
+}
+
+/**
+ * popups_form_alter adds this submit handler to the theme pages.
+ * Set a per-theme jQuery content selector that gets passed into the js settings. 
+ *
+ * @param $form
+ * @param $form_state
+ */
+function popups_theme_settings_form_submit($form, &$form_state) {
+  $theme = $form_state['values']['popups_theme'];
+  $content_selector = $form_state['values']['popups_content_selector'];
+  variable_set('popups_'. $theme .'_content_selector', $content_selector);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/popups_admin.info	Fri Dec 31 13:41:08 2010 +0100
@@ -0,0 +1,14 @@
+; $Id: popups_admin.info,v 1.1.6.3 2009/03/05 19:52:48 starbow Exp $
+name = Popups: Administration Links
+description = Uses the Popups API to add popups to various administration pages.
+package = User interface
+core = 6.x
+dependencies[] = popups
+
+
+; Information added by drupal.org packaging script on 2009-03-21
+version = "6.x-2.0-alpha5"
+core = "6.x"
+project = "popups"
+datestamp = "1237597273"
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/popups_admin.module	Fri Dec 31 13:41:08 2010 +0100
@@ -0,0 +1,103 @@
+<?php
+// $Id: popups_admin.module,v 1.1.6.7 2009/03/21 00:57:16 starbow Exp $
+
+/**
+ * @file
+ * Uses the popups API to add some popups to admin pages.
+ * 
+ * @todo
+ * Adding Javascript into popups doesn't always work.
+ *   tabledrag onmouse up action.
+ *   user.js and teaser.js bugs.
+ * 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).
+ * Comment view: popup is too small to contain comment.
+ */
+
+/**
+ * hook_popups
+ * 
+ * This implements hook_popups, defined in popups_get_popups.
+ * It adds page-in-popup behavior to the core admin pages.
+ * See the comments in popups_add_popups for explination of the options.
+ *
+ */
+function popups_admin_popups() {
+    
+  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/block/list/*' => 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.
+      '#tabs-wrapper a[href$=admin/content/taxonomy/add/vocabulary]', // Add vocabulary
+      '#taxonomy-overview-vocabularies td a:contains('. t('edit vocabulary') .')', // edit vocabulary
+      '#taxonomy-overview-vocabularies td a:contains('. t('list terms') .')' => array( // list terms
+        'noUpdate' => TRUE,
+      ),
+      '#taxonomy-overview-vocabularies td a:contains('. t('add terms') .')' => array(  // add terms
+        'noUpdate' => TRUE,
+      ),
+    ),
+    'admin/content/types' => array( // Content Type admin page
+      '#tabs-wrapper a[href$=admin/content/types/add]',  // Add content type
+      'table td:nth-child(4) a, table td:nth-child(5) a, table td:nth-child(7) a' // edit, add field, delete
+    ),
+    'admin/content/types/list' => array( // Content Type admin page
+      '#tabs-wrapper a[href$=admin/content/types/add]',  // Add content type
+      'table td:nth-child(4) a, table td:nth-child(5) a, table td:nth-child(7) a' // edit, add field, delete
+    ),
+    'admin/content/node' => array( // Existing Content admin page
+      '#node-admin-content td a:contains('. t('edit') .')',  // edit
+    ),
+//    'page_node_form' => array( // Node edit form
+    'node/add/*' => array( // Node edit form
+      'a[href$=filter/tips]' => array( // Fixes insane "More information..." link
+        'noUpdate' => TRUE,
+      )      
+    ),
+    'admin/content/comment' => array( // Comments admin page.
+      'table td:nth-child(2) a' => array( // view (TODO: popup too small)
+        'noUpdate' => TRUE,
+      ),
+      '#comment-admin-overview td a:contains('. t('edit') .')', // edit
+    ),
+    '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 
+        'noUpdate' => 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]', 
+      '#user-admin-account td:nth-child(2) a' => array( // View the user
+        'noUpdate' => 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,
+        'noUpdate' => TRUE,
+      ),
+    ),
+        
+  );
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/popups_test.info	Fri Dec 31 13:41:08 2010 +0100
@@ -0,0 +1,14 @@
+; $Id: popups_test.info,v 1.1.4.3 2009/03/05 19:52:49 starbow Exp $
+name = Popups: Test Page
+description = Test the Popups API.
+package = User interface
+core = 6.x
+dependencies[] = popups
+
+
+; Information added by drupal.org packaging script on 2009-03-21
+version = "6.x-2.0-alpha5"
+core = "6.x"
+project = "popups"
+datestamp = "1237597273"
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/popups_test.module	Fri Dec 31 13:41:08 2010 +0100
@@ -0,0 +1,227 @@
+<?php
+// $Id: popups_test.module,v 1.1.4.6 2009/03/19 15:53:44 starbow Exp $
+
+/**
+ * @file
+ * Page for testing the Popups API.
+ */
+
+
+// **************************************************************************
+// CORE HOOK FUNCTIONS   ****************************************************
+// **************************************************************************
+
+/**
+ * Implementation of hook_menu().
+ *
+ * @return array of new menu items.
+ */
+function popups_test_menu() {
+  // Items for testing.
+  $items['popups/test'] = array(
+    'title' => 'Popup Test',
+    'page callback' => '_popups_test_popups',
+    'type' => MENU_CALLBACK,
+    'access callback' => TRUE,
+  );
+  $items['popups/test/response'] = array(
+    'page callback' => '_popups_test_response',
+    'type' => MENU_CALLBACK,
+    'access callback' => TRUE,
+  );
+  $items['popups/test/namechange'] = array(
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('_popups_test_namechange'),
+    'type' => MENU_CALLBACK,
+    'access callback' => TRUE,
+  );
+  $items['popups/test/old'] = array(
+    'title' => 'Popup Test',
+    'page callback' => '_popups_test_popups_old',
+    'type' => MENU_CALLBACK,
+    'access callback' => TRUE,
+  );  
+  return $items;
+}
+
+/**
+ *  Implementation of hook_popups().
+ * 
+ * This implements hook_popups, defined in popups_get_popups.
+ * See the comments in popups_add_popups for explination of the options.
+ * Adding popup behavior to the core admin pages has been moved to popups_admin.
+ *
+ * @return: Array of link selectors to apply popup behavior to.
+ *          Keyed by path or form_id.
+ */
+function popups_test_popups() {   
+  return array(
+    'popups/test' => array( // test page.
+//    '*' => array( // test page.
+      '#test-popup' => array( 
+//        'additionalJavascript' => array('misc/collapse.js'),
+//        'forceReturn' => 'node/add/story',
+      ),
+    ),  
+  );
+}
+
+// **************************************************************************
+// TESTING   ****************************************************************
+// **************************************************************************
+
+function _popups_test_popups() {
+  popups_add_popups();
+  $output = '<ol id="test-list">';
+  $output .= '<li>'. l("Pop up entire local page.", 'popups/test/response', 
+               array('attributes' => array('class' => 'popups')));
+  $output .= "<li>". l("Pop with options (href override).", 'popups/test/', 
+               array('attributes' => array('class' => 'popups', 'on-popups-options' => '{href: "test/response"}')));
+  $output .= "<li>". l("Pop with options (width=200px).", 'popups/test/response', 
+               array('attributes' => array('class' => 'popups', 'on-popups-options' => '{width: "200px"}')));
+  $output .= "<li class=\"popups\" on-popups-options=\"{href: 'test/response'}\">Non-link popup</li>";
+  $output .= '<li>'. l("Add Story (hook)", 'node/add/story',
+               array( 'attributes' => array('id' => 'test-popup')));
+  $output .= '<li>'. l("Add Story (attribute).", 'node/add/story',
+               array( 'attributes' => array('class' => 'popups-form')));
+               
+  $output .= '<li>'. l("Change Settings and ajax update entire content area: ", 
+                       'admin/settings/popups',
+                       array( 'attributes' => array('class' => 'popups-form'),
+                     ));
+  $output .= " (Auto Fade checkbox is: " . (variable_get('popups_autoclose_final_message', 1) ? 'on' : 'off') . ')';
+
+  $output .= '<li>'. l("Change Settings and ajax update only single target.", 'admin/settings/popups',
+               array( 'attributes' => array('id' => 'reload-target'), 
+               ));
+  $output .= "<span id='response2'> (Auto Fade checkbox is: " . (variable_get('popups_autoclose_final_message', 1) ? 'on' : 'off') . ')</span>';
+  popups_add_popups(array('#reload-target'=>array('targetSelectors'=>array('#response2'))));   
+      
+  $output .= '<li>'. l("Change Settings and ajax update multiple targets with data from other page (tricky!).", 'admin/settings/popups',
+             array( 'attributes' => array(
+                'id' => 'foo',
+                'class' => 'popups-form',
+                'on-popups-options' => '{targetSelectors: {"#edit-popups-always-scan-wrapper": "#foo", "#edit-popups-popup-final-message-wrapper": "#test-list li:first"}, forceReturn: "admin/settings/popups"}')));
+
+  $output .= '<li>'. l("Change Settings and reload entire page.", 
+                       'admin/settings/popups',
+                       array( 'attributes' => array('class' => 'popups-form-reload'),
+                     ));
+                     
+  $output .= '<li>'. l("Pop up defined by popups_add_popups rule.", 'popups/test/response', 
+                     array('attributes' => array('id' => 'rule-test')));
+  popups_add_popups(array('#rule-test'=>array('width'=>'300px')));       
+  $output .= '<li>'. l('Ajax update just Page Title (only works if you theme uses id="page-title")', 'popups/test/namechange', 
+                     array('attributes' => array('id' => 'title-test')));
+  popups_add_popups(array('#title-test'=>array('titleSelectors'=>array('#page-title'), 'noUpdate'=> TRUE, 'forceReturn'=>'popups/test/namechange')));       
+
+  global $user;
+  $output .= "<li>You are user number $user->uid</li>";
+  if ($user->uid == 0) {
+    $output .= '<li>'. l("Login and ajax refresh content area.", 'user', 
+                       array('attributes' => array('class' => 'popups-form')));
+    $output .= '<li>'. l("Login and reload entire page.", 'user', 
+                       array('attributes' => array('class' => 'popups-form-reload')));                   
+    $output .= '<li>'. l("Login and do not reload anything.", 'user', 
+                       array('attributes' => array('class' => 'popups-form-noupdate')));                   
+  }
+  else {
+    $output .= '<li>'. l("Logout (need to surpress warning b/c session is dumped)", 'logout', 
+                     array('attributes' => array('id' => 'logout')));
+  }
+  // Need to have the rule outside the else, or it won't get loaded on ajax reload.
+  popups_add_popups(array('#logout'=>array('noUpdate'=>TRUE, 'reloadOnError'=>TRUE))); 
+
+  $output .= '<li>'. l("Add Poll (test inline)", 'node/add/poll', 
+                       array('attributes' => array('class' => 'popups-form')));
+    
+  $output .= "</ol>";                
+  return $output;
+}
+
+function _popups_test_popups_old() {
+//  drupal_set_message('Popup Test Page: If you edit your page.tpl.php to wrap the print $messages in a div with id="popit", this message will popup on page load');
+  popups_add_popups();
+  $output = '<ul id="test-list">';
+  $output .= '<li>'. l("Pop up entire local page.", 'popups/test/response', 
+               array('attributes' => array('class' => 'popups')));
+  $output .= "<li>". l("Pop with options (href override).", 'popups/test/', 
+               array('attributes' => array('class' => 'popups', 'on-popups-options' => '{href: "test/response"}')));
+  $output .= "<li>". l("Pop with options (width=200px).", 'popups/test/response', 
+               array('attributes' => array('class' => 'popups', 'on-popups-options' => '{width: "200px"}')));
+  $output .= "<li class=\"popups\" on-popups-options=\"{href: 'test/response'}\">Non-link popup</li>";
+  $output .= '<li>'. l("Add Story (hook)", 'node/add/story',
+               array( 'attributes' => array('id' => 'test-popup')));
+  $output .= '<li>'. l("Add Story (attribute).", 'node/add/story',
+               array( 'attributes' => array('class' => 'popups-form')));
+               
+  $output .= '<li>'. l("Change Settings and ajax update entire content area: ", 
+                       'admin/settings/popups',
+                       array( 'attributes' => array('class' => 'popups-form'),
+                     ));
+  $output .= " (Auto Fade checkbox is: " . (variable_get('popups_popup_final_message', 1) ? 'on' : 'off') . ')';
+
+  $output .= '<li>'. l("Change Settings and ajax update only single target.", 'admin/settings/popups',
+               array( 'attributes' => array('id' => 'reload-target'), 
+               ));
+  $output .= "<span id='response2'> (Auto Fade checkbox is: " . (variable_get('popups_popup_final_message', 1) ? 'on' : 'off') . ')</span>';
+  popups_add_popups(array('#reload-target'=>array('targetSelectors'=>array('#response2'))));   
+      
+  $output .= '<li>'. l("Change Settings and ajax update multiple targets with data from other page (tricky!).", 'admin/settings/popups',
+             array( 'attributes' => array(
+                'id' => 'foo',
+                'class' => 'popups-form',
+                'on-popups-options' => '{targetSelectors: {"#edit-popups-always-scan-wrapper": "#foo", "#edit-popups-popup-final-message-wrapper": "#test-list li:first"}, forceReturn: "admin/settings/popups"}')));
+
+  $output .= '<li>'. l("Change Settings and reload entire page.", 
+                       'admin/settings/popups',
+                       array( 'attributes' => array('class' => 'popups-form-reload'),
+                     ));
+                     
+  $output .= '<li>'. l("Pop up defined by popups_add_popups rule.", 'popups/test/response', 
+                     array('attributes' => array('id' => 'rule-test')));
+  popups_add_popups(array('#rule-test'=>array('width'=>'300px')));       
+  $output .= '<li>'. l('Ajax update just Page Title (only works if you theme uses id="page-title")', 'popups/test/namechange', 
+                     array('attributes' => array('id' => 'title-test')));
+  popups_add_popups(array('#title-test'=>array('titleSelectors'=>array('#page-title'), 'noUpdate'=> TRUE, 'forceReturn'=>'popups/test/namechange')));       
+
+  global $user;
+  $output .= "<li>You are user number $user->uid</li>";
+  if ($user->uid == 0) {
+    $output .= '<li>'. l("Login and ajax refresh content area.", 'user', 
+                       array('attributes' => array('class' => 'popups-form')));
+    $output .= '<li>'. l("Login and reload entire page.", 'user', 
+                       array('attributes' => array('class' => 'popups-form-reload')));                   
+    $output .= '<li>'. l("Login and do not reload anything.", 'user', 
+                       array('attributes' => array('class' => 'popups-form-noupdate')));                   
+  }
+  else {
+    $output .= '<li>'. l("Logout (need to surpress warning b/c session is dumped)", 'logout', 
+                     array('attributes' => array('id' => 'logout')));
+  }
+  // Need to have the rule outside the else, or it won't get loaded on ajax reload.
+  popups_add_popups(array('#logout'=>array('noUpdate'=>TRUE, 'reloadOnError'=>TRUE))); 
+
+  $output .= '<li>'. l("Add Poll (test inline)", 'node/add/poll', 
+                       array('attributes' => array('class' => 'popups-form')));
+    
+  $output .= "</ul>";                
+  return $output;
+}
+
+function _popups_test_response() {
+  drupal_set_title("Popup Test Two");
+  return '<div>Hello World</div><a href="#" class="popups">Popup chaining test</a>';
+}
+
+function _popups_test_namechange() {
+  drupal_set_title("New Name for Test Page");
+  $form = array();
+
+  $form['popups_popup_final_message'] = array(
+    '#type' => 'submit',
+    '#value' => t('Test Name Change'),
+  );
+  
+  return $form;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/skins/basic/basic.css	Fri Dec 31 13:41:08 2010 +0100
@@ -0,0 +1,29 @@
+.popups-box {
+  border: 2px solid #EDF5FA;
+  -moz-border-radius: 8px;
+  -webkit-border-radius: 8px;
+  opacity: 0.9;
+}
+.popups-title {
+  border-bottom: 1px solid #b4d7f0;
+  background-color: #d4e7f3;
+  color: #455067;
+  margin-bottom: 0.25em;
+  padding: 0.25em;
+  -moz-border-radius-topleft: 5px;
+  -webkit-border-radius-topleft: 5px;
+}
+.popups-title .popups-close a {
+  color: red;  
+}
+.popups-box input {
+  margin: 0.1em;
+}
+.popups-box input[type="text"]:focus, .popups-box input[type="password"]:focus, .popups-box textarea:focus {
+  background-color: #FFA;
+  outline: thin solid grey;
+}
+a.popups-processed {
+  padding-right: 12px;
+  background: url(popup-icon.png) no-repeat right;
+}
\ No newline at end of file
Binary file skins/basic/popup-icon.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/skins/blue/blue.css	Fri Dec 31 13:41:08 2010 +0100
@@ -0,0 +1,66 @@
+/*  
+  Blue Popup Skin
+  Author: Tj Holowaychuk
+  Link: http://vision-media.ca
+*/ 
+#popups-overlay {
+  background: #fff;
+  opacity:.80;
+}
+.popups-box {
+  padding: 25px;
+  width: 510px;
+  border: none;
+  background: transparent url(images/tile-v.png) 0 0 repeat-y;
+  overflow: visible;
+}       
+.popups-title {
+  position: absolute;
+  top: -65px;
+  left: -11px;
+  margin: 0;
+  padding: 0;
+  height: 65px;
+  width: 570px;
+  line-height: 75px;
+  background: transparent url(images/sprites.png) 0 0 no-repeat;
+}
+.popups-title .title {
+  text-indent: 25px;
+  text-shadow: #fff 1px 1px 1px;
+  color: #437acf;
+}
+.popups-title .popups-close a {
+  position: absolute;
+  top: 4px;
+  right: -7px;
+  display: block;
+  width: 16px;
+  height: 16px;
+  text-indent: -9999999px;
+  outline: none;
+  cursor: pointer;
+  background: transparent url(images/sprites.png) -577px -18px no-repeat;   
+}
+.popups-title .popups-close a:hover {
+  background-position: -577px -52px;
+}
+.popups-title .popups-close a:active {
+  background-position: -577px -35px;   
+}    
+.popups-body {
+  padding: 0 25px;
+  height: 250px;
+  overflow-y: auto;
+  overflow-x: hidden; 
+}  
+.popups-footer {
+  position: absolute;
+  bottom: -90px;
+  left: -11px;
+  width: 570px;
+  height: 90px;
+  background: transparent url(images/sprites.png) 0 -110px no-repeat; 
+}
+
+
Binary file skins/blue/images/sprites.png has changed
Binary file skins/blue/images/tile-v.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/skins/facebook/facebook.css	Fri Dec 31 13:41:08 2010 +0100
@@ -0,0 +1,102 @@
+#popups-overlay {
+  background-color:transparent;
+}
+#popups-loading {
+  width:248px;
+  position:absolute;
+  display:none;
+  opacity:1;
+  -moz-border-radius: 8px;
+  -webkit-border-radius: 8px;
+  z-index:99;
+}
+#popups-loading span.popups-loading-message {
+  background:#FFF url(loading-large.gif) no-repeat 8px center;
+  display:block;
+  color:#444444;
+  font-family:Arial;
+  font-size:22px;
+  font-weight:bold;
+  height:36px;
+  line-height:36px;
+  padding:0 40px;
+}
+#popups-loading table, 
+.popups-box table { 
+	margin:0px; 
+}
+#popups-loading tbody,
+.popups-box tbody { 
+	border:none; 
+}
+.popups-box tr { 
+	background-color:transparent;
+}
+td.popups-border {
+  background: url(popups-border.png);
+  background-color:transparent;
+  filter:progid:DXImageTransform.Microsoft.Alpha(opacity=85);
+}
+td.popups-tl,
+td.popups-tr,
+td.popups-bl,
+td.popups-br {
+  background-repeat: no-repeat;
+  height:10px;
+  padding:0px;
+}
+td.popups-tl { background-position: 0px 0px; }
+td.popups-t,
+td.popups-b {
+  background-position: 0px -40px;
+  background-repeat: repeat-x;
+  height:10px;
+}
+td.popups-tr { background-position: 0px -10px; }
+td.popups-cl,
+td.popups-cr {
+  background-position: -10px 0;
+  background-repeat: repeat-y;
+  width:10px;
+}
+td.popups-cl,
+td.popups-cr,
+td.popups-c { padding:0; }
+td.popups-bl { background-position: 0px -20px; }
+td.popups-br { background-position: 0px -30px; }
+
+.popups-box,
+#popups-loading {
+  border: 0px solid #454545;
+  opacity:1;
+  overflow:hidden;
+  padding:0;
+  background-color:transparent;
+}
+.popups-container {
+  overflow:auto;
+  max-height:100%;
+  background-color:#fff;
+}
+.popups-title {
+  -moz-border-radius-topleft: 0px;
+  -webkit-border-radius-topleft: 0px;
+  margin-bottom:0px;
+  background-color:#6D84B4;
+  border:1px solid #3B5998;
+  padding:4px 10px 5px;
+  color:white;
+  font-size:14px;
+  font-weight:bold;
+}
+.popups-body {
+  background-color:#fff;
+  padding:8px;
+}
+.popups-box .popups-buttons,
+.popups-box .popups-footer { 
+  background-color:#fff; 
+}
+.popups-title .popups-close a {
+  color: #fff;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/skins/facebook/facebook.js	Fri Dec 31 13:41:08 2010 +0100
@@ -0,0 +1,88 @@
+
+/**
+ * Custom theming for the popupsLoading.
+ */
+Drupal.theme.popupLoading = function() {
+console.log("Drupal.theme.popupLoading: Facebook");
+  var loading;
+  loading += '<div id="popups-loading">';
+  loading += '  <table>';
+  loading += '    <tr>';
+  loading += '      <td class="popups-tl popups-border"></td>';
+  loading += '      <td class="popups-t popups-border"></td>';
+  loading += '      <td class="popups-tr popups-border" id="popups-tr"></td>';
+  loading += '    </tr>';
+  loading += '    <tr>';
+  loading += '      <td class="popups-cl popups-border"></td>';
+  loading += '      <td class="popups-c">';
+  loading += '        <span class="popups-loading-message">Loading...</span>';
+  loading += '      </td>';
+  loading += '      <td class="popups-cr popups-border"></td>';
+  loading += '    </tr>';
+  loading += '    <tr>';
+  loading += '      <td class="popups-bl popups-border"></td>';
+  loading += '      <td class="popups-b popups-border"></td>';
+  loading += '      <td class="popups-br popups-border"></td>';
+  loading += '    </tr>';
+  loading += '  </table>';
+  loading += '</div>';
+  return loading;
+};
+
+Drupal.theme.popupTemplate = function (popupId) {
+console.log("Drupal.theme.popupTemplate: Facebook");
+  var template;
+  template += '<div id="' + popupId + '" class ="popups-box">';
+  template += '  <table>';
+  template += '    <tr>';
+  template += '      <td class="popups-tl popups-border"></td>';
+  template += '      <td class="popups-t popups-border"></td>';
+  template += '      <td class="popups-tr popups-border"></td>';
+  template += '    </tr>';
+  template += '    <tr>';
+  template += '      <td class="popups-cl popups-border"></td>';
+  template += '      <td class="popups-c">';
+  template += '        <div class="popups-container">';
+  template += '          <div class="popups-title">';
+  template += '            <div class="popups-close"><a href="#">' + Drupal.t('Close') + '</a></div>';
+  template += '            <div class="title">%title</div>';
+  template += '            <div class="clear-block"></div>';
+  template += '          </div>';
+  template += '          <div class="popups-body">%body</div>';
+  template += '          <div class="popups-buttons">%buttons</div>';
+  template += '          <div class="popups-footer"></div>';
+  template += '        </div>';
+  template += '      </td>';
+  template += '      <td class="popups-cr popups-border"></td>';
+  template += '    </tr>';
+  template += '    <tr>';
+  template += '      <td class="popups-bl popups-border"></td>';
+  template += '      <td class="popups-b popups-border"></td>';
+  template += '      <td class="popups-br popups-border"></td>';
+  template += '    </tr>';
+  template += '  </table>';
+  template += '</div>';
+  return template;
+};
+
+/**
+ * We need to resize the popups-container as well as the popups if it scrolls
+ */
+Drupal.behaviors.resizePopupsContainer = function() {
+  var popup = Popups.activePopup();
+  if (popup) {
+    var $popupsContainer = $('#' + popup.id + ' .popups-container');
+    if ($popupsContainer.length) {
+      var popupHeight = $popupsContainer.height();
+      var windowHeight = $(window).height();
+      if (popupHeight > (0.9 * windowHeight) ) { // Must fit in 90% of window.
+        // we make this slightly smaller than popups so that it fits inside
+        popupHeight = 0.85 * windowHeight;
+        $popupsContainer.height(popupHeight);
+      }
+      // needs an extra 20px as the bottom dropshadow looks cutoff
+      var $popup = popup.$popup();
+      $popup.height($popup.height() + 20);
+    }
+  }
+};
\ No newline at end of file
Binary file skins/facebook/loading-large.gif has changed
Binary file skins/facebook/loading.gif has changed
Binary file skins/facebook/popups-border.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/translations/ja.po	Fri Dec 31 13:41:08 2010 +0100
@@ -0,0 +1,116 @@
+# $Id: ja.po,v 1.1.2.1 2008/09/15 10:09:10 imagine Exp $
+# -----------------------------------------------------------------------------
+# Japanese translation of Drupal (modules-popups)
+#
+# Copyright (c) 2008       Drupal Japan  ( http://drupal.jp/ )  /
+#                          Takafumi      ( jp.drupal@imagine **reverse order**)
+#
+# Generated from file:
+#  popups-popup.tpl.php,v 1.1.2.4 2008/09/15 01:48:52 starbow
+#  popups.module,v 1.11.2.5 2008/09/15 01:48:53 starbow
+#  popups.info,v 1.4 2008/03/11 16:56:08 starbow
+#  popups.js,v 1.9.2.6 2008/09/15 01:48:52 starbow
+#
+# -----------------------------------------------------------------------------
+msgid ""
+msgstr ""
+"POT-Creation-Date: 2008-09-15 18:30+0900\n"
+"Last-Translator: Takafumi <jp.drupal@imagine **reverse order**>\n"
+"Language-Team: Drupal Japan\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n!=1);\n"
+
+#: popups-popup.tpl.php:6
+msgid "Close"
+msgstr "閉じる"
+
+#: popups.module:153
+msgid "Popup Settings"
+msgstr "ポップアップの設定"
+
+#: popups.module:158
+msgid "Content Selector"
+msgstr "コンテンツセレクタ"
+
+#: popups.module:160
+msgid "jQuery selector to define the page's content area on this theme."
+msgstr "このテーマでページのコンテンツ領域を定義するための、jQueryセレクタを入力します。"
+
+#: popups.module:306
+msgid "edit vocabulary"
+msgstr "ボキャブラリの編集"
+
+#: popups.module:307
+msgid "list terms"
+msgstr "タームのリスト"
+
+#: popups.module:311
+msgid "add terms"
+msgstr "タームの追加"
+
+#: popups.module:329;346
+msgid "edit"
+msgstr "編集"
+
+#: popups.module:427
+msgid "Do NOT auto-close final message."
+msgstr "最後のメッセージを自動で閉じない"
+
+#: popups.module:44 popups.info:0
+msgid "Popups"
+msgstr "Popups"
+
+#: popups.module:46
+msgid "Configure the page-in-a-dialog behavior."
+msgstr "ダイアログでのページの振る舞いを設定します。"
+
+#: popups.module:51;57
+msgid "Popup Test"
+msgstr "ポップアップのテスト"
+
+#: popups.module:0
+msgid "popups"
+msgstr "popups"
+
+#: popups.info:0
+msgid "General dialog creation utilities"
+msgstr "全般的なダイアログ生成ユーティリティ"
+
+#: popups.info:0
+msgid "User interface"
+msgstr "ユーザインターフェイス"
+
+#: popups.js:0
+msgid "[Popup]"
+msgstr "[ポップアップ]"
+
+#: popups.js:0
+msgid "There are unsaved changes on this page, which you will lose if you continue."
+msgstr "このページに保存されていない変更があります。 継続した場合、その変更は失われます。"
+
+#: popups.js:0
+msgid "Save Changes"
+msgstr "変更を保存"
+
+#: popups.js:0
+msgid "Continue"
+msgstr "継続"
+
+#: popups.js:0
+msgid "Cancel"
+msgstr "キャンセル"
+
+#: popups.js:0
+msgid "Warning: Please Confirm"
+msgstr "警告:確認してください"
+
+#: popups.js:0
+msgid "OK"
+msgstr "OK"
+
+#: popups.js:0
+msgid "Sorry, there is no additional help for this page"
+msgstr "このページの補足ヘルプはありません"
+