You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@weex.apache.org by ta...@apache.org on 2017/07/28 09:11:54 UTC

[15/43] incubator-weex git commit: * [html5] implementate the integrated components as render's plugin, which should have no directly dependency on render core.

* [html5] implementate the integrated components as render's plugin, which should have no directly dependency on render core.


Project: http://git-wip-us.apache.org/repos/asf/incubator-weex/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-weex/commit/b1a7c02a
Tree: http://git-wip-us.apache.org/repos/asf/incubator-weex/tree/b1a7c02a
Diff: http://git-wip-us.apache.org/repos/asf/incubator-weex/diff/b1a7c02a

Branch: refs/heads/0.16-dev
Commit: b1a7c02a2903edceb705698cc2181e3d6569267a
Parents: a83dfc2
Author: MrRaindrop <te...@gmail.com>
Authored: Mon Jul 17 21:18:20 2017 +0800
Committer: MrRaindrop <te...@gmail.com>
Committed: Mon Jul 17 21:20:06 2017 +0800

----------------------------------------------------------------------
 html5/render/vue/components/a.js                |  90 +--
 html5/render/vue/components/div.js              |  43 +-
 html5/render/vue/components/image.js            |  18 +-
 html5/render/vue/components/index.js            |  34 +-
 html5/render/vue/components/input.js            | 130 +++--
 html5/render/vue/components/scrollable/cell.js  |  39 ++
 .../render/vue/components/scrollable/header.js  | 102 ++--
 html5/render/vue/components/scrollable/index.js |  29 +
 html5/render/vue/components/scrollable/list.js  |  82 +++
 .../vue/components/scrollable/list/cell.js      |  36 --
 .../vue/components/scrollable/list/index.js     |  77 ---
 .../vue/components/scrollable/list/listMixin.js |  47 --
 .../vue/components/scrollable/list/style.js     |  34 --
 .../components/scrollable/loading-indicator.js  |  17 +-
 .../render/vue/components/scrollable/loading.js | 141 ++---
 .../vue/components/scrollable/mixins/index.js   |   7 +
 .../vue/components/scrollable/mixins/list.js    |  46 ++
 .../components/scrollable/mixins/scrollable.js  | 267 +++++++++
 .../render/vue/components/scrollable/refresh.js | 157 ++---
 .../vue/components/scrollable/scroller.js       | 135 +++--
 .../render/vue/components/scrollable/style.css  |  66 +++
 .../vue/components/scrollable/waterfall.js      | 573 ++++++++++---------
 html5/render/vue/components/slider/index.js     |  69 +--
 html5/render/vue/components/slider/indicator.js |  18 +-
 .../render/vue/components/slider/slideMixin.js  |  46 +-
 .../vue/components/slider/slider-neighbor.js    |  12 +-
 html5/render/vue/components/slider/slider.js    |  75 +++
 html5/render/vue/components/switch.js           | 114 ++--
 html5/render/vue/components/text.js             |  52 +-
 html5/render/vue/components/textarea.js         | 104 ++--
 html5/render/vue/components/video.js            | 107 ++--
 html5/render/vue/components/web.js              |  98 ++--
 html5/render/vue/core/style.js                  |  35 --
 html5/render/vue/env/global.js                  |   6 +
 html5/render/vue/index.js                       |   6 +-
 html5/render/vue/mixins/base.js                 |   6 -
 html5/render/vue/mixins/index.js                |   2 -
 html5/render/vue/mixins/scrollable.js           | 233 --------
 html5/render/vue/styles/base.css                |  67 ---
 39 files changed, 1674 insertions(+), 1546 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b1a7c02a/html5/render/vue/components/a.js
----------------------------------------------------------------------
diff --git a/html5/render/vue/components/a.js b/html5/render/vue/components/a.js
index b4b3ef0..1d8946f 100644
--- a/html5/render/vue/components/a.js
+++ b/html5/render/vue/components/a.js
@@ -16,7 +16,6 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { extractComponentStyle, trimTextVNodes, createEventMap } from '../core'
 // import { validateStyles } from '../validator'
 
 const _css = `
@@ -26,43 +25,56 @@ const _css = `
 `
 let cnt = 0
 
-export default {
-  name: 'weex-a',
-  props: {
-    href: String
-  },
-  mounted () {
-    const $el = this.$el
-    const id = $el.id
+function getA (weex) {
+  const {
+    extractComponentStyle,
+    trimTextVNodes,
+    createEventMap
+  } = weex
+
+  return {
+    name: 'weex-a',
+    props: {
+      href: String
+    },
+    mounted () {
+      const $el = this.$el
+      const id = $el.id
 
-    /**
-     * if there is a child component already triggered a click handler, then
-     * this link jumping should be prevented.
-     */
-    $el.addEventListener('click', (e) => {
-      const el = e._triggered && e._triggered.el
-      if (el && (el !== $el) && !el.querySelector(`#${id}`)) {
-        e.preventDefault()
-      }
-    })
-  },
-  render (createElement) {
-    /* istanbul ignore next */
-    // if (process.env.NODE_ENV === 'development') {
-    //   validateStyles('a', this.$vnode.data && this.$vnode.data.staticStyle)
-    // }
-    this._renderHook()
-    const id = cnt++
-    return createElement('html:a', {
-      attrs: {
-        'weex-type': 'a',
-        'id': `weex-a-${id}`,
-        href: this.href
-      },
-      on: createEventMap(this),
-      staticClass: 'weex-a weex-ct',
-      staticStyle: extractComponentStyle(this)
-    }, trimTextVNodes(this.$slots.default))
-  },
-  _css
+      /**
+       * if there is a child component already triggered a click handler, then
+       * this link jumping should be prevented.
+       */
+      $el.addEventListener('click', (e) => {
+        const el = e._triggered && e._triggered.el
+        if (el && (el !== $el) && !el.querySelector(`#${id}`)) {
+          e.preventDefault()
+        }
+      })
+    },
+    render (createElement) {
+      /* istanbul ignore next */
+      // if (process.env.NODE_ENV === 'development') {
+      //   validateStyles('a', this.$vnode.data && this.$vnode.data.staticStyle)
+      // }
+      const id = cnt++
+      return createElement('html:a', {
+        attrs: {
+          'weex-type': 'a',
+          'id': `weex-a-${id}`,
+          href: this.href
+        },
+        on: createEventMap(this),
+        staticClass: 'weex-a weex-ct',
+        staticStyle: extractComponentStyle(this)
+      }, trimTextVNodes(this.$slots.default))
+    },
+    _css
+  }
+}
+
+export default {
+  init (weex) {
+    weex.registerComponent('a', getA(weex))
+  }
 }

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b1a7c02a/html5/render/vue/components/div.js
----------------------------------------------------------------------
diff --git a/html5/render/vue/components/div.js b/html5/render/vue/components/div.js
index 63cdbc1..8f05163 100644
--- a/html5/render/vue/components/div.js
+++ b/html5/render/vue/components/div.js
@@ -16,8 +16,6 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-// import { validateStyles } from '../validator'
-import { extractComponentStyle, trimTextVNodes, createEventMap } from '../core'
 
 const _css = `
 body > .weex-div {
@@ -25,20 +23,31 @@ body > .weex-div {
 }
 `
 
+function getDiv (weex) {
+  const {
+    extractComponentStyle,
+    trimTextVNodes,
+    createEventMap
+  } = weex
+
+  return {
+    name: 'weex-div',
+    render (createElement) {
+      return createElement('html:div', {
+        attrs: { 'weex-type': 'div' },
+        on: createEventMap(this),
+        staticClass: 'weex-div weex-ct',
+        staticStyle: extractComponentStyle(this)
+      }, trimTextVNodes(this.$slots.default))
+    },
+    _css
+  }
+}
+
 export default {
-  name: 'weex-div',
-  render (createElement) {
-    /* istanbul ignore next */
-    // if (process.env.NODE_ENV === 'development') {
-    //   validateStyles('div', this.$vnode.data && this.$vnode.data.staticStyle)
-    // }
-    this._renderHook()
-    return createElement('html:div', {
-      attrs: { 'weex-type': 'div' },
-      on: createEventMap(this),
-      staticClass: 'weex-div weex-ct',
-      staticStyle: extractComponentStyle(this)
-    }, trimTextVNodes(this.$slots.default))
-  },
-  _css
+  init (weex) {
+    const div = getDiv(weex)
+    weex.registerComponent('div', div)
+    weex.registerComponent('container', div)
+  }
 }

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b1a7c02a/html5/render/vue/components/image.js
----------------------------------------------------------------------
diff --git a/html5/render/vue/components/image.js b/html5/render/vue/components/image.js
index ba44d2a..d1fe664 100644
--- a/html5/render/vue/components/image.js
+++ b/html5/render/vue/components/image.js
@@ -17,8 +17,7 @@
  * under the License.
  */
 
-import { extractComponentStyle, createEventMap } from '../core'
-import { extend } from '../utils'
+let extractComponentStyle, createEventMap, extend
 
 const _css = `
 .weex-image, .weex-img {
@@ -53,7 +52,8 @@ function preProcessSrc (context, url, mergedStyle) {
   }) || url
 }
 
-export default {
+const image = {
+  name: 'weex-image',
   props: {
     src: String,
     placeholder: String,
@@ -79,7 +79,6 @@ export default {
     // const style = this._normalizeInlineStyles(this.$vnode.data)
     const resizeStyle = getResizeStyle(this)
     const style = extractComponentStyle(this)
-    this._renderHook()
     return createElement('figure', {
       attrs: {
         'weex-type': 'image',
@@ -93,3 +92,14 @@ export default {
   },
   _css
 }
+
+export default {
+  init (weex) {
+    extractComponentStyle = weex.extractComponentStyle
+    createEventMap = weex.createEventMap
+    extend = weex.utils.extend
+
+    weex.registerComponent('image', image)
+    weex.registerComponent('img', image)
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b1a7c02a/html5/render/vue/components/index.js
----------------------------------------------------------------------
diff --git a/html5/render/vue/components/index.js b/html5/render/vue/components/index.js
index 87ddeb0..5fdbc39 100644
--- a/html5/render/vue/components/index.js
+++ b/html5/render/vue/components/index.js
@@ -22,47 +22,23 @@ import a from './a'
 import div from './div'
 import image from './image'
 import input from './input'
-import header from './scrollable/header'
-import list from './scrollable/list'
-import waterfall from './scrollable/waterfall'
-import cell from './scrollable/list/cell'
-import scroller from './scrollable/scroller'
+import scrollable from './scrollable'
 import slider from './slider'
-import neighbor from './slider/slider-neighbor'
-// import indicator from './warning'
-import indicator from './slider/indicator'
-// import refresh from './warning'
-// import loading from './warning'
-import refresh from './scrollable/refresh'
-import loading from './scrollable/loading'
-import loadingIndicator from './scrollable/loading-indicator'
 import text from './text'
 import textarea from './textarea'
 import video from './video'
 import web from './web'
 
-export default {
+export default [
   a,
   div,
-  container: div,
   image,
-  img: image,
   input,
-  switch: _switch,
-  header,
-  list,
-  waterfall,
-  cell,
-  scroller,
+  _switch,
+  scrollable,
   slider,
-  cycleslider: slider,
-  'slider-neighbor': neighbor,
-  indicator,
-  refresh,
-  loading,
-  'loading-indicator': loadingIndicator,
   text,
   textarea,
   video,
   web
-}
+]

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b1a7c02a/html5/render/vue/components/input.js
----------------------------------------------------------------------
diff --git a/html5/render/vue/components/input.js b/html5/render/vue/components/input.js
index 1454988..0e8d466 100644
--- a/html5/render/vue/components/input.js
+++ b/html5/render/vue/components/input.js
@@ -21,10 +21,8 @@
  * @fileOverview Input component.
  * Support v-model only if vue version is large than 2.2.0
  */
-import { extractComponentStyle, createEventMap } from '../core'
-import { inputCommon } from '../mixins'
-import { extend, mapFormEvents, appendCss } from '../utils'
-// import { validateStyles } from '../validator'
+let extractComponentStyle, createEventMap
+let extend, mapFormEvents, appendCss
 
 const ID_PREFIX_PLACEHOLDER_COLOR = 'wipt_plc_'
 const ID_PREFIX_INPUT = 'wipt_'
@@ -68,61 +66,77 @@ function processStyle (vm) {
   return styles
 }
 
-export default {
-  mixins: [inputCommon],
-  props: {
-    type: {
-      type: String,
-      default: 'text',
-      validator (value) {
-        return [
-          'email', 'number', 'password', 'search', 'tel', 'text', 'url', 'date',
-          'datetime', 'time'
-          // unsupported type:
-          // button, checkbox, color, file, hidden, image,
-          // month, radio, range, reset, submit, week,
-        ].indexOf(value) !== -1
-      }
-    },
-    value: String,
-    placeholder: String,
-    disabled: {
-      type: [String, Boolean],
-      default: false
-    },
-    autofocus: {
-      type: [String, Boolean],
-      default: false
-    },
-    maxlength: [String, Number],
-    returnKeyType: String
-  },
+function getInput (weex) {
+  const { inputCommon } = weex.mixins
 
-  render (createElement) {
-    if (!this._id) {
-      this._id = idCount++
-    }
-    const events = extend(createEventMap(this), mapFormEvents(this))
-    this._renderHook()
-    return createElement('html:input', {
-      attrs: {
-        'weex-type': 'input',
-        id: `${ID_PREFIX_INPUT}${this._id}`,
-        type: this.type,
-        value: this.value,
-        disabled: (this.disabled !== 'false' && this.disabled !== false),
-        autofocus: (this.autofocus !== 'false' && this.autofocus !== false),
-        placeholder: this.placeholder,
-        maxlength: this.maxlength,
-        'returnKeyType': this.returnKeyType
+  return {
+    name: 'weex-input',
+    mixins: [inputCommon],
+    props: {
+      type: {
+        type: String,
+        default: 'text',
+        validator (value) {
+          return [
+            'email', 'number', 'password', 'search', 'tel', 'text', 'url', 'date',
+            'datetime', 'time'
+            // unsupported type:
+            // button, checkbox, color, file, hidden, image,
+            // month, radio, range, reset, submit, week,
+          ].indexOf(value) !== -1
+        }
+      },
+      value: String,
+      placeholder: String,
+      disabled: {
+        type: [String, Boolean],
+        default: false
       },
-      domProps: {
-        value: this.value
+      autofocus: {
+        type: [String, Boolean],
+        default: false
       },
-      on: this.createKeyboardEvent(events),
-      staticClass: 'weex-input weex-el',
-      staticStyle: processStyle(this)
-    })
-  },
-  _css
+      maxlength: [String, Number],
+      returnKeyType: String
+    },
+
+    render (createElement) {
+      if (!this._id) {
+        this._id = idCount++
+      }
+      const events = extend(createEventMap(this), mapFormEvents(this))
+      return createElement('html:input', {
+        attrs: {
+          'weex-type': 'input',
+          id: `${ID_PREFIX_INPUT}${this._id}`,
+          type: this.type,
+          value: this.value,
+          disabled: (this.disabled !== 'false' && this.disabled !== false),
+          autofocus: (this.autofocus !== 'false' && this.autofocus !== false),
+          placeholder: this.placeholder,
+          maxlength: this.maxlength,
+          'returnKeyType': this.returnKeyType
+        },
+        domProps: {
+          value: this.value
+        },
+        on: this.createKeyboardEvent(events),
+        staticClass: 'weex-input weex-el',
+        staticStyle: processStyle(this)
+      })
+    },
+    _css
+  }
+}
+
+export default {
+  init (weex) {
+    extractComponentStyle = weex.extractComponentStyle
+    createEventMap = weex.createEventMap
+    extend = weex.utils.extend
+    mapFormEvents = weex.utils.mapFormEvents
+    appendCss = weex.utils.appendCss
+
+    weex.registerComponent('input', getInput(weex))
+  }
 }

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b1a7c02a/html5/render/vue/components/scrollable/cell.js
----------------------------------------------------------------------
diff --git a/html5/render/vue/components/scrollable/cell.js b/html5/render/vue/components/scrollable/cell.js
new file mode 100644
index 0000000..fb6ca1e
--- /dev/null
+++ b/html5/render/vue/components/scrollable/cell.js
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+function getCell (weex) {
+  const { extractComponentStyle, createEventMap } = weex
+  return {
+    name: 'weex-cell',
+    render (createElement) {
+      return createElement('section', {
+        attrs: { 'weex-type': 'cell' },
+        on: createEventMap(this),
+        staticClass: 'weex-cell weex-ct',
+        staticStyle: extractComponentStyle(this)
+      }, this.$slots.default)
+    }
+  }
+}
+
+export default {
+  init (weex) {
+    weex.registerComponent('cell', getCell(weex))
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b1a7c02a/html5/render/vue/components/scrollable/header.js
----------------------------------------------------------------------
diff --git a/html5/render/vue/components/scrollable/header.js b/html5/render/vue/components/scrollable/header.js
index cbb4783..35fe72a 100644
--- a/html5/render/vue/components/scrollable/header.js
+++ b/html5/render/vue/components/scrollable/header.js
@@ -16,62 +16,70 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { supportSticky } from '../../utils/style'
-import { extractComponentStyle, createEventMap } from '../../core'
 
-export default {
-  data () {
-    return {
-      sticky: false,
-      initTop: 0,
-      placeholder: null,
-      supportSticky: supportSticky()
-    }
-  },
+function getHeader (weex) {
+  const { extractComponentStyle, createEventMap } = weex
+  const { supportSticky } = weex.utils
 
-  mounted () {
-    this.initTop = this.$el.offsetTop
-    this.placeholder = window.document.createElement('div')
-  },
+  return {
+    data () {
+      return {
+        sticky: false,
+        initTop: 0,
+        placeholder: null,
+        supportSticky: supportSticky()
+      }
+    },
 
-  updated () {
-    if (!this.sticky) {
+    mounted () {
       this.initTop = this.$el.offsetTop
-    }
-  },
-
-  methods: {
-    addSticky () {
-      this.sticky = true
-      this.placeholder.style.display = 'block'
-      this.placeholder.style.width = this.$el.offsetWidth + 'px'
-      this.placeholder.style.height = this.$el.offsetHeight + 'px'
-      this.$el.parentNode.insertBefore(this.placeholder, this.$el)
+      this.placeholder = window.document.createElement('div')
     },
 
-    removeSticky () {
-      this.sticky = false
-      try {
-        this.$el.parentNode.removeChild(this.placeholder)
+    updated () {
+      if (!this.sticky) {
+        this.initTop = this.$el.offsetTop
       }
-      catch (e) {
+    },
+
+    methods: {
+      addSticky () {
+        this.sticky = true
+        this.placeholder.style.display = 'block'
+        this.placeholder.style.width = this.$el.offsetWidth + 'px'
+        this.placeholder.style.height = this.$el.offsetHeight + 'px'
+        this.$el.parentNode.insertBefore(this.placeholder, this.$el)
+      },
+
+      removeSticky () {
+        this.sticky = false
+        try {
+          this.$el.parentNode.removeChild(this.placeholder)
+        }
+        catch (e) {
+        }
       }
+    },
+
+    render (createElement) {
+      /* istanbul ignore next */
+      // if (process.env.NODE_ENV === 'development') {
+      //   validateStyles('header', this.$vnode.data && this.$vnode.data.staticStyle)
+      // }
+      return createElement('html:header', {
+        attrs: { 'weex-type': 'header' },
+        on: createEventMap(this),
+        ref: 'header',
+        staticClass: 'weex-header weex-ct',
+        class: { 'weex-sticky': this.sticky, 'weex-ios-sticky': this.supportSticky },
+        staticStyle: extractComponentStyle(this)
+      }, this.$slots.default)
     }
-  },
+  }
+}
 
-  render (createElement) {
-    /* istanbul ignore next */
-    // if (process.env.NODE_ENV === 'development') {
-    //   validateStyles('header', this.$vnode.data && this.$vnode.data.staticStyle)
-    // }
-    this._renderHook()
-    return createElement('html:header', {
-      attrs: { 'weex-type': 'header' },
-      on: createEventMap(this),
-      ref: 'header',
-      staticClass: 'weex-header weex-ct',
-      class: { 'weex-sticky': this.sticky, 'weex-ios-sticky': this.supportSticky },
-      staticStyle: extractComponentStyle(this)
-    }, this.$slots.default)
+export default {
+  init (weex) {
+    weex.registerComponent('header', getHeader(weex))
   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b1a7c02a/html5/render/vue/components/scrollable/index.js
----------------------------------------------------------------------
diff --git a/html5/render/vue/components/scrollable/index.js b/html5/render/vue/components/scrollable/index.js
new file mode 100644
index 0000000..3c68404
--- /dev/null
+++ b/html5/render/vue/components/scrollable/index.js
@@ -0,0 +1,29 @@
+import list from './list'
+import scroller from './scroller'
+import waterfall from './waterfall'
+import cell from './cell'
+import header from './header'
+import loading from './loading'
+import refresh from './refresh'
+import loadingIndicator from './loading-indicator'
+
+import './style.css'
+
+const modules = [
+  list,
+  scroller,
+  waterfall,
+  cell,
+  header,
+  loading,
+  refresh,
+  loadingIndicator
+]
+
+export default {
+  init (weex) {
+    modules.forEach(function (mod) {
+      weex.install(mod)
+    })
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b1a7c02a/html5/render/vue/components/scrollable/list.js
----------------------------------------------------------------------
diff --git a/html5/render/vue/components/scrollable/list.js b/html5/render/vue/components/scrollable/list.js
new file mode 100644
index 0000000..aa6740b
--- /dev/null
+++ b/html5/render/vue/components/scrollable/list.js
@@ -0,0 +1,82 @@
+/*
+ * 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 { scrollable, list as listMixin } from './mixins'
+
+function getList (weex) {
+  const {
+    extractComponentStyle,
+    createEventMap
+  } = weex
+
+  return {
+    name: 'weex-list',
+    mixins: [scrollable, listMixin],
+    computed: {
+      wrapperClass () {
+        const classArray = ['weex-list', 'weex-list-wrapper', 'weex-ct']
+        this._refresh && classArray.push('with-refresh')
+        this._loading && classArray.push('with-loading')
+        return classArray.join(' ')
+      }
+    },
+
+    methods: {
+      createChildren (h) {
+        const slots = this.$slots.default || []
+        this._cells = slots.filter(vnode => {
+          if (!vnode.tag || !vnode.componentOptions) return false
+          return true
+        })
+        return [
+          h('html:div', {
+            ref: 'inner',
+            staticClass: 'weex-list-inner weex-ct'
+          }, this._cells)
+        ]
+      }
+    },
+
+    render (createElement) {
+      this.weexType = 'list'
+
+      this.$nextTick(() => {
+        this.updateLayout()
+      })
+
+      return createElement('main', {
+        ref: 'wrapper',
+        attrs: { 'weex-type': 'list' },
+        staticClass: this.wrapperClass,
+        on: createEventMap(this, {
+          scroll: this.handleListScroll,
+          touchstart: this.handleTouchStart,
+          touchmove: this.handleTouchMove,
+          touchend: this.handleTouchEnd
+        }),
+        staticStyle: extractComponentStyle(this)
+      }, this.createChildren(createElement))
+    }
+  }
+}
+
+export default {
+  init (weex) {
+    weex.registerComponent('list', getList(weex))
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b1a7c02a/html5/render/vue/components/scrollable/list/cell.js
----------------------------------------------------------------------
diff --git a/html5/render/vue/components/scrollable/list/cell.js b/html5/render/vue/components/scrollable/list/cell.js
deleted file mode 100644
index d7ddbf7..0000000
--- a/html5/render/vue/components/scrollable/list/cell.js
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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 { validateStyles } from '../../../validator'
-import { extractComponentStyle, createEventMap } from '../../../core'
-
-export default {
-  render (createElement) {
-    /* istanbul ignore next */
-    // if (process.env.NODE_ENV === 'development') {
-    //   validateStyles('cell', this.$vnode.data && this.$vnode.data.staticStyle)
-    // }
-    this._renderHook()
-    return createElement('section', {
-      attrs: { 'weex-type': 'cell' },
-      on: createEventMap(this),
-      staticClass: 'weex-cell weex-ct',
-      staticStyle: extractComponentStyle(this)
-    }, this.$slots.default)
-  }
-}

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b1a7c02a/html5/render/vue/components/scrollable/list/index.js
----------------------------------------------------------------------
diff --git a/html5/render/vue/components/scrollable/list/index.js b/html5/render/vue/components/scrollable/list/index.js
deleted file mode 100644
index 79e6d7d..0000000
--- a/html5/render/vue/components/scrollable/list/index.js
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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 { extractComponentStyle, createEventMap } from '../../../core'
-import { scrollable } from '../../../mixins'
-// import { validateStyles } from '../../../validator'
-import listMixin from './listMixin'
-
-export default {
-  name: 'list',
-  mixins: [scrollable, listMixin],
-  computed: {
-    wrapperClass () {
-      const classArray = ['weex-list', 'weex-list-wrapper', 'weex-ct']
-      this._refresh && classArray.push('with-refresh')
-      this._loading && classArray.push('with-loading')
-      return classArray.join(' ')
-    }
-  },
-
-  methods: {
-    createChildren (h) {
-      const slots = this.$slots.default || []
-      this._cells = slots.filter(vnode => {
-        if (!vnode.tag || !vnode.componentOptions) return false
-        return true
-      })
-      return [
-        h('html:div', {
-          ref: 'inner',
-          staticClass: 'weex-list-inner weex-ct'
-        }, this._cells)
-      ]
-    }
-  },
-
-  render (createElement) {
-    this.weexType = 'list'
-
-    /* istanbul ignore next */
-    // if (process.env.NODE_ENV === 'development') {
-    //   validateStyles('list', this.$vnode.data && this.$vnode.data.staticStyle)
-    // }
-    this.$nextTick(() => {
-      this.updateLayout()
-    })
-
-    this._renderHook()
-    return createElement('main', {
-      ref: 'wrapper',
-      attrs: { 'weex-type': 'list' },
-      staticClass: this.wrapperClass,
-      on: createEventMap(this, {
-        scroll: this.handleListScroll,
-        touchstart: this.handleTouchStart,
-        touchmove: this.handleTouchMove,
-        touchend: this.handleTouchEnd
-      }),
-      staticStyle: extractComponentStyle(this)
-    }, this.createChildren(createElement))
-  }
-}

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b1a7c02a/html5/render/vue/components/scrollable/list/listMixin.js
----------------------------------------------------------------------
diff --git a/html5/render/vue/components/scrollable/list/listMixin.js b/html5/render/vue/components/scrollable/list/listMixin.js
deleted file mode 100644
index d32d406..0000000
--- a/html5/render/vue/components/scrollable/list/listMixin.js
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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 { supportSticky } from '../../../utils/style'
-
-export default {
-  methods: {
-    handleListScroll (event) {
-      this.handleScroll(event)
-
-      if (supportSticky()) {
-        return
-      }
-
-      const scrollTop = this.$el.scrollTop
-      const h = this.$children.filter(vm => vm.$refs.header)
-
-      if (h.length <= 0) {
-        return
-      }
-
-      for (let i = 0; i < h.length; i++) {
-        if (h[i].initTop < scrollTop) {
-          h[i].addSticky()
-        }
-        else {
-          h[i].removeSticky()
-        }
-      }
-    }
-  }
-}

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b1a7c02a/html5/render/vue/components/scrollable/list/style.js
----------------------------------------------------------------------
diff --git a/html5/render/vue/components/scrollable/list/style.js b/html5/render/vue/components/scrollable/list/style.js
deleted file mode 100644
index 0a6aec0..0000000
--- a/html5/render/vue/components/scrollable/list/style.js
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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.
- */
-export default `
-body > .weex-list {
-  max-height: 100%;
-}
-
-.weex-list-wrapper {
-  -webkit-overflow-scrolling: touch;
-  overflow-y: scroll !important;
-  overflow-x: hidden;
-}
-
-.weex-list-inner {
-  -webkit-overflow-scrolling: touch;
-  width: 100%;
-}
-`

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b1a7c02a/html5/render/vue/components/scrollable/loading-indicator.js
----------------------------------------------------------------------
diff --git a/html5/render/vue/components/scrollable/loading-indicator.js b/html5/render/vue/components/scrollable/loading-indicator.js
index 59f141e..aa289bd 100644
--- a/html5/render/vue/components/scrollable/loading-indicator.js
+++ b/html5/render/vue/components/scrollable/loading-indicator.js
@@ -16,8 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { extractComponentStyle } from '../../core'
-import { getRgb, loopArray, getStyleSheetById } from '../../utils'
+let extractComponentStyle, getRgb, loopArray, getStyleSheetById
 
 const _css = `
 .weex-refresh-indicator,
@@ -180,10 +179,10 @@ function processStyle (vm) {
   return style
 }
 
-export default {
+const loadingIndicator = {
+  name: 'weex-loading-indicator',
   render (createElement) {
     this.weexType = 'loading-indicator'
-    this._renderHook()
     return createElement('mark', {
       attrs: { 'weex-type': 'loading-indicator' },
       staticClass: 'weex-loading-indicator weex-ct',
@@ -192,3 +191,13 @@ export default {
   },
   _css
 }
+
+export default {
+  init (weex) {
+    extractComponentStyle = weex.extractComponentStyle
+    getRgb = weex.utils.getRgb
+    loopArray = weex.utils.loopArray
+    getStyleSheetById = weex.utils.getStyleSheetById
+    weex.registerComponent('loading-indicator', loadingIndicator)
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b1a7c02a/html5/render/vue/components/scrollable/loading.js
----------------------------------------------------------------------
diff --git a/html5/render/vue/components/scrollable/loading.js b/html5/render/vue/components/scrollable/loading.js
index 9e6fa04..cd37151 100644
--- a/html5/render/vue/components/scrollable/loading.js
+++ b/html5/render/vue/components/scrollable/loading.js
@@ -16,85 +16,92 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { extractComponentStyle } from '../../core'
-import LoadingIndicator from './loading-indicator'
 
-export default {
-  components: { LoadingIndicator },
-  props: {
-    display: {
-      type: String,
-      default: 'show',
-      validator (value) {
-        return ['show', 'hide'].indexOf(value) !== -1
+function getLoading () {
+  const { extractComponentStyle } = weex
+
+  return {
+    name: 'weex-loading',
+    props: {
+      display: {
+        type: String,
+        default: 'show',
+        validator (value) {
+          return ['show', 'hide'].indexOf(value) !== -1
+        }
+      }
+    },
+    data () {
+      return {
+        height: -1,
+        viewHeight: 0
       }
-    }
-  },
-  data () {
-    return {
-      height: -1,
-      viewHeight: 0
-    }
-  },
-  mounted () {
-    this.viewHeight = this.$el.offsetHeight
-    if (this.display === 'hide') {
-      this.height = 0
-    }
-    else {
-      this.height = this.viewHeight
-    }
-  },
-  watch: {
-    height (val) {
-      this.$el.style.height = `${val}px`
     },
-    display (val) {
-      if (val === 'hide') {
+    mounted () {
+      this.viewHeight = this.$el.offsetHeight
+      if (this.display === 'hide') {
         this.height = 0
       }
       else {
         this.height = this.viewHeight
       }
-    }
-  },
-  methods: {
-    pulling (offsetY = 0) {
-      this.height = offsetY
     },
-    pullingUp (offsetY) {
-      this.$el.style.transition = `height 0s`
-      this.pulling(offsetY)
-    },
-    pullingEnd () {
-      this.$el.style.transition = `height .2s`
-      if (this.height >= this.viewHeight) {
-        this.pulling(this.viewHeight)
-        this.$emit('loading')
-      }
-      else {
-        this.pulling(0)
+    watch: {
+      height (val) {
+        this.$el.style.height = `${val}px`
+      },
+      display (val) {
+        if (val === 'hide') {
+          this.height = 0
+        }
+        else {
+          this.height = this.viewHeight
+        }
       }
     },
-    getChildren () {
-      const children = this.$slots.default || []
-      if (this.display === 'show') {
-        return children
+    methods: {
+      pulling (offsetY = 0) {
+        this.height = offsetY
+      },
+      pullingUp (offsetY) {
+        this.$el.style.transition = `height 0s`
+        this.pulling(offsetY)
+      },
+      pullingEnd () {
+        this.$el.style.transition = `height .2s`
+        if (this.height >= this.viewHeight) {
+          this.pulling(this.viewHeight)
+          this.$emit('loading')
+        }
+        else {
+          this.pulling(0)
+        }
+      },
+      getChildren () {
+        const children = this.$slots.default || []
+        if (this.display === 'show') {
+          return children
+        }
+        return children.filter(vnode => {
+          return vnode.componentOptions
+            && vnode.componentOptions.tag !== 'loading-indicator'
+        })
       }
-      return children.filter(vnode => {
-        return vnode.componentOptions
-          && vnode.componentOptions.tag !== 'loading-indicator'
-      })
+    },
+    render (createElement) {
+      this.$parent._loading = this
+      return createElement('aside', {
+        ref: 'loading',
+        attrs: { 'weex-type': 'loading' },
+        staticClass: 'weex-loading weex-ct',
+        staticStyle: extractComponentStyle(this)
+      }, this.getChildren())
     }
-  },
-  render (createElement) {
-    this.$parent._loading = this
-    this._renderHook()
-    return createElement('aside', {
-      ref: 'loading',
-      attrs: { 'weex-type': 'loading' },
-      staticClass: 'weex-loading weex-ct',
-      staticStyle: extractComponentStyle(this)
-    }, this.getChildren())
+  }
+}
+
+export default {
+  init (weex) {
+    weex.registerComponent('loading', getLoading(weex))
   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b1a7c02a/html5/render/vue/components/scrollable/mixins/index.js
----------------------------------------------------------------------
diff --git a/html5/render/vue/components/scrollable/mixins/index.js b/html5/render/vue/components/scrollable/mixins/index.js
new file mode 100644
index 0000000..7b9b304
--- /dev/null
+++ b/html5/render/vue/components/scrollable/mixins/index.js
@@ -0,0 +1,7 @@
+import scrollable from './scrollable'
+import list from './list'
+
+export {
+  scrollable,
+  list
+}

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b1a7c02a/html5/render/vue/components/scrollable/mixins/list.js
----------------------------------------------------------------------
diff --git a/html5/render/vue/components/scrollable/mixins/list.js b/html5/render/vue/components/scrollable/mixins/list.js
new file mode 100644
index 0000000..9c5ca8f
--- /dev/null
+++ b/html5/render/vue/components/scrollable/mixins/list.js
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+
+export default {
+  methods: {
+    handleListScroll (event) {
+      this.handleScroll(event)
+
+      if (weex.utils.supportSticky()) {
+        return
+      }
+
+      const scrollTop = this.$el.scrollTop
+      const h = this.$children.filter(vm => vm.$refs.header)
+
+      if (h.length <= 0) {
+        return
+      }
+
+      for (let i = 0; i < h.length; i++) {
+        if (h[i].initTop < scrollTop) {
+          h[i].addSticky()
+        }
+        else {
+          h[i].removeSticky()
+        }
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b1a7c02a/html5/render/vue/components/scrollable/mixins/scrollable.js
----------------------------------------------------------------------
diff --git a/html5/render/vue/components/scrollable/mixins/scrollable.js b/html5/render/vue/components/scrollable/mixins/scrollable.js
new file mode 100644
index 0000000..0d78db9
--- /dev/null
+++ b/html5/render/vue/components/scrollable/mixins/scrollable.js
@@ -0,0 +1,267 @@
+/*
+ * 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.
+ */
+
+const DEFAULT_OFFSET_ACCURACY = 10
+const DEFAULT_LOADMORE_OFFSET = 0
+
+function getThrottledScroll (context) {
+  const scale = weex.config.env.scale
+  if (!context._throttleScroll) {
+    const wrapper = context.$refs.wrapper
+    const inner = context.$refs.inner
+    let preOffset = (context.scrollDirection === 'horizontal'
+        ? wrapper.scrollLeft
+        : wrapper.scrollTop)
+      || 0
+    context._throttleScroll = weex.utils.throttle(function (evt) {
+      const offset = context.scrollDirection === 'horizontal'
+        ? wrapper.scrollLeft
+        : wrapper.scrollTop
+      const indent = parseInt(context.offsetAccuracy) * scale
+      function triggerScroll () {
+        const rect = inner.getBoundingClientRect()
+        evt.contentSize = { width: rect.width, height: rect.height }
+        evt.contentOffset = {
+          x: wrapper.scrollLeft,
+          /**
+           * positive direciton for y-axis is down.
+           * so should use negative operation on scrollTop.
+           *
+           *  (0,0)---------------> x
+           *       |
+           *       |
+           *       |
+           *       |
+           *       v y
+           *
+           */
+          y: -wrapper.scrollTop
+        }
+        context.$emit('scroll', evt)
+      }
+      if (Math.abs(offset - preOffset) >= indent) {
+        triggerScroll()
+        preOffset = offset
+      }
+    }, 16, true)
+  }
+  return context._throttleScroll
+}
+
+export default {
+  props: {
+    loadmoreoffset: {
+      type: [String, Number],
+      default: DEFAULT_LOADMORE_OFFSET,
+      validator (value) {
+        const val = parseInt(value)
+        return !isNaN(val) && val >= DEFAULT_LOADMORE_OFFSET
+      }
+    },
+
+    offsetAccuracy: {
+      type: [Number, String],
+      default: DEFAULT_OFFSET_ACCURACY,
+      validator (value) {
+        const val = parseInt(value)
+        return !isNaN(val) && val >= DEFAULT_OFFSET_ACCURACY
+      }
+    }
+  },
+
+  created () {
+    // should call resetLoadmore() to enable loadmore event.
+    this._loadmoreReset = true
+  },
+
+  methods: {
+    updateLayout () {
+      const wrapper = this.$refs.wrapper
+      if (wrapper) {
+        const rect = wrapper.getBoundingClientRect()
+        this._wrapperWidth = rect.width
+        this._wrapperHeight = rect.height
+      }
+      const inner = this.$refs.inner
+      const children = inner && inner.children
+      if (inner) {
+        const rect = inner.getBoundingClientRect()
+        this._innerWidth = rect.width
+        this._innerHeight = rect.height
+      }
+      const loadingEl = this._loading && this._loading.$el
+      const refreshEl = this._refresh && this._refresh.$el
+      if (loadingEl) {
+        this._innerHeight -= loadingEl.getBoundingClientRect().height
+      }
+      if (refreshEl) {
+        this._innerHeight -= refreshEl.getBoundingClientRect().height
+      }
+      // inner width is always the viewport width somehow in horizontal
+      // scoller, therefore the inner width should be reclaculated.
+      if (this.scrollDirection === 'horizontal' && children) {
+        this._innerWidth = weex.utils.getRangeWidth(inner)
+      }
+    },
+
+    resetLoadmore () {
+      this._loadmoreReset = true
+    },
+
+    /**
+     * process sticky children in scrollable components.
+     * current only support list and vertical scroller.
+     */
+    processSticky () {
+      /**
+       * current browser support 'sticky' or '-webkit-sticky', so there's no need
+       * to do further more.
+       */
+      if (weex.utils.supportSticky()) {
+        return
+      }
+      // current only support list and vertical scroller.
+      if (this.scrollDirection === 'horizontal') {
+        return
+      }
+      const stickyChildren = this._stickyChildren
+      const len = stickyChildren && stickyChildren.length || 0
+      if (len <= 0) { return }
+
+      const container = this.$el
+      if (!container) { return }
+      const scrollTop = container.scrollTop
+
+      let stickyChild
+      for (let i = 0; i < len; i++) {
+        stickyChild = stickyChildren[i]
+        if (stickyChild._initOffsetTop < scrollTop) {
+          stickyChild._addSticky()
+        }
+        else {
+          stickyChild._removeSticky()
+        }
+      }
+    },
+
+    handleScroll (event) {
+      weex.utils.getThrottleLazyload(25, this.$el, 'scroll')()
+      getThrottledScroll(this)(event)
+
+      this.processSticky()
+
+      // fire loadmore event.
+      const inner = this.$refs.inner
+      if (inner) {
+        const innerLength = this.scrollDirection === 'horizontal'
+          ? this._innerWidth
+          : this._innerHeight
+        if (!this._innerLength) {
+          this._innerLength = innerLength
+        }
+        if (this._innerLength !== innerLength) {
+          this._innerLength = innerLength
+          this._loadmoreReset = true
+        }
+        if (this._loadmoreReset && this.reachBottom(this.loadmoreoffset)) {
+          this._loadmoreReset = false
+          this.$emit('loadmore', event)
+        }
+      }
+    },
+
+    reachTop () {
+      const wrapper = this.$refs.wrapper
+      return (!!wrapper) && (wrapper.scrollTop <= 0)
+    },
+
+    reachBottom (offset) {
+      const wrapper = this.$refs.wrapper
+      const inner = this.$refs.inner
+      offset = parseInt(offset || 0) * weex.config.env.scale
+
+      if (wrapper && inner) {
+        const key = this.scrollDirection === 'horizontal'
+          ? 'width'
+          : 'height'
+        const innerLength = this[`_inner${key[0].toUpperCase()}${key.substr(1)}`]
+        const wrapperLength = this[`_wrapper${key[0].toUpperCase()}${key.substr(1)}`]
+        const scrollOffset = this.scrollDirection === 'horizontal'
+          ? wrapper.scrollLeft
+          : wrapper.scrollTop
+        return scrollOffset >= innerLength - wrapperLength - offset
+      }
+      return false
+    },
+
+    handleTouchStart (event) {
+      // event.preventDefault()
+      // event.stopPropagation()
+      if (this._loading || this._refresh) {
+        const touch = event.changedTouches[0]
+        this._touchParams = {
+          reachTop: this.reachTop(),
+          reachBottom: this.reachBottom(),
+          startTouchEvent: touch,
+          startX: touch.pageX,
+          startY: touch.pageY,
+          timeStamp: event.timeStamp
+        }
+      }
+    },
+
+    handleTouchMove (event) {
+      // event.preventDefault()
+      // event.stopPropagation()
+      if (this._touchParams) {
+        const inner = this.$refs.inner
+        const { startY, reachTop, reachBottom } = this._touchParams
+        if (inner) {
+          const touch = event.changedTouches[0]
+          const offsetY = touch.pageY - startY
+          this._touchParams.offsetY = offsetY
+          if (reachTop && this._refresh) {
+            this._refresh.pullingDown(offsetY)
+          }
+          else if (reachBottom && this._loading) {
+            this._loading.pullingUp(-offsetY)
+          }
+        }
+      }
+    },
+
+    handleTouchEnd (event) {
+      // event.preventDefault()
+      // event.stopPropagation()
+      if (this._touchParams) {
+        const inner = this.$refs.inner
+        const { reachTop, reachBottom } = this._touchParams
+        if (inner) {
+          if (reachTop && this._refresh) {
+            this._refresh.pullingEnd()
+          }
+          else if (reachBottom && this._loading) {
+            this._loading.pullingEnd()
+          }
+        }
+      }
+      delete this._touchParams
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b1a7c02a/html5/render/vue/components/scrollable/refresh.js
----------------------------------------------------------------------
diff --git a/html5/render/vue/components/scrollable/refresh.js b/html5/render/vue/components/scrollable/refresh.js
index f0afca4..214a189 100644
--- a/html5/render/vue/components/scrollable/refresh.js
+++ b/html5/render/vue/components/scrollable/refresh.js
@@ -16,93 +16,100 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { extractComponentStyle } from '../../core'
-import LoadingIndicator from './loading-indicator'
-import { createEvent } from '../../utils'
 
-export default {
-  components: { LoadingIndicator },
-  props: {
-    display: {
-      type: String,
-      default: 'show',
-      validator (value) {
-        return ['show', 'hide'].indexOf(value) !== -1
+function getRefresh (weex) {
+  const { extractComponentStyle } = weex
+  const { createEvent } = weex.utils
+
+  return {
+    name: 'weex-refresh',
+    props: {
+      display: {
+        type: String,
+        default: 'show',
+        validator (value) {
+          return ['show', 'hide'].indexOf(value) !== -1
+        }
+      }
+    },
+    data () {
+      return {
+        lastDy: 0,
+        viewHeight: 0,
+        height: -1
       }
-    }
-  },
-  data () {
-    return {
-      lastDy: 0,
-      viewHeight: 0,
-      height: -1
-    }
-  },
-  mounted () {
-    this.viewHeight = this.$el.offsetHeight
-    if (this.display === 'hide') {
-      this.height = 0
-    }
-    else {
-      this.height = this.viewHeight
-    }
-  },
-  watch: {
-    height (val) {
-      this.$el.style.height = `${val}px`
     },
-    display (val) {
-      if (val === 'hide') {
+    mounted () {
+      this.viewHeight = this.$el.offsetHeight
+      if (this.display === 'hide') {
         this.height = 0
       }
       else {
         this.height = this.viewHeight
       }
-    }
-  },
-  methods: {
-    pulling (offsetY = 0) {
-      this.height = offsetY
-      this.$emit('pullingdown', createEvent(this, 'pullingdown', {
-        dy: offsetY - this.lastDy,
-        pullingDistance: offsetY,
-        viewHeight: this.viewHeight
-      }))
-      this.lastDy = offsetY
     },
-    pullingDown (offsetY) {
-      this.$el.style.transition = `height 0s`
-      this.pulling(offsetY)
-    },
-    pullingEnd () {
-      this.$el.style.transition = `height .2s`
-      if (this.height >= this.viewHeight) {
-        this.pulling(this.viewHeight)
-        this.$emit('refresh')
-      }
-      else {
-        this.pulling(0)
+    watch: {
+      height (val) {
+        this.$el.style.height = `${val}px`
+      },
+      display (val) {
+        if (val === 'hide') {
+          this.height = 0
+        }
+        else {
+          this.height = this.viewHeight
+        }
       }
     },
-    getChildren () {
-      const children = this.$slots.default || []
-      if (this.display === 'show') {
-        return children
+    methods: {
+      pulling (offsetY = 0) {
+        this.height = offsetY
+        this.$emit('pullingdown', createEvent(this, 'pullingdown', {
+          dy: offsetY - this.lastDy,
+          pullingDistance: offsetY,
+          viewHeight: this.viewHeight
+        }))
+        this.lastDy = offsetY
+      },
+      pullingDown (offsetY) {
+        this.$el.style.transition = `height 0s`
+        this.pulling(offsetY)
+      },
+      pullingEnd () {
+        this.$el.style.transition = `height .2s`
+        if (this.height >= this.viewHeight) {
+          this.pulling(this.viewHeight)
+          this.$emit('refresh')
+        }
+        else {
+          this.pulling(0)
+        }
+      },
+      getChildren () {
+        const children = this.$slots.default || []
+        if (this.display === 'show') {
+          return children
+        }
+        return children.filter(vnode => {
+          return vnode.componentOptions
+            && vnode.componentOptions.tag !== 'loading-indicator'
+        })
       }
-      return children.filter(vnode => {
-        return vnode.componentOptions
-          && vnode.componentOptions.tag !== 'loading-indicator'
-      })
+    },
+    render (createElement) {
+      this.$parent._refresh = this
+      return createElement('aside', {
+        ref: 'refresh',
+        attrs: { 'weex-type': 'refresh' },
+        staticClass: 'weex-refresh weex-ct',
+        staticStyle: extractComponentStyle(this)
+      }, this.getChildren())
     }
-  },
-  render (createElement) {
-    this.$parent._refresh = this
-    this._renderHook()
-    return createElement('aside', {
-      ref: 'refresh',
-      attrs: { 'weex-type': 'refresh' },
-      staticClass: 'weex-refresh weex-ct',
-      staticStyle: extractComponentStyle(this)
-    }, this.getChildren())
+  }
+}
+
+export default {
+  init (weex) {
+    weex.registerComponent('refresh', getRefresh(weex))
   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b1a7c02a/html5/render/vue/components/scrollable/scroller.js
----------------------------------------------------------------------
diff --git a/html5/render/vue/components/scrollable/scroller.js b/html5/render/vue/components/scrollable/scroller.js
index 1eebab3..cda95b8 100644
--- a/html5/render/vue/components/scrollable/scroller.js
+++ b/html5/render/vue/components/scrollable/scroller.js
@@ -16,77 +16,88 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { extractComponentStyle, createEventMap } from '../../core'
-import { scrollable } from '../../mixins'
-// import { validateStyles } from '../../validator'
-import listMixin from './list/listMixin'
 
-export default {
-  mixins: [scrollable, listMixin],
-  props: {
-    scrollDirection: {
-      type: [String],
-      default: 'vertical',
-      validator (value) {
-        return ['horizontal', 'vertical'].indexOf(value) !== -1
+import { scrollable, list as listMixin } from './mixins'
+
+function getScroller (weex) {
+  const {
+    extractComponentStyle,
+    createEventMap
+  } = weex
+
+  return {
+    name: 'weex-scroller',
+    mixins: [scrollable, listMixin],
+    props: {
+      scrollDirection: {
+        type: [String],
+        default: 'vertical',
+        validator (value) {
+          return ['horizontal', 'vertical'].indexOf(value) !== -1
+        }
       }
-    }
-  },
+    },
 
-  computed: {
-    wrapperClass () {
-      const classArray = ['weex-scroller', 'weex-scroller-wrapper', 'weex-ct']
-      if (this.scrollDirection === 'horizontal') {
-        classArray.push('weex-scroller-horizontal')
+    computed: {
+      wrapperClass () {
+        const classArray = ['weex-scroller', 'weex-scroller-wrapper', 'weex-ct']
+        if (this.scrollDirection === 'horizontal') {
+          classArray.push('weex-scroller-horizontal')
+        }
+        else {
+          classArray.push('weex-scroller-vertical')
+        }
+        return classArray.join(' ')
       }
-      else {
-        classArray.push('weex-scroller-vertical')
+    },
+
+    methods: {
+      createChildren (h) {
+        const slots = this.$slots.default || []
+        this._cells = slots.filter(vnode => {
+          if (!vnode.tag || !vnode.componentOptions) return false
+          return true
+        })
+        return [
+          h('html:div', {
+            ref: 'inner',
+            staticClass: 'weex-scroller-inner weex-ct'
+          }, this._cells)
+        ]
       }
-      return classArray.join(' ')
-    }
-  },
+    },
 
-  methods: {
-    createChildren (h) {
-      const slots = this.$slots.default || []
-      this._cells = slots.filter(vnode => {
-        if (!vnode.tag || !vnode.componentOptions) return false
-        return true
-      })
-      return [
-        h('html:div', {
-          ref: 'inner',
-          staticClass: 'weex-scroller-inner weex-ct'
-        }, this._cells)
-      ]
-    }
-  },
+    render (createElement) {
+      this.weexType = 'scroller'
 
-  render (createElement) {
-    this.weexType = 'scroller'
+      /* istanbul ignore next */
+      // if (process.env.NODE_ENV === 'development') {
+      //   validateStyles('scroller', this.$vnode.data && this.$vnode.data.staticStyle)
+      // }
 
-    /* istanbul ignore next */
-    // if (process.env.NODE_ENV === 'development') {
-    //   validateStyles('scroller', this.$vnode.data && this.$vnode.data.staticStyle)
-    // }
+      this._cells = this.$slots.default || []
+      this.$nextTick(() => {
+        this.updateLayout()
+      })
 
-    this._cells = this.$slots.default || []
-    this.$nextTick(() => {
-      this.updateLayout()
-    })
+      return createElement('main', {
+        ref: 'wrapper',
+        attrs: { 'weex-type': 'scroller' },
+        on: createEventMap(this, {
+          scroll: this.handleScroll,
+          touchstart: this.handleTouchStart,
+          touchmove: this.handleTouchMove,
+          touchend: this.handleTouchEnd
+        }),
+        staticClass: this.wrapperClass,
+        staticStyle: extractComponentStyle(this)
+      }, this.createChildren(createElement))
+    }
+  }
+}
 
-    this._renderHook()
-    return createElement('main', {
-      ref: 'wrapper',
-      attrs: { 'weex-type': 'scroller' },
-      on: createEventMap(this, {
-        scroll: this.handleScroll,
-        touchstart: this.handleTouchStart,
-        touchmove: this.handleTouchMove,
-        touchend: this.handleTouchEnd
-      }),
-      staticClass: this.wrapperClass,
-      staticStyle: extractComponentStyle(this)
-    }, this.createChildren(createElement))
+export default {
+  init (weex) {
+    weex.registerComponent('scroller', getScroller(weex))
   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b1a7c02a/html5/render/vue/components/scrollable/style.css
----------------------------------------------------------------------
diff --git a/html5/render/vue/components/scrollable/style.css b/html5/render/vue/components/scrollable/style.css
new file mode 100644
index 0000000..c026113
--- /dev/null
+++ b/html5/render/vue/components/scrollable/style.css
@@ -0,0 +1,66 @@
+body > .weex-list,
+body > .weex-scroller,
+body > .weex-waterfall {
+  max-height: 100%;
+}
+
+.weex-list-wrapper,
+.weex-scroller-wrapper,
+.weex-waterfall-wrapper {
+  -webkit-overflow-scrolling: touch;
+}
+
+.weex-list-wrapper,
+.weex-waterfall-wrapper {
+  overflow-y: scroll !important;
+  overflow-x: hidden !important;
+}
+
+.weex-list-inner,
+.weex-scroller-inner,
+.weex-waterfall-inner {
+  -webkit-overflow-scrolling: touch;
+}
+
+.weex-waterfall-inner-columns {
+  -webkit-flex-direction: row;
+      -ms-flex-direction: row;
+          flex-direction: row;
+  -webkit-box-orient: horizontal;
+}
+
+.weex-scroller-wrapper.weex-scroller-vertical {
+  overflow-x: hidden;
+  overflow-y: scroll;
+}
+
+.weex-scroller-wrapper.weex-scroller-horizontal {
+  overflow-x: scroll;
+  overflow-y: hidden;
+}
+
+.weex-scroller-horizontal .weex-scroller-inner {
+  -webkit-flex-direction: row;
+      -ms-flex-direction: row;
+          flex-direction: row;
+  -webkit-box-orient: horizontal;
+  height: 100%;
+}
+
+.weex-cell {
+  width: 100%;
+}
+
+.weex-refresh,
+.weex-loading {
+  -webkit-box-align: center;
+  -webkit-align-items: center;
+      -ms-flex-align: center;
+          align-items: center;
+  -webkit-box-pack: center;
+  -webkit-justify-content: center;
+      -ms-flex-pack: center;
+          justify-content: center;
+  width: 100%;
+  overflow: hidden;
+}

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b1a7c02a/html5/render/vue/components/scrollable/waterfall.js
----------------------------------------------------------------------
diff --git a/html5/render/vue/components/scrollable/waterfall.js b/html5/render/vue/components/scrollable/waterfall.js
index 012c1d2..82b2295 100644
--- a/html5/render/vue/components/scrollable/waterfall.js
+++ b/html5/render/vue/components/scrollable/waterfall.js
@@ -21,317 +21,330 @@
  * @fileoverview waterfall
  * NOTE: only support full screen width waterfall.
  */
-import { extractComponentStyle, createEventMap } from '../../core'
-import { scrollable } from '../../mixins'
+
+import { scrollable } from './mixins'
 
 const NORMAL_GAP_SIZE = 32
 const DEFAULT_COLUMN_COUNT = 1
 
-export default {
-  mixins: [scrollable],
-  props: {
-    /**
-     * specified gap size.
-     * value can be number or 'normal'. 'normal' (32px) by default.
-     */
-    columnGap: {
-      type: [String, Number],
-      default: 'normal',
-      validator (val) {
-        if (!val || val === 'normal') {
-          return true
+function getWaterfall (weex) {
+  const {
+    extractComponentStyle,
+    createEventMap
+  } = weex
+
+  return {
+    name: 'weex-waterfall',
+    mixins: [scrollable],
+    props: {
+      /**
+       * specified gap size.
+       * value can be number or 'normal'. 'normal' (32px) by default.
+       */
+      columnGap: {
+        type: [String, Number],
+        default: 'normal',
+        validator (val) {
+          if (!val || val === 'normal') {
+            return true
+          }
+          val = parseInt(val)
+          return !isNaN(val) && val > 0
+        }
+      },
+      /**
+       * the maximum column counts.
+       * value can be number or 'auto'. 1 by default.
+       */
+      columnCount: {
+        type: [String, Number],
+        default: DEFAULT_COLUMN_COUNT,
+        validator (val) {
+          val = parseInt(val)
+          return !isNaN(val) && val > 0
+        }
+      },
+      /**
+       * the mimimum column width.
+       * value can be number or 'auto'. 'auto' by default.
+       */
+      columnWidth: {
+        type: [String, Number],
+        default: 'auto',
+        validator (val) {
+          if (!val || val === 'auto') {
+            return true
+          }
+          val = parseInt(val)
+          return !isNaN(val) && val > 0
         }
-        val = parseInt(val)
-        return !isNaN(val) && val > 0
       }
     },
-    /**
-     * the maximum column counts.
-     * value can be number or 'auto'. 1 by default.
-     */
-    columnCount: {
-      type: [String, Number],
-      default: DEFAULT_COLUMN_COUNT,
-      validator (val) {
-        val = parseInt(val)
-        return !isNaN(val) && val > 0
-      }
+
+    mounted () {
+      this._nextTick()
     },
-    /**
-     * the mimimum column width.
-     * value can be number or 'auto'. 'auto' by default.
-     */
-    columnWidth: {
-      type: [String, Number],
-      default: 'auto',
-      validator (val) {
-        if (!val || val === 'auto') {
-          return true
-        }
-        val = parseInt(val)
-        return !isNaN(val) && val > 0
-      }
-    }
-  },
 
-  mounted () {
-    this._nextTick()
-  },
+    updated () {
+      this.$nextTick(this._nextTick())
+    },
 
-  updated () {
-    this.$nextTick(this._nextTick())
-  },
+    methods: {
+      _createChildren (h, rootStyle) {
+        const slots = this.$slots.default || []
+        this._headers = []
+        this._others = []
+        this._cells = slots.filter(vnode => {
+          if (!vnode.tag || !vnode.componentOptions) return false
+          const tag = vnode.componentOptions.tag
+          if (tag === 'refresh' || tag === 'loading') {
+            this[`_${tag}`] = vnode
+            return false
+          }
+          if (tag === 'header') {
+            this._headers.push(vnode)
+            return false
+          }
+          if (tag !== 'cell') {
+            this._others.push(vnode)
+            return false
+          }
+          return true
+        })
+        this._reCalc(rootStyle)
+        this._genColumns(h)
+        let children = []
+        this._refresh && children.push(this._refresh)
+        children = children
+          .concat(this._headers)
+          .concat(this._others)
+        children.push(h('html:div', {
+          ref: 'columns',
+          staticClass: 'weex-waterfall-inner-columns weex-ct'
+        }, this._columns))
+        this._loading && children.push(this._loading)
+        return [
+          h('html:div', {
+            ref: 'inner',
+            staticClass: 'weex-waterfall-inner weex-ct'
+          }, children)
+        ]
+      },
 
-  methods: {
-    _createChildren (h, rootStyle) {
-      const slots = this.$slots.default || []
-      this._headers = []
-      this._others = []
-      this._cells = slots.filter(vnode => {
-        if (!vnode.tag || !vnode.componentOptions) return false
-        const tag = vnode.componentOptions.tag
-        if (tag === 'refresh' || tag === 'loading') {
-          this[`_${tag}`] = vnode
-          return false
+      _reCalc (rootStyle) {
+        /**
+         * NOTE: columnGap and columnWidth can't both be auto.
+         * NOTE: the formula:
+         *  totalWidth = n * w + (n - 1) * gap
+         * 1. if columnCount = n then calc w
+         * 2. if columnWidth = w then calc n
+         * 3. if columnWidth = w and columnCount = n then calc totalWidth
+         *    3.1 if totalWidth < ctWidth then increase columnWidth
+         *    3.2 if totalWidth > ctWidth then decrease columnCount
+         */
+        let width, gap, cnt, ctWidth
+        const scale = weex.config.env.scale
+        const el = this.$el
+        function getCtWidth (width, style) {
+          const padding = style.padding
+            ? parseInt(style.padding) * 2
+            : parseInt(style.paddingLeft || 0) + parseInt(style.paddingRight || 0)
+          return width - padding
         }
-        if (tag === 'header') {
-          this._headers.push(vnode)
-          return false
+        if (el && el.nodeType === 1) {  // already mounted
+          const cstyle = window.getComputedStyle(el)
+          ctWidth = getCtWidth(el.getBoundingClientRect().width, cstyle)
         }
-        if (tag !== 'cell') {
-          this._others.push(vnode)
-          return false
+        else {  // not mounted.
+          // only support full screen width for waterfall component.
+          ctWidth = getCtWidth(document.documentElement.clientWidth, rootStyle)
         }
-        return true
-      })
-      this._reCalc(rootStyle)
-      this._genColumns(h)
-      let children = []
-      this._refresh && children.push(this._refresh)
-      children = children
-        .concat(this._headers)
-        .concat(this._others)
-      children.push(h('html:div', {
-        ref: 'columns',
-        staticClass: 'weex-waterfall-inner-columns weex-ct'
-      }, this._columns))
-      this._loading && children.push(this._loading)
-      return [
-        h('html:div', {
-          ref: 'inner',
-          staticClass: 'weex-waterfall-inner weex-ct'
-        }, children)
-      ]
-    },
 
-    _reCalc (rootStyle) {
-      /**
-       * NOTE: columnGap and columnWidth can't both be auto.
-       * NOTE: the formula:
-       *  totalWidth = n * w + (n - 1) * gap
-       * 1. if columnCount = n then calc w
-       * 2. if columnWidth = w then calc n
-       * 3. if columnWidth = w and columnCount = n then calc totalWidth
-       *    3.1 if totalWidth < ctWidth then increase columnWidth
-       *    3.2 if totalWidth > ctWidth then decrease columnCount
-       */
-      let width, gap, cnt, ctWidth
-      const scale = weex.config.env.scale
-      const el = this.$el
-      function getCtWidth (width, style) {
-        const padding = style.padding
-          ? parseInt(style.padding) * 2
-          : parseInt(style.paddingLeft || 0) + parseInt(style.paddingRight || 0)
-        return width - padding
-      }
-      if (el && el.nodeType === 1) {  // already mounted
-        const cstyle = window.getComputedStyle(el)
-        ctWidth = getCtWidth(el.getBoundingClientRect().width, cstyle)
-      }
-      else {  // not mounted.
-        // only support full screen width for waterfall component.
-        ctWidth = getCtWidth(document.documentElement.clientWidth, rootStyle)
-      }
-
-      gap = this.columnGap
-      if (gap && gap !== 'normal') {
-        gap = parseInt(gap)
-      }
-      else {
-        gap = NORMAL_GAP_SIZE
-      }
-      gap = gap * scale
-
-      width = this.columnWidth
-      cnt = this.columnCount
-      if (width && width !== 'auto') {
-        width = parseInt(width) * scale
-      }
-      if (cnt && cnt !== 'auto') {
-        cnt = parseInt(cnt)
-      }
+        gap = this.columnGap
+        if (gap && gap !== 'normal') {
+          gap = parseInt(gap)
+        }
+        else {
+          gap = NORMAL_GAP_SIZE
+        }
+        gap = gap * scale
 
-      // 0. if !columnCount && !columnWidth
-      if (cnt === 'auto' && width === 'auto') {
-        if (process.env.NODE_ENV === 'development') {
-          console.warn(`[vue-render] waterfall's columnWidth and columnCount shouldn't`
-          + ` both be auto at the same time.`)
-          cnt = DEFAULT_COLUMN_COUNT
-          width = ctWidth
+        width = this.columnWidth
+        cnt = this.columnCount
+        if (width && width !== 'auto') {
+          width = parseInt(width) * scale
         }
-      }
-      // 1. if columnCount = n then calc w.
-      else if (cnt !== 'auto' && width === 'auto') {
-        width = (ctWidth - (cnt - 1) * gap) / cnt
-      }
-      // 2. if columnWidth = w then calc n.
-      else if (cnt === 'auto' && width !== 'auto') {
-        cnt = (ctWidth + gap) / (width + gap)
-      }
-      // 3. if columnWidth = w and columnCount = n then calc totalWidth
-      else if (cnt !== 'auto' && width !== 'auto') {
-        let totalWidth
-        const adjustCountAndWidth = () => {
-          totalWidth = cnt * width + (cnt - 1) * gap
-          if (totalWidth < ctWidth) {
-            width += (ctWidth - totalWidth) / cnt
-          }
-          else if (totalWidth > ctWidth && cnt > 1) {
-            cnt--
-            adjustCountAndWidth()
-          }
-          else if (totalWidth > ctWidth) {  // cnt === 1
+        if (cnt && cnt !== 'auto') {
+          cnt = parseInt(cnt)
+        }
+
+        // 0. if !columnCount && !columnWidth
+        if (cnt === 'auto' && width === 'auto') {
+          if (process.env.NODE_ENV === 'development') {
+            console.warn(`[vue-render] waterfall's columnWidth and columnCount shouldn't`
+            + ` both be auto at the same time.`)
+            cnt = DEFAULT_COLUMN_COUNT
             width = ctWidth
           }
         }
-        adjustCountAndWidth()
-      }
-      this._columnCount = cnt
-      this._columnWidth = width
-      this._columnGap = gap
-    },
-
-    _genColumns (createElement) {
-      this._columns = []
-      const cells = this._cells
-      const columnCnt = this._columnCount
-      const len = cells.length
-      const columnCells = this._columnCells = Array(columnCnt).join('.').split('.').map(function () { return [] })
-      // spread cells to the columns using simpole polling algorithm.
-      for (let i = 0; i < len; i++) {
-        (cells[i].data.attrs || (cells[i].data.attrs = {}))['data-cell'] = i
-        columnCells[i % columnCnt].push(cells[i])
-      }
-      for (let i = 0; i < columnCnt; i++) {
-        this._columns.push(createElement('html:div', {
-          ref: `column${i}`,
-          attrs: {
-            'data-column': i
-          },
-          staticClass: 'weex-ct',
-          staticStyle: {
-            width: this._columnWidth + 'px',
-            marginLeft: i === 0 ? 0 : this._columnGap + 'px'
+        // 1. if columnCount = n then calc w.
+        else if (cnt !== 'auto' && width === 'auto') {
+          width = (ctWidth - (cnt - 1) * gap) / cnt
+        }
+        // 2. if columnWidth = w then calc n.
+        else if (cnt === 'auto' && width !== 'auto') {
+          cnt = (ctWidth + gap) / (width + gap)
+        }
+        // 3. if columnWidth = w and columnCount = n then calc totalWidth
+        else if (cnt !== 'auto' && width !== 'auto') {
+          let totalWidth
+          const adjustCountAndWidth = () => {
+            totalWidth = cnt * width + (cnt - 1) * gap
+            if (totalWidth < ctWidth) {
+              width += (ctWidth - totalWidth) / cnt
+            }
+            else if (totalWidth > ctWidth && cnt > 1) {
+              cnt--
+              adjustCountAndWidth()
+            }
+            else if (totalWidth > ctWidth) {  // cnt === 1
+              width = ctWidth
+            }
           }
-        }, columnCells[i]))
-      }
-    },
+          adjustCountAndWidth()
+        }
+        this._columnCount = cnt
+        this._columnWidth = width
+        this._columnGap = gap
+      },
 
-    _nextTick () {
-      this._reLayoutChildren()
-    },
+      _genColumns (createElement) {
+        this._columns = []
+        const cells = this._cells
+        const columnCnt = this._columnCount
+        const len = cells.length
+        const columnCells = this._columnCells = Array(columnCnt).join('.').split('.').map(function () { return [] })
+        // spread cells to the columns using simpole polling algorithm.
+        for (let i = 0; i < len; i++) {
+          (cells[i].data.attrs || (cells[i].data.attrs = {}))['data-cell'] = i
+          columnCells[i % columnCnt].push(cells[i])
+        }
+        for (let i = 0; i < columnCnt; i++) {
+          this._columns.push(createElement('html:div', {
+            ref: `column${i}`,
+            attrs: {
+              'data-column': i
+            },
+            staticClass: 'weex-ct',
+            staticStyle: {
+              width: this._columnWidth + 'px',
+              marginLeft: i === 0 ? 0 : this._columnGap + 'px'
+            }
+          }, columnCells[i]))
+        }
+      },
 
-    _reLayoutChildren () {
-      /**
-       * treat the shortest column bottom as the match standard.
-       * whichever cell exceeded it would be rearranged.
-       * 1. m = shortest column bottom.
-       * 2. get all cell ids who is below m.
-       * 3. calculate which cell should be in which column.
-       */
-      const columnCnt = this._columnCount
-      const columnDoms = []
-      const columnAppendFragments = []
-      const columnBottoms = []
-      let minBottom = Number.MAX_SAFE_INTEGER
-      let minBottomColumnIndex = 0
+      _nextTick () {
+        this._reLayoutChildren()
+      },
 
-      // 1. find the shortest column bottom.
-      for (let i = 0; i < columnCnt; i++) {
-        const columnDom = this._columns[i].elm
-        const lastChild = columnDom.lastElementChild
-        const bottom = lastChild ? lastChild.getBoundingClientRect().bottom : 0
-        columnDoms.push(columnDom)
-        columnBottoms[i] = bottom
-        columnAppendFragments.push(document.createDocumentFragment())
-        if (bottom < minBottom) {
-          minBottom = bottom
-          minBottomColumnIndex = i
-        }
-      }
+      _reLayoutChildren () {
+        /**
+         * treat the shortest column bottom as the match standard.
+         * whichever cell exceeded it would be rearranged.
+         * 1. m = shortest column bottom.
+         * 2. get all cell ids who is below m.
+         * 3. calculate which cell should be in which column.
+         */
+        const columnCnt = this._columnCount
+        const columnDoms = []
+        const columnAppendFragments = []
+        const columnBottoms = []
+        let minBottom = Number.MAX_SAFE_INTEGER
+        let minBottomColumnIndex = 0
 
-      // 2. get all cell ids who is below m.
-      const belowCellIds = []
-      const belowCells = {}
-      for (let i = 0; i < columnCnt; i++) {
-        if (i === minBottomColumnIndex) {
-          continue
+        // 1. find the shortest column bottom.
+        for (let i = 0; i < columnCnt; i++) {
+          const columnDom = this._columns[i].elm
+          const lastChild = columnDom.lastElementChild
+          const bottom = lastChild ? lastChild.getBoundingClientRect().bottom : 0
+          columnDoms.push(columnDom)
+          columnBottoms[i] = bottom
+          columnAppendFragments.push(document.createDocumentFragment())
+          if (bottom < minBottom) {
+            minBottom = bottom
+            minBottomColumnIndex = i
+          }
         }
-        const columnDom = columnDoms[i]
-        const cellsInColumn = columnDom.querySelectorAll('section.weex-cell')
-        const len = cellsInColumn.length
-        for (let j = len - 1; j >= 0; j--) {
-          const cellDom = cellsInColumn[j]
-          const cellRect = cellDom.getBoundingClientRect()
-          if (cellRect.top > minBottom) {
-            const id = ~~cellDom.getAttribute('data-cell')
-            belowCellIds.push(id)
-            belowCells[id] = { elm: cellDom, height: cellRect.height }
-            columnBottoms[i] -= cellRect.height
+
+        // 2. get all cell ids who is below m.
+        const belowCellIds = []
+        const belowCells = {}
+        for (let i = 0; i < columnCnt; i++) {
+          if (i === minBottomColumnIndex) {
+            continue
+          }
+          const columnDom = columnDoms[i]
+          const cellsInColumn = columnDom.querySelectorAll('section.weex-cell')
+          const len = cellsInColumn.length
+          for (let j = len - 1; j >= 0; j--) {
+            const cellDom = cellsInColumn[j]
+            const cellRect = cellDom.getBoundingClientRect()
+            if (cellRect.top > minBottom) {
+              const id = ~~cellDom.getAttribute('data-cell')
+              belowCellIds.push(id)
+              belowCells[id] = { elm: cellDom, height: cellRect.height }
+              columnBottoms[i] -= cellRect.height
+            }
           }
         }
-      }
 
-      // 3. calculate which cell should be in which column and rearrange them
-      //  in the dom tree.
-      belowCellIds.sort(function (a, b) { return a > b })
-      const cellIdsLen = belowCellIds.length
-      function addToShortestColumn (belowCell) {
-        // find shortest bottom.
-        minBottom = Math.min(...columnBottoms)
-        minBottomColumnIndex = columnBottoms.indexOf(minBottom)
-        const { elm: cellElm, height: cellHeight } = belowCell
-        columnAppendFragments[minBottomColumnIndex].appendChild(cellElm)
-        columnBottoms[minBottomColumnIndex] += cellHeight
-      }
-      for (let i = 0; i < cellIdsLen; i++) {
-        addToShortestColumn(belowCells[belowCellIds[i]])
-      }
-      for (let i = 0; i < columnCnt; i++) {
-        columnDoms[i].appendChild(columnAppendFragments[i])
+        // 3. calculate which cell should be in which column and rearrange them
+        //  in the dom tree.
+        belowCellIds.sort(function (a, b) { return a > b })
+        const cellIdsLen = belowCellIds.length
+        function addToShortestColumn (belowCell) {
+          // find shortest bottom.
+          minBottom = Math.min(...columnBottoms)
+          minBottomColumnIndex = columnBottoms.indexOf(minBottom)
+          const { elm: cellElm, height: cellHeight } = belowCell
+          columnAppendFragments[minBottomColumnIndex].appendChild(cellElm)
+          columnBottoms[minBottomColumnIndex] += cellHeight
+        }
+        for (let i = 0; i < cellIdsLen; i++) {
+          addToShortestColumn(belowCells[belowCellIds[i]])
+        }
+        for (let i = 0; i < columnCnt; i++) {
+          columnDoms[i].appendChild(columnAppendFragments[i])
+        }
       }
+    },
+
+    render (createElement) {
+      this.weexType = 'waterfall'
+      this._cells = this.$slots.default || []
+      this.$nextTick(() => {
+        this.updateLayout()
+      })
+      const mergedStyle = extractComponentStyle(this)
+      return createElement('main', {
+        ref: 'wrapper',
+        attrs: { 'weex-type': 'waterfall' },
+        on: createEventMap(this, {
+          scroll: this.handleScroll,
+          touchstart: this.handleTouchStart,
+          touchmove: this.handleTouchMove,
+          touchend: this.handleTouchEnd
+        }),
+        staticClass: 'weex-waterfall weex-waterfall-wrapper weex-ct',
+        staticStyle: mergedStyle
+      }, this._createChildren(createElement, mergedStyle))
     }
-  },
+  }
+}
 
-  render (createElement) {
-    this.weexType = 'waterfall'
-    this._cells = this.$slots.default || []
-    this.$nextTick(() => {
-      this.updateLayout()
-    })
-    this._renderHook()
-    const mergedStyle = extractComponentStyle(this)
-    return createElement('main', {
-      ref: 'wrapper',
-      attrs: { 'weex-type': 'waterfall' },
-      on: createEventMap(this, {
-        scroll: this.handleScroll,
-        touchstart: this.handleTouchStart,
-        touchmove: this.handleTouchMove,
-        touchend: this.handleTouchEnd
-      }),
-      staticClass: 'weex-waterfall weex-waterfall-wrapper weex-ct',
-      staticStyle: mergedStyle
-    }, this._createChildren(createElement, mergedStyle))
+export default {
+  init (weex) {
+    weex.registerComponent('waterfall', getWaterfall(weex))
   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b1a7c02a/html5/render/vue/components/slider/index.js
----------------------------------------------------------------------
diff --git a/html5/render/vue/components/slider/index.js b/html5/render/vue/components/slider/index.js
index f37be08..90c8d6f 100644
--- a/html5/render/vue/components/slider/index.js
+++ b/html5/render/vue/components/slider/index.js
@@ -1,68 +1,9 @@
-/*
- * 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 { validateStyles } from '../../validator'
-// import indicator from './indicator'
-import slideMixin from './slideMixin'
+import slider from './slider'
+import indicator from './indicator'
 
 export default {
-  mixins: [slideMixin],
-  props: {
-    index: {
-      type: [String, Number],
-      default: 0
-    },
-    'auto-play': {
-      type: [String, Boolean],
-      default: false
-    },
-    interval: {
-      type: [String, Number],
-      default: 3000
-    },
-    infinite: {
-      type: [String, Boolean],
-      default: true
-    }
-  },
-
-  watch: {
-    index () {
-      this.currentIndex = this._normalizeIndex(this.index)
-    }
-  },
-
-  data () {
-    return {
-      frameCount: 0,
-      currentIndex: this.index
-    }
-  },
-
-  beforeCreate () {
-    this.weexType = 'slider'
-  },
-
-  render (createElement) {
-    /* istanbul ignore next */
-    // if (process.env.NODE_ENV === 'development') {
-    //   validateStyles('slider', this.$vnode.data && this.$vnode.data.staticStyle)
-    // }
-    return this._renderSlides(createElement)
+  init (weex) {
+    weex.install(slider)
+    weex.install(indicator)
   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b1a7c02a/html5/render/vue/components/slider/indicator.js
----------------------------------------------------------------------
diff --git a/html5/render/vue/components/slider/indicator.js b/html5/render/vue/components/slider/indicator.js
index e38bd6c..62b95a3 100644
--- a/html5/render/vue/components/slider/indicator.js
+++ b/html5/render/vue/components/slider/indicator.js
@@ -16,8 +16,6 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { extractComponentStyle } from '../../core'
-import { extend, extendKeys } from '../../utils'
 
 const _css = `
 .weex-indicator {
@@ -48,6 +46,8 @@ const _css = `
 }
 `
 
+let extractComponentStyle, extend, extendKeys
+
 function getIndicatorItemStyle (spec, isActive) {
   const style = {}
   style['background-color'] = spec[isActive ? 'itemSelectedColor' : 'itemColor']
@@ -157,8 +157,8 @@ function _reLayout (context, virtualRect, ltbr) {
   })
 }
 
-export default {
-  name: 'indicator',
+const indicator = {
+  name: 'weex-indicator',
   methods: {
     show: function () {
       this.$el.style.visibility = 'visible'
@@ -175,8 +175,16 @@ export default {
     this.count = count
     this.active = active
     if (!this.count) { return }
-    this._renderHook()
     return _render(this, createElement)
   },
   _css
 }
+
+export default {
+  init (weex) {
+    extractComponentStyle = weex.extractComponentStyle
+    extend = weex.utils.extend
+    extendKeys = weex.utils.extendKeys
+    weex.registerComponent('indicator', indicator)
+  }
+}