Wednesday, July 06, 2005

Dynamic Select

Sometime ago in QuirksBlog came across this post which lead to the this link which provides an elegant solution to dynamic select boxes. This set my refactoring mind rolling :) and I figured instead of using an existing attribute why not use the name/id of the controlling select-box !! That done my lazy mind decided one was not enough and so I decided my dynamic select script must control multiple select-boxes. The HTML form for such a thing looks somewhat like this
<form name="testForm">

  <select name="regionId">
    <option value="_select_"> -  Select - </option>
    <option value="1">England</option>
    <option value="2">France</option>
  </select>

  <select name="businessId">
    <option value="_select_"> -  Select - </option>
    <option regionId="1" value="1">Cards</option>
    <option regionId="1" value="2">Loans</option>
    <option regionId="2" value="2">Loans</option>
  </select>

  <select name="productId">
    <option value="_select_"> -  Select - </option>
    <option regionId="1" businessId="1" value="1">GoldCard</option>
    <option regionId="1" businessId="2" value="4">HomeLoan</option>
    <option regionId="2" businessId="2" value="4">HomeLoan</option>
    <option regionId="1" businessId="2" value="2">PersonalLoan</option>
    <option regionId="2" businessId="2" value="2">PersonalLoan</option>
  </select>

</form>
The javascript for achieving this is below
function addID(tags) {
  if ( tags && document.getElementsByTagName ) {
    for ( i = 0; i < tags.length; i++ ) {
      var elements = document.getElementsByTagName(tags[i]);
      for ( j = 0; j < elements.length; j++ ) {
        var elementName = elements[j].getAttribute("name");
        if ( elementName ) {
          elements[j].setAttribute("id", elementName);
        }
      } // enfor j
    } // endfor i
  } // endif
}
function gel(eName) {
  if ( document.getElementById ) {
    return document.getElementById(eName);
  }
  return null;
}
function addHandler(target, eventName, eventHandler) {
  if ( target ) {
    if ( target.addEventListener ) {
      target.addEventListener(eventName, eventHandler, true);
      return true;
    } else if ( target.attachEvent ) {
      target.attachEvent("on" + eventName, eventHandler);
      return true;
    }
  }
  return false;
}
function addChainedSelect(sourceSelect, targetSelects) {
  var numTargets = targetSelects.length;
  var originalTargets = new Array(numTargets);
  for ( i = 0; i < targetSelects.length; i++ ) {
    if ( targetSelects[i] && targetSelects[i].cloneNode ) {
      originalTargets[i] = targetSelects[i].cloneNode(true);
      if ( targetSelects[i].getAttribute("chainedSelect") ) {
        var chainedSelects = targetSelects[i].getAttribute("chainedSelect");
        chainedSelects += "," + sourceSelect.name;
        targetSelects[i].setAttribute("chainedSelect", chainedSelects);
      } else {
        targetSelects[i].setAttribute("chainedSelect", sourceSelect.name);
      }
    }
  }
  
  addHandler(sourceSelect, "change", function() {
    refreshSelect(sourceSelect, targetSelects, originalTargets);
  });
}
function refreshSelect(sourceSelect, targetSelects, originalTargets) {
  var curValue = sourceSelect.value;
  var srcName = sourceSelect.getAttribute("name");
  for ( i = 0; i < targetSelects.length; i++ ) {
    clearSelect(targetSelects[i]);
    var option = originalTargets[i].getElementsByTagName("option");
    var regEx = new RegExp(curValue);
    for ( j = 0; j < option.length; j++ ) {
      if ( /_select_/.test(option[j].value) || regEx.test(option[j].getAttribute(srcName)) ) {
        targetSelects[i].appendChild(option[j].cloneNode(true));
      }
    } // endfor j
    if ( targetSelects[i].getAttribute("chainedSelect") ) {
      var chainedSelects = targetSelects[i].getAttribute("chainedSelect").split(",");
      for ( j = 0; j < chainedSelects.length; j++ ) {
        var chainName = chainedSelects[j];
        var chainValue = document.getElementById(chainName).value;
        var regEx = new RegExp(chainValue);
        var option = targetSelects[i].getElementsByTagName("option");
        for ( k = 0; k < option.length; k++ ) {
          if ( !/_select_/.test(option[k].value) && !regEx.test(option[k].getAttribute(chainName)) ) {
            targetSelects[i].removeChild(option[k]);
          }
        } // endfor k
      } // endfor j
    } // endif additional chainedSelects
  } // endfor i
}
function clearSelect(selectBox) {
  if ( selectBox ) {
    var option = selectBox.getElementsByTagName("option");
    while ( option[0] ) {
      selectBox.removeChild(option[0]);
    }
  }
}
And the onload calls to initate it all :)
window.onload = function() {
  addID(["input", "select", "div", "a"]);
  if ( document.getElementById ) {
    addChainedSelect(gel("regionId"), [gel("businessId"), gel("productId")]);
    addChainedSelect(gel("businessId"), [gel("productId")]);
  } // endif gEBI
} // end winload
Have tested it out only in Firefox and that too not extensively so watchout for those bugs ;)

No comments: