DomUtils =
#
# Runs :callback if the DOM has loaded, otherwise runs it on load
#
documentReady: do ->
loaded = false
window.addEventListener("DOMContentLoaded", -> loaded = true)
(callback) -> if loaded then callback() else window.addEventListener("DOMContentLoaded", callback)
#
# Adds a list of elements to a page.
# Note that adding these nodes all at once (via the parent div) is significantly faster than one-by-one.
#
addElementList: (els, overlayOptions) ->
parent = document.createElement("div")
parent.id = overlayOptions.id if overlayOptions.id?
parent.className = overlayOptions.className if overlayOptions.className?
parent.appendChild(el) for el in els
document.documentElement.appendChild(parent)
parent
#
# Remove an element from its DOM tree.
#
removeElement: (el) -> el.parentNode.removeChild el
#
# Takes an array of XPath selectors, adds the necessary namespaces (currently only XHTML), and applies them
# to the document root. The namespaceResolver in evaluateXPath should be kept in sync with the namespaces
# here.
#
makeXPath: (elementArray) ->
xpath = []
for element in elementArray
xpath.push(".//" + element, ".//xhtml:" + element)
xpath.join(" | ")
# Evaluates an XPath on the whole document, or on the contents of the fullscreen element if an element is
# fullscreen.
evaluateXPath: (xpath, resultType) ->
contextNode =
if document.webkitIsFullScreen then document.webkitFullscreenElement else document.documentElement
namespaceResolver = (namespace) ->
if (namespace == "xhtml") then "http://www.w3.org/1999/xhtml" else null
document.evaluate(xpath, contextNode, namespaceResolver, resultType, null)
#
# Returns the first visible clientRect of an element if it exists. Otherwise it returns null.
#
getVisibleClientRect: (element) ->
# Note: this call will be expensive if we modify the DOM in between calls.
clientRects = (Rect.copy clientRect for clientRect in element.getClientRects())
for clientRect in clientRects
# If the link has zero dimensions, it may be wrapping visible
# but floated elements. Check for this.
if (clientRect.width == 0 || clientRect.height == 0)
for child in element.children
computedStyle = window.getComputedStyle(child, null)
# Ignore child elements which are not floated and not absolutely positioned for parent elements with
# zero width/height
continue if (computedStyle.getPropertyValue('float') == 'none' &&
computedStyle.getPropertyValue('position') != 'absolute')
childClientRect = @getVisibleClientRect(child)
continue if childClientRect == null or childClientRect.width < 3 or childClientRect.height < 3
return childClientRect
else
clientRect = @cropRectToVisible clientRect
continue if clientRect == null or clientRect.width < 3 or clientRect.height < 3
# eliminate invisible elements (see test_harnesses/visibility_test.html)
computedStyle = window.getComputedStyle(element, null)
if (computedStyle.getPropertyValue('visibility') != 'visible' ||
computedStyle.getPropertyValue('display') == 'none')
continue
return clientRect
null
#
# Bounds the rect by the current viewport dimensions. If the rect is offscreen or has a height or width < 3
# then null is returned instead of a rect.
#
cropRectToVisible: (rect) ->
boundedRect = Rect.create(
Math.max(rect.left, 0)
Math.max(rect.top, 0)
rect.right
rect.bottom
)
if boundedRect.top >= window.innerHeight - 4 or boundedRect.left >= window.innerWidth - 4
null
else
boundedRect
#
# Get the client rects for the elements in a