compiler/render.js

/**
 * @class Renderer
 */
function Renderer () {}

function render (ast, data) {
  let nodes = []
  ast.forEach((astNode) => {
    let node = null
    let type = astNode.type
    if (type === 'text') {
      node = document.createTextNode(astNode.text)
      this.handleTextNode(node, astNode, data)
    } else if (type === 'note') {
      node = document.createComment(astNode.note)
      this.handleNoteNode(node, astNode, data)
    } else if (type === 'tag') {
      node = document.createElement(astNode.tag)
      this.handleTagNode(node, astNode, data)
    }
    if (astNode.children) {
      let children = render.call(this, astNode.children, data)
      children.forEach(child => {
        node.appendChild(child)
      })
    }
    nodes.push(node)
  })
  return nodes
}

function replaceMatchesWord (str, data) {
  let content = str.match(/\{\{(.*)\}\}/)
  if (content !== null) {
    return content[1].replace(/(?<=[{[,+-/*\s.])\w+(?=[+-/*\s.,}\]])/g, (match, p1) => {
      return data[match] ? `data.${match}` : match
    })
  }
}

function myEval (expression, data) {
  var Fn = Function
  return new Fn('data', `return ${expression}`)(data)
}

/**
 * @description Renderer the ast, defined in Renderer.prototype
 * @for Renderer
 * @method
 * @param { Array } ast the ast Array
 * @param { Object } data the template data
 * @return { Array } the dom Array
 */
Renderer.prototype.render = render
/**
 * @description This is a callback to handle text node when Renderering,
 * override this function, you can handle text node as you like.
 * @param { obejct } node DOMElment || DOMNode
 * @param { object } astNode The ast expression of this node
 * @param { object } data the template data
 * @example
 * // you can use Tpl's overrideRenderer to override this function
 * var tpl = new Tpl('abcdefg', {
 *  textTip: 'text handle: '
 * })
 * tpl.overrideRenderer('handleTextNode', function (node, astNode, data) {
 *  let text = astNode.text
 *  node.nodeValue = data[textTip] + text
 * })
 * tpl.parse().Renderer().getDom()[0] // "text handle: abcdefg"
 */
Renderer.prototype.handleTextNode = function (node, astNode, data) {
  let text = astNode.text
  node.nodeValue = myEval(replaceMatchesWord(text, data), data) || text
}
/**
 * @description This is a callback to handle note node when Renderering,
 * override this function, you can handle note node as you like.
 * @param { obejct } node DOMElment || DOMNode
 * @param { object } astNode The ast expression of this node
 * @param { object } data the template data
 * @example
 * // the usage is simliar to handleTextNode
 */
Renderer.prototype.handleNoteNode = function (node, astNode, data) {
  let note = astNode.note
  node.nodeValue = myEval(replaceMatchesWord(note, data), data) || note
}
/**
 * @description This is a callback to handle tag node when Renderering,
 * override this function, you can handle tag node as you like.
 * @param { obejct } node DOMElment || DOMNode
 * @param { object } astNode The ast expression of this node
 * @param { object } data the template data
 * @example
 * // the usage is simliar to handleTextNode
 */
Renderer.prototype.handleTagNode = function (node, astNode, data) {
  let attrs = astNode.attrs
  if (attrs) {
    for (let attrKey in attrs) {
      let attr = attrs[attrKey]
      node.setAttribute(attrKey, myEval(replaceMatchesWord(attr, data), data) || attr)
    }
  }
}

export default Renderer