text: define("metamorph",
    [],
    function() {
      "use strict";
      // ==========================================================================
      // Project:   metamorph
      // Copyright: ©2011 My Company Inc. All rights reserved.
      // ==========================================================================
  
      var K = function() {},
          guid = 0,
          document = this.document,
          disableRange = ('undefined' === typeof ENV ? {} : ENV).DISABLE_RANGE_API,
  
          // Feature-detect the W3C range API, the extended check is for IE9 which only partially supports ranges
          supportsRange = (!disableRange) && document && ('createRange' in document) && (typeof Range !== 'undefined') && Range.prototype.createContextualFragment,
  
          // Internet Explorer prior to 9 does not allow setting innerHTML if the first element
          // is a "zero-scope" element. This problem can be worked around by making
          // the first node an invisible text node. We, like Modernizr, use &shy;
          needsShy = document && (function() {
            var testEl = document.createElement('div');
            testEl.innerHTML = "<div></div>";
            testEl.firstChild.innerHTML = "<script></script>";
            return testEl.firstChild.innerHTML === '';
          })(),
  
  
          // IE 8 (and likely earlier) likes to move whitespace preceeding
          // a script tag to appear after it. This means that we can
          // accidentally remove whitespace when updating a morph.
          movesWhitespace = document && (function() {
            var testEl = document.createElement('div');
            testEl.innerHTML = "Test: <script type='text/x-placeholder'></script>Value";
            return testEl.childNodes[0].nodeValue === 'Test:' &&
                    testEl.childNodes[2].nodeValue === ' Value';
          })();
  
      // Constructor that supports either Metamorph('foo') or new
      // Metamorph('foo');
      //
      // Takes a string of HTML as the argument.
  
      var Metamorph = function(html) {
        var self;
  
        if (this instanceof Metamorph) {
          self = this;
        } else {
          self = new K();
        }
  
        self.innerHTML = html;
        var myGuid = 'metamorph-'+(guid++);
        self.start = myGuid + '-start';
        self.end = myGuid + '-end';
  
        return self;
      };
  
      K.prototype = Metamorph.prototype;
  
      var rangeFor, htmlFunc, removeFunc, outerHTMLFunc, appendToFunc, afterFunc, prependFunc, startTagFunc, endTagFunc;
  
      outerHTMLFunc = function() {
        return this.startTag() + this.innerHTML + this.endTag();
      };
  
      startTagFunc = function() {
        /*
         * We replace chevron by its hex code in order to prevent escaping problems.
         * Check this thread for more explaination:
         * http://stackoverflow.com/questions/8231048/why-use-x3c-instead-of-when-generating-html-from-javascript
         */
        return "<script id='" + this.start + "' type='text/x-placeholder'>\x3C/script>";
      };
  
      endTagFunc = function() {
        /*
         * We replace chevron by its hex code in order to prevent escaping problems.
         * Check this thread for more explaination:
         * http://stackoverflow.com/questions/8231048/why-use-x3c-instead-of-when-generating-html-from-javascript
         */
        return "<script id='" + this.end + "' type='text/x-placeholder'>\x3C/script>";
      };
  
      // If we have the W3C range API, this process is relatively straight forward.
      if (supportsRange) {
  
        // Get a range for the current morph. Optionally include the starting and
        // ending placeholders.
        rangeFor = function(morph, outerToo) {
          var range = document.createRange();
          var before = document.getElementById(morph.start);
          var after = document.getElementById(morph.end);
  
          if (outerToo) {
            range.setStartBefore(before);
            range.setEndAfter(after);
          } else {
            range.setStartAfter(before);
            range.setEndBefore(after);
          }
  
          return range;
        };
  
        htmlFunc = function(html, outerToo) {
          // get a range for the current metamorph object
          var range = rangeFor(this, outerToo);
  
          // delete the contents of the range, which will be the
          // nodes between the starting and ending placeholder.
          range.deleteContents();
  
          // create a new document fragment for the HTML
          var fragment = range.createContextualFragment(html);
  
          // insert the fragment into the range
          range.insertNode(fragment);
        };
  
        /**
        * @public
        * 
        * Remove this object (including starting and ending
        * placeholders).
        *
        * @method remove
        */
        removeFunc = function() {
          // get a range for the current metamorph object including
          // the starting and ending placeholders.
          var range = rangeFor(this, true);
  
          // delete the entire range.
          range.deleteContents();
        };
  
        appendToFunc = function(node) {
          var range = document.createRange();
          range.setStart(node);
          range.collapse(false);
          var frag = range.createContextualFragment(this.outerHTML());
          node.appendChild(frag);
        };
  
        afterFunc = function(html) {
          var range = document.createRange();
          var after = document.getElementById(this.end);
  
          range.setStartAfter(after);
          range.setEndAfter(after);
  
          var fragment = range.createContextualFragment(html);
          range.insertNode(fragment);
        };
  
        prependFunc = function(html) {
          var range = document.createRange();
          var start = document.getElementById(this.start);
  
          range.setStartAfter(start);
          range.setEndAfter(start);
  
          var fragment = range.createContextualFragment(html);
          range.insertNode(fragment);
        };
  
      } else {
        /*
         * This code is mostly taken from jQuery, with one exception. In jQuery's case, we
         * have some HTML and we need to figure out how to convert it into some nodes.
         *
         * In this case, jQuery needs to scan the HTML looking for an opening tag and use
         * that as the key for the wrap map. In our case, we know the parent node, and
         * can use its type as the key for the wrap map.
         **/
        var wrapMap = {
          select: [ 1, "<select multiple='multiple'>", "</select>" ],
          fieldset: [ 1, "<fieldset>", "</fieldset>" ],
          table: [ 1, "<table>", "</table>" ],
          tbody: [ 2, "<table><tbody>", "</tbody></table>" ],
          tr: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
          colgroup: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
          map: [ 1, "<map>", "</map>" ],
          _default: [ 0, "", "" ]
        };
  
        var findChildById = function(element, id) {
          if (element.getAttribute('id') === id) { return element; }
  
          var len = element.childNodes.length, idx, node, found;
          for (idx=0; idx<len; idx++) {
            node = element.childNodes[idx];
            found = node.nodeType === 1 && findChildById(node, id);
            if (found) { return found; }
          }
        };
  
        var setInnerHTML = function(element, html) {
          var matches = [];
          if (movesWhitespace) {
            // Right now we only check for script tags with ids with the
            // goal of targeting morphs.
            html = html.replace(/(\s+)(<script id='([^']+)')/g, function(match, spaces, tag, id) {
              matches.push([id, spaces]);
              return tag;
            });
          }
  
          element.innerHTML = html;
  
          // If we have to do any whitespace adjustments do them now
          if (matches.length > 0) {
            var len = matches.length, idx;
            for (idx=0; idx<len; idx++) {
              var script = findChildById(element, matches[idx][0]),
                  node = document.createTextNode(matches[idx][1]);
              script.parentNode.insertBefore(node, script);
            }
          }
        };
  
        /*
         * Given a parent node and some HTML, generate a set of nodes. Return the first
         * node, which will allow us to traverse the rest using nextSibling.
         *
         * We need to do this because innerHTML in IE does not really parse the nodes.
         */
        var firstNodeFor = function(parentNode, html) {
          var arr = wrapMap[parentNode.tagName.toLowerCase()] || wrapMap._default;
          var depth = arr[0], start = arr[1], end = arr[2];
  
          if (needsShy) { html = '&shy;'+html; }
  
          var element = document.createElement('div');
  
          setInnerHTML(element, start + html + end);
  
          for (var i=0; i<=depth; i++) {
            element = element.firstChild;
          }
  
          // Look for &shy; to remove it.
          if (needsShy) {
            var shyElement = element;
  
            // Sometimes we get nameless elements with the shy inside
            while (shyElement.nodeType === 1 && !shyElement.nodeName) {
              shyElement = shyElement.firstChild;
            }
  
            // At this point it's the actual unicode character.
            if (shyElement.nodeType === 3 && shyElement.nodeValue.charAt(0) === "\u00AD") {
              shyElement.nodeValue = shyElement.nodeValue.slice(1);
            }
          }
  
          return element;
        };
  
        /*
         * In some cases, Internet Explorer can create an anonymous node in
         * the hierarchy with no tagName. You can create this scenario via:
         *
         *     div = document.createElement("div");
         *     div.innerHTML = "<table>&shy<script></script><tr><td>hi</td></tr></table>";
         *     div.firstChild.firstChild.tagName //=> ""
         *
         * If our script markers are inside such a node, we need to find that
         * node and use *it* as the marker.
         */
        var realNode = function(start) {
          while (start.parentNode.tagName === "") {
            start = start.parentNode;
          }
  
          return start;
        };
  
        /*
         * When automatically adding a tbody, Internet Explorer inserts the
         * tbody immediately before the first <tr>. Other browsers create it
         * before the first node, no matter what.
         *
         * This means the the following code:
         *
         *     div = document.createElement("div");
         *     div.innerHTML = "<table><script id='first'></script><tr><td>hi</td></tr><script id='last'></script></table>
         *
         * Generates the following DOM in IE:
         *
         *     + div
         *       + table
         *         - script id='first'
         *         + tbody
         *           + tr
         *             + td
         *               - "hi"
         *           - script id='last'
         *
         * Which means that the two script tags, even though they were
         * inserted at the same point in the hierarchy in the original
         * HTML, now have different parents.
         *
         * This code reparents the first script tag by making it the tbody's
         * first child.
         *
         */
        var fixParentage = function(start, end) {
          if (start.parentNode !== end.parentNode) {
            end.parentNode.insertBefore(start, end.parentNode.firstChild);
          }
        };
  
        htmlFunc = function(html, outerToo) {
          // get the real starting node. see realNode for details.
          var start = realNode(document.getElementById(this.start));
          var end = document.getElementById(this.end);
          var parentNode = end.parentNode;
          var node, nextSibling, last;
  
          // make sure that the start and end nodes share the same
          // parent. If not, fix it.
          fixParentage(start, end);
  
          // remove all of the nodes after the starting placeholder and
          // before the ending placeholder.
          node = start.nextSibling;
          while (node) {
            nextSibling = node.nextSibling;
            last = node === end;
  
            // if this is the last node, and we want to remove it as well,
            // set the `end` node to the next sibling. This is because
            // for the rest of the function, we insert the new nodes
            // before the end (note that insertBefore(node, null) is
            // the same as appendChild(node)).
            //
            // if we do not want to remove it, just break.
            if (last) {
              if (outerToo) { end = node.nextSibling; } else { break; }
            }
  
            node.parentNode.removeChild(node);
  
            // if this is the last node and we didn't break before
            // (because we wanted to remove the outer nodes), break
            // now.
            if (last) { break; }
  
            node = nextSibling;
          }
  
          // get the first node for the HTML string, even in cases like
          // tables and lists where a simple innerHTML on a div would
          // swallow some of the content.
          node = firstNodeFor(start.parentNode, html);
  
          // copy the nodes for the HTML between the starting and ending
          // placeholder.
          while (node) {
            nextSibling = node.nextSibling;
            parentNode.insertBefore(node, end);
            node = nextSibling;
          }
        };
  
        // remove the nodes in the DOM representing this metamorph.
        //
        // this includes the starting and ending placeholders.
        removeFunc = function() {
          var start = realNode(document.getElementById(this.start));
          var end = document.getElementById(this.end);
  
          this.html('');
          start.parentNode.removeChild(start);
          end.parentNode.removeChild(end);
        };
  
        appendToFunc = function(parentNode) {
          var node = firstNodeFor(parentNode, this.outerHTML());
          var nextSibling;
  
          while (node) {
            nextSibling = node.nextSibling;
            parentNode.appendChild(node);
            node = nextSibling;
          }
        };
  
        afterFunc = function(html) {
          // get the real starting node. see realNode for details.
          var end = document.getElementById(this.end);
          var insertBefore = end.nextSibling;
          var parentNode = end.parentNode;
          var nextSibling;
          var node;
  
          // get the first node for the HTML string, even in cases like
          // tables and lists where a simple innerHTML on a div would
          // swallow some of the content.
          node = firstNodeFor(parentNode, html);
  
          // copy the nodes for the HTML between the starting and ending
          // placeholder.
          while (node) {
            nextSibling = node.nextSibling;
            parentNode.insertBefore(node, insertBefore);
            node = nextSibling;
          }
        };
  
        prependFunc = function(html) {
          var start = document.getElementById(this.start);
          var parentNode = start.parentNode;
          var nextSibling;
          var node;
  
          node = firstNodeFor(parentNode, html);
          var insertBefore = start.nextSibling;
  
          while (node) {
            nextSibling = node.nextSibling;
            parentNode.insertBefore(node, insertBefore);
            node = nextSibling;
          }
        };
      }
  
      Metamorph.prototype.html = function(html) {
        this.checkRemoved();
        if (html === undefined) { return this.innerHTML; }
  
        htmlFunc.call(this, html);
  
        this.innerHTML = html;
      };
  
      Metamorph.prototype.replaceWith = function(html) {
        this.checkRemoved();
        htmlFunc.call(this, html, true);
      };
  
      Metamorph.prototype.remove = removeFunc;
      Metamorph.prototype.outerHTML = outerHTMLFunc;
      Metamorph.prototype.appendTo = appendToFunc;
      Metamorph.prototype.after = afterFunc;
      Metamorph.prototype.prepend = prependFunc;
      Metamorph.prototype.startTag = startTagFunc;
      Metamorph.prototype.endTag = endTagFunc;
  
      Metamorph.prototype.isRemoved = function() {
        var before = document.getElementById(this.start);
        var after = document.getElementById(this.end);
  
        return !before || !after;
      };
  
      Metamorph.prototype.checkRemoved = function() {
        if (this.isRemoved()) {
          throw new Error("Cannot perform operations on a Metamorph that is not in the DOM.");
        }
      };
  
      return Metamorph;
    });
  