From 81c195f29898eeb1d9e6d413eac314cbbbf7c2cd Mon Sep 17 00:00:00 2001
From: Earl Miles <merlin@logrus.com>
Date: Sat, 13 Dec 2008 20:42:33 +0000
Subject: [PATCH] Add dependent form fields tool from Views.

---
 includes/dependent.inc |  62 ++++++++++++++
 js/dependent.js        | 182 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 244 insertions(+)
 create mode 100644 includes/dependent.inc
 create mode 100644 js/dependent.js

diff --git a/includes/dependent.inc b/includes/dependent.inc
new file mode 100644
index 00000000..2a78b149
--- /dev/null
+++ b/includes/dependent.inc
@@ -0,0 +1,62 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Provide dependent checkboxes that can be easily used in forms.
+ *
+ * This system will ensure that form items are invisible if the dependency is
+ * not met. What this means is that you set the #dependency of an item to a
+ * list of form ids that must be set, and the list of values that qualify.
+ *
+ * For a simple use, setting an item to be dependent upon a select box, if
+ * any of the listed values are selected, the item will be visible. Otherwise,
+ * the item will be visible.
+ *
+ * If dependent upon multiple items, all of the items must contain one of the
+ * acceptable values.
+ *
+ * Checkboxes don't have their own id, so you need to add one in a div
+ * around the checkboxes via #prefix and #suffix. You actually need to add TWO
+ * divs because it's the parent that gets hidden. Also be sure to retain the
+ * 'expand_checkboxes' in the #process array, because the views process will
+ * override it.
+ *
+ * For radios, because they are selected a little bit differently, instead of
+ * using the CSS id, use: radio:NAME where NAME is the #name of the property.
+ * This can be quickly found by looking at the HTML of the generated form, but
+ * it is usually derived from the array which contains the item. For example,
+ * $form['menu']['type'] would have a name of menu[type]. This name is the same
+ * field that is used to determine where in $form_state['values'] you will find
+ * the value of the form.
+ *
+ * Usage:
+ *
+ * First, ensure this tool is loaded:
+ * @code { ctools_include('dependent'); }
+ *
+ * On any form item, add
+ * - @code '#process' => 'ctools_dependent_process' @endcode
+ * - @code '#dependency' => array('id-of-form-without-the-#' => array(list, of, values, that, make, this, gadget, visible)); @endcode
+ */
+
+/**
+ * Process callback to add dependency to form items.
+ */
+function ctools_dependent_process($element, $edit, &$form_state, &$form) {
+  if (isset($element['#dependency'])) {
+    if (!isset($element['#dependency_count'])) {
+      $element['#dependency_count'] = 1;
+    }
+    if (!empty($form_state['ajax'])) {
+      $form_state['js settings']['CTools']['dependent'][$element['#id']] = array('num' => $element['#dependency_count'], 'values' => $element['#dependency']);
+    }
+    else {
+      ctools_add_js('dependent');
+      $options['CTools']['dependent'][$element['#id']] = array('num' => $element['#dependency_count'], 'values' => $element['#dependency']);
+      drupal_add_js($options, 'setting');
+    }
+  }
+
+  return $element;
+}
diff --git a/js/dependent.js b/js/dependent.js
new file mode 100644
index 00000000..5f2be002
--- /dev/null
+++ b/js/dependent.js
@@ -0,0 +1,182 @@
+// $Id$
+/**
+ * @file dependent.js
+ *
+ * Written by dmitrig01 (Dmitri Gaskin) for CTools; this provides dependent
+ * visibility for form items in CTools' ajax forms.
+ *
+ * To your $form item definition add:
+ * - '#process' => array('CTools_process_dependency'),
+ * - Add '#dependency' => array('id-of-form-item' => array(list, of, values, that, 
+     make, this, item, show),
+ *
+ * Special considerations:
+ * - radios are harder. Because Drupal doesn't give radio groups individual ids,
+ *   use 'radio:name-of-radio' 
+ *
+ * - Checkboxes don't have their own id, so you need to add one in a div 
+ *   around the checkboxes via #prefix and #suffix. You actually need to add TWO
+ *   divs because it's the parent that gets hidden. Also be sure to retain the
+ *   'expand_checkboxes' in the #process array, because the CTools process will
+ *   override it.
+ */
+
+Drupal.CTools = Drupal.CTools || {};
+Drupal.CTools.dependent = {};
+
+Drupal.CTools.dependent.bindings = {};
+Drupal.CTools.dependent.activeBindings = {};
+Drupal.CTools.dependent.activeTriggers = [];
+
+Drupal.CTools.dependent.inArray = function(array, search_term) {
+  var i = array.length;
+  if (i > 0) {
+	 do {
+		if (array[i] == search_term) {
+		   return true;
+		}
+	 } while (i--);
+  }
+  return false;
+}
+
+
+Drupal.CTools.dependent.autoAttach = function() {
+  // Clear active bindings and triggers.
+  for (i in Drupal.CTools.dependent.activeTriggers) {
+    jQuery(Drupal.CTools.dependent.activeTriggers[i]).unbind('change');
+  }
+  Drupal.CTools.dependent.activeTriggers = [];
+  Drupal.CTools.dependent.activeBindings = {};
+  Drupal.CTools.dependent.bindings = {};
+
+  if (!Drupal.settings.CTools) {
+    return;
+  }
+
+  // Iterate through all relationships
+  for (id in Drupal.settings.CTools.dependent) {
+
+    // Drupal.CTools.dependent.activeBindings[id] is a boolean, 
+    // whether the binding is active or not.  Defaults to no.
+    Drupal.CTools.dependent.activeBindings[id] = 0;
+    // Iterate through all possible values
+    for(bind_id in Drupal.settings.CTools.dependent[id].values) {
+      // This creates a backward relationship.  The bind_id is the ID
+      // of the element which needs to change in order for the id to hide or become shown.  
+      // The id is the ID of the item which will be conditionally hidden or shown.
+      // Here we're setting the bindings for the bind
+      // id to be an empty array if it doesn't already have bindings to it
+      if (!Drupal.CTools.dependent.bindings[bind_id]) {
+        Drupal.CTools.dependent.bindings[bind_id] = [];
+      }
+      // Add this ID
+      Drupal.CTools.dependent.bindings[bind_id].push(id);
+      // Big long if statement.
+      // Drupal.settings.CTools.dependent[id].values[bind_id] holds the possible values
+
+      if (bind_id.substring(0, 6) == 'radio:') {
+        var trigger_id = "input[@name='" + bind_id.substring(6) + "']";
+      }
+      else {
+        var trigger_id = '#' + bind_id;
+      }
+
+      Drupal.CTools.dependent.activeTriggers.push(trigger_id);
+
+
+      var getValue = function(item, trigger) {
+        if (item.substring(0, 6) == 'radio:') {
+          var val = jQuery(trigger + ':checked').val();
+        }
+        else {
+          switch (jQuery(trigger).attr('type')) {
+            case 'checkbox':
+              var val = jQuery(trigger).attr('checked') || 0;
+              break;
+            default:
+              var val = jQuery(trigger).val();
+          }
+        }
+        return val;
+      }
+
+      var setChangeTrigger = function(trigger_id, bind_id) {
+        // Triggered when change() is clicked.
+        var changeTrigger = function() {
+          var val = getValue(bind_id, trigger_id);
+
+          for (i in Drupal.CTools.dependent.bindings[bind_id]) {
+            var id = Drupal.CTools.dependent.bindings[bind_id][i];
+            
+            // Fix numerous errors
+            if (typeof id != 'string') {
+              continue;
+            }
+
+            // This bit had to be rewritten a bit because two properties on the
+            // same set caused the counter to go up and up and up.
+            if (!Drupal.CTools.dependent.activeBindings[id]) {
+              Drupal.CTools.dependent.activeBindings[id] = {};
+            }
+
+            if (Drupal.CTools.dependent.inArray(Drupal.settings.CTools.dependent[id].values[bind_id], val)) {
+              Drupal.CTools.dependent.activeBindings[id][bind_id] = 'bind';
+            }
+            else {
+              delete Drupal.CTools.dependent.activeBindings[id][bind_id];
+            }
+
+            var len = 0;
+            for (i in Drupal.CTools.dependent.activeBindings[id]) {
+              len++;
+            }
+
+            var object = jQuery('#' + id + '-wrapper');
+            if (!object.size()) {
+              object = jQuery('#' + id).parent();
+            }
+
+            if (Drupal.settings.CTools.dependent[id].num <= len) {
+              // Show if the element if criteria is matched
+              object.show(0);
+            }
+            else {
+              // Otherwise hide
+              object.hide(0);
+            }
+          }
+        }
+
+        jQuery(trigger_id).change(function() {
+          // Trigger the internal change function
+          // the attr('id') is used because closures are more confusing
+          changeTrigger(trigger_id, bind_id);
+        });
+        // Trigger initial reaction
+        changeTrigger(trigger_id, bind_id);
+      }
+      setChangeTrigger(trigger_id, bind_id);
+    }
+  }
+}
+
+Drupal.behaviors.CToolsDependent = function (context) {
+  Drupal.CTools.dependent.autoAttach();
+
+  // Really large sets of fields are too slow with the above method, so this
+  // is a sort of hacked one that's faster but much less flexible.
+  $("select.ctools-master-dependent:not(.ctools-processed)")
+    .addClass('ctools-processed')
+    .change(function() {
+      var val = $(this).val();
+      if (val == 'all') {
+        $('.ctools-dependent-all').show(0);
+      }
+      else {
+        $('.ctools-dependent-all').hide(0);
+        $('.ctools-dependent-' + val).show(0);
+      }
+    })
+    .trigger('change');
+}
-- 
GitLab