blob: 2c7e4e5509d6f930063986f168881ea8ca36d1c9 [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.
*/
/* global Event */
'use strict'
import { watchIfNeeded } from './appearWatcher'
import { isArray } from '../utils'
// const lazyload = require('./lazyload')
// const animation = require('./animation')
const RENDERING_INDENT = 800
const _instanceMap = {}
const typeMap = {}
const scrollableTypes = [
'scroller',
'hscroller',
'vscroller',
'list',
'hlist',
'vlist'
]
export default function ComponentManager (id) {
this.id = id
this.batched = false
this.updates = []
this.componentMap = {}
_instanceMap[this.id] = this
}
ComponentManager.getInstance = instanceId => _instanceMap[instanceId]
ComponentManager.registerComponent = (type, definition) => {
typeMap[type] = definition
}
ComponentManager.getScrollableTypes = () => scrollableTypes
ComponentManager.prototype = {
getWeexInstance () {
if (!this._weexInstance) {
this._weexInstance = global.weex
? global.weex.getInstance(this.id)
: null
}
return this._weexInstance
},
// Fire a event 'renderbegin'/'renderend' on body element.
rendering () {
function _renderingEnd () {
// get weex instance root
window.dispatchEvent(new Event('renderend'))
this._renderingTimer = null
}
if (this._renderingTimer) {
clearTimeout(this._renderingTimer)
this._renderingTimer = setTimeout(
_renderingEnd.bind(this),
RENDERING_INDENT
)
}
else {
window.dispatchEvent(new Event('renderbegin'))
this._renderingTimer = setTimeout(
_renderingEnd.bind(this),
RENDERING_INDENT
)
}
},
getComponent (ref) {
return this.componentMap[ref]
},
removeComponent (ref) {
const self = this
if (!ref || !this.componentMap[ref]) {
return
}
// remove from this.componentMap cursively
(function _removeCursively (_ref) {
const child = self.componentMap[_ref]
const listeners = child._listeners
const children = child.data.children
if (children && children.length) {
for (let i = 0, l = children.length; i < l; i++) {
_removeCursively(children[i].ref)
}
}
// remove events from _ref component
if (listeners) {
for (const type in listeners) {
child.node.removeEventListener(type, listeners[type])
}
}
delete child._listeners
delete child.node._listeners
// remove _ref component
delete self.componentMap[_ref]
})(ref)
},
createElement (data, nodeType) {
let ComponentType = typeMap[data.type]
if (!ComponentType) {
ComponentType = typeMap['div']
}
data.instanceId = this.id
const component = new ComponentType(data, nodeType)
const ref = data.ref
this.componentMap[ref] = component
component.node.setAttribute('data-ref', ref)
return component
},
/**
* createBody: generate root component
* @param {object} element
*/
createBody (element) {
console.log('[h5-render] createBody', element)
if (this.componentMap['_root']) {
return
}
const nodeType = element.type
element.type = 'root'
element.rootId = this.getWeexInstance().rootId
element.ref = '_root'
const root = this.createElement(element, nodeType)
const body = document.querySelector('#' + this.getWeexInstance().rootId)
|| document.body
body.appendChild(root.node)
root._appended = true
this.handleAppend(root)
},
appendChild (parentRef, data) {
let parent = this.componentMap[parentRef]
if (this.componentMap[data.ref] || !parent) {
return
}
if (parentRef === '_root' && !parent) {
parent = this.createElement({
type: 'root',
rootId: this.getWeexInstance().rootId,
ref: '_root'
})
parent._appended = true
}
const child = parent.appendChild(data)
// In some parent component the implementation of method
// appendChild didn't return the component at all, therefor
// child maybe a undefined object.
if (child) {
child.parentRef = parentRef
}
if (child && parent._appended) {
this.handleAppend(child)
}
},
// appendChildren (ref, elements) {
// for (let i = 0; i < elements.length; i++) {
// this.appendChild(ref, elements[i])
// }
// },
removeElement (ref) {
if (isArray(ref)) {
return ref.map(r => this.removeElement(r))
}
const component = this.componentMap[ref]
// fire event for rendering dom on body elment.
this.rendering()
if (component && component.parentRef) {
const parent = this.componentMap[component.parentRef]
component.onRemove && component.onRemove()
parent.removeChild(component)
}
else if (!component) {
console.error(`[h5-render] component of ref '${ref}' does not exist.`)
}
else {
console.error(`[h5-render] parent component '${component.parentRef}' does not exist.`)
}
},
moveElement (ref, parentRef, index) {
const component = this.componentMap[ref]
const newParent = this.componentMap[parentRef]
const oldParentRef = component.parentRef
let children, before, i, l
if (!component) {
return console.error(`[h5-render] component of ref '${ref}' does not exist.`)
}
if (!newParent) {
return console.error(
`[h5-render] parent component '${parentRef}' does not exist.`)
}
if (index < -1) {
index = -1
return console.error('[h5-render] index cannot be less than -1.')
}
// fire event for rendering.
this.rendering()
children = newParent.data.children
if (children
&& children.length
&& index !== -1
&& index < children.length) {
before = this.componentMap[newParent.data.children[index].ref]
}
// remove from oldParent.data.children
if (oldParentRef && this.componentMap[oldParentRef]) {
children = this.componentMap[oldParentRef].data.children
if (children && children.length) {
for (i = 0, l = children.length; i < l; i++) {
if (children[i].ref === ref) {
break
}
}
if (l > i) {
children.splice(i, 1)
}
}
}
newParent.insertBefore(component, before)
component.onMove && component.onMove(parentRef, index)
},
insertBefore (ref, data) {
let child, parent
const before = this.componentMap[ref]
child = this.componentMap[data.ref]
before && (parent = this.componentMap[before.parentRef])
if (child || !parent || !before) {
return
}
child = this.createElement(data)
if (child) {
child.parentRef = before.parentRef
parent.insertBefore(child, before)
}
else {
return
}
if (this.componentMap[before.parentRef]._appended) {
this.handleAppend(child)
}
},
/**
* addElement
* If index is larget than any child's index, the
* element will be appended behind.
* @param {string} parentRef
* @param {obj} element (data of the component)
* @param {number} index
*/
addElement (parentRef, element, index) {
// fire event for rendering dom on body elment.
this.rendering()
const parent = this.componentMap[parentRef]
if (!parent) {
return
}
const children = parent.data.children
// -1 means append as the last.
if (index < -1) {
index = -1
return console.error('[h5-render] index cannot be less than -1.')
}
if (children && children.length
&& children.length > index
&& index !== -1) {
this.insertBefore(children[index].ref, element)
}
else {
this.appendChild(parentRef, element)
}
},
addEvent (ref, type) {
const component = this.componentMap[ref]
if (!component) {
return console.error(`[h5-render] component of ref '${ref}' does not exist.`)
}
component.bindEvents([type])
},
removeEvent (ref, type) {
const component = this.componentMap[ref]
if (!component) {
return console.error(`[h5-render] component of ref '${ref}' does not exist.`)
}
component.unbindEvents([type])
},
updateAttrs (ref, attrs) {
const component = this.componentMap[ref]
if (!component) {
return console.error(`[h5-render] component of ref '${ref}' does not exist.`)
}
component.updateAttrs(attrs)
},
// setStyle (ref, key, value) {
// const component = this.componentMap[ref]
// if (!component) {
// return console.error(`[h5-render] component of ref '${ref}' does not exist.`)
// }
// component.updateStyle({ [key]: value })
// },
updateStyle (ref, style) {
const component = this.componentMap[ref]
if (!component) {
return console.error(`[h5-render] component of ref '${ref}' does not exist.`)
}
component.updateStyle(style)
},
handleAppend (component) {
component._appended = true
component.onAppend && component.onAppend()
// invoke onAppend on children recursively
const children = component.data.children
if (children) {
for (let i = 0; i < children.length; i++) {
const child = this.componentMap[children[i].ref]
if (child) {
this.handleAppend(child)
}
}
}
// watch appear/disappear of the component if needed
watchIfNeeded(component)
// do lazyload if needed
component.fireLazyload()
// lazyload.startIfNeeded(component);
},
createFinish (callback) {
window.dispatchEvent(new Event('weex:createfinish'))
},
updateFinish (callback) {
window.dispatchEvent(new Event('weex:updatefinish'))
},
refreshFinish (callback) {
window.dispatchEvent(new Event('weex:refreshfinish'))
}
}