blob: d90733d1d3918c4c3a66b0345814cf3b12f14a5a [file] [log] [blame]
/*
* 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.
*/
'use strict'
import { extend } from '../../utils'
import { ComponentManager } from '../../dom'
import { isComponentAppear } from '../../dom/appearWatcher'
import * as operate from './operate'
import * as position from './position'
import flexbox from './flexbox'
import { makeImageLazy, fireLazyload } from './lazyload'
const appearEvts = ['appear', 'disappear']
/**
* check whether a event is valid to dispatch.
* @param {Component} comp the component that this event is to trigger on.
* @param {string} type the event type.
* @return {Boolean} is it valid to dispatch.
*/
function isEventValid (comp, type) {
// if a component has aleary triggered 'appear' event, then
// the 'appear' even† can't be triggered again utill the
// 'disappear' event triggered.
if (appearEvts.indexOf(type) <= -1) {
return true
}
if (comp._appear === undefined && type === 'disappear') {
return false
}
let res
if (comp._appear === undefined && type === 'appear') {
res = true
}
else {
res = type !== comp._appear
}
res && (comp._appear = type)
return res
}
export default function Component (data, nodeType) {
this.data = data
this.node = this.create(nodeType)
this.createChildren()
this.updateAttrs(this.data.attr || {})
// issue: when add element to a list in lifetime hook 'ready', the
// styles is set to the classStyle, not style. This is a issue
// that jsframework should do something about.
const classStyle = this.data.classStyle
classStyle && this.updateStyle(this.data.classStyle)
this.updateStyle(this.data.style || {})
this.bindEvents(this.data.event || [])
}
Component.prototype = {
getComponentManager () {
return ComponentManager.getInstance(this.data.instanceId)
},
getWeexInstance () {
return this.getComponentManager().getWeexInstance()
},
getParent () {
return this.getComponentManager().componentMap[this.parentRef]
},
getParentScroller () {
if (this.isInScrollable()) {
return this._parentScroller
}
return null
},
getRootScroller () {
if (this.isInScrollable()) {
let scroller = this._parentScroller
let parent = scroller._parentScroller
while (parent) {
scroller = parent
parent = scroller._parentScroller
}
return scroller
}
return null
},
getRootContainer () {
const root = this.getWeexInstance().getRoot()
|| document.body
return root
},
isScrollable () {
const t = this.data.type
return ComponentManager.getScrollableTypes().indexOf(t) !== -1
},
isInScrollable () {
if (typeof this._isInScrollable === 'boolean') {
return this._isInScrollable
}
const parent = this.getParent()
if (parent
&& (typeof parent._isInScrollable !== 'boolean')
&& !parent.isScrollable()) {
if (parent.data.ref === '_root') {
this._isInScrollable = false
return false
}
this._isInScrollable = parent.isInScrollable()
this._parentScroller = parent._parentScroller
return this._isInScrollable
}
if (parent && typeof parent._isInScrollable === 'boolean') {
this._isInScrollable = parent._isInScrollable
this._parentScroller = parent._parentScroller
return this._isInScrollable
}
if (parent && parent.isScrollable()) {
this._isInScrollable = true
this._parentScroller = parent
return true
}
},
// dispatch a specified event on this.node
// - type: event type
// - data: event data
// - config: event config object
// - bubbles
// - cancelable
dispatchEvent (type, data, config) {
if (!isEventValid(this, type)) {
return
}
const event = document.createEvent('HTMLEvents')
config = config || {}
event.initEvent(type, config.bubbles || false, config.cancelable || false)
!data && (data = {})
event.data = extend({}, data)
extend(event, data)
this.node.dispatchEvent(event)
},
onAppend: function () {
const evts = this.data.event
if (!evts || !evts.length) { return }
let flag = false
for (let i = 0, l = evts.length; i < l; i++) {
if (evts[i] === 'appear') {
flag = true
break
}
}
if (!flag) {
return
}
// trigger 'appear' event in the next tick.
setTimeout(() => {
if (isComponentAppear(this)) {
this.dispatchEvent('appear', { direction: '' })
}
}, 0)
},
addAppendHandler (cb) {
let pre
if (this.onAppend) {
pre = this.onAppend.bind(this)
}
this.onAppend = function () {
pre && pre.call(this)
cb && cb.call(this)
}.bind(this)
},
// change src to img-src for lib.img to fire lazyload later.
enableLazyload (src) {
if (this.node) {
makeImageLazy(this.node, src)
}
else {
console.error('[h5-render] this.node does not exist.')
}
},
// target can be both weex component and dom element.
fireLazyload (target) {
!target && (target = this)
fireLazyload(target)
},
attr: {}, // attr setters
style: {}, // style setters
// event funcs
// - 1. 'updator' for updating attrs or styles with out triggering messages.
// - 2. 'extra' for binding extra data.
// - 3. 'setter' set a specified event handler.
// funcs should be functions like this: (take 'change' event as a example)
// {
// change: {
// updator () {
// return {
// attrs: {
// checked: this.checked
// }
// }
// },
// extra () {
// return {
// value: this.checked
// }
// }
// }
// }
event: {},
clearAttr () {
},
clearStyle () {
this.node.cssText = ''
}
}
// extend operations.
extend(Component.prototype, operate)
// extend attr and style setters from 'position' and 'flexbox'.
extend(Component.prototype, position)
extend(Component.prototype.style, flexbox.style)