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