⌈⌋ ⎇ branch:  Bitrhythm


Artifact Content

Artifact 21ef6a1b47e4938af1695d77cf62895961a1f8bab9c43f1c6bf56a554d0c35cd:


import _each from './each'
import { tmpl } from 'riot-tmpl'
import {
  CONDITIONAL_DIRECTIVE,
  LOOP_DIRECTIVE,
  IS_DIRECTIVE,
  REF_DIRECTIVES,
  YIELD_TAG
} from './../common/global-variables'

import createRefDirective from './../common/util/tags/create-ref-directive'
import createIfDirective from './../common/util/tags/create-if-directive'
import isBoolAttr from './../common/util/checks/is-boolean-attribute'
import walkNodes from './../common/util/dom/walk-nodes'
import getAttribute from './../common/util/dom/get-attribute'
import setAttribute from './../common/util/dom/set-attribute'
import each from './../common/util/misc/each'
import contains from './../common/util/misc/contains'
import warn from './../common/util/misc/warn'
import getTag from './../common/util/tags/get'
import initChild from './../common/util/tags/init-child'
import createTag from './tag'

/**
 * Walk the tag DOM to detect the expressions to evaluate
 * @this Tag
 * @param   { HTMLElement } root - root tag where we will start digging the expressions
 * @param   { Boolean } mustIncludeRoot - flag to decide whether the root must be parsed as well
 * @returns { Array } all the expressions found
 */
export function parseExpressions(root, mustIncludeRoot) {
  const expressions = []

  walkNodes(root, (dom) => {
    const type = dom.nodeType
    let attr
    let tagImpl

    if (!mustIncludeRoot && dom === root) return

    // text node
    if (type === 3 && dom.parentNode.tagName !== 'STYLE' && tmpl.hasExpr(dom.nodeValue))
      expressions.push({dom, expr: dom.nodeValue})

    if (type !== 1) return

    const isVirtual = dom.tagName === 'VIRTUAL'

    // loop. each does it's own thing (for now)
    if (attr = getAttribute(dom, LOOP_DIRECTIVE)) {
      if(isVirtual) setAttribute(dom, 'loopVirtual', true) // ignore here, handled in _each
      expressions.push(_each(dom, this, attr))
      return false
    }

    // if-attrs become the new parent. Any following expressions (either on the current
    // element, or below it) become children of this expression.
    if (attr = getAttribute(dom, CONDITIONAL_DIRECTIVE)) {
      expressions.push(createIfDirective(dom, this, attr))
      return false
    }

    if (attr = getAttribute(dom, IS_DIRECTIVE)) {
      if (tmpl.hasExpr(attr)) {
        expressions.push({
          isRtag: true,
          expr: attr,
          dom,
          attrs: [].slice.call(dom.attributes)
        })

        return false
      }
    }

    // if this is a tag, stop traversing here.
    // we ignore the root, since parseExpressions is called while we're mounting that root
    tagImpl = getTag(dom)

    if(isVirtual) {
      if(getAttribute(dom, 'virtualized')) {dom.parentElement.removeChild(dom) } // tag created, remove from dom
      if(!tagImpl && !getAttribute(dom, 'virtualized') && !getAttribute(dom, 'loopVirtual'))  // ok to create virtual tag
        tagImpl = { tmpl: dom.outerHTML }
    }

    if (tagImpl && (dom !== root || mustIncludeRoot)) {
      const hasIsDirective = getAttribute(dom, IS_DIRECTIVE)
      if(isVirtual && !hasIsDirective) { // handled in update
        // can not remove attribute like directives
        // so flag for removal after creation to prevent maximum stack error
        setAttribute(dom, 'virtualized', true)
        const tag = createTag(
          {tmpl: dom.outerHTML},
          {root: dom, parent: this},
          dom.innerHTML
        )

        expressions.push(tag) // no return, anonymous tag, keep parsing
      } else {
        if (hasIsDirective && isVirtual)
          warn(`Virtual tags shouldn't be used together with the "${IS_DIRECTIVE}" attribute - https://github.com/riot/riot/issues/2511`)

        expressions.push(
          initChild(
            tagImpl,
            {
              root: dom,
              parent: this
            },
            dom.innerHTML,
            this
          )
        )
        return false
      }
    }

    // attribute expressions
    parseAttributes.apply(this, [dom, dom.attributes, (attr, expr) => {
      if (!expr) return
      expressions.push(expr)
    }])
  })

  return expressions
}

/**
 * Calls `fn` for every attribute on an element. If that attr has an expression,
 * it is also passed to fn.
 * @this Tag
 * @param   { HTMLElement } dom - dom node to parse
 * @param   { Array } attrs - array of attributes
 * @param   { Function } fn - callback to exec on any iteration
 */
export function parseAttributes(dom, attrs, fn) {
  each(attrs, (attr) => {
    if (!attr) return false

    const name = attr.name
    const bool = isBoolAttr(name)
    let expr

    if (contains(REF_DIRECTIVES, name) && dom.tagName.toLowerCase() !== YIELD_TAG) {
      expr =  createRefDirective(dom, this, name, attr.value)
    } else if (tmpl.hasExpr(attr.value)) {
      expr = {dom, expr: attr.value, attr: name, bool}
    }

    fn(attr, expr)
  })
}