Index: hierarchical_select-rtl.css
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/hierarchical_select/hierarchical_select-rtl.css,v
retrieving revision 1.1
diff -u -F^f -r1.1 hierarchical_select-rtl.css
--- hierarchical_select-rtl.css	14 Mar 2008 08:29:42 -0000	1.1
+++ hierarchical_select-rtl.css	4 Apr 2008 05:55:34 -0000
@@ -1,19 +1,53 @@
 /* $Id: hierarchical_select-rtl.css,v 1.1 2008/03/14 08:29:42 wimleers Exp $ */
 
-div.hierarchical-select-container select.hierarchical-select-select,
-div.hierarchical-select-container .hierarchical-select-add-to-dropbox {
+
+/* The hierarchical select. */
+.hierarchical-select-wrapper .hierarchical-select {
+  margin-bottom: 1em;
+}
+
+.hierarchical-select-wrapper .hierarchical-select * {
   margin: 0;
+}
+
+.hierarchical-select-wrapper .hierarchical-select > * {
   margin-left: .5em; /* margin-right: .5em; */
-  margin-bottom: 1em;
   float: right; /* float: left; */
 }
 
+
+/* Level labels styles. */
+.hierarchical-select-level-labels-style-bold .hierarchical-select select option:first-child {
+  font-weight: bold;
+}
+
+.hierarchical-select-level-labels-style-inversed .hierarchical-select select option:first-child {
+  background-color: #000000;
+  color: #FFFFFF;
+}
+
+.hierarchical-select-level-labels-style-underlined .hierarchical-select select option:first-child {
+  text-decoration: underline;
+}
+
+
+/* Dropbox limit warning.*/
+p.hierarchical-select-dropbox-limit-warning {
+  padding: 0;
+  color: #F7A54F;
+  font-size: 110%;
+  padding-left: .5em;
+}
+
+
+/* The dropbox table. */
 .dropbox-title {
   font-size: 115%;
   color: #898989;
+  margin-bottom: 0.2em;
 }
 
-.hierarchical-select-dropbox {
+.hierarchical-select-wrapper table.dropbox {
   margin: 0px;
   width: auto;
   max-width: 100%;
@@ -26,14 +60,14 @@
   margin-right: 1px; /* margin-left: 1px; */
 }
 
-.hierarchical-select-dropbox tbody {
+.hierarchical-select-wrapper table.dropbox tbody {
   margin: 0;
   padding: 0;
   border: 1px solid grey;
 }
 
 tr.dropbox-entry {
-  line-height: 1em;
+  line-height: 1.3em;
   padding: .3em .6em;
 }  
 
@@ -64,37 +98,39 @@
   padding-right: .5em;
 }
 
-.hierarchical-select-remove-from-dropbox {
+td.dropbox-remove *,
+td.dropbox-remove a:link,
+td.dropbox-remove a:visited {
+  margin: 0px;
+  padding: 0px;
   color: #F7A54F;
-  cursor: pointer;
 }
 
-.dropbox-is-empty {
+tr.dropbox-is-empty {
   padding: .5em 1em;
 }
 
 
-/* Level labels styles */
-
-.hierarchical-select-level-label-bold {
-  font-weight: bold;
+/* The "Update" button and help text for when Javascript is disabled. */
+.hierarchical-select-wrapper .nojs .update-button {
+  margin-top: 0;  
 }
 
-.hierarchical-select-level-label-inversed {
-  background-color: #000000;
-  color: #FFFFFF;
+.hierarchical-select-wrapper .nojs .help-text {
+  font-size: 90%;
+  color: grey;
+  display: block;
+  border: 1px dotted black;
+  min-width: 30em;
+  max-width: 45em;
+  padding: .8em;
 }
 
-.hierarchical-select-level-label-underlined {
-  text-decoration: underline;
+.hierarchical-select-wrapper .nojs .help-text .warning {
+  color: red;
 }
 
-
-/* Dropbox limit */
-
-p.hierarchical-select-dropbox-limit-warning {
+.hierarchical-select-wrapper .nojs .help-text .solutions {
+  margin: 0;
   padding: 0;
-  color: #F7A54F;
-  font-size: 110%;
-  padding-left: .5em;
 }
Index: hierarchical_select.css
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/hierarchical_select/hierarchical_select.css,v
retrieving revision 1.14
diff -u -F^f -r1.14 hierarchical_select.css
--- hierarchical_select.css	20 Jan 2008 11:09:51 -0000	1.14
+++ hierarchical_select.css	4 Apr 2008 05:55:34 -0000
@@ -1,19 +1,53 @@
 /* $Id: hierarchical_select.css,v 1.14 2008/01/20 11:09:51 wimleers Exp $ */
 
-div.hierarchical-select-container select.hierarchical-select-select,
-div.hierarchical-select-container .hierarchical-select-add-to-dropbox {
+
+/* The hierarchical select. */
+.hierarchical-select-wrapper .hierarchical-select {
+  margin-bottom: 1em;
+}
+
+.hierarchical-select-wrapper .hierarchical-select * {
   margin: 0;
+}
+
+.hierarchical-select-wrapper .hierarchical-select > * {
   margin-right: .5em;
-  margin-bottom: 1em;
   float: left;
 }
 
+
+/* Level labels styles. */
+.hierarchical-select-level-labels-style-bold .hierarchical-select select option:first-child {
+  font-weight: bold;
+}
+
+.hierarchical-select-level-labels-style-inversed .hierarchical-select select option:first-child {
+  background-color: #000000;
+  color: #FFFFFF;
+}
+
+.hierarchical-select-level-labels-style-underlined .hierarchical-select select option:first-child {
+  text-decoration: underline;
+}
+
+
+/* Dropbox limit warning.*/
+p.hierarchical-select-dropbox-limit-warning {
+  padding: 0;
+  color: #F7A54F;
+  font-size: 110%;
+  padding-left: .5em;
+}
+
+
+/* The dropbox table. */
 .dropbox-title {
   font-size: 115%;
   color: #898989;
+  margin-bottom: 0.2em;
 }
 
-.hierarchical-select-dropbox {
+.hierarchical-select-wrapper table.dropbox {
   margin: 0px;
   width: auto;
   max-width: 100%;
@@ -26,14 +60,14 @@
   margin-left: 1px;
 }
 
-.hierarchical-select-dropbox tbody {
+.hierarchical-select-wrapper table.dropbox tbody {
   margin: 0;
   padding: 0;
   border: 1px solid grey;
 }
 
 tr.dropbox-entry {
-  line-height: 1em;
+  line-height: 1.3em;
   padding: .3em .6em;
 }  
 
@@ -64,37 +98,39 @@
   padding-right: .5em;
 }
 
-.hierarchical-select-remove-from-dropbox {
+td.dropbox-remove *,
+td.dropbox-remove a:link,
+td.dropbox-remove a:visited {
+  margin: 0px;
+  padding: 0px;
   color: #F7A54F;
-  cursor: pointer;
 }
 
-.dropbox-is-empty {
+tr.dropbox-is-empty {
   padding: .5em 1em;
 }
 
 
-/* Level labels styles */
-
-.hierarchical-select-level-label-bold {
-  font-weight: bold;
+/* The "Update" button and help text for when Javascript is disabled. */
+.hierarchical-select-wrapper .nojs .update-button {
+  margin-top: 0;  
 }
 
-.hierarchical-select-level-label-inversed {
-  background-color: #000000;
-  color: #FFFFFF;
+.hierarchical-select-wrapper .nojs .help-text {
+  font-size: 90%;
+  color: grey;
+  display: block;
+  border: 1px dotted black;
+  min-width: 30em;
+  max-width: 45em;
+  padding: .8em;
 }
 
-.hierarchical-select-level-label-underlined {
-  text-decoration: underline;
+.hierarchical-select-wrapper .nojs .help-text .warning {
+  color: red;
 }
 
-
-/* Dropbox limit */
-
-p.hierarchical-select-dropbox-limit-warning {
+.hierarchical-select-wrapper .nojs .help-text .solutions {
+  margin: 0;
   padding: 0;
-  color: #F7A54F;
-  font-size: 110%;
-  padding-left: .5em;
 }
Index: hierarchical_select.js
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/hierarchical_select/hierarchical_select.js,v
retrieving revision 1.39
diff -u -F^f -r1.39 hierarchical_select.js
--- hierarchical_select.js	14 Mar 2008 08:31:30 -0000	1.39
+++ hierarchical_select.js	4 Apr 2008 05:55:34 -0000
@@ -1,418 +1,149 @@
 // $Id: hierarchical_select.js,v 1.39 2008/03/14 08:31:30 wimleers Exp $
 
-var HierarchicalSelect = HierarchicalSelect || {};
+// TODO: use HTML client storage when available. Only for caching the results of the hierarchical select.
 
-HierarchicalSelect.dropboxContent = new Array();
+Drupal.HierarchicalSelect = {};
 
-HierarchicalSelect.context = function() {
-  return $("form div.form-item");
+Drupal.HierarchicalSelect.context = function() {
+  return $("form .hierarchical-select-wrapper");
 };
 
-HierarchicalSelect.setting = function(hsid, settingName, newValue) {
+Drupal.HierarchicalSelect.initialize = function() {
+  for (var hsid in Drupal.settings.HierarchicalSelect.settings) {
+    this.transform(hsid);
+    this.attachBindings(hsid);
+//    this.checkDropboxLimit(hsid, true);
+  }
+};
+
+Drupal.HierarchicalSelect.setting = function(hsid, settingName, newValue) {
   // Global settings.
   if (hsid == 'global') {
-    return Drupal.settings.hierarchical_select[settingName];
+    return Drupal.settings.HierarchicalSelect[settingName];
   }
   else {
     // Per-Hierarchical Select settings.
     if (undefined === newValue) {
-      return Drupal.settings.hierarchical_select.settings[hsid][settingName];
+      return Drupal.settings.HierarchicalSelect.settings[hsid][settingName];
     }
     else {
-      Drupal.settings.hierarchical_select.settings[hsid][settingName] = newValue;
+      Drupal.settings.HierarchicalSelect.settings[hsid][settingName] = newValue;
     }
   }
 };
 
-HierarchicalSelect.waitToggle = function(hsid) {
-  if ($('#hierarchical-select-' + hsid +'-container').css('opacity') != 0.5) {
+Drupal.HierarchicalSelect.transform = function(hsid) {
+  var removeString = $('#hierarchical-select-'+ hsid +'-wrapper .dropbox .dropbox-remove:first', this.context).text();
+
+  $('#hierarchical-select-'+ hsid +'-wrapper', this.context)
+  // Hide the .nojs div.
+  .find('.nojs').remove().end()
+  // Find all .dropbox-remove cells in the dropbox table.
+  .find('.dropbox .dropbox-remove')
+  // Hide the children of these table cells. We're not removing them because
+  // we want to continue to use the "Remove" checkboxes.
+  .find('*').hide().end()
+  // Put a "Remove" link there instead.
+  .append('<a href="#">'+ removeString +'</a>');
+};
+
+Drupal.HierarchicalSelect.waitToggle = function(hsid) {
+  if ($('#hierarchical-select-' + hsid +'-wrapper').css('opacity') != 0.5) {
     // Disable *all* submit buttons in this form, as well as all input-related
     // elements of the current hierarchical select.
-    $('form[#hierarchical-select-' + hsid +'-container] input[@type=submit]')
-    .add('#hierarchical-select-' + hsid +'-container .hierarchical-select-input').children()
+    $('form[#hierarchical-select-' + hsid +'-wrapper] input[@type=submit]')
+    .add('#hierarchical-select-' + hsid +'-wrapper .hierarchical-select *')
     .attr('disabled', 'disabled');
 
     // Make everything related to the hierarchical select form item transparent.
-    $('#hierarchical-select-' + hsid +'-container').css('opacity', 0.5);
+    $('#hierarchical-select-' + hsid +'-wrapper').css('opacity', 0.5);
 
     // Indicate that the user has to wait.
     $('body').css('cursor', 'wait');  
   }
   else {
-    $('form[#hierarchical-select-' + hsid +'-container] input[@type=submit]')
-    .add('#hierarchical-select-' + hsid +'-container .hierarchical-select-input').children()
+    $('form[#hierarchical-select-' + hsid +'-wrapper] input[@type=submit]')
+    .add('#hierarchical-select-' + hsid +'-wrapper .hierarchical-select *')
     .removeAttr('disabled');
 
-    $('#hierarchical-select-' + hsid +'-container').css('opacity', 1);
+    $('#hierarchical-select-' + hsid +'-wrapper').css('opacity', 1);
 
     $('body').css('cursor', 'auto');  
   }
 };
 
-// Should always be called *after* attachBindings(), because this method
-// disables the "Add" button, which is only available after that method call.
-HierarchicalSelect.checkDropboxLimit = function(hsid, initial) {
-  var HS = HierarchicalSelect;
-  var dropboxLimit = HS.setting(hsid, 'dropboxLimit');
-  var multiple = HS.setting(hsid, 'multiple');
-
-  // Set default value for the "initial" parameter.
-  initial = (undefined === initial) ? false : initial;
-
-  if (multiple && dropboxLimit > 0) {
-    if (HS.dropboxContent[hsid].length == dropboxLimit) {
-      $('#hierarchical-select-'+ hsid +'-container .hierarchical-select-input')
-      .css('opacity', 0.5)
-      // TODO: should be translatable. But that's a lot easier in Drupal 6, so let's postpone it.
-      .after('<p class="hierarchical-select-dropbox-limit-warning">You\'ve reached the maximal number of items you can select.</p>')
-      .children()
-      .attr('disabled', 'disabled');
-      $('#hierarchical-select-'+ hsid +'-container p.hierarchical-select-dropbox-limit-warning', HS.context)
-      .hide()
-      .show((initial) ? 0 : 'fast');
-    }
-    else if (HS.dropboxContent[hsid].length < dropboxLimit && $('#hierarchical-select-'+ hsid +'-container p.hierarchical-select-dropbox-limit-warning', HS.context).size()) {
-      $('#hierarchical-select-'+ hsid +'-container .hierarchical-select-input')
-      .css('opacity', 1)
-      .children()
-      .removeAttr('disabled');
-      $('#hierarchical-select-'+ hsid +'-container p.hierarchical-select-dropbox-limit-warning', HS.context)
-      .hide('fast', function() {
-        $(this).remove();
-      });
-    }
-  }
-};
-
-HierarchicalSelect.updateOriginalSelect = function(hsid) {
-  var $selects = $('select.hierarchical-select-'+ hsid +'-select', this.context);
-  var $options = $('select.hierarchical-select-'+ hsid +'-original-select option', this.context);
-
-  // Reset the current selection in the original select.
-  $('select.hierarchical-select-'+ hsid  +'-original-select option:selected', this.context).each(function() {
-    $(this).removeAttr('selected');
-  });
-
-  var rootLevelValue = $('select#hierarchical-select-'+ hsid +'-select-level-0', this.context).val() || 'none';
-
-  // Update it to the current selection.
-  var currentSelectionIsLabelOrNone = (typeof(rootLevelValue) == "string" && rootLevelValue.match(/^(none|label_\d+)$/));
-  var somethingSelectedInDropbox = (this.setting(hsid, 'multiple') && this.dropboxContent[hsid].length);
-  if (rootLevelValue.match(/^(all|none|label_\d+)$/)) {
-    if (rootLevelValue == 'all') {
-      $options.attr('selected', 'selected'); // Select all options.
-    }
-    else {
-      $options.filter('option[@value=""]').attr('selected', 'selected'); // Select the "<none>" option.
-    }
-
-    // Get all sublevel selects, hide them (collapse effect) and remove them.
-    $selects.gt(0)
-    .hide(this.setting(hsid, 'animationDelay'), function() {
-      $(this).remove();
-    });
-  }
-  else if (currentSelectionIsLabelOrNone && !somethingSelectedInDropbox) {
-    // This is for compatibility with Drupal's Taxonomy form items. They have
-    // a "- None selected -" option, with the value "". We *must* select it if
-    // we want to select nothing.
-    // A similar system is used in the content_taxonomy implementation of HS,
-    // to allow deselection of an item when multiple select is enabled.
-    // TODO: make sure Drupal standardizes on a form item with a value "" to
-    // select nothing. Perhaps I should also make Hierarchical Select use this
-    // for its "<none>" option?
-    $options.filter('option[@value=""]').attr('selected', 'selected');
-  }
-  else if (!this.setting(hsid, 'saveLineage')) {
-    // Get the deepest valid value of the current selection.
-    var level = $selects.length - 1;
-    var deepestSelectValue = '';
-    do {
-      deepestSelectValue = $selects.eq(level).val();
-      level--;
-    } while (level >= 0 && deepestSelectValue.match(/label_\d+/));
-
-    // Update the original select.
-    $options.filter('option[@value="'+ deepestSelectValue +'"]').attr('selected', 'selected');
-  }
-  else {
-    // Select each hierarchical select's selected option in the original
-    // select (thus effectively saving the term lineage).
-    $selects
-    .each(function() { // Can be done cleaner in jQuery 1.2, because .val() can then accept an array.
-      $options.filter('option[@value='+ $(this).val() +']').attr('selected', 'selected');
-    });
-  }
-
-  // If multiple select is enabled, also add the selections in the dropbox.
-  if (this.setting(hsid, 'multiple')) {
-    for (var i = 0; i < this.dropboxContent[hsid].length; i++) {
-      for (var j = 0; j < this.dropboxContent[hsid][i].length; j++) {
-        $options
-        .filter('option[@value='+ this.dropboxContent[hsid][i][j] +']')
-        .attr('selected', 'selected');
-      }
-    }
-  }
-};
-
-HierarchicalSelect.initialize = function() {
-  for (var hsid in Drupal.settings.hierarchical_select.settings) {
-    // If multiple select is enabled, intialize the dropbox content array.
-    if (this.setting(hsid, 'multiple')) {
-     this.dropboxContent[hsid] = this.setting(hsid, 'initialDropboxLineagesSelections');
-    }
-
-    $('select.hierarchical-select-'+ hsid +'-original-select') // No context used here, for Safari 3 compatibility. See http://drupal.org/node/227739.
-    // Hide the standard select.
-    .hide(0)
-    // Add a unique container div after the standard select.
-    .after('<div id="hierarchical-select-'+ hsid +'-container" class="hierarchical-select-container clear-block" />');
-
-    // Now load the initial HTML *without* using AHAH.
-    $('div#hierarchical-select-'+ hsid +'-container', this.context)
-    .html(this.setting(hsid, 'initial'));
-    
-    // Mirror the 'error' class from the original select.
-    var classAttribute = $('select.hierarchical-select-'+ hsid +'-original-select', this.context).attr('class');
-    classAttribute = (classAttribute != null) ? classAttribute : ''; // Work-around for Internet Explorer 6/7 compatibility. See http://drupal.org/node/229513.
-    var classes = classAttribute.split(' ');
-    for (var i = 0; i < classes.length; i++) { // TODO: I'm sure this can be done cleaner!
-      if (classes[i] == 'error') {
-        $('select.hierarchical-select-'+ hsid +'-select', this.context).addClass('error');
-        break;
-      }
-    }
-
-    this.updateOriginalSelect(hsid);
-    this.attachBindings(hsid);
-    this.checkDropboxLimit(hsid, true);
-  }
-};
-
-HierarchicalSelect.attachBindings = function(hsid, dropboxOnly) {
-  var HS = HierarchicalSelect;
-
-  // Update event: attach to every select of the current Hierarchical Select.
-  $('select.hierarchical-select-'+ hsid +'-select', this.context)
+Drupal.HierarchicalSelect.attachBindings = function(hsid, dropboxOnly) {
+  $('#hierarchical-select-'+ hsid +'-wrapper', this.context)
+  // "Update" event will be attached to:
+  // - selects in the .hierarchical-select div;
+  .find('.hierarchical-select select')
   .unbind()
-  .change(function(x) {
-    return function() { HS.update(x, $(this).val()); };
+  .change(function(_hsid) {
+    return function() { Drupal.HierarchicalSelect.update(_hsid); };
+  }(hsid)).end()
+
+  // - if the dropbox is enabled: anchors in the .dropbox-remove cells in the
+  //   .dropbox table.
+  .find('.dropbox .dropbox-remove a')
+  .unbind()
+  .click(function(_hsid) {
+    return function() { /* Check the checkbox, then… */ Drupal.HierarchicalSelect.update(_hsid, $(this).val()); };
+  }(hsid)).end()
+
+  // "Add" event will be attached to:
+  // - the add button in the .hierarchical-select div.
+  .find('.hierarchical-select input').unbind().click(function(_hsid) {
+    return function() { Drupal.HierarchicalSelect.add(_hsid); };
   }(hsid));
-
-  if (this.setting(hsid, 'multiple')) {
-    if (!dropboxOnly) {
-      // Add the "Add" button.
-      $('div#hierarchical-select-'+ hsid +'-container .hierarchical-select-input', this.context)
-      .append(this.setting(hsid, 'addButton'));
-
-      // Add event: attach to the "Add" button.
-      $('#hierarchical-select-'+ hsid +'-add-to-dropbox', this.context)
-      .unbind()
-      .click(function(x) {
-        return function() { HS.add(x); };
-      }(hsid));
-    }
-
-    for (var i = 0; i < this.dropboxContent[hsid].length; i++) {
-      // Remove event: attach to the "Remove" links.
-      $('#hierarchical-select-'+ hsid +'-remove-'+ i + '-from-dropbox', this.context)
-      .unbind()
-      .click(function(x, y) {
-        return function() { HS.remove(x, y); };
-      }(hsid, i));
-    }    
-  }
 };
 
-HierarchicalSelect.getFullSelection = function(hsid, selection) {
-  var $selects = $('select.hierarchical-select-'+ hsid +'-select', this.context);
-
-  // Make sure selection is always an array.
-  if ("string" == typeof(selection)) {
-    selection = new Array(selection);
-  }
-
-  if (this.setting(hsid, 'saveLineage')) {
-    var lineageSelection = new Array();
-    for (var level = 0; level < $selects.size(); level++) {
-      var s = $('select#hierarchical-select-'+ hsid +'-select-level-'+ level, this.context).val();
-      lineageSelection[level] = s;
-      if (s == selection) {
-        // Don't go collect values from levels deeper than the clicked level,
-        // they have to be tossed away anyway.
-        break;
-      }
-    }
+Drupal.HierarchicalSelect.update = function(hsid, selection) {
+  // TODO: optimizations in special cases:
+  /*
+  // Don't query the server in special cases.
+  if (selection.match(/^(all|none|label_\d+)$/)) {
+    Drupal.HierarchicalSelect.updateOriginalSelect(hsid);
   }
+  else { … }
+  */
 
-  return (undefined === lineageSelection) ? selection : lineageSelection;
-};
+  var animationDelay = Drupal.HierarchicalSelect.setting(hsid, 'animationDelay');
 
-HierarchicalSelect.post = function(hsid, fullSelection, dropboxSelection, type) {
-  var post = new Object();
-  post['hsid'] = hsid;
-  if (typeof(fullSelection) == "string") {
-    post['selection'] = fullSelection;
-  }
-  else {
-    post['selection'] = fullSelection.join('|');
-  }
-  post['dropbox_selection'] = dropboxSelection.join('|');
-  post['module'] = this.setting(hsid, 'module');
-  post['save_lineage'] = this.setting(hsid, 'saveLineage');
-  post['enforce_deepest'] = this.setting(hsid, 'enforceDeepest');
-  post['all_option'] = this.setting(hsid, 'allOption');
-  post['level_labels'] = this.setting(hsid, 'levelLabels');
-  post['params'] = this.setting(hsid, 'params');
-  post['required'] = this.setting(hsid, 'required');
-  post['dropbox_title'] = this.setting(hsid, 'dropboxTitle');
-  post['type'] = type;
+  var $selects = $('#hierarchical-select-'+ hsid +'wrapper .hierarchical-select select', Drupal.HierarchicalSelect.context);
+  var lastUnchanged = $selects.index($('#hierarchical-select-'+ hsid +'wrapper .hierarchical-select select option[@value='+ selection +']', Drupal.HierarchicalSelect.context).parent()[0]);
 
-  return post;
-};
+  Drupal.HierarchicalSelect.waitToggle(hsid);
 
-HierarchicalSelect.add = function(hsid) {
-  var HS = HierarchicalSelect;
-  var $selects = $('select.hierarchical-select-'+ hsid +'-select', HS.context);
-
-  // Get all selected items.
-  var dropboxSelection = new Array();
-  $('select.hierarchical-select-'+ hsid  +'-original-select option:selected', this.context).each(function() {
-    dropboxSelection.push($(this).val());
-  });
 
-  HS.waitToggle(hsid);
+  // Drop out the selects of the levels deeper than the select of
+  // the level that just changed.
+  $selects.gt(lastUnchanged).DropOutLeft(animationDelay);
+  // Set up the ajax form.
+  var options = {
+    url: Drupal.HierarchicalSelect.setting('global', 'url'),
+    target: '#hierarchical-select-'+ hsid +'wrapper',
+    success:  function(response, status, hsid) {
+      // Hide the loaded selects after the one that was just changed, then  drop
+      // them in.
+      $selects.gt(lastUnchanged).hide(0).DropInLeft(animationDelay);
 
-  $.ajax({
-    type: "POST",
-    url: HS.setting('global', 'url'),
-    data: HS.post(hsid, Array(), dropboxSelection, 'dropbox-add'),
-    dataType: "json",
-    success: function(json) {
-      $('div#hierarchical-select-'+ hsid +'-container .hierarchical-select-input', HS.context)
-      .html(json.hierarchicalSelect);
-      $('div#hierarchical-select-'+ hsid +'-container .hierarchical-select-dropbox', HS.context)
-      .html(json.dropbox);
-
-      HS.dropboxContent[hsid] = json.dropboxLineagesSelections;
-
-      HS.waitToggle(hsid);
-      HS.updateOriginalSelect(hsid); // In theory we don't have to do this, but it's a safety net: it will only set valid selections.
-      HS.attachBindings(hsid);
-      HS.checkDropboxLimit(hsid);
+      Drupal.HierarchicalSelect.waitToggle(hsid);
+      Drupal.HierarchicalSelect.attachBindings(hsid);
     }
-  });
-};
-
-HierarchicalSelect.remove = function(hsid, dropboxEntry) {
-  var HS = HierarchicalSelect;
-  var $selects = $('select.hierarchical-select-'+ hsid +'-select', HS.context);
-
-  // Add the selections of all items in the dropbox to the selection, except
-  // for the one that has to be removed. If we submit this, the server will
-  // reconstruct all lineages and thus remove the removed selection.
-  var dropboxSelection = new Array();
-  for (var i = 0; i < HS.dropboxContent[hsid].length; i++) {
-    if (i != dropboxEntry) { // Don't add the entry that's being removed to the selection!
-      for (var j = 0; j < HS.dropboxContent[hsid][i].length; j++) {
-        dropboxSelection.push(HS.dropboxContent[hsid][i][j]);
-      }
-    }
-  }
-  // Now remove the deepest item of the entry of the dropbox that's being
-  // removed.
-  // In case of a tree with multiple parents, the same item can exist in
-  // different entries, and thus it would stay in the selection. When the
-  // server then reconstructs all lineages, the lineage we're removing, will
-  // also be reconstructed: it will seem as if the removing didn't work!
-  // This will not break removing dropbox entries for hierarchies without
-  // multiple parents, since items at the deepest level are always unique to
-  // that specific lineage.
-  // Easier explanation at http://drupal.org/node/221210#comment-733715.
-  var deepestItemIndex = HS.dropboxContent[hsid][dropboxEntry].length - 1;
-  var deepestItem = HS.dropboxContent[hsid][dropboxEntry][deepestItemIndex];
-  for (var i = 0; i < dropboxSelection.length; i++) {  
-    if (deepestItem == dropboxSelection[i]) {
-      dropboxSelection = dropboxSelection.slice(0, i).concat(dropboxSelection.slice(i + 1));
-    }
-  }
-
-  // Get the deepest valid value of the current selection.
-  var level = $selects.length - 1;
-  var deepestSelectValue = '';
-  do {
-    deepestSelectValue = $selects.eq(level).val();
-    level--;
-  } while (level >= 0 && deepestSelectValue.match(/label_\d+/));
-  
-  HS.waitToggle(hsid);
-
-  $.ajax({
-    type: "POST",
-    url: HS.setting('global', 'url'),
-    data: HS.post(hsid, HS.getFullSelection(hsid, deepestSelectValue), dropboxSelection, 'dropbox-remove'),
-    dataType: "json",
-    success: function(json) {
-      $('div#hierarchical-select-'+ hsid +'-container .hierarchical-select-input', HS.context)
-      .html(json.hierarchicalSelect);
-      $('div#hierarchical-select-'+ hsid +'-container .hierarchical-select-dropbox', HS.context)
-      .html(json.dropbox);
-
-      HS.dropboxContent[hsid] = json.dropboxLineagesSelections;
-
-      HS.waitToggle(hsid);
-      HS.updateOriginalSelect(hsid);
-      HS.attachBindings(hsid);
-      HS.checkDropboxLimit(hsid);
-    }
-  });
-};
-
-HierarchicalSelect.update = function(hsid, selection) {
-  var HS = HierarchicalSelect;
-
-  // Don't query the server in special cases.
-  if (selection.match(/^(all|none|label_\d+)$/)) {
-    HS.updateOriginalSelect(hsid);
-  }
-  else {    
-    var animationDelay = HS.setting(hsid, 'animationDelay');
-
-    var $selects = $('select.hierarchical-select-'+ hsid +'-select', HS.context);
-    var lastUnchanged = $selects.index($('select.hierarchical-select-'+ hsid +'-select option[@value='+ selection +']', HS.context).parent()[0]);
-
-    HS.waitToggle(hsid);
-
-    // Drop out the *original* selects of the levels deeper than the select of
-    // the level that just changed.
-    $selects.gt(lastUnchanged).DropOutLeft(animationDelay);
-
-    $.ajax({
-      type: "POST",
-      url: HS.setting('global', 'url'),
-      data: HierarchicalSelect.post(hsid, HS.getFullSelection(hsid, selection), Array(), 'hierarchical-select'),
-      dataType: "json",
-      success: function(json) {
-        $('div#hierarchical-select-'+ hsid +'-container .hierarchical-select-input', HS.context)
-        .html(json.hierarchicalSelect);
-
-        $selects = $('select.hierarchical-select-'+ hsid + '-select', HS.context);
-
-        // Hide the loaded selects after the one that was just changed, then  drop
-        // them in.
-        $selects.gt(lastUnchanged).hide(0).DropInLeft(animationDelay);
-
-        HS.waitToggle(hsid);
-        HS.updateOriginalSelect(hsid);
-        HS.attachBindings(hsid);
-      }
-    });
-  }
+   };
+  $('form[#hierarchical-select-' + hsid +'-wrapper]', Drupal.HierarchicalSelect.context)
+  .ajaxSubmit(options);
 };
 
 if (Drupal.jsEnabled) {
   $(document).ready(function() {
-    HierarchicalSelect.initialize();
+    // If you set Drupal.settings.HierarchicalSelect.pretendNoJS to *anything*,
+    // and as such, Hierarchical Select won't initialize its Javascript! It
+    // will seem as if your browser had Javascript disabled.
+    if (undefined != Drupal.settings.HierarchicalSelect.pretendNoJS) {
+      return false;
+    }
+    
+    Drupal.HierarchicalSelect.initialize();
   });
 }
Index: hierarchical_select.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/hierarchical_select/hierarchical_select.module,v
retrieving revision 1.40
diff -u -F^f -r1.40 hierarchical_select.module
--- hierarchical_select.module	3 Mar 2008 16:44:26 -0000	1.40
+++ hierarchical_select.module	4 Apr 2008 05:55:36 -0000
@@ -3,12 +3,17 @@
 
 /**
  * @file
- * This module defines the "hierarchical_select" form element, which is a
- * greatly enhanced way for letting the user select an option in a hierarchy.
+ * This module defines the "hierarchical_select" form element type, which is a
+ * greatly enhanced way for letting the user select items in a hierarchy.
  */
 
+// TODO: check if we should move #hierarchical_select_settings' child properties up a level, rename #multiple to #dropbox
+// TODO: allow to set #size of the selects in the hierarchical select
 
-// Enable default support for some modules, if they are enabled.
+define('HIERARCHICAL_SELECT_JSON_PATH', 'hierarchical_select_json');
+
+
+// Enable out-of-the-box support for some modules, if they are enabled.
 $modules = array('taxonomy', 'content_taxonomy', 'subscriptions_taxonomy');
 foreach ($modules as $module) {
   if (module_exists($module)) {
@@ -16,6 +21,7 @@ foreach ($modules as $module) {
   }
 }
 
+
 //----------------------------------------------------------------------------
 // Drupal core hooks.
 
@@ -25,20 +31,20 @@ foreach ($modules as $module) {
 function hierarchical_select_menu($may_cache) {
   if (!$maycache) {
     $items[] = array(
-      'path' => 'hierarchical_select_json',
-      'callback' => 'hierarchical_select_json',
-      'type' => MENU_CALLBACK,
+      'path'               => HIERARCHICAL_SELECT_JSON_PATH,
+      'callback'           => 'hierarchical_select_json',
+      'type'               => MENU_CALLBACK,
       // TODO: Needs improvements. Ideally, this would inherit the permissions
       // of the form the Hierarchical Select was in.
-      'access' => user_access('access content'),
+      'access'             => user_access('access content'),
     );
     $items[] = array(
-      'path' => 'admin/settings/hierarchical_select',
-      'title' => t('Hierarchical Select'),
-      'description' => t('Configure site-wide settings for the Hierarchical Select form element.'),
-      'callback' => 'drupal_get_form',
+      'path'               => 'admin/settings/hierarchical_select',
+      'title'              => t('Hierarchical Select'),
+      'description'        => t('Configure site-wide settings for the Hierarchical Select form element.'),
+      'callback'           => 'drupal_get_form',
       'callback arguments' => array('hierarchical_select_admin_settings'),
-      'type' => MENU_NORMAL_ITEM,
+      'type'               => MENU_NORMAL_ITEM,
     );
   }
   return $items;
@@ -81,84 +87,26 @@ function hierarchical_select_elements() 
 // Menu callbacks.
 
 /**
- * Menu callback; JSON callback: generates and outputs the appropriate HTML.
+ * Menu callback; format=text/json; generates and outputs the appropriate HTML.
  */
 function hierarchical_select_json() {
-  // We are returning JavaScript, so tell the browser. Ripped from Drupal 6's
+  // We are returning Javascript, so tell the browser. Ripped from Drupal 6's
   // drupal_json() function.
   drupal_set_header('Content-Type: text/javascript; charset=utf-8');
 
-  // Extract the common parameters.
-  $hsid              = $_POST['hsid'];
-  $module            = $_POST['module'];
-  $selection         = (!strstr($_POST['selection'], '|')) ? $_POST['selection'] : explode('|', $_POST['selection']);
-  $dropbox_selection = (!strstr($_POST['dropbox_selection'], '|')) ? $_POST['dropbox_selection'] : explode('|', $_POST['dropbox_selection']);
-  $save_lineage      = _hierarchical_select_str_to_bool($_POST['save_lineage']);
-  $enforce_deepest   = _hierarchical_select_str_to_bool($_POST['enforce_deepest']);
-  $all_option        = _hierarchical_select_str_to_bool($_POST['all_option']);
-  $level_labels      = explode('|', $_POST['level_labels']);
-  $params            = unserialize($_POST['params']);
-  $dropbox_title     = $_POST['dropbox_title'];
-  $required          = _hierarchical_select_str_to_bool($_POST['required']);
-
-  switch ($_POST['type']) {
-    case 'hierarchical-select':
-      $hierarchy = hierarchical_select_get_hierarchy(
-        $module,
-        $selection,
-        $save_lineage, 
-        $enforce_deepest,
-        $all_option,
-        $level_labels,
-        $params,
-        $required,
-        FALSE // No dropbox when only updating the hierarchical select!
-      );   
-      $hierarchical_select_html = theme('hierarchical_select_render_selects', $hsid, $hierarchy);
-
-      // Return output as JSON.
-      print drupal_to_js(array(
-        'hierarchicalSelect' => $hierarchical_select_html,
-      ));
-      break;
-
-    case 'dropbox-add':
-      // We want to render an empty select.
-      $selection = -1;
-    case 'dropbox-remove':
-    default:
-      $dropbox = hierarchical_select_get_dropbox(
-        $module,
-        $dropbox_selection,
-        $save_lineage,
-        $level_labels,
-        $params,
-        $dropbox_title
-      );
-      $dropbox_html = theme('hierarchical_select_render_dropbox', $hsid, $dropbox);
+  //dvm($_POST['form_build_id']);
 
-      $hierarchy = hierarchical_select_get_hierarchy(
-        $module,
-        $selection,
-        $save_lineage, 
-        $enforce_deepest,
-        $all_option,
-        $level_labels,
-        $params,
-        $required,
-        $dropbox
-      );   
-      $hierarchical_select_html = theme('hierarchical_select_render_selects', $hsid, $hierarchy);
-
-      // Return output as JSON.
-      print drupal_to_js(array(
-        'hierarchicalSelect' => $hierarchical_select_html,
-        'dropbox' => $dropbox_html,
-        'dropboxLineagesSelections' => $dropbox->lineages_selections
-      ));
-      break;
-  }
+  //$form = drupal_retrieve_form($_POST['form_id'], 'page'); // TODO: find work-around to pass additional required parameters!
+  $form = drupal_get_form($_POST['form_id']);
+
+  // Ignore the token sent from the browser, as it doesn't correspond
+  // to the form we create here.
+  // $form['#token'] = FALSE;
 
+  $rendered_form = drupal_render($form);
+  //dpm($rendered_form);
+
+  print drupal_to_js($rendered_form);
   exit;
 }
 
@@ -167,66 +115,26 @@ function hierarchical_select_json() {
 // Forms API callbacks.
 
 /**
- * Hierarchical select form element processing function.
+ * Hierarchical select form element type processing function.
  */
 function hierarchical_select_process($element) {
   static $hsid;
 
-  // Render a hierarchical select as a normal select, it's the JavaScript that
-  // will turn it into a hierarchical select.
-  $element['#type'] = 'select';
-
-  if (!isset($hsid)) {
-    $hsid = 0;
-
-    $url = base_path();
-    $url .= variable_get('clean_url', 0) ? '' : 'index.php?q=';
-    $url .= 'hierarchical_select_json';
-
-    // Add the CSS and JS, set the URL that should be used by all hierarchical
-    // selects.
-    drupal_add_css(drupal_get_path('module', 'hierarchical_select') .'/hierarchical_select.css');
-    jquery_interface_add();
-    drupal_add_js(drupal_get_path('module', 'hierarchical_select') .'/hierarchical_select.js');
-    drupal_add_js(array('hierarchical_select' => array('url' => $url)), 'setting');
-  }
-  else {
-    $hsid++;
-  }
+  $hsid = (!isset($hsid)) ? 0 : $hsid++;
+  $element['hsid'] = array('#type' => 'value', '#value' => $hsid);
 
+  // Setup Javascript and add settings specifically for the current
+  // hierarchical select.
+  _hierarchical_select_setup_js();
   extract(_hierarchical_select_extract_settings($element));
-
-  // If the form item is not required, then we must ensure we have a "<none>"
-  // option in the original select. If one exists already, we overwrite it: we
-  // want exactly the same text here as in the hierarchical select.
-  if (!$required) {
-    $element['#options'] = array('' => '<'. t('none') .'>') + $element['#options'];
-  }
-  
-  // Render the initial HTML.
-  $dropbox = (!$multiple) ? FALSE : hierarchical_select_get_dropbox($module, $selection, $save_lineage, $level_labels, $params, $dropbox_title);
-  $hierarchy = hierarchical_select_get_hierarchy($module, ($multiple) ? -1 : $selection, $save_lineage, $enforce_deepest, $all_option, $level_labels, $params, $required, $dropbox);
-  $initial = theme('hierarchical_select_render_initial_html', $hsid, $hierarchy, $dropbox);
-
   drupal_add_js(
     array(
-      'hierarchical_select' => array(
+      'HierarchicalSelect' => array(
         'settings' => array(
           $hsid => array(
             'animationDelay' => ($animation_delay == 0) ? variable_get('hierarchical_select_animation_delay', 400) : $animation_delay,
-            'initial' => $initial,
-            'initialDropboxLineagesSelections' => (!$dropbox) ? NULL : $dropbox->lineages_selections,
-            'addButton' => theme('hierarchical_select_dropbox_add_button', $hsid),
             'module' => $module,
-            'enforceDeepest' => $enforce_deepest,
-            'saveLineage' => $save_lineage,
-            'allOption' => $all_option,
-            'levelLabels' => implode('|', $level_labels),
             'params' => serialize($params),
-            'dropboxTitle' => $dropbox_title,
-            'dropboxLimit' => $dropbox_limit,
-            'required' => $required,
-            'multiple' => $multiple, // Enables the dropbox.
           ),
         ),
       )
@@ -234,17 +142,93 @@ function hierarchical_select_process($el
     'setting'
   );
 
-  // If the "save lineage" option is enabled, make the select a multiple select.
-  if ($save_lineage) {
-    $element['#multiple'] = TRUE;
+  // Calculate the selections in both the hierarchical select and the dropbox,
+  // we need these before we can render anything.
+  list($hs_selection, $db_selection) = _hierarchical_select_process_calculate_selections($element);
+
+  // Ensure that #tree is enabled!
+  $element['#tree'] = TRUE;
+
+  // Render the hierarchical select.
+  $hierarchy = _hierarchical_select_hierarchy_generate($module, $hs_selection, $save_lineage, $enforce_deepest, $level_labels, $params, $required, $dropbox);
+  $element['hierarchical_select'] = array(
+    '#prefix' => '<div class="hierarchical-select clear-block">',
+    '#suffix' => '</div>',
+  );
+  $element['hierarchical_select']['selects'] = _hierarchical_select_process_render_hs_selects($hsid, $hierarchy);
+
+
+  if ($element['#multiple']) {
+    // Append an "Add" button to the selects.    
+    $element['hierarchical_select']['dropbox-add'] = array(
+      '#type'       => 'button', 
+      '#value'      => t('Add'),
+      '#attributes' => array('class' => 'hierarchical-select-input'),
+    );
+
+    // Add a validate callback, to validate that the dropbox limit was not
+    // exceeded.
+    $element['#validate'] = array('_hierarchical_select_validate' => array());
+
+    $dropbox = _hierarchical_select_dropbox_generate($module, $db_selection, $save_lineage, $level_labels, $params, $dropbox_title);
+
+    if ($dropbox_limit > 0) { // Zero as dropbox limit means no limit.
+      if (count($dropbox->lineages) == $dropbox_limit) {
+        $element['dropbox_limit_warning'] = array(
+          '#value'  => t("You've reached the maximal number of items you can select."),
+          '#prefix' => '<p class="hierarchical-select-dropbox-limit-warning">',
+          '#suffix' => '</p>',
+        );
+        
+        // Disable all child form elements of $element['hierarchical_select].
+        _hierarchical_select_mark_as_disabled($element['hierarchical_select']);
+      }
+    }
+
+    // Add the hidden part of the dropbox. This will be used to store the
+    // currently selected lineages.
+    $element['dropbox']['hidden'] = array(
+      '#prefix' => '<div class="dropbox-hidden">',
+      '#suffix' => '</div>',
+    );
+    $element['dropbox']['hidden'] = _hierarchical_select_process_render_db_hidden($hsid, $dropbox);
+
+    // Add the dropbox-as-a-table that will be visible to the user.
+    $element['dropbox']['visible'] = _hierarchical_select_process_render_db_visible($hsid, $dropbox);
   }
+  
+  // This button and accompanying help text will be hidden when Javascript is
+  // enabled.
+  $element['nojs'] = array(
+    '#prefix' => '<div class="nojs">',
+    '#suffix' => '</div>',
+  );
+  $element['nojs']['update_button'] = array(
+    '#type'       => 'button',
+    '#value'      => t('Update'),
+    '#attributes' => array('class' => 'update-button'),
+  );
 
-  // If multiple select is enabled, then add a validate callback that will
-  // ensure the dropbox limit won't be exceeded.
-  $element['#validate'] = array('_hierarchical_select_validate' => array(_hierarchical_select_extract_settings($element)));
+  $element['nojs']['update_button_help_text'] = array(
+    '#value'  => _hierarchical_select_nojs_helptext((bool) $element['#multiple']),
+    '#prefix' => '<div class="help-text">',
+    '#suffix' => '</div>',
+  );
+  
 
-  // Set the unique class.
-  $element['#attributes']['class'] .= " hierarchical-select-original-select hierarchical-select-$hsid-original-select";
+  // Ensure the render order is correct.
+  $element['hierarchical_select']['#weight']   = 0;
+  $element['dropbox_limit_warning']['#weight'] = 1;
+  $element['dropbox']['#weight']               = 2;
+  $element['nojs']['#weight']                  = 3;
+  
+  // This prevents values from in $element['#post'] to be used instead of the
+  // generated default values (#default_value).
+  // For example: $element['hierarchical_select']['selects']['0']['#default_value'] is set
+  // to 'label_0' after an "Add" operation. When $element['#post'] is NOT
+  // unset, the corresponding value in $element['#post'] will be used instead
+  // of the default value that was set. This is undesired behavior.
+  unset($element['#post']);
 
   return $element;
 }
@@ -252,17 +236,31 @@ function hierarchical_select_process($el
 /**
  * Hierarchical select form element validate callback.
  */
-function _hierarchical_select_validate($element, $settings) {
-  // Extract the settings for this hierarchical select from the settings we're
-  // receiving via the extra parameter.
-  extract($settings);
-
-  // Generate the dropbox again and use it to count the number of lineages
-  // that the user selected. This must be below the dropbox limit.
-  if ($dropbox_limit > 0) { // Zero as dropbox limit means no limit.
-    $dropbox = (!$multiple) ? FALSE : hierarchical_select_get_dropbox($module, $selection, $save_lineage, array(), $params, '');
-    if (count($dropbox->lineages) > $dropbox_limit) {
-      form_error($element, t("You've selected %select-count items, but you're only allowed to select %dropbox-limit items.", array('%select-count' => count($dropbox->lineages), '%dropbox-limit' => $dropbox_limit)));
+function _hierarchical_select_validate($element) {
+  // If the dropbox is enabled and a dropbox limit is configured, check if
+  // this limit is not exceeded.
+  if ($element['#multiple']) {
+    extract(_hierarchical_select_extract_settings($element));
+
+    if ($dropbox_limit > 0) { // Zero as dropbox limit means no limit.
+      // TRICKY: #validate is not called upon the initial rendering. Running
+      // _form_validate($element) doesn't help either.      
+      $lineage_count = count($element['#value']['dropbox']['hidden']['lineages_selections']);
+      if ($lineage_count > $dropbox_limit) {
+        // TRICKY: this should propagate the error down to the children, but
+        // this doesn't seem to happen, since for example the selects of the
+        // hierarchical select don't get the error class set. Further
+        // investigation needed.
+        form_error(
+          $element,
+          t("You've selected %lineage-count items, but you're only allowed to select %dropbox-limit items.",
+            array(
+              '%lineage-count' => $lineage_count,
+              '%dropbox-limit' => $dropbox_limit
+            )
+          )
+        );
+      }
     }
   }
 }
@@ -309,8 +307,395 @@ function hierarchical_select_admin_setti
 
 
 //----------------------------------------------------------------------------
-// Private functions.
- 
+// Forms API #process callback:
+// Calculation of hierarchical select and dropbox selection.
+
+/**
+ * Get the current (flat) selection of the hierarchical select.
+ *
+ * This selection is updatable by the user, because the values are retrieved
+ * from the selects in $element['hierarchical_select']['selects'].
+ *
+ * @param $element
+ *   A hierarchical_select form element.
+ * @return
+ *   An array (bag) containing the ids of the selected items in the
+ *   hierarchical select.
+ */
+function _hierarchical_select_process_get_hs_selection($element) {
+  $hs_selection = array();
+
+  if (count($element['#value']['hierarchical_select']['selects'])) {
+    if ($element['#hierarchical_select_settings']['save_lineage']) {
+      foreach($element['#value']['hierarchical_select']['selects'] as $key => $value) {
+        $hs_selection[] = $value;
+      }
+    }
+    else {
+      // Get the value of the last select. (Only the deepest item gets
+      // saved).
+      $hs_selection = array(end($element['#value']['hierarchical_select']['selects']));
+    }
+  }
+  
+  return $hs_selection;
+}
+
+/**
+ * Get the current (flat) selection of the dropbox.
+ *
+ * This selection is not updatable by the user, because the values are
+ * retrieved from the hidden values in
+ * $element['dropbox']['hidden']['lineages_selections']. This selection can
+ * only be updated by the server, i.e. when the user clicks the "Add" button.
+ * But this selection can still be reduced in size if the user has marked
+ * dropbox entries (lineages) for removal.
+ *
+ * @param $element
+ *   A hierarchical_select form element.
+ * @return
+ *   An array (bag) containing the ids of the selected items in the
+ *   dropbox.
+ */
+function _hierarchical_select_process_get_db_selection($element) {
+  $db_selection = array();
+
+  if (count($element['#value']['dropbox']['hidden']['lineages_selections'])) {
+    // This is only present in #value if at least one "Remove" checkbox was
+    // checked, so ensure that we're doing something valid.
+    $remove_from_db_selection = (!isset($element['#value']['dropbox']['visible']['lineages'])) ? array() : array_keys($element['#value']['dropbox']['visible']['lineages']);
+
+    // Add all selections to the dropbox selection, except for the ones that
+    // are scheduled for removal.
+    foreach ($element['#value']['dropbox']['hidden']['lineages_selections'] as $x => $selection) {
+      if (!in_array($x, $remove_from_db_selection)) {
+        $db_selection = array_merge($db_selection, unserialize($selection));
+      }
+    }
+
+    // Ensure that the last item of each selection that was scheduled for
+    // removal is completely absent from the dropbox selection.
+    // In case of a tree with multiple parents, the same item can exist in
+    // different entries, and thus it would stay in the selection. When the
+    // server then reconstructs all lineages, the lineage we're removing, will
+    // also be reconstructed: it will seem as if the removing didn't work!
+    // This will not break removing dropbox entries for hierarchies without
+    // multiple parents, since items at the deepest level are always unique to
+    // that specific lineage.
+    // Easier explanation at http://drupal.org/node/221210#comment-733715.
+    foreach ($remove_from_db_selection as $key => $x) {
+      $item = end(unserialize($element['#value']['dropbox']['hidden']['lineages_selections'][$x]));
+      $position = array_search($item, $db_selection);
+      if ($position) {
+        unset($db_selection[$position]);
+      }
+    }
+    $db_selection = array_unique($db_selection);
+  }
+
+  return $db_selection;
+}
+
+/**
+ * Calculates the flat selections of both the hierarchical select and the
+ * dropbox.
+ * 
+ * @param $element
+ *   A hierarchical_select form element.
+ * @return
+ *   An array of the following structure:
+ *   array(
+ *     $hierarchical_select_selection = array(), // Flat list of selected ids.
+ *     $dropbox_selection = array(),
+ *   )
+ *   with both of the subarrays flat lists of selected ids. The
+ *   _hierarchical_select_hierarchy_generate() and
+ *   _hierarchical_select_dropbox_generate() functions should be applied on
+ *   these respective subarrays.
+ *
+ * @see _hierarchical_select_hierarchy_generate()
+ * @see _hierarchical_select_dropbox_generate()
+ */
+function _hierarchical_select_process_calculate_selections($element) {
+  $hs_selection = array(); // hierarchical select selection
+  $db_selection = array(); // dropbox selection
+
+
+  // Aliases for more readable code.
+  $dropbox = (bool) $element['#multiple'];
+  $op = $element['#post']['op'];
+
+
+  if (empty($element['#post'])) {
+    $value = (isset($element['#value'])) ? $element['#value'] : $element['#default_value'];
+    if ($dropbox) {
+      $db_selection = $value;
+    }
+    else {
+      $hs_selection = $value;
+    }
+  }
+  else {
+    if ($dropbox && $op == t('Add')) {
+      $hs_selection = _hierarchical_select_process_get_hs_selection($element);
+      $db_selection = _hierarchical_select_process_get_db_selection($element);
+
+      // Add $hs_selection to $db_selection (automatically filters to keep
+      // only the unique ones), and reset $hs_selection.
+      $db_selection = array_merge($db_selection, $hs_selection);
+      $hs_selection = array();
+    }
+    else {
+      // This handles both the case of $op == t('Update') and the case of any
+      // other submit button, e.g. the "Preview" button.
+      $hs_selection = _hierarchical_select_process_get_hs_selection($element);
+      if ($dropbox) {
+        $db_selection = _hierarchical_select_process_get_db_selection($element);
+      }
+    }
+  }
+
+  // Prevent doubles in either array.
+  $hs_selection = array_unique($hs_selection);
+  $db_selection = array_unique($db_selection);
+
+  return array($hs_selection, $db_selection);
+}
+
+
+//----------------------------------------------------------------------------
+// Forms API #process callback:
+// Rendering (generation of FAPI code) of hierarchical select and dropbox.
+
+/**
+ * Render the selects in the hierarchical select.
+ *
+ * @param $hsid
+ *   A hierarchical select id.
+ * @param $hierarchy
+ *   A hierarchy object.
+ * @return
+ *   A structured array for use in the Forms API.
+ */
+function _hierarchical_select_process_render_hs_selects($hsid, $hierarchy) {
+  $form['#tree'] = TRUE;
+
+  foreach ($hierarchy->lineage as $depth => $selected_item) {
+    $form[$depth] = array(
+      '#type' => 'select',
+      '#options' => $hierarchy->levels[$depth],
+      '#default_value' => $selected_item,
+      // We need to skip the check of valid options, because they may be
+      // modified after each update.
+      '#DANGEROUS_SKIP_CHECK' => TRUE,
+      // Use a #theme callback to prevent the select from being wrapped in a
+      // div. This simplifies the CSS and JS code.
+      '#theme' => 'hierarchical_select_select',
+    );    
+  }
+  return $form;
+}
+
+/**
+ * Render the hidden part of the dropbox.
+ *
+ * @param $hsid
+ *   A hierarchical select id.
+ * @param $dropbox
+ *   A dropbox object.
+ * @return
+ *   A structured array for use in the Forms API.
+ */
+function _hierarchical_select_process_render_db_hidden($hsid, $dropbox) {
+  $element['#tree'] = TRUE;
+
+  foreach ($dropbox->lineages_selections as $x => $lineage_selection) {
+    // TRICKY: we have to use #type = 'hidden' here, because 'value' doesn't
+    // maintain the values after the form is submitted, or at least cannot
+    // provide it to a #process callback, not in $element nor in $form_values.
+    $element['lineages_selections'][$x] = array('#type' => 'hidden', '#value' => serialize($lineage_selection));
+  }
+  return $element;
+}
+
+/**
+ * Render the visible part of the dropbox.
+ *
+ * @param $hsid
+ *   A hierarchical select id.
+ * @param $dropbox
+ *   A dropbox object.
+ * @return
+ *   A structured array for use in the Forms API.
+ */
+function _hierarchical_select_process_render_db_visible($hsid, $dropbox) {
+  $element['#tree'] = TRUE;  
+  $element['#theme'] = 'hierarchical_select_dropbox_table';
+
+
+  // This information is necessary for the #theme callback.
+  $element['title']     = array('#type' => 'value', '#value' => $dropbox->title);
+  $element['separator'] = array('#type' => 'value', '#value' => '›');
+  $element['is_empty']  = array('#type' => 'value', '#value' => empty($dropbox->lineages));
+
+
+  if (!empty($dropbox->lineages)) {
+    foreach ($dropbox->lineages as $x => $lineage) {
+
+      // Store position information for the lineage. This will be used in the
+      // #theme callback.
+      $element['lineages'][$x] = array(
+        '#zebra' => (($x + 1) % 2 == 0) ? 'even' : 'odd',
+        '#first' => ($x == 0) ? 'first' : '',
+        '#last'  => ($x == count($dropbox->lineages) - 1) ? 'last' : '',
+      );
+
+      // Create a 'markup' element for each item in the lineage.
+      foreach ($lineage as $depth => $item) {
+        // The item is selected when save_lineage is enabled (i.e. each item
+        // will be selected), or when the item is the last item in the current
+        // lineage.
+        $is_selected = $dropbox->save_lineage || ($depth == count($lineage) - 1);
+      
+        $element['lineages'][$x][$depth] = array(
+          '#value' => $item['label'],
+          '#prefix' => '<span class="dropbox-item'. (($is_selected) ? ' dropbox-selected-item' : '') .'">',
+          '#suffix' => '</span>',
+        );
+      }
+
+      // Finally, create a "Remove" checkbox for the lineage.
+      $element['lineages'][$x]['remove'] = array(
+        '#type' => 'checkbox',
+        '#title' => t('Remove'),
+      );
+    }
+  }
+  
+  return $element;
+}
+
+
+//----------------------------------------------------------------------------
+// Private functions. 
+
+/**
+ * Helper function to add the required Javascript files and settings.
+ */
+function _hierarchical_select_setup_js() {
+  static $ran_once;
+  
+  if (!$ran_once) {
+    $ran_once = TRUE;
+
+    $url = base_path();
+    $url .= variable_get('clean_url', 0) ? '' : 'index.php?q=';
+    $url .= HIERARCHICAL_SELECT_JSON_PATH;
+
+    // Add the CSS and JS, set the URL that should be used by all hierarchical
+    // selects.
+    drupal_add_css(drupal_get_path('module', 'hierarchical_select') .'/hierarchical_select.css');
+    jquery_interface_add();
+    jquery_form_add();
+    drupal_add_js(drupal_get_path('module', 'hierarchical_select') .'/hierarchical_select.js');
+    drupal_add_js(array('HierarchicalSelect' => array('url' => $url)), 'setting');
+  }
+}
+
+/**
+ * Helper function to extract the settings from a form element.
+ *
+ * @param $element
+ *   A form element.
+ * @return
+ *   An array of setting name - setting value pairs.
+ */
+function _hierarchical_select_extract_settings($element) {
+  return array(
+    'module'          => $element['#hierarchical_select_settings']['module'],
+    'enforce_deepest' => (bool) $element['#hierarchical_select_settings']['enforce_deepest'],
+    'save_lineage'    => (bool) $element['#hierarchical_select_settings']['save_lineage'],
+    'level_labels'    => (array) $element['#hierarchical_select_settings']['level_labels'],
+    'animation_delay' => (int) $element['#hierarchical_select_settings']['animation_delay'],
+    'dropbox_title'   => $element['#hierarchical_select_settings']['dropbox_title'],
+    'dropbox_limit'   => (int) $element['#hierarchical_select_settings']['dropbox_limit'],
+    'params'          => $element['#hierarchical_select_settings']['params'],
+    'all_option'      => (bool) $element['#hierarchical_select_settings']['all_option'],
+    'required'        => (bool) $element['#required'],
+    'multiple'        => (bool) $element['#multiple'],
+    // When the #value property is empty, we're rendering this form (and thus
+    // the form element) for the first time. When it's no longer empty, this
+    // means that the validation failed and that we must keep the option that
+    // was selected by the user.
+//    'selection'       => $selection, //(!empty($element['#value'])) ? $selection : $element['#default_value'],
+//    'dropbox_selection'=> $dropbox_selection,
+  );
+}
+
+/**
+ * Helper function that adds the JS to reposition the exposed filters of a
+ * View just once.
+ */
+function _hierarchical_select_views_exposed_filters_reposition() {
+  static $js_added;
+  
+  if (!isset($js_added)) {
+    drupal_add_js(drupal_get_path('module', 'hierarchical_select') .'/modules/views.js', 'module');
+  }
+}
+
+/**
+ * Helper function that marks every element in the given element as disabled.
+ *
+ * @param &$element
+ *   The element of which we want to mark all elements as disabled.
+ * @return
+ *   A structured array for use in the Forms API.
+ */
+function _hierarchical_select_mark_as_disabled(&$element) {
+  $element['#disabled'] = TRUE;
+
+  // Recurse through all children:
+  foreach (element_children($element) as $key) {
+    if (isset($element[$key]) && $element[$key]) {
+      _hierarchical_select_mark_as_disabled($element[$key]);
+    }
+  }
+}
+
+/**
+ * Helper function that generates the help text is that is displayed to the
+ * user when Javascript is disabled.
+ *
+ * @param $dropbox_is_enabled
+ *   Indicates if the dropbox is enabled or not, the help text will be
+ *   adjusted depending on this value.
+ * @return
+ *   The generated help text (in HTML).
+ */
+function _hierarchical_select_nojs_helptext($dropbox_is_enabled) {
+  $output = '';
+
+  // The options that will be used in the unordered list.
+  $items = array(
+    t('<u>enable Javascript</u> in your browser and then refresh this page, for a much enhanced experience.'),
+    t('<u>click the <em>Update</em> button</u> every time you want to update the selection'),
+  );
+  $items[1] .= (!$dropbox_is_enabled) ? '.' : t(", or when you've checked some checkboxes for entries in the dropbox you'd like to remove.");
+
+  $output .= '<span class="warning">';
+  $output .= t("You don't have Javascript enabled.");
+  $output .= '</span> ';
+  $output .= t("But don't worry: you can still use this web site! You have two options:");
+  $output .= theme('item_list', $items, NULL, 'ul', array('class' => 'solutions'));
+  
+  return $output;
+}
+
+
+//----------------------------------------------------------------------------
+// Hierarchy object generation functions.
+
 /**
  * Generate the hierarchy object.
  *
@@ -322,21 +707,19 @@ function hierarchical_select_admin_setti
  *   Whether the "save lineage" option is enabled or not.
  * @param $enforce_deepest
  *   Whether the "enforce deepest" option is enabled or not.
- * @param $all_option
- *   Prepends a new option, that allows you to select all items at once.
  * @param $level_labels
  *   An array of labels, one per level. Optional.
  * @param $params
  *   An array of parameters, which may be necessary for some implementations.
  *   Optional.
  * @param $required
- *   Whether the form item is required or not. (#required Forms API property)
+ *   Whether the form element is required or not. (#required in Forms API)
  * @param $dropbox
  *   A dropbox object, or FALSE.
  * @return
  *   A hierarchy object.
  */
-function hierarchical_select_get_hierarchy($module, $selection, $save_lineage, $enforce_deepest, $all_option, $level_labels = array(), $params = array(), $required, $dropbox = FALSE) {
+function _hierarchical_select_hierarchy_generate($module, $selection, $save_lineage, $enforce_deepest, $level_labels = array(), $params = array(), $required, $dropbox = FALSE) {
   $hierarchy = new stdClass();
 
   //
@@ -344,7 +727,7 @@ function hierarchical_select_get_hierarc
   //
 
   // Validate and clean up the selection.
-  $selection = _hierarchical_select_validate_selection($selection, $module, $params);
+  $selection = _hierarchical_select_hierarchy_validate($selection, $module, $params);
 
   // If save_linage is enabled, reconstruct the lineage. This is necessary
   // because e.g. the taxonomy module stores the terms by order of weight and
@@ -385,10 +768,10 @@ function hierarchical_select_get_hierarc
     // If it's disabled, we have to generate one ourselves based on the
     // (deepest) selected item.
     if ($save_lineage) {
-      // When the form item is optional, the "<none>" option can be selected,
-      // thus only the first level will be displayed. As a result, we won't
-      // receive an array as the selection, but only a single item. We convert
-      // this into an array.
+      // When the form element is optional, the "<none>" option can be
+      // selected, thus only the first level will be displayed. As a result,
+      // we won't receive an array as the selection, but only a single item.
+      // We convert this into an array.
       $hierarchy->lineage = (is_array($selection)) ? $selection : array(0 => $selection);
     }
     else {
@@ -406,7 +789,7 @@ function hierarchical_select_get_hierarc
   // If enforce_deepest is enabled, ensure that the lineage goes as deep as
   // possible: append values of items that will be selected by default.
   if ($enforce_deepest && !in_array($hierarchy->lineage[0], array('none', 'label_0'))) {
-    $hierarchy->lineage = _hierarchial_select_enforce_deepest_selection($hierarchy->lineage, $hierarchy->levels[0], $module, $params);
+    $hierarchy->lineage = _hierarchical_select_hierarchy_enforce_deepest($hierarchy->lineage, $hierarchy->levels[0], $module, $params);
   }
 
   //
@@ -416,11 +799,6 @@ function hierarchical_select_get_hierarc
   // Start building the levels, initialize with the root level.
   $hierarchy->levels[0] = module_invoke($module, 'hierarchical_select_root_level', $params);
 
-  // Prepend an "all" option to the root level when a dropbox is in use.
-  if ($dropbox && $all_option) {
-    $hierarchy->levels[0] = array('all' => '<'. t('all') .'>') + $hierarchy->levels[0];
-  }
-
   // Prepend a "<none>" option to the root level when:
   // - the form item is optional.
   // - enforce_deepest is enabled (use case: when level labels are disabled,
@@ -450,7 +828,7 @@ function hierarchical_select_get_hierarc
       $hierarchy->levels[$depth] = array('label_'. $depth => $level_labels[$depth]) + $hierarchy->levels[$depth];
     }
 
-    // … and have to add one more level if appropriate.
+    // … and add one more level if appropriate.
     $parent = $hierarchy->lineage[$max_depth];
     if (module_invoke($module, 'hierarchical_select_valid_item', $parent, $params)) {
       $children = module_invoke($module, 'hierarchical_select_children', $parent, $params);
@@ -468,6 +846,92 @@ function hierarchical_select_get_hierarc
 }
 
 /**
+ * Reset the selection if no valid item was selected. The first item in the
+ * array corresponds to the first selected term. As soon as an invalid item
+ * is encountered, the lineage from that level to the deeper levels should be
+ * unset. This is so to ignore selection of a level label.
+ *
+ * @param $selection
+ *   Either a single item id or an array of item ids.
+ * @param $module
+ *   The module that should be used for HS hooks.
+ * @param $params
+ *   The module that should be passed to HS hooks.
+ * @return
+ *   The updated selection.
+ */
+function _hierarchical_select_hierarchy_validate($selection, $module, $params) {
+  // TODO: check if this can be removed.
+  //$selection = (is_array($selection)) ? $selection : array($selection);
+
+  $valid = TRUE;
+  $selection_levels = count($selection);
+  for ($i = 0; $i < $selection_levels; $i++) {
+    // print "level $i<br>";
+    // As soon as one invalid item has been found, we'll stop validating; all
+    // subsequently selected items will be removed from the selection.
+    // print 'valid: '. (int)$valid;
+    if ($valid) {
+      $valid = module_invoke($module, 'hierarchical_select_valid_item', $selection[$i], $params);
+      if ($i > 0) {
+        $parent = $selection[$i - 1];
+        $child = $selection[$i];
+        $children = array_keys(module_invoke($module, 'hierarchical_select_children', $parent, $params));
+        $valid = $valid && in_array($child, $children);
+      }
+      // print $selection[$i] . ' was valid: '. (int)$valid .'<br>';
+    }
+    if (!$valid) {
+      // print 'unsetting selection of level '. $i;
+      unset($selection[$i]);
+    }
+  }
+
+  if (empty($selection)) {
+    $selection = -1;
+  }
+
+  return $selection;
+}
+
+/**
+ * Helper function to update the lineage of the hierarchy to ensure that the
+ * user selects an item in the deepest level of the hierarchy.
+ *
+ * @param $lineage
+ *   The lineage up to the deepest selection the user has made so far.
+ * @param $root_level
+ *   The options in the root level.
+ * @param $module
+ *   The module that should be used for HS hooks.
+ * @param $params
+ *   The params that should be passed to HS hooks.
+ * @return
+ *   The updated lineage.
+ */
+function _hierarchical_select_hierarchy_enforce_deepest($lineage, $root_level, $module, $params) {
+  // Use the deepest item as the first parent. Then apply this algorithm:
+  // 1) get the parent's children, stop if no children
+  // 2) choose the first child as the option that is selected by default, by
+  //    adding it to the lineage of the hierarchy
+  // 3) make this child the parent, go to step 1.
+  $parent = end($lineage); // The last item in the lineage is the deepest one.
+  $children = module_invoke($module, 'hierarchical_select_children', $parent, $params);
+  while (count($children)) {
+    $first_child = reset(array_keys($children));
+    $lineage[] = $first_child;
+    $parent = $first_child;
+    $children = module_invoke($module, 'hierarchical_select_children', $parent, $params);
+  }
+
+  return $lineage;
+}
+
+
+//----------------------------------------------------------------------------
+// Dropbox object generation functions.
+
+/**
  * Generate the dropbox object.
  *
  * @param $module
@@ -486,7 +950,7 @@ function hierarchical_select_get_hierarc
  * @return
  *   A dropbox object.
  */
-function hierarchical_select_get_dropbox($module, $selection, $save_lineage, $level_labels = array(), $params = array(), $title = '') {
+function _hierarchical_select_dropbox_generate($module, $selection, $save_lineage, $level_labels = array(), $params = array(), $title = '') {
   $dropbox = new stdClass();
 
   $dropbox->title = (!empty($title)) ? $title : t('All selections');
@@ -494,37 +958,26 @@ function hierarchical_select_get_dropbox
   $dropbox->lineages_selections = array();
   
   // Clean selection.
-  if (is_array($selection)) {
-    foreach ($selection as $key => $item) {
-      if (!module_invoke($module, 'hierarchical_select_valid_item', $item, $params)) {
-        unset($selection[$key]);
-      }
-    }
-  }
-  else {
-    if (!module_invoke($module, 'hierarchical_select_valid_item', $selection, $params)) {
-      $selection = array();
+  foreach ($selection as $key => $item) {
+    if (!module_invoke($module, 'hierarchical_select_valid_item', $item, $params)) {
+      unset($selection[$key]);
     }
   }
 
   if (!empty($selection)) {
-    // Remove all duplicate values from the selection. We'll work with this
-    // *set* (repeating values don't matter) of selected items in the code.
-    $selection = (is_array($selection)) ? array_unique($selection) : array($selection);
-
-    // Store the "save lineage" setting, needed in the theming layer.
+    // Store the "save lineage" setting, needed in the rendering layer.
     $dropbox->save_lineage = $save_lineage;
 
     if ($save_lineage) {
-      $dropbox->lineages = _hierarchical_select_reconstruct_lineages_save_lineage_enabled($module, $selection, $params);
+      $dropbox->lineages = _hierarchical_select_dropbox_reconstruct_lineages_save_lineage_enabled($module, $selection, $params);
     }
     else {
-      // Retrieve the lineage of each item. Ignore invalid items.
+      // Retrieve the lineage of each item.
       foreach ($selection as $item) {
-        if (module_invoke($module, 'hierarchical_select_valid_item', $item, $params)) {
-          $dropbox->lineages[] = module_invoke($module, 'hierarchical_select_lineage', $item, $params);
-        }
+        $dropbox->lineages[] = module_invoke($module, 'hierarchical_select_lineage', $item, $params);
       }
+
+      // We will also need the labels of each item in the rendering layer.
       foreach ($dropbox->lineages as $id => $lineage) {
         foreach ($lineage as $level => $item) {
           $dropbox->lineages[$id][$level] = array('value' => $item, 'label' => module_invoke($module, 'hierarchical_select_item_get_label', $item, $params));
@@ -552,36 +1005,6 @@ function hierarchical_select_get_dropbox
   return $dropbox;
 }
 
-
-/**
- * Helper function to extract the settings from a form item.
- *
- * @param $element
- *   A form item.
- * @return
- *   An array of setting name - setting value pairs.
- */
-function _hierarchical_select_extract_settings($element) {
-  return array(
-    'module'          => $element['#hierarchical_select_settings']['module'],
-    'enforce_deepest' => (bool) $element['#hierarchical_select_settings']['enforce_deepest'],
-    'save_lineage'    => (bool) $element['#hierarchical_select_settings']['save_lineage'],
-    'level_labels'    => (array) $element['#hierarchical_select_settings']['level_labels'],
-    'animation_delay' => (int) $element['#hierarchical_select_settings']['animation_delay'],
-    'dropbox_title'   => $element['#hierarchical_select_settings']['dropbox_title'],
-    'dropbox_limit'   => (int) $element['#hierarchical_select_settings']['dropbox_limit'],
-    'params'          => $element['#hierarchical_select_settings']['params'],
-    'all_option'      => (bool) $element['#hierarchical_select_settings']['all_option'],
-    'required'        => (bool) $element['#required'],
-    'multiple'        => (bool) $element['#multiple'],
-    // When the #value property is empty, we're rendering this form (and thus
-    // the form element) for the first time. When it's no longer empty, this
-    // means that the validation failed and that we must keep the option that
-    // was selected by the user.
-    'selection'       => (!empty($element['#value'])) ? $element['#value'] : $element['#default_value'],
-  );
-}
-
 /**
  * Helper function to reconstruct the lineages given a set of selected items
  * and the fact that the "save lineage" option is enabled.
@@ -602,7 +1025,7 @@ function _hierarchical_select_extract_se
  * @return
  *   An array of dropbox lineages.
  */
-function _hierarchical_select_reconstruct_lineages_save_lineage_enabled($module, $selection, $params) {
+function _hierarchical_select_dropbox_reconstruct_lineages_save_lineage_enabled($module, $selection, $params) {
   // We have to reconstruct all lineages from the given set of selected
   // items. That means: we have to reconstruct every possible combination!
   $lineages = array();
@@ -652,104 +1075,6 @@ function _hierarchical_select_reconstruc
 }
 
 /**
- * Helper function to update the lineage of the hierarchy to ensure that the
- * user selects an item in the deepest level of the hierarchy.
- *
- * @param $lineage
- *   The lineage up to the deepest selection the user has made so far.
- * @param $root_level
- *   The options in the root level.
- * @param $module
- *   The module that should be used for HS hooks.
- * @param $params
- *   The params that should be passed to HS hooks.
- * @return
- *   The updated lineage.
- */
-function _hierarchial_select_enforce_deepest_selection($lineage, $root_level, $module, $params) {
-  // Use the deepest item as the first parent. Then apply this algorithm:
-  // 1) get the parent's children, stop if no children
-  // 2) choose the first child as the option that is selected by default, by
-  //    adding it to the lineage of the hierarchy
-  // 3) make this child the parent, go to step 1.
-  $parent = end($lineage); // The last item in the lineage is the deepest one.
-  $children = module_invoke($module, 'hierarchical_select_children', $parent, $params);
-  while (count($children)) {
-    $first_child = reset(array_keys($children));
-    $lineage[] = $first_child;
-    $parent = $first_child;
-    $children = module_invoke($module, 'hierarchical_select_children', $parent, $params);
-  }
-
-  return $lineage;
-}
-
-/**
- * Reset the selection if no valid item was selected. If an array is passed
- * (this happens when the "save lineage" option is enabled), then the first
- * item in the array corresponds to the first selected term. As soon as an
- * invalid item is encountered, the lineage from that level to the deeper
- * levels should be unset. This is so to ignore selection of a level label.
- *
- * @param $selection
- *   Either a single item id or an array of item ids.
- * @param $module
- *   The module that should be used for HS hooks.
- * @param $params
- *   The module that should be passed to HS hooks.
- * @return
- *   The updated selection.
- */
-function _hierarchical_select_validate_selection($selection, $module, $params) {
-  // Reset if no item was selected or the item's id could not be validated.
-  $selection = (empty($selection)) ? -1 : $selection;
-  if (is_array($selection)) {
-    // The "save lineage" option is enabled because $selection is an array.
-    $valid = TRUE;
-    for ($i = 0; $i < count($selection); $i++) {
-      if ($valid) {
-        $valid = module_invoke($module, 'hierarchical_select_valid_item', $selection[$i], $params);
-      }
-      if (!$valid) {
-        unset($selection[$i]);
-      }
-    }
-    if (empty($selection)) {
-      $selection = -1;
-    }
-  }
-  elseif ($selection != 'none' && !module_invoke($module, 'hierarchical_select_valid_item', $selection, $params)) {
-    $selection = -1;
-  }
-  return $selection;
-}
-
-/**
- * Helper function that adds the JS to reposition the exposed filters of a
- * View just once.
- */
-function _hierarchical_select_views_exposed_filters_reposition() {
-  static $js_added;
-  
-  if (!isset($js_added)) {
-    drupal_add_js(drupal_get_path('module', 'hierarchical_select') .'/modules/views.js', 'module');
-  }
-}
-
-/**
- * Convert a "true" or "false" string into the corresponding boolean value,
- * while ignoring the case.
- *
- * @param $string
- *   The string to convert.
- * @return
- *   The boolean value.
- */
-function _hierarchical_select_str_to_bool($string) {
-  return (strcasecmp($string, 'TRUE') == 0);
-}
-
-/**
  * Dropbox lineages sorting callback.
  *
  * @param $lineage_a
@@ -801,147 +1126,116 @@ function _hierarchical_select_dropbox_li
  */
 
 /**
- * Render the selects.
+ * Format a hierarchical select.
  *
- * @param $hsid
- *   A hierarchical select id.
- * @param $hierarchy
- *   A hierarchy object.
+ * @param $element
+ *   An associative array containing the properties of the element.
  * @return
- *   The rendered HTML.
+ *   A themed HTML string representing the form element.
  */
-function theme_hierarchical_select_render_selects($hsid, $hierarchy) {
-  $output = '';
-  $level_labels_style = variable_get('hierarchical_select_level_labels_style', 'none');
+function theme_hierarchical_select($element) {
+ $output = '';
 
-  for ($depth = 0; $depth < count($hierarchy->lineage); $depth++) {
-    $output .= '<select id="hierarchical-select-'. $hsid .'-select-level-'. $depth .'" class="form-select hierarchical-select-select hierarchical-select-'. $hsid .'-select">';
-    foreach ($hierarchy->levels[$depth] as $value => $label) {
-      $output .= '<option';
-      if (preg_match('/^label_\d+$/', $value) && $level_labels_style != 'none') {
-        $output .= ' class="hierarchical-select-level-label-'. $level_labels_style .'"';
-      }
-      if ($value == $hierarchy->lineage[$depth]) {
-        $output .= ' selected="selected" value="'. check_plain($value) .'"><strong>'. check_plain($label) .'</strong></option>';
-      }
-      else {
-        $output .= ' value="'. check_plain($value) .'">'. check_plain($label) .'</option>';
-      }
-    }
-    $output .= '</select>';
-  }
+ // Update $element['#attributes']['class'].
+ $hsid = $element['hsid']['#value'];
+ $level_labels_style = variable_get('hierarchical_select_level_labels_style', 'none');
+ $classes = array(
+   'hierarchical-select-wrapper',
+   "hierarchical-select-level-labels-style-$level_labels_style",
+ );
+ $element['#attributes']['class'] .= ' '. implode(' ', $classes);
+ $element['#attributes']['id'] = "hierarchical-select-$hsid-wrapper";
+ 
+ $output .= theme(
+   'form_element',
+   array(
+     '#title' => $element['#title'], 
+     '#description' => $element['#description'], 
+     '#id' => $element['#id'], 
+     '#required' => $element['#required'], 
+     '#error' => $element['#error'],
+   ),
+   '<div '. drupal_attributes($element['#attributes']) .'>'. $element['#children'] .'</div>'
+ );
 
-  return $output;
+ return $output;
 }
 
 /**
- * Render the dropbox.
+ * Format a select in the .hierarchial-select div: prevent it from being
+ * wrapped in a div. This simplifies the CSS and JS code.
  *
- * @param $hsid
- *   A hierarchical select id.
- * @param $dropbox
- *   A dropbox object.
+ * @param $element
+ *   An associative array containing the properties of the element.
  * @return
- *   The rendered HTML.
+ *   A themed HTML string representing the form element.
  */
-function theme_hierarchical_select_render_dropbox($hsid, $dropbox) {
+function theme_hierarchical_select_select($element) {
+  $select = '';
+  $size = $element['#size'] ? ' size="' . $element['#size'] . '"' : '';
+  _form_set_class($element, array('form-select'));
+  $multiple = isset($element['#multiple']) && $element['#multiple'];
+  return '<select name="'. $element['#name'] .''. ($multiple ? '[]' : '') .'"'. ($multiple ? ' multiple="multiple" ' : '') . drupal_attributes($element['#attributes']) .' id="'. $element['#id'] .'" '. $size .'>'. form_select_options($element) .'</select>';
+}
+
+/**
+ * Forms API theming callback for the dropbox. Renders the dropbox as a table.
+ *
+ * @param $element
+ *   An element for which the #theme property was set to this function.
+ * @return
+ *   A themed HTML string.
+ */
+function theme_hierarchical_select_dropbox_table($element) {
   $output = '';
+  
+
+  $title     = $element['title']['#value'];
+  $separator = $element['separator']['#value'];
+  $is_empty  = $element['is_empty']['#value'];
 
-  $separator = '›';
   $separator_html = '<span class="dropbox-item-separator">'. $separator .'</span>';
 
+
+  $output .= '<table class="dropbox">';
+  $output .= '<caption class="dropbox-title">'. $title .'</caption>';
   $output .= '<tbody>';
-    
-  if (!empty($dropbox->lineages)) {
-    foreach ($dropbox->lineages as $id => $lineage) {
-      // Preparation: get the labels of the lineage.
-      $lineage_labels = array();
-      for ($level = 0; $level < count($lineage); $level++) {
-        $lineage_labels[] = $lineage[$level]['label'];
-      }
 
-      $zebra = (($id + 1) % 2 == 0) ? 'even' : 'odd';
-      $first = ($id == 0) ? 'first' : '';
-      $last = ($id == count($dropbox->lineages) - 1) ? 'last' : '';
+  if (!$is_empty) {
+    // Each lineage in the dropbox coresponds to an entry in the dropbox table.
+    $lineage_count = count(element_children($element['lineages']));
+    for ($x = 0; $x < $lineage_count; $x++) {
+      $db_entry = $element['lineages'][$x];
+      $zebra = $db_entry['#zebra'];
+      $first = $db_entry['#first'];
+      $last  = $db_entry['#last'];
+      // The deepest level is the number of child levels minus one. This "one"
+      // is the element for the "Remove" checkbox.
+      $deepest_level = count(element_children($db_entry)) - 1;
 
       $output .= '<tr class="dropbox-entry '. $first .' '. $last .' '. $zebra .'">';
-
-      // If the "save lineage" option is enabled: select every item. Otherwise
-      // only select the last item.
       $output .= '<td>';
-      if ($dropbox->save_lineage) {
-        $output .= '<span class="dropbox-selected-item">'. implode('</span>'. $separator_html .'<span class="dropbox-selected-item">', $lineage_labels) .'</span>';
-      }
-      else {
-        $output .= '<span class="dropbox-item">'. implode('</span>'. $separator_html .'<span class="dropbox-item">', array_slice($lineage_labels, 0, count($lineage_labels) - 1)) .'</span>';
-        if (count($lineage_labels) > 1) {
+      // Each item in a lineage is separated by the separator string.
+      for ($depth = 0; $depth < $deepest_level; $depth++) {
+        $output .= drupal_render($db_entry[$depth]);
+
+        if ($depth < $deepest_level - 1) {
           $output .= $separator_html;
         }
-        $output .= '<span class="dropbox-selected-item">'. $lineage_labels[count($lineage_labels) - 1] .'</span>';
       }
       $output .= '</td>';
-
-      // Add a column with a "Remove" link.
-      $output .= '<td><a id="hierarchical-select-'. $hsid .'-remove-'. $id .'-from-dropbox" class="hierarchical-select-remove-from-dropbox hierarchical-select-'. $hsid .'-remove-from-dropbox">'. t('Remove') .'</a></td>';
-
+      $output .= '<td class="dropbox-remove">'. drupal_render($db_entry['remove']) .'</td>';
       $output .= '</tr>';
     }
   }
   else {
-    $output .= '<tr class="dropbox-entry first last dropbox-is-empty"><td>'. t('Nothing has been selected yet.') .'</td></tr>';
+    $output .= '<tr class="dropbox-entry first last dropbox-is-empty"><td>';
+    $output .= t('Nothing has been selected yet.');
+    $output .= '</td></tr>';
   }
 
-  // Add the dropbox title as a table caption.
-  $output .= '<caption class="dropbox-title">'. check_plain($dropbox->title) .'</caption>';
-
   $output .= '</tbody>';
-
-  return $output;
-}
-
-/**
- * Render the initial hierarchical select.
- *
- * @param $hsid
- *   A hierarchical select id.
- * @param $hierarchy
- *   A hierarchy object.
- * @param $dropbox
- *   A dropbox object.
- * @return
- *   The rendered HTML.
- */
-function theme_hierarchical_select_render_initial_html($hsid, $hierarchy, $dropbox) {
-  $output = '';
-
-  $output .= '<div class="hierarchical-select-input clear-block">';
-  $output .= theme('hierarchical_select_render_selects', $hsid, $hierarchy);
-  $output .= '</div>';
-
-  if ($dropbox) {
-    $output .= '<table class="hierarchical-select-dropbox">';
-    $output .= theme('hierarchical_select_render_dropbox', $hsid, $dropbox);
-    $output .= '</table>';
-  }
-
-  return $output;
-}
-
-/**
- * Render the add button.
- *
- * @param $hsid
- *   A hierarchical select id.
- * @return
- *   The rendered HTML.
- */
-function theme_hierarchical_select_dropbox_add_button($hsid) {
-  $output = '';
-
-  $output .= '<input id="hierarchical-select-'. $hsid .'-add-to-dropbox"';
-  $output .= ' class="hierarchical-select-add-to-dropbox form-submit"';
-  $output .= ' type="button"';
-  $output .= ' value="'. t('Add') .'" />';
+  $output .= '</table>';
 
   return $output;
 }
Index: modules/taxonomy.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/hierarchical_select/modules/taxonomy.inc,v
retrieving revision 1.21
diff -u -F^f -r1.21 taxonomy.inc
--- modules/taxonomy.inc	29 Mar 2008 02:05:44 -0000	1.21
+++ modules/taxonomy.inc	4 Apr 2008 05:55:36 -0000
@@ -8,8 +8,7 @@
 define('HS_TAXONOMY_DONT_SAVE_LINEAGE', 0);
 define('HS_TAXONOMY_DO_ENFORCE_DEEPEST', 1);
 define('HS_TAXONOMY_DONT_ENFORCE_DEEPEST', 0);
-define('HS_TAXONOMY_DO_ADD_ALL_OPTION', 1);
-define('HS_TAXONOMY_DONT_ADD_ALL_OPTION', 0);
+
 
 //----------------------------------------------------------------------------
 // Hierarchical Select hooks.
@@ -81,21 +80,6 @@ function taxonomy_hierarchical_select_fo
       ),
     );
 
-    $form['hierarchical_select']['all_option'] = array(
-      '#type' => 'select',
-      '#title' => t('Option to select all items at once'),
-      '#description' => t(
-        'This setting is only available if you\'ve enabled multiple select.<br
-        />After enabling this setting, a new "&lt;all&gt;" option will be
-        prepended the root level select. By choosing this option, all items in
-        the hierarchy will be selected at once.'),
-      '#options' => array(
-        HS_TAXONOMY_DONT_ADD_ALL_OPTION => t('Disabled'),
-        HS_TAXONOMY_DO_ADD_ALL_OPTION => t('Enabled'),
-      ),
-      '#default_value' => variable_get("hierarchical_select_all_option_$vid", HS_TAXONOMY_DONT_ADD_ALL_OPTION),
-    );
-
     $form['hierarchical_select']['level_labels'] = array(
       '#tree' => TRUE,
       '#type' => 'fieldset',
@@ -334,7 +318,7 @@ function hierarchical_select_taxonomy_fo
  */
 function hierarchical_select_taxonomy_form_vocabulary_submit($form_id, $form_values) {
   $vid = $form_values['vid'];
-  $settings = array('status', 'save_lineage', 'enforce_deepest', 'all_option');
+  $settings = array('status', 'save_lineage', 'enforce_deepest');
   variable_set("hierarchical_select_multiple_{$vid}", $form_values['hierarchical_select_multiple']);
   foreach ($settings as $setting) {
     variable_set("hierarchical_select_{$setting}_{$vid}", $form_values['hierarchical_select'][$setting]);
@@ -424,10 +408,12 @@ function _taxonomy_hierarchical_select_g
 function taxonomy_hierarchical_select_update_form_item(&$form_item, $vid) {
   $enforce_deepest = variable_get("hierarchical_select_enforce_deepest_$vid", HS_TAXONOMY_DONT_ENFORCE_DEEPEST);
 
+  unset($form_item['#options']);
+  unset($form_item['#theme']);
+
   $form_item['#multiple'] = variable_get("hierarchical_select_multiple_{$vid}", 0);
   $form_item['#hierarchical_select_settings']['save_lineage'] = _taxonomy_hierarchical_select_get_save_linage($vid);
   $form_item['#hierarchical_select_settings']['enforce_deepest'] = $enforce_deepest;
-  $form_item['#hierarchical_select_settings']['all_option'] = (bool) variable_get("hierarchical_select_all_option_$vid", HS_TAXONOMY_DONT_ADD_ALL_OPTION);
   if (variable_get("hierarchical_select_level_labels_status_$vid", 0) == 1) {
     $form_item['#hierarchical_select_settings']['level_labels'][0] = variable_get("hierarchical_select_level_0_{$vid}", '');
     if ($enforce_deepest == HS_TAXONOMY_DONT_ENFORCE_DEEPEST) {
