
/**
 * The emptyText function will take care of 
 * specialized "Enter something here" behavior 
 * for form fields.  It will make sure the text 
 * is displaying when the field is empty and 
 * gone when the user clicks on it to enter 
 * something.  
 * 
 * Here's how to use this function:
 * 
 * <input 
 *    id="date"                  [optional]
 *    name="date"                [optional]
 *    value="{$date}"            [optional]
 *    class="normalclass"        [optional]
 *    emptyvalue="Enter date"    [required]
 *    emptyclass="grayedout"     [optional]
 *  />
 * <script>emptyText()</script>
 * 
 * Notice the two extra non-standard attributes,
 * emptyvalue and emptyclass.  These specify the 
 * text and class that will be given to the 
 * field when it is empty.  Like the class 
 * attribute, emptyclass may include more than 
 * one class.  If emptyclass is not specified, 
 * the class will not change when the field is 
 * empty.
 * 
 * You can optionally pass an argument to the 
 * emptyText function that is an id or a 
 * reference to the element you wish to effect.  
 * This can be useful for dynamically created 
 * elements or to avoid ambiguity.  If you don't 
 * pass any arguments to the emptyText function, 
 * it will search backward from the <script> 
 * element for an <input> element.  It is 
 * recommended keep the  <script> element close 
 * to the <input> element.  
 * 
 * It has the added benefit that if a field 
 * contains the empty text when the 
 * form is submitted, the empty text won't get 
 * sent to the server. This  relieves the form 
 * handling code from having to check for the 
 * empty text.
 * 
 * This should almost always "just work".  One 
 * exception is if you want to clear the input 
 * element from javascript.  Instead of setting 
 * input.value = '', use input.clear().  That 
 * will ensure the empty text is  showing after 
 * the input is cleared.
 * 
 * Requires prototype.js
 * 
 * @author Russ Black
 * 
 */

 // Use a function to create a namespace to prevent name collisions 
 // with other libraries
(function() {

  // define it with bouncyCase and lowercase.
  emptyText = emptytext = function(input) {
    if (!input) {
      input = findLastInput()
    }
    input = $(input)
    if (input.emptytext) {
      // this element has already been styled.
      return
    }

    // make a new input element to hold the empty text. Leave it anonymous
    // (no "name" attribute) so it won't get submitted with the form.
    var emptytext = document.createElement('input')

    // give it the appropriate class
    if (input.hasAttribute('emptyclass')) {
      emptytext.className = input.getAttribute('emptyclass')
    }
    else {
      emptytext.className = input.className
    }

    if (input.hasAttribute("id")) {
      emptytext.id = input.id + '-emptytext'
    }

    // copy size attribute
    if (input.hasAttribute('size')) {
      emptytext.size = input.getAttribute('size')
    }

    // populate it an empty value
    if (input.hasAttribute('emptyvalue')) {
      emptytext.value = input.getAttribute('emptyvalue')
    }

    // insert emptytext before input
    input.parentNode.insertBefore(emptytext, input)

    // give input a reference to its emptytext
    input.emptytext = emptytext

    // assume it doesn't currently have focus
    input.hasFocus = false

    // wrap prototype's clear function so we can catch this event and treat it like an empty blur
    input.oldclear = input.clear
    input.clear = function() {
      var r = input.oldclear();
      if (!input.hasFocus) onBlur(input);
      return r
    }

    // wrap focus function so we can trap the event
    input.oldfocus = input.focus
    input.focus = function() {
      onFocus(input);
      input.oldfocus();
    }

    // add focus listeners
    Event.observe(input, 'blur', function() { onBlur(input) })
    Event.observe(input, 'focus', function() { onFocus(input) })
    Event.observe(emptytext, 'focus', function() { input.focus() })

    emptyTextFields.push(input)
    var len = emptyTextFields.length

    // set initial state
    onBlur(input)

    // schedule a css scan
    setTimeout(function() {
      if (len == emptyTextFields.length) {
        scanAllCss()
      }
    }, 500)
  }

  Event.observe(window, 'load', scanAllCss)
  var emptyTextFields = new Array()

  /**
  * Called when focus leaves the input field.  Shows the empty text if the input 
  * field is empty
  */
  function onBlur(input) {
    if (input.value.length == 0) {
      input.emptytext.style.display = ''
      input.style.display = 'none'
    }
    else {
      input.emptytext.style.display = 'none'
      input.style.display = ''
    }
    input.hasFocus = false
  }

  /**
  * Called when the input field receives focus.  Hides the empty text, shows input field
  */
  function onFocus(input) {
    input.emptytext.style.display = 'none'
    input.style.display = ''
    input.hasFocus = true
  }

  /**
  * This function looks for any id-specific css selectors on the emptytext 
  * fields and adds corresponding css rules for the emptytext fields.
  * This allows css designers to style at the ID level and still have the 
  * emptytext field look right.  
  */
  function scanAllCss() {
    // see if any of our fields have an id.  If not, we can 
    // skip this step.
    var hasId = false
    for (var j = 0; j < emptyTextFields.length; j++) {
      var field = emptyTextFields[j]
      if (field.hasAttribute("id")) {
        hasId = true
        break
      }
    }
    if (hasId) {
      for (var i = 0; i < document.styleSheets.length; i++) {
        var ss = document.styleSheets[i]
        scanCss(ss)
      }
    }
  }

  function scanCss(ss) {
    try {
      // scan imports
      if (ss.imports) {
        for (var i = 0; i < ss.imports.length; i++) {
          scanCss(ss.imports[i])
        }
      }

      var rules;
      if (typeof (ss.cssRules) != "undefined") rules = ss.cssRules; // ff and safari
      else if (typeof (ss.rules) != "undefined") rules = ss.rules; // ID
      if (typeof (rules) != "undefined") {
        for (var i = 0; i < rules.length; i++) {
          var rule = rules[i]

          if (rule.styleSheet) // imported style sheet
          {
            scanCss(rule.styleSheet)
          }
          else {
            for (var j = 0; j < emptyTextFields.length; j++) {
              var field = emptyTextFields[j]
              if (field.hasAttribute("id")) {
                var id = field.id
                var rex = new RegExp("#" + id + "([^a-zA-Z0-9_-]|$)")

                if (rule.selectorText && rule.selectorText.search(rex) >= 0 && rule.selectorText.indexOf("-emptytext") == -1) {
                  var newRule = rule.selectorText.replace(rex, "#" + id + "-emptytext") + '{' + rule.style.cssText + '}'

                  addCss(newRule)
                }
              }
            }
          }
        }
      }
    }
    catch (e) {
      //console.log("Caught " + e)
    }
  }

  function addCss(cssCode) {
    var styleElement = document.createElement("style");
    styleElement.type = "text/css";
    if (styleElement.styleSheet) {
      styleElement.styleSheet.cssText = cssCode;
    } else {
      styleElement.appendChild(document.createTextNode(cssCode));
    }
    document.getElementsByTagName("head")[0].appendChild(styleElement);
  }

  function findLastInput() {
    var elem = document.body.lastChild
    while (elem) {
      if (elem.tagName.toLowerCase() == "script") {
        return findPreviousEmptyTextInput(elem)
      }
      elem = elem.lastChild
    }
    return null
  }

  function findPreviousTerminalNode(node) {
    if (node.previousSibling) {
      node = node.previousSibling
      while (node.lastChild) node = node.lastChild
      return node
    }
    else if (node.parentNode) return findPreviousTerminalNode(node.parentNode)
    else return null
  }

  function findPreviousEmptyTextInput(elem) {
    var node = $(findPreviousTerminalNode(elem))
    while (node && !(node.tagName && node.tagName.toLowerCase() == "input" && node.hasAttribute("emptyvalue"))) {
      node = $(findPreviousTerminalNode(node))
    }
    return node
  }

})() // end wrap

