"
testEl.firstChild.innerHTML = ""
testEl.firstChild.innerHTML is ""
)()
# 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 and (->
testEl = document.createElement("div")
testEl.innerHTML = "Test: Value"
testEl.childNodes[0].nodeValue is "Test:" and testEl.childNodes[2].nodeValue is " Value"
)()
# Constructor that supports either Metamorph('foo') or new
# Metamorph('foo');
#
# Takes a string of HTML as the argument.
Metamorph = (html) ->
self = undefined
if this instanceof Metamorph
self = this
else
self = new K()
self.innerHTML = html
myGuid = "metamorph-" + (guid++)
self.start = myGuid + "-start"
self.end = myGuid + "-end"
self
K:: = Metamorph::
rangeFor = undefined
htmlFunc = undefined
removeFunc = undefined
outerHTMLFunc = undefined
appendToFunc = undefined
afterFunc = undefined
prependFunc = undefined
startTagFunc = undefined
endTagFunc = undefined
outerHTMLFunc = ->
@startTag() + @innerHTML + @endTag()
startTagFunc = ->
#
# * 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
#
""
endTagFunc = ->
#
# * 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
#
""
# 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 = (morph, outerToo) ->
range = document.createRange()
before = document.getElementById(morph.start)
after = document.getElementById(morph.end)
if outerToo
range.setStartBefore before
range.setEndAfter after
else
range.setStartAfter before
range.setEndBefore after
range
htmlFunc = (html, outerToo) ->
# get a range for the current metamorph object
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
fragment = range.createContextualFragment(html)
# insert the fragment into the range
range.insertNode fragment
removeFunc = ->
# get a range for the current metamorph object including
# the starting and ending placeholders.
range = rangeFor(this, true)
# delete the entire range.
range.deleteContents()
appendToFunc = (node) ->
range = document.createRange()
range.setStart node
range.collapse false
frag = range.createContextualFragment(@outerHTML())
node.appendChild frag
afterFunc = (html) ->
range = document.createRange()
after = document.getElementById(@end)
range.setStartAfter after
range.setEndAfter after
fragment = range.createContextualFragment(html)
range.insertNode fragment
prependFunc = (html) ->
range = document.createRange()
start = document.getElementById(@start)
range.setStartAfter start
range.setEndAfter start
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.
###
wrapMap =
select: [1, ""]
fieldset: [1, ""]
table: [1, "
", "
"]
tbody: [2, "
", "
"]
tr: [3, "
", "
"]
colgroup: [2, "
", "
"]
map: [1, ""]
_default: [0, "", ""]
findChildById = (element, id) ->
return element if element.getAttribute("id") is id
len = element.childNodes.length
idx = undefined
node = undefined
found = undefined
idx = 0
while idx < len
node = element.childNodes[idx]
found = node.nodeType is 1 and findChildById(node, id)
return found if found
idx++
setInnerHTML = (element, html) ->
matches = []
if movesWhitespace
# Right now we only check for script tags with ids with the
# goal of targeting morphs.
html = html.replace(/(\s+)(
hi
";
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.
###
realNode = (start) ->
start = start.parentNode while start.parentNode.tagName is ""
start
###
When automatically adding a tbody, Internet Explorer inserts the
tbody immediately before the first
. Other browsers create it
before the first node, no matter what.
This means the the following code:
div = document.createElement("div");
div.innerHTML = "
hi
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.
###
fixParentage = (start, end) ->
end.parentNode.insertBefore start, end.parentNode.firstChild if start.parentNode isnt end.parentNode
htmlFunc = (html, outerToo) ->
# get the real starting node. see realNode for details.
start = realNode(document.getElementById(@start))
end = document.getElementById(@end)
parentNode = end.parentNode
node = undefined
nextSibling = undefined
last = undefined
# 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 is 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.
break if last
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 = ->
start = realNode(document.getElementById(@start))
end = document.getElementById(@end)
@html ""
start.parentNode.removeChild start
end.parentNode.removeChild end
appendToFunc = (parentNode) ->
node = firstNodeFor(parentNode, @outerHTML())
nextSibling = undefined
while node
nextSibling = node.nextSibling
parentNode.appendChild node
node = nextSibling
afterFunc = (html) ->
# get the real starting node. see realNode for details.
end = document.getElementById(@end)
insertBefore = end.nextSibling
parentNode = end.parentNode
nextSibling = undefined
node = undefined
# 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 = (html) ->
start = document.getElementById(@start)
parentNode = start.parentNode
nextSibling = undefined
node = undefined
node = firstNodeFor(parentNode, html)
insertBefore = start.nextSibling
while node
nextSibling = node.nextSibling
parentNode.insertBefore node, insertBefore
node = nextSibling
Metamorph::html = (html) ->
@checkRemoved()
return @innerHTML if html is `undefined`
htmlFunc.call this, html
@innerHTML = html
Metamorph::replaceWith = (html) ->
@checkRemoved()
htmlFunc.call this, html, true
Metamorph::remove = removeFunc
Metamorph::outerHTML = outerHTMLFunc
Metamorph::appendTo = appendToFunc
Metamorph::after = afterFunc
Metamorph::prepend = prependFunc
Metamorph::startTag = startTagFunc
Metamorph::endTag = endTagFunc
Metamorph::isRemoved = ->
before = document.getElementById(@start)
after = document.getElementById(@end)
not before or not after
Metamorph::checkRemoved = ->
throw new Error("Cannot perform operations on a Metamorph that is not in the DOM.") if @isRemoved()
window.Metamorph = Metamorph
) this