diff --git a/docs/guide/all-sites-mip/page-api.md b/docs/guide/all-sites-mip/page-api.md index a2c24386..da0234ca 100644 --- a/docs/guide/all-sites-mip/page-api.md +++ b/docs/guide/all-sites-mip/page-api.md @@ -25,7 +25,7 @@ if (isRootPage) { 如果页面需要展现全页面级别的遮罩层(如弹出对话框时),因为 iframe 的关系,并不能遮挡头部,如下图左边所示。 -![Page Mask](http://boscdn.bpc.baidu.com/assets/mip2/page/page-mask.png) +![Page Mask](http://boscdn.bpc.baidu.com/assets/mip2/page/page-mask-2.png) 而调用 `togglePageMask` 方法可以就通知 Page 把头部也进行遮挡,从而完成全页面的遮罩,如上图右边所示。 diff --git a/packages/mip/examples/page/index.html b/packages/mip/examples/page/index.html index 51d511e2..f4331a42 100644 --- a/packages/mip/examples/page/index.html +++ b/packages/mip/examples/page/index.html @@ -108,7 +108,9 @@

共享数据 checklist:

Go to Data -
+ Anchor + Anchor2 + Anchor3

@@ -122,8 +124,8 @@

共享数据 checklist:

Go to Test Go to Tree (viewer.open) Go to Tree(Cross Origin) - Go to Tree - + Go to Tree +

@@ -141,7 +143,7 @@

共享数据 checklist:

Reliable - Load instantly and never show the downasaur, even in uncertain network conditions. Fast - Respond quickly to user interactions with silky smooth animations and no janky scrolling.

-
+

Progressive Web Apps are user experiences that have the reach of the web, and are: Reliable - Load instantly and never show the downasaur, even in uncertain network conditions. diff --git a/packages/mip/src/components/mip-bind/bind.js b/packages/mip/src/components/mip-bind/bind.js index 11121563..be13372b 100644 --- a/packages/mip/src/components/mip-bind/bind.js +++ b/packages/mip/src/components/mip-bind/bind.js @@ -114,7 +114,7 @@ class Bind { if (typeof data === 'object') { let origin = JSON.stringify(win.m) - this.compile.upadteData(JSON.parse(origin)) + this.compile.updateData(JSON.parse(origin)) let classified = this.normalize(data) // need compile - $set if (compile) { diff --git a/packages/mip/src/components/mip-bind/compile.js b/packages/mip/src/components/mip-bind/compile.js index fb3b8150..3cd62ebf 100644 --- a/packages/mip/src/components/mip-bind/compile.js +++ b/packages/mip/src/components/mip-bind/compile.js @@ -194,7 +194,7 @@ class Compile { } } - upadteData (data) { + updateData (data) { this.origin = data } diff --git a/packages/mip/src/components/mip-bind/observer.js b/packages/mip/src/components/mip-bind/observer.js index c8644a78..84446cbc 100644 --- a/packages/mip/src/components/mip-bind/observer.js +++ b/packages/mip/src/components/mip-bind/observer.js @@ -4,6 +4,7 @@ */ import Deps from './deps' +import {isObject} from './util' class Observer { /* @@ -25,7 +26,7 @@ class Observer { * @param {Object} depMap supporting dependencies map */ walk (data, depMap) { - if (typeof data !== 'object' || typeof depMap !== 'object') { + if (!isObject(data) || !isObject(depMap)) { return } diff --git a/packages/mip/src/components/mip-shell/index.js b/packages/mip/src/components/mip-shell/index.js index bdf46897..4d2c38db 100644 --- a/packages/mip/src/components/mip-shell/index.js +++ b/packages/mip/src/components/mip-shell/index.js @@ -283,6 +283,9 @@ class MipShell extends CustomElement { borderColor, backgroundColor = '#ffffff' } = pageMeta.header + if (this.targetPageTitle) { + title = pageMeta.header.title = this.targetPageTitle + } let showBackIcon = !pageMeta.view.isIndex let headerHTML = ` @@ -497,11 +500,14 @@ class MipShell extends CustomElement { /** * priority of header.title: * 1. (to.meta.title) - * 2. route.meta.header.title (findMetaById(id).header.title) + * 2. targetPageMeta.header.title (findMetaById(id).header.title) * 3. innerText (to.meta.defaultTitle) */ let targetPageMeta = fn.extend(true, {}, this.findMetaByPageId(targetPageId)) - document.title = targetPageMeta.header.title = to.meta.title || targetPageMeta.header.title || to.meta.defaultTitle + this.targetPageTitle = to.meta.header + ? to.meta.header.title || targetPageMeta.header.title || to.meta.header.defaultTitle + : targetPageMeta.header.title + document.title = targetPageMeta.header.title = this.targetPageTitle // Transition direction let isForward diff --git a/packages/mip/src/index.js b/packages/mip/src/index.js index 084b0835..69f930b9 100644 --- a/packages/mip/src/index.js +++ b/packages/mip/src/index.js @@ -27,6 +27,7 @@ import MipShell from './components/mip-shell/index' import registerCustomElement from './register-element' import sleepWakeModule from './sleep-wake-module' import performance from './performance' +import templates from './util/templates' import mip1PolyfillInstall from './mip1-polyfill/index' import monitorInstall from './log/monitor' @@ -101,6 +102,8 @@ let mip = { sandbox, css: {}, push, + performance, + templates, prerenderElement: Resources.prerenderElement, builtinComponents: { // MipShell 应该删除,不符合命名 diff --git a/packages/mip/src/page/index.js b/packages/mip/src/page/index.js index 43b0951a..8df70b2f 100644 --- a/packages/mip/src/page/index.js +++ b/packages/mip/src/page/index.js @@ -26,6 +26,7 @@ import { import {customEmit} from '../util/custom-event' import viewport from '../viewport' +import performance from '../performance' import '../styles/mip.less' /** @@ -65,7 +66,8 @@ class Page { } try { - const anchor = document.getElementById(decodeURIComponent(hash.slice(1))) + const anchor = document.getElementById(hash.slice(1)) || + document.getElementById(decodeURIComponent(hash.slice(1))) /* istanbul ignore next */ if (anchor) { @@ -170,8 +172,17 @@ class Page { ensureMIPShell() this.initPageId() - // scroll to current hash if exists - this.scrollToHash(window.location.hash) + /** + * scroll to anchor after all the elements loaded + * fix: https://github.com/mipengine/mip2/issues/125 + */ + performance.on('update', timing => { + if (timing.MIPFirstScreen) { + // scroll to current hash if exists + this.scrollToHash(window.location.hash) + } + }) + window.addEventListener(CUSTOM_EVENT_SCROLL_TO_ANCHOR, (e) => { this.scrollToHash(e.detail[0]) }) @@ -243,10 +254,12 @@ class Page { customEmit(window, event.name, event.data) this.children.forEach(pageMeta => { - pageMeta.targetWindow.postMessage({ - type: MESSAGE_CROSS_ORIGIN, - data: event - }, '*') + if (pageMeta.targetWindow) { + pageMeta.targetWindow.postMessage({ + type: MESSAGE_CROSS_ORIGIN, + data: event + }, '*') + } }) } else { window.parent.postMessage({ diff --git a/packages/mip/src/util/dom/rect.js b/packages/mip/src/util/dom/rect.js index 469e6738..4e37dc8b 100644 --- a/packages/mip/src/util/dom/rect.js +++ b/packages/mip/src/util/dom/rect.js @@ -142,7 +142,10 @@ export default { setScrollTop (top) { if (setterElement) { setterElement.style.top = top + 'px' - setterElement.scrollIntoView(true) + // 如果页面头部是一个图片(或者其他后续会改变高度的元素) + // 直接执行 scrollIntoView 会导致页面一打开就滚动到半当中,而不是头部 + // setTimeout 0 可以解决这个问题 + setTimeout(() => setterElement.scrollIntoView(true), 0) } else { this.scrollingElement.scrollTop = top } diff --git a/packages/mip/src/viewer.js b/packages/mip/src/viewer.js index 15713edd..155de640 100644 --- a/packages/mip/src/viewer.js +++ b/packages/mip/src/viewer.js @@ -405,14 +405,15 @@ let viewer = { if (this.isIframed) { this.lockBodyScroll() - // Fix iphone 5s UC and ios 9 safari bug. // While the back button is clicked, // the cached page has some problems. - // So we are forced to load the page in iphone 5s UC - // and iOS 9 safari. + // So we are forced to load the page in below conditions: + // 1. IOS 8 + UC + // 2. IOS 9 & 10 + Safari + // 3. IOS 8 & 9 & 10 + UC & BaiduApp & Baidu let needBackReload = (iosVersion === '8' && platform.isUc() && screen.width === 320) || - (iosVersion === '9' && platform.isSafari()) || - (iosVersion === '10' && platform.isSafari()) + ((iosVersion === '9' || iosVersion === '10') && platform.isSafari()) || + ((iosVersion === '8' || iosVersion === '9' || iosVersion === '10') && (platform.isUc() || platform.isBaiduApp() || platform.isBaidu())) if (needBackReload) { window.addEventListener('pageshow', e => { if (e.persisted) { diff --git a/packages/mip/src/vue-custom-element/utils/props.js b/packages/mip/src/vue-custom-element/utils/props.js index 15303a96..4cf19bee 100644 --- a/packages/mip/src/vue-custom-element/utils/props.js +++ b/packages/mip/src/vue-custom-element/utils/props.js @@ -62,44 +62,50 @@ export function convertAttributeValue (value, type) { * 解析 vue 组件的 props * * @see https://vuejs.org/v2/guide/components-props.html#Prop-Casing-camelCase-vs-kebab-case - * @param {Array|Object} propsDef props collection + * @param {Object} component definition * @param {Object} props extract props */ -function extractProps (propsDef, props) { - if (isArray(propsDef)) { - propsDef.forEach(prop => { - let camelCaseProp = camelize(prop) - props.camelCase.indexOf(camelCaseProp) === -1 && props.camelCase.push(camelCaseProp) - props.types[prop] = getPropType(propsDef[camelCaseProp]) +function extractProps (def, propTypes) { + if (isArray(def.props)) { + def.props.forEach(prop => { + let camelizeName = camelize(prop) + if (!propTypes[camelizeName]) { + propTypes[camelizeName] = getPropType(def.props[prop]) + } }) - } else if (propsDef && typeof propsDef === 'object') { - for (let prop in propsDef) { - let camelCaseProp = camelize(prop) - props.camelCase.indexOf(camelCaseProp) === -1 && props.camelCase.push(camelCaseProp) - props.types[prop] = getPropType(propsDef[camelCaseProp]) + } else if (typeof def.props === 'object') { + for (let prop in def.props) { + let camelizeName = camelize(prop) + if (!propTypes[camelizeName]) { + propTypes[camelizeName] = getPropType(def.props[prop]) + } } } + + if (def.extends && def.extends.props) { + extractProps(def.extends, propTypes) + } + + if (def.mixins) { + def.mixins.forEach(mixin => extractProps(mixin, propTypes)) + } + + return propTypes } // Extract props from component definition, no matter if it's array or object -export function getProps (componentDefinition = {}) { +export function getProps (def = {}) { let props = { camelCase: [], hyphenate: [], types: {} } - if (componentDefinition.mixins) { - componentDefinition.mixins.forEach(mixin => extractProps(mixin.props, props)) - } - - if (componentDefinition.extends && componentDefinition.extends.props) { - extractProps(componentDefinition.extends.props, props) - } - - extractProps(componentDefinition.props, props) + let propTypes = extractProps(def, {}) - props.camelCase.forEach(prop => props.hyphenate.push(hyphenate(prop))) + props.camelCase = Object.keys(propTypes) + props.hyphenate = Object.keys(propTypes).map(key => hyphenate(key)) + props.types = propTypes return props } diff --git a/packages/mip/test/e2e/cases/index.html b/packages/mip/test/e2e/cases/index.html index 18d065d6..7162f243 100644 --- a/packages/mip/test/e2e/cases/index.html +++ b/packages/mip/test/e2e/cases/index.html @@ -35,6 +35,7 @@ Go to Tree Go to API page.forward() + Go to Scroll to Anchor diff --git a/packages/mip/test/e2e/cases/scroll-to-anchor.html b/packages/mip/test/e2e/cases/scroll-to-anchor.html new file mode 100644 index 00000000..d46328ce --- /dev/null +++ b/packages/mip/test/e2e/cases/scroll-to-anchor.html @@ -0,0 +1,130 @@ + + + + + MIP Index Page + + + + + + + +

+ +
+ + Anchor2 + +

+ Progressive Web Apps are user experiences that have the reach of the web, and are: +Reliable - Load instantly and never show the downasaur, even in uncertain network conditions. +Fast - Respond quickly to user interactions with silky smooth animations and no janky scrolling. +

+ + Go to Tree + Go to API + page.forward() + + + + Anchor + Anchor3 + +

+

+ Progressive Web Apps are user experiences that have the reach of the web, and are: +Reliable - Load instantly and never show the downasaur, even in uncertain network conditions. +Fast - Respond quickly to user interactions with silky smooth animations and no janky scrolling. +

+

+ Progressive Web Apps are user experiences that have the reach of the web, and are: +Reliable - Load instantly and never show the downasaur, even in uncertain network conditions. +Fast - Respond quickly to user interactions with silky smooth animations and no janky scrolling. +

+

+ Progressive Web Apps are user experiences that have the reach of the web, and are: +Reliable - Load instantly and never show the downasaur, even in uncertain network conditions. +Fast - Respond quickly to user interactions with silky smooth animations and no janky scrolling. +

+ +

+ Progressive Web Apps are user experiences that have the reach of the web, and are: +Reliable - Load instantly and never show the downasaur, even in uncertain network conditions. +Fast - Respond quickly to user interactions with silky smooth animations and no janky scrolling. +

+ + + + + + + + diff --git a/packages/mip/test/e2e/specs/scroll-to-anchor.js b/packages/mip/test/e2e/specs/scroll-to-anchor.js new file mode 100644 index 00000000..365d4cfe --- /dev/null +++ b/packages/mip/test/e2e/specs/scroll-to-anchor.js @@ -0,0 +1,107 @@ +/** + * @file scroll to anchor e2e test case (standalone 模式) + * @author panyuqi + * @description 测试流程: + * 1. 打开 scroll-to-anchor.html + * 2. 点击 滚动到锚点处 + * 3. 在当前页面刷新,滚动到锚点处 + * 4. 打开 index.html 通过 mip-link 跳转到 scroll-to-anchor.html 验证 iframe 下的情况 + * 5. 点击 iframe 中的 滚动到锚点处 + * + * fix ISSUE: https://github.com/mipengine/mip2/issues/125 + */ + +let INDEX_PAGE_URL +let SCROLL_PAGE_URL +let positionY = 0 // anchor's position in Yaxis +let positionY2 = 0 +let positionY3 = 0 + +module.exports = { + 'open page': function (browser) { + INDEX_PAGE_URL = `${browser.globals.devServerURL}/test/e2e/cases/index.html` + SCROLL_PAGE_URL = `${browser.globals.devServerURL}/test/e2e/cases/scroll-to-anchor.html` + + browser + // open index.html + .url(SCROLL_PAGE_URL) + // show mip-shell + .waitForElementVisible('body', 2000) + }, + 'scroll to correct position when click an anchor': function (browser) { + const anchor1 = '#anchor' + const anchor2 = '#anchor-%E8%AE' + const anchor3 = '#anchor-设置轮播时间间隔' + const hashOfAnchor3 = '#anchor-%E8%AE%BE%E7%BD%AE%E8%BD%AE%E6%92%AD%E6%97%B6%E9%97%B4%E9%97%B4%E9%9A%94' + + browser + // save anchor's position + .getLocation(anchor3, function (result) { + positionY2 = result.value.y + }) + // click anchor + .waitForClick(anchor3) + .pause(1000) + // hash changed + .assert.urlContains(hashOfAnchor3) + .execute(function () { + // it should scroll to top + this.assert.equal(window.MIP.viewport.getScrollTop(), positionY3) + }) + // save anchor's position + .getLocation('.encoded-anchor', function (result) { + positionY2 = result.value.y + }) + // click anchor + .waitForClick('.encoded-anchor') + .pause(1000) + // hash changed + .assert.urlContains(anchor2) + .execute(function () { + // it should scroll to top + this.assert.equal(window.MIP.viewport.getScrollTop(), positionY2) + }) + // save anchor's position + .getLocation(anchor1, function (result) { + positionY = result.value.y + }) + // click anchor + .waitForClick(anchor1) + .pause(1000) + // hash changed + .assert.urlContains(anchor1) + .execute(function () { + // it should scroll to top + this.assert.equal(window.MIP.viewport.getScrollTop(), positionY) + }) + }, + 'scroll to correct position when refresh': function (browser) { + browser + .refresh() + // hash contains #anchor + .assert.urlContains('#anchor') + .pause(1000) + .execute(function () { + // it should scroll to top + this.assert.equal(window.MIP.viewport.getScrollTop(), positionY) + }) + }, + 'open scroll page in iframe': function (browser) { + browser + // open index.html + .url(INDEX_PAGE_URL) + // open tree.html + .waitForClick('.scroll-to-anchor-link') + .enterIframe(SCROLL_PAGE_URL, () => { + browser + // click anchor + .waitForClick('#anchor') + .pause(1000) + .execute(function () { + // it should scroll to top + this.assert.equal(window.MIP.viewport.getScrollTop(), positionY) + }) + }) + .end() + } +} diff --git a/packages/mip/test/specs/components/mip-bind.spec.js b/packages/mip/test/specs/components/mip-bind.spec.js index efa498f4..d418bd71 100644 --- a/packages/mip/test/specs/components/mip-bind.spec.js +++ b/packages/mip/test/specs/components/mip-bind.spec.js @@ -168,7 +168,7 @@ describe('mip-bind', function () { title: 'changed' }) - MIP.$recompile() + // MIP.$recompile() expect(window.m.global).to.eql({ data: { @@ -264,6 +264,13 @@ describe('mip-bind', function () { expect(MIP.getData('loading')).to.be.true }) + + it('should compile smoothly even if data turn to null', function () { + MIP.setData({loc: null}) + MIP.$recompile() + + expect(window.m.loc).to.be.null + }) }) describe('watch', function () { diff --git a/packages/mip/test/specs/vue-custom-element/utils/props.spec.js b/packages/mip/test/specs/vue-custom-element/utils/props.spec.js index 3c87d9fa..8c7c3ea8 100644 --- a/packages/mip/test/specs/vue-custom-element/utils/props.spec.js +++ b/packages/mip/test/specs/vue-custom-element/utils/props.spec.js @@ -73,6 +73,17 @@ describe('vue-custom-element/utils/props', function () { propB: Number } }) + + expect(getProps({ + props: ['prop-a', 'prop-b', 'prop-b'] + })).to.deep.equal({ + camelCase: ['propA', 'propB'], + hyphenate: ['prop-a', 'prop-b'], + types: { + propA: String, + propB: String + } + }) }) it('not defined', function () { @@ -154,6 +165,30 @@ describe('vue-custom-element/utils/props', function () { }) }) + it('mixins2', function () { + let mixins2 = { + props: { + attr: Boolean + } + } + + let mixins1 = { + mixins: [mixins2] + } + + let props = getProps({ + mixins: [mixins1] + }) + + expect(props).to.deep.equal({ + camelCase: ['attr'], + hyphenate: ['attr'], + types: { + attr: Boolean + } + }) + }) + it('extends', function () { let props = getProps({ extends: { @@ -171,12 +206,12 @@ describe('vue-custom-element/utils/props', function () { }) expect(props).to.deep.equal({ - camelCase: ['propA', 'propB', 'propC'], - hyphenate: ['prop-a', 'prop-b', 'prop-c'], + camelCase: ['propB', 'propC', 'propA'], + hyphenate: ['prop-b', 'prop-c', 'prop-a'], types: { + propC: Number, propA: String, - propB: Number, - propC: Number + propB: Number } }) })