text: /* 
   * The MIT License
   *
   * Copyright (c) 2012 James Allardice
   *
   * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 
   * to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
   * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
   *
   * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
   *
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
   * THE SOFTWARE.
   */
  
  // Defines the global Placeholders object along with various utility methods
  (function (global) {
  
      "use strict";
  
      // Cross-browser DOM event binding
      function addEventListener(elem, event, fn) {
          if (elem.addEventListener) {
              return elem.addEventListener(event, fn, false);
          }
          if (elem.attachEvent) {
              return elem.attachEvent("on" + event, fn);
          }
      }
  
      // Check whether an item is in an array (we don't use Array.prototype.indexOf so we don't clobber any existing polyfills - this is a really simple alternative)
      function inArray(arr, item) {
          var i, len;
          for (i = 0, len = arr.length; i < len; i++) {
              if (arr[i] === item) {
                  return true;
              }
          }
          return false;
      }
  
      // Move the caret to the index position specified. Assumes that the element has focus
      function moveCaret(elem, index) {
          var range;
          if (elem.createTextRange) {
              range = elem.createTextRange();
              range.move("character", index);
              range.select();
          } else if (elem.selectionStart) {
              elem.focus();
              elem.setSelectionRange(index, index);
          }
      }
  
      // Attempt to change the type property of an input element
      function changeType(elem, type) {
          try {
              elem.type = type;
              return true;
          } catch (e) {
              // You can't change input type in IE8 and below
              return false;
          }
      }
  
      // Expose public methods
      global.Placeholders = {
          Utils: {
              addEventListener: addEventListener,
              inArray: inArray,
              moveCaret: moveCaret,
              changeType: changeType
          }
      };
  
  }(this));
  
  (function (global) {
  
      "use strict";
  
      var validTypes = [
              "text",
              "search",
              "url",
              "tel",
              "email",
              "password",
              "number",
              "textarea"
          ],
  
          // The list of keycodes that are not allowed when the polyfill is configured to hide-on-input
          badKeys = [
  
              // The following keys all cause the caret to jump to the end of the input value
              27, // Escape
              33, // Page up
              34, // Page down
              35, // End
              36, // Home
  
              // Arrow keys allow you to move the caret manually, which should be prevented when the placeholder is visible
              37, // Left
              38, // Up
              39, // Right
              40, // Down
  
              // The following keys allow you to modify the placeholder text by removing characters, which should be prevented when the placeholder is visible
              8, // Backspace
              46 // Delete
          ],
  
          // Styling variables
          placeholderStyleColor = "#ccc",
          placeholderClassName = "placeholdersjs",
          classNameRegExp = new RegExp("(?:^|\\s)" + placeholderClassName + "(?!\\S)"),
  
          // These will hold references to all elements that can be affected. NodeList objects are live, so we only need to get those references once
          inputs, textareas,
  
          // The various data-* attributes used by the polyfill
          ATTR_CURRENT_VAL = "data-placeholder-value",
          ATTR_ACTIVE = "data-placeholder-active",
          ATTR_INPUT_TYPE = "data-placeholder-type",
          ATTR_FORM_HANDLED = "data-placeholder-submit",
          ATTR_EVENTS_BOUND = "data-placeholder-bound",
          ATTR_OPTION_FOCUS = "data-placeholder-focus",
          ATTR_OPTION_LIVE = "data-placeholder-live",
  
          // Various other variables used throughout the rest of the script
          test = document.createElement("input"),
          head = document.getElementsByTagName("head")[0],
          root = document.documentElement,
          Placeholders = global.Placeholders,
          Utils = Placeholders.Utils,
          hideOnInput, liveUpdates, keydownVal, styleElem, styleRules, placeholder, timer, form, elem, len, i;
  
      // No-op (used in place of public methods when native support is detected)
      function noop() {}
  
      // Hide the placeholder value on a single element. Returns true if the placeholder was hidden and false if it was not (because it wasn't visible in the first place)
      function hidePlaceholder(elem) {
          var type;
          if (elem.value === elem.getAttribute(ATTR_CURRENT_VAL) && elem.getAttribute(ATTR_ACTIVE) === "true") {
              elem.setAttribute(ATTR_ACTIVE, "false");
              elem.value = "";
              elem.className = elem.className.replace(classNameRegExp, "");
  
              // If the polyfill has changed the type of the element we need to change it back
              type = elem.getAttribute(ATTR_INPUT_TYPE);
              if (type) {
                  elem.type = type;
              }
              return true;
          }
          return false;
      }
  
      // Show the placeholder value on a single element. Returns true if the placeholder was shown and false if it was not (because it was already visible)
      function showPlaceholder(elem) {
          var type,
              val = elem.getAttribute(ATTR_CURRENT_VAL);
          if (elem.value === "" && val) {
              elem.setAttribute(ATTR_ACTIVE, "true");
              elem.value = val;
              elem.className += " " + placeholderClassName;
  
              // If the type of element needs to change, change it (e.g. password inputs)
              type = elem.getAttribute(ATTR_INPUT_TYPE);
              if (type) {
                  elem.type = "text";
              } else if (elem.type === "password") {
                  if (Utils.changeType(elem, "text")) {
                      elem.setAttribute(ATTR_INPUT_TYPE, "password");
                  }
              }
              return true;
          }
          return false;
      }
  
      function handleElem(node, callback) {
  
          var handleInputs, handleTextareas, elem, len, i;
  
          // Check if the passed in node is an input/textarea (in which case it can't have any affected descendants)
          if (node && node.getAttribute(ATTR_CURRENT_VAL)) {
              callback(node);
          } else {
  
              // If an element was passed in, get all affected descendants. Otherwise, get all affected elements in document
              handleInputs = node ? node.getElementsByTagName("input") : inputs;
              handleTextareas = node ? node.getElementsByTagName("textarea") : textareas;
  
              // Run the callback for each element
              for (i = 0, len = handleInputs.length + handleTextareas.length; i < len; i++) {
                  elem = i < handleInputs.length ? handleInputs[i] : handleTextareas[i - handleInputs.length];
                  callback(elem);
              }
          }
      }
  
      // Return all affected elements to their normal state (remove placeholder value if present)
      function disablePlaceholders(node) {
          handleElem(node, hidePlaceholder);
      }
  
      // Show the placeholder value on all appropriate elements
      function enablePlaceholders(node) {
          handleElem(node, showPlaceholder);
      }
  
      // Returns a function that is used as a focus event handler
      function makeFocusHandler(elem) {
          return function () {
  
              // Only hide the placeholder value if the (default) hide-on-focus behaviour is enabled
              if (hideOnInput && elem.value === elem.getAttribute(ATTR_CURRENT_VAL) && elem.getAttribute(ATTR_ACTIVE) === "true") {
  
                  // Move the caret to the start of the input (this mimics the behaviour of all browsers that do not hide the placeholder on focus)
                  Utils.moveCaret(elem, 0);
  
              } else {
  
                  // Remove the placeholder
                  hidePlaceholder(elem);
              }
          };
      }
  
      // Returns a function that is used as a blur event handler
      function makeBlurHandler(elem) {
          return function () {
              showPlaceholder(elem);
          };
      }
  
      // Functions that are used as a event handlers when the hide-on-input behaviour has been activated - very basic implementation of the "input" event
      function makeKeydownHandler(elem) {
          return function (e) {
              keydownVal = elem.value;
  
              //Prevent the use of the arrow keys (try to keep the cursor before the placeholder)
              if (elem.getAttribute(ATTR_ACTIVE) === "true") {
                  if (keydownVal === elem.getAttribute(ATTR_CURRENT_VAL) && Utils.inArray(badKeys, e.keyCode)) {
                      if (e.preventDefault) {
                          e.preventDefault();
                      }
                      return false;
                  }
              }
          };
      }
      function makeKeyupHandler(elem) {
          return function () {
              var type;
  
              if (elem.getAttribute(ATTR_ACTIVE) === "true" && elem.value !== keydownVal) {
  
                  // Remove the placeholder
                  elem.className = elem.className.replace(classNameRegExp, "");
                  elem.value = elem.value.replace(elem.getAttribute(ATTR_CURRENT_VAL), "");
                  elem.setAttribute(ATTR_ACTIVE, false);
  
                  // If the type of element needs to change, change it (e.g. password inputs)
                  type = elem.getAttribute(ATTR_INPUT_TYPE);
                  if (type) {
                      elem.type = type;
                  }
              }
  
              // If the element is now empty we need to show the placeholder
              if (elem.value === "") {
                  elem.blur();
                  Utils.moveCaret(elem, 0);
              }
          };
      }
      function makeClickHandler(elem) {
          return function () {
              if (elem === document.activeElement && elem.value === elem.getAttribute(ATTR_CURRENT_VAL) && elem.getAttribute(ATTR_ACTIVE) === "true") {
                  Utils.moveCaret(elem, 0);
              }
          };
      }
  
      // Returns a function that is used as a submit event handler on form elements that have children affected by this polyfill
      function makeSubmitHandler(form) {
          return function () {
  
              // Turn off placeholders on all appropriate descendant elements
              disablePlaceholders(form);
          };
      }
  
      // Bind event handlers to an element that we need to affect with the polyfill
      function newElement(elem) {
  
          // If the element is part of a form, make sure the placeholder string is not submitted as a value
          if (elem.form) {
              form = elem.form;
  
              // Set a flag on the form so we know it's been handled (forms can contain multiple inputs)
              if (!form.getAttribute(ATTR_FORM_HANDLED)) {
                  Utils.addEventListener(form, "submit", makeSubmitHandler(form));
                  form.setAttribute(ATTR_FORM_HANDLED, "true");
              }
          }
  
          // Bind event handlers to the element so we can hide/show the placeholder as appropriate
          Utils.addEventListener(elem, "focus", makeFocusHandler(elem));
          Utils.addEventListener(elem, "blur", makeBlurHandler(elem));
  
          // If the placeholder should hide on input rather than on focus we need additional event handlers
          if (hideOnInput) {
              Utils.addEventListener(elem, "keydown", makeKeydownHandler(elem));
              Utils.addEventListener(elem, "keyup", makeKeyupHandler(elem));
              Utils.addEventListener(elem, "click", makeClickHandler(elem));
          }
  
          // Remember that we've bound event handlers to this element
          elem.setAttribute(ATTR_EVENTS_BOUND, "true");
          elem.setAttribute(ATTR_CURRENT_VAL, placeholder);
  
          // If the element doesn't have a value, set it to the placeholder string
          showPlaceholder(elem);
      }
  
      Placeholders.nativeSupport = test.placeholder !== void 0;
  
      if (!Placeholders.nativeSupport) {
  
          // Get references to all the input and textarea elements currently in the DOM (live NodeList objects to we only need to do this once)
          inputs = document.getElementsByTagName("input");
          textareas = document.getElementsByTagName("textarea");
  
          // Get any settings declared as data-* attributes on the root element (currently the only options are whether to hide the placeholder on focus or input and whether to auto-update)
          hideOnInput = root.getAttribute(ATTR_OPTION_FOCUS) === "false";
          liveUpdates = root.getAttribute(ATTR_OPTION_LIVE) !== "false";
  
          // Create style element for placeholder styles (instead of directly setting style properties on elements - allows for better flexibility alongside user-defined styles)
          styleElem = document.createElement("style");
          styleElem.type = "text/css";
  
          // Create style rules as text node
          styleRules = document.createTextNode("." + placeholderClassName + " { color:" + placeholderStyleColor + "; }");
  
          // Append style rules to newly created stylesheet
          if (styleElem.styleSheet) {
              styleElem.styleSheet.cssText = styleRules.nodeValue;
          } else {
              styleElem.appendChild(styleRules);
          }
  
          // Prepend new style element to the head (before any existing stylesheets, so user-defined rules take precedence)
          head.insertBefore(styleElem, head.firstChild);
  
          // Set up the placeholders
          for (i = 0, len = inputs.length + textareas.length; i < len; i++) {
              elem = i < inputs.length ? inputs[i] : textareas[i - inputs.length];
  
              // Get the value of the placeholder attribute, if any. IE10 emulating IE7 fails with getAttribute, hence the use of the attributes node
              placeholder = elem.attributes.placeholder;
              if (placeholder) {
  
                  // IE returns an empty object instead of undefined if the attribute is not present
                  placeholder = placeholder.nodeValue;
  
                  // Only apply the polyfill if this element is of a type that supports placeholders, and has a placeholder attribute with a non-empty value
                  if (placeholder && Utils.inArray(validTypes, elem.type)) {
                      newElement(elem);
                  }
              }
          }
  
          // If enabled, the polyfill will repeatedly check for changed/added elements and apply to those as well
          timer = setInterval(function () {
              for (i = 0, len = inputs.length + textareas.length; i < len; i++) {
                  elem = i < inputs.length ? inputs[i] : textareas[i - inputs.length];
  
                  // Only apply the polyfill if this element is of a type that supports placeholders, and has a placeholder attribute with a non-empty value
                  placeholder = elem.attributes.placeholder;
                  if (placeholder) {
                      placeholder = placeholder.nodeValue;
                      if (placeholder && Utils.inArray(validTypes, elem.type)) {
  
                          // If the element hasn't had event handlers bound to it then add them
                          if (!elem.getAttribute(ATTR_EVENTS_BOUND)) {
                              newElement(elem);
                          }
  
                          // If the placeholder value has changed or not been initialised yet we need to update the display
                          if (placeholder !== elem.getAttribute(ATTR_CURRENT_VAL) || (elem.type === "password" && !elem.getAttribute(ATTR_INPUT_TYPE))) {
  
                              // Attempt to change the type of password inputs (fails in IE < 9)
                              if (elem.type === "password" && !elem.getAttribute(ATTR_INPUT_TYPE) && Utils.changeType(elem, "text")) {
                                  elem.setAttribute(ATTR_INPUT_TYPE, "password");
                              }
  
                              // If the placeholder value has changed and the placeholder is currently on display we need to change it
                              if (elem.value === elem.getAttribute(ATTR_CURRENT_VAL)) {
                                  elem.value = placeholder;
                              }
  
                              // Keep a reference to the current placeholder value in case it changes via another script
                              elem.setAttribute(ATTR_CURRENT_VAL, placeholder);
                          }
                      }
                  }
              }
  
              // If live updates are not enabled cancel the timer
              if (!liveUpdates) {
                  clearInterval(timer);
              }
          }, 100);
      }
  
      // Expose public methods
      Placeholders.disable = Placeholders.nativeSupport ? noop : disablePlaceholders;
      Placeholders.enable = Placeholders.nativeSupport ? noop : enablePlaceholders;
  
  }(this));
  