johnthethird
3/11/2012 - 6:21 PM

Batman binding for executing script blocks in views

Batman binding for executing script blocks in views

# Execscripts.coffee
# John Lynch / Rigel Group, LLC
# Open source under the MIT License.
#
# Create a new binding that marks a specific node as having children scripts blocks, and then executes those script
# blocks as soon as the node has been added to the DOM by Batman. (This is necessary as many jQuery-type plugins 
# wont work if run on an isolated node before it has been added to the DOM.)
# 
# (Thanks to SO for some of this code. http://stackoverflow.com/questions/220188/how-can-i-determine-if-a-dynamically-created-dom-element-has-been-added-to-the-d)
#
# <div data-execscripts="true">
#   <h1>Title</h1>
#   <script type="text/javascript">
#     alert("Hi Mom!");
#   </script
# </div>
#
#

Batman.DOM.readers.execscripts = (node, key, context, renderer) ->
  new Batman.DOM.ExecscriptBinding(node, key, context, renderer)
  true

class Batman.DOM.ExecscriptBinding extends Batman.DOM.AbstractBinding
  bindImmediately: false
  constructor: ->
    super
    # Only run the script blocks once this node has been added to the DOM by Batman
    @_executeOnLoad(@node, @_exec_body_scripts) if @value

  _exec_body_scripts: (elem) =>
    scripts = []

    for child in elem.childNodes
      scripts.push(child) if @_isScriptNode(child)

    for script in scripts
      script.parentNode?.removeChild(script)
      @_evalScript(script)

  _isScriptNode: (elem) =>
    elem.nodeName && elem.nodeName.toLowerCase() == "script" && (!elem.type || elem.type.toLowerCase() == "text/javascript")

  _evalScript: (elem) =>
    data = (elem.text || elem.textContent || elem.innerHTML || "" )
    head = document.getElementsByTagName("head")[0] || document.documentElement
    script = document.createElement("script")
    script.type = "text/javascript"

    try
      script.appendChild(document.createTextNode(data))
    catch e
      script.text = data

    head.insertBefore(script, head.firstChild)
    head.removeChild(script)

  _isInDOMTree: (node) =>
   !! @_findUltimateAncestor(node).body

  _findUltimateAncestor: (node) =>
   ancestor = node
   (ancestor = ancestor.parentNode) while ancestor.parentNode
   ancestor

  _executeOnLoad: (node, func) =>
    if @_isInDOMTree(node) then func(node) else setTimeout( (() => @_executeOnLoad(node, func)), 100)