asia-weex-vue-render4light
Version:
Web renderer for weex project written in Vue DSL.
365 lines (342 loc) • 8.36 kB
JavaScript
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { getTransformer } from 'wxv-transformer'
import {
isArray,
isDef,
isPrimitive,
dispatchNativeEvent
} from '../utils'
import config from '../config'
const {
weexBuiltInComponents
} = config
const appearEventsMap = {
appear: 'appear',
disappear: 'disappear',
offsetAppear: 'offset-appear',
offsetDisappear: 'offset-disappear'
}
/**
* remove text nodes in the nodes array.
* @param {Array} nodes
* @return {Array} nodes without text nodes.
*/
export function trimTextVNodes (vnodes) {
if (isArray(vnodes)) {
return vnodes.filter(vnode => !!vnode.tag)
}
return vnodes
}
/**
* ==================================================
* method to transform args passed to createElement
* for render function.
* ==================================================
*/
// should share with precompiler.
const metaMap = {
figure: ['img', 'image', 'figure'],
p: ['text', 'p'],
div: ['container', 'div'],
section: ['cell']
}
const checkMap = Object.keys(metaMap).reduce(function (pre, targetTag) {
const tagArr = metaMap[targetTag]
tagArr.forEach(function (fromTag) {
pre[fromTag] = targetTag
})
return pre
}, {})
const _stdTagMap = {
p: 'text',
figure: 'image',
section: 'cell'
}
function getStdTag (tag) {
const stdTag = _stdTagMap[tag]
return stdTag || tag
}
const precompiledClassMap = {
div: {
'weex-ct': true,
'weex-div': true
},
image: {
'weex-el': true,
'weex-image': true
},
text: {
'weex-el': true,
'weex-text': true
},
cell: {
'weex-ct': true,
'weex-cell': true
},
a: {
'weex-ct': true,
'weex-a': true
}
}
function isPrecompiled (tag) {
return config.weexBuiltInComponents.indexOf(tag) > -1
}
export function transformRender (ctx, h) {
return function (
tag,
data,
children,
normalizationType,
alwaysNormalize
) {
if (isArray(data) || isPrimitive(data)) {
normalizationType = children
children = data
data = {}
}
if (!isDef(data)) {
data = {}
}
if (isDef(data.is)) {
tag = data.is
}
if (typeof tag === 'string') {
data = transformData(this, data, tag)
tag = transformTag(this, tag)
}
else { // direct component options / constructor
data = transformData(this, data, undefined)
}
return h.call(
this,
tag,
data,
children,
normalizationType,
alwaysNormalize
)
}.bind(ctx)
}
export function transformTag (ctx, tag) {
const elementTag = checkMap[tag]
return elementTag || tag
}
/**
* Tell whether a element is contained in a element who has
* a attribute 'bubble'=true.
* @param {HTMLElement} el
*/
// function inBubble (el) {
// if (typeof el._inBubble === 'boolean') {
// return el._inBubble
// }
// const parents = []
// let parent = el.parentElement
// let inBubble
// while (parent && parent !== document.body) {
// if (typeof parent._inBubble === 'boolean') {
// inBubble = parent._inBubble
// break
// }
// const attr = parent.getAttribute('bubble')
// if (attr !== '') {
// inBubble = attr === true || attr === 'true'
// break
// }
// parents.push(parent)
// parent = parent.parentElement
// }
// el._inBubble = inBubble
// for (let i = 0, l = parents.length; i < l; i++) {
// parents[i]._inBubble = inBubble
// }
// return inBubble
// }
function bindEvents (ctx, evts, attrs, tag, appearAttached) {
for (const key in evts) {
const appearEvtName = appearEventsMap[key]
if (appearEvtName) {
attrs[`data-evt-${appearEvtName}`] = ''
if (!appearAttached.value) {
appearAttached.value = true
attrs['weex-appear'] = ''
}
}
else {
attrs[`data-evt-${key}`] = ''
if (key !== 'click') {
// should stop propagation by default.
// TODO: should test inBubble first.
const handler = evts[key]
if (isArray(evts[key])) {
handler.unshift(ctx.$stopPropagation)
}
else {
evts[key] = [ctx.$stopPropagation, handler]
}
}
}
}
if (evts.click) {
evts.weex$tap = evts.click
evts.click = ctx.$stopOuterA
}
if (evts.scroll) {
evts.weex$scroll = evts.scroll
delete evts.scroll
}
}
function transformOn (ctx, data, tag) {
let { on, nativeOn } = data
if (weexBuiltInComponents.indexOf(tag) > -1) {
/**
* for div, image, text, cell, a, ...
* user should bind all events without .native.
*/
nativeOn = null
delete data.nativeOn
}
if (isDef(weex._components[tag])) {
/**
* for slider, list, ...
* user should bind events without .native.
* in our events handling, all events should transfer to
* .native binding.
*/
delete data.nativeOn
nativeOn = null
if (on) {
nativeOn = data.nativeOn = on
}
on = null
delete data.on
}
let attrs = data.attrs
if (!attrs) {
attrs = data.attrs = {}
}
const appearAttached = {
value: false
}
if (on) {
bindEvents(ctx, on, attrs, tag, appearAttached)
}
if (nativeOn) {
bindEvents(ctx, nativeOn, attrs, tag, appearAttached)
}
/**
* binding a weex$tap to <a> element to stop propagation if there
* is no bubbles=true flag showing on parents.
*/
if (tag === 'a') {
if (!on) {
on = data.on = {}
}
// if (!checkBubble(el)) {
let evt = on['weex$tap']
if (!evt) {
on['weex$tap'] = ctx.$stopPropagation
}
else if (Array.isArray(evt)) {
evt.unshift(ctx.$stopPropagation)
}
else {
evt = [ctx.$stopPropagation, evt]
}
// }
}
}
function transformClass (data, tag) {
let { class: classData } = data
const tagClassObj = precompiledClassMap[tag]
if (!classData) {
classData = data.class = []
}
if (classData && isArray(classData)) {
data.class = classData.concat(Object.keys(tagClassObj))
}
else if (typeof classData === 'object') {
Object.assign(classData, tagClassObj)
}
}
function transformStyle (ctx, data, tag) {
const { style } = data
if (!style) { return }
const transformer = getTransformer(getStdTag(tag))
if (transformer) {
data.style = ctx._px2rem(transformer.transform(style), 75)
}
else {
data.style = ctx._px2rem(style, 75)
}
}
/**
* transformAttrs:
* - add weex-type attrs for precompiledTags.
* - image.resize: transform to directive weex-resize.
*/
function transformAttrs (data, tag) {
let { attrs, directives } = data
if (!attrs) {
attrs = data.attrs = {}
}
attrs['weex-type'] = tag
if (tag === 'image') {
const { src, resize } = attrs
if (src) {
attrs['data-img-src'] = src
}
if (resize) {
if (!directives) {
directives = data.directives = []
}
directives.push({
name: 'weex-resize',
value: attrs.resize
})
}
}
}
export function transformData (ctx, data, tag) {
if (isArray(data)) {
// parameter data is ommited.
return data
}
const isP = isPrecompiled(tag)
// class
isP && transformClass(data, tag)
// style
transformStyle(ctx, data, tag)
// attrs
isP && transformAttrs(data, tag)
// on
transformOn(ctx, data, tag)
return data
}
export function mapNativeEvents (ctx, map) {
const eventMap = {}
for (const origEvent in map) {
eventMap[origEvent] = function (evt) {
const el = evt.target
dispatchNativeEvent(el, map[origEvent])
}
}
return eventMap
}