| /* |
| * 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) |