Artifact
1c97ba2ac55207d366f5e6ea92481e99e27d6ea3675e2beb030bb53c361cd50f:
import { tmpl } from 'riot-tmpl'
import each from './../common/util/misc/each'
import contains from './../common/util/misc/contains'
import riotSettings from '../../settings'
import isBlank from './../common/util/checks/is-blank'
import isObject from './../common/util/checks/is-object'
import isString from './../common/util/checks/is-string'
import isFunction from './../common/util/checks/is-function'
import removeAttribute from './../common/util/dom/remove-attribute'
import getAttribute from './../common/util/dom/get-attribute'
import setAttribute from './../common/util/dom/set-attribute'
import createPlaceholder from './../common/util/dom/create-placeholder'
import toggleVisibility from './../common/util/dom/toggle-visibility'
import styleObjectToString from './../common/util/dom/style-object-to-string'
import isEventAttribute from './../common/util/misc/is-event-attribute'
import setEventHandler from './setEventHandler'
import initChild from './../common/util/tags/init-child'
import arrayishRemove from './../common/util/tags/arrayish-remove'
import inheritParentProps from './../common/util/tags/inherit-parent-properties'
import replaceVirtual from './../common/util/tags/replace-virtual'
import {
__TAG_IMPL,
ATTRS_PREFIX,
SHOW_DIRECTIVE,
HIDE_DIRECTIVE,
IE_VERSION,
CASE_SENSITIVE_ATTRIBUTES
} from './../common/global-variables'
/**
* Update dynamically created data-is tags with changing expressions
* @param { Object } expr - expression tag and expression info
* @param { Tag } parent - parent for tag creation
* @param { String } tagName - tag implementation we want to use
*/
export function updateDataIs(expr, parent, tagName) {
let tag = expr.tag || expr.dom._tag
let ref
const { head } = tag ? tag.__ : {}
const isVirtual = expr.dom.tagName === 'VIRTUAL'
if (tag && expr.tagName === tagName) {
tag.update()
return
}
// sync _parent to accommodate changing tagnames
if (tag) {
// need placeholder before unmount
if(isVirtual) {
ref = createPlaceholder()
head.parentNode.insertBefore(ref, head)
}
tag.unmount(true)
}
// unable to get the tag name
if (!isString(tagName)) return
expr.impl = __TAG_IMPL[tagName]
// unknown implementation
if (!expr.impl) return
expr.tag = tag = initChild(
expr.impl, {
root: expr.dom,
parent,
tagName
},
expr.dom.innerHTML,
parent
)
each(expr.attrs, a => setAttribute(tag.root, a.name, a.value))
expr.tagName = tagName
tag.mount()
// root exist first time, after use placeholder
if (isVirtual) replaceVirtual(tag, ref || tag.root)
// parent is the placeholder tag, not the dynamic tag so clean up
parent.__.onUnmount = () => {
const delName = tag.opts.dataIs
arrayishRemove(tag.parent.tags, delName, tag)
arrayishRemove(tag.__.parent.tags, delName, tag)
tag.unmount()
}
}
/**
* Nomalize any attribute removing the "riot-" prefix
* @param { String } attrName - original attribute name
* @returns { String } valid html attribute name
*/
export function normalizeAttrName(attrName) {
if (!attrName) return null
attrName = attrName.replace(ATTRS_PREFIX, '')
if (CASE_SENSITIVE_ATTRIBUTES[attrName]) attrName = CASE_SENSITIVE_ATTRIBUTES[attrName]
return attrName
}
/**
* Update on single tag expression
* @this Tag
* @param { Object } expr - expression logic
* @returns { undefined }
*/
export function updateExpression(expr) {
if (this.root && getAttribute(this.root,'virtualized')) return
const dom = expr.dom
// remove the riot- prefix
const attrName = normalizeAttrName(expr.attr)
const isToggle = contains([SHOW_DIRECTIVE, HIDE_DIRECTIVE], attrName)
const isVirtual = expr.root && expr.root.tagName === 'VIRTUAL'
const { isAnonymous } = this.__
const parent = dom && (expr.parent || dom.parentNode)
const { keepValueAttributes } = riotSettings
// detect the style attributes
const isStyleAttr = attrName === 'style'
const isClassAttr = attrName === 'class'
const isValueAttr = attrName === 'value'
let value
// if it's a tag we could totally skip the rest
if (expr._riot_id) {
if (expr.__.wasCreated) {
expr.update()
// if it hasn't been mounted yet, do that now.
} else {
expr.mount()
if (isVirtual) {
replaceVirtual(expr, expr.root)
}
}
return
}
// if this expression has the update method it means it can handle the DOM changes by itself
if (expr.update) return expr.update()
const context = isToggle && !isAnonymous ? inheritParentProps.call(this) : this
// ...it seems to be a simple expression so we try to calculate its value
value = tmpl(expr.expr, context)
const hasValue = !isBlank(value)
const isObj = isObject(value)
// convert the style/class objects to strings
if (isObj) {
if (isClassAttr) {
value = tmpl(JSON.stringify(value), this)
} else if (isStyleAttr) {
value = styleObjectToString(value)
}
}
// remove original attribute
if (expr.attr &&
(
// the original attribute can be removed only if we are parsing the original expression
!expr.wasParsedOnce ||
// or its value is false
value === false ||
// or if its value is currently falsy...
// We will keep the "value" attributes if the "keepValueAttributes"
// is enabled though
(!hasValue && (!isValueAttr || isValueAttr && !keepValueAttributes))
)
) {
// remove either riot-* attributes or just the attribute name
removeAttribute(dom, getAttribute(dom, expr.attr) ? expr.attr : attrName)
}
// for the boolean attributes we don't need the value
// we can convert it to checked=true to checked=checked
if (expr.bool) value = value ? attrName : false
if (expr.isRtag) return updateDataIs(expr, this, value)
if (expr.wasParsedOnce && expr.value === value) return
// update the expression value
expr.value = value
expr.wasParsedOnce = true
// if the value is an object (and it's not a style or class attribute) we can not do much more with it
if (isObj && !isClassAttr && !isStyleAttr && !isToggle) return
// avoid to render undefined/null values
if (!hasValue) value = ''
// textarea and text nodes have no attribute name
if (!attrName) {
// about #815 w/o replace: the browser converts the value to a string,
// the comparison by "==" does too, but not in the server
value += ''
// test for parent avoids error with invalid assignment to nodeValue
if (parent) {
// cache the parent node because somehow it will become null on IE
// on the next iteration
expr.parent = parent
if (parent.tagName === 'TEXTAREA') {
parent.value = value // #1113
if (!IE_VERSION) dom.nodeValue = value // #1625 IE throws here, nodeValue
} // will be available on 'updated'
else dom.nodeValue = value
}
return
}
switch (true) {
// handle events binding
case isFunction(value):
if (isEventAttribute(attrName)) {
setEventHandler(attrName, value, dom, this)
}
break
// show / hide
case isToggle:
toggleVisibility(dom, attrName === HIDE_DIRECTIVE ? !value : value)
break
// handle attributes
default:
if (expr.bool) {
dom[attrName] = value
}
if (isValueAttr && dom.value !== value) {
dom.value = value
} else if (hasValue && value !== false) {
setAttribute(dom, attrName, value)
}
// make sure that in case of style changes
// the element stays hidden
if (isStyleAttr && dom.hidden) toggleVisibility(dom, false)
}
}
/**
* Update all the expressions in a Tag instance
* @this Tag
* @param { Array } expressions - expression that must be re evaluated
*/
export default function update(expressions) {
each(expressions, updateExpression.bind(this))
}