/***
* Created by lzq on 2018/11/02
* Page.js
*/
import $ from "jquery";
import Constant from "./Constant";
/**
* 对数组进行遍历
* @access private
* @param {Array} array
* @param {Function} itemCallback:function(value,index){}
*/
function forEach(array, itemCallback) {
for (let i = 0, len = array.length; i < len; i++) {
if (itemCallback) {
itemCallback.call(array[i], array[i], i);
}
}
}
/**
* 跳转到指定的历史
* @access private
* @param {number} index - 历史位置
* 注释:当前的位置为0 index负值为回退,index正数为前进 都以1开始
* 例如 -1 为当前页之前的页面,1为当前页之后的页面,0为刷新当前页面
*/
function go(index) {
this.ctmobile.router.go(index);
}
/**
* 创建Page的DOM结构
* @access private
*/
function createPageDOM() {
const self = this;
/***
* 根据pageId克隆模板
*/
this._pDom = $(this.ctmobile.templateDB[this.pageId])[0];
/***
* 设置page真实的id
*/
this._pDom.setAttribute("id", this.id);
/***
* 改变page中所有包含id的属性的值都加入id前缀(根据情况进行扩展)
* label的for属性
* input的list属性
* datalist的id属性
*/
forEach(this._pDom.querySelectorAll("[id]"), function () {
this.setAttribute("id", self.id + this.getAttribute("id"));
});
forEach(this._pDom.querySelectorAll("label[for]"), function () {
this.setAttribute("for", self.id + this.getAttribute("for"));
});
forEach(this._pDom.querySelectorAll("input[list]"), function () {
this.setAttribute("list", self.id + this.getAttribute("list"));
});
forEach(this._pDom.querySelectorAll("datalist[id]"), function () {
this.setAttribute("id", self.id + this.getAttribute("id"));
});
/***
* 获取page的页面过渡类型
*/
if (this._pDom.getAttribute("ct-data-transition")) {
this.transition = this._pDom.getAttribute("ct-data-transition");
}
/***
* 注册Page的默认事件
* 在addEventListeners之前的代码调用fireEvent方法去触发事件,因为只有注册后才能触发事件
*/
addEventListeners.call(this);
/***
* 对page中的通知(BorasdcastReceiver)进行处理
*/
if (this._pDom.getAttribute("ct-data-intentfilter-action")) {
this.ctmobile.registerReceiver({
el: this._pDom,
action: this._pDom.getAttribute("ct-data-intentfilter-action"),
priority: this._pDom.getAttribute("ct-data-intentfilter-priority") ? parseInt(this._pDom.getAttribute("ct-data-intentfilter-priority")) : 0,
categorys: this._pDom.getAttribute("ct-data-intentfilter-categorys") ? this._pDom.getAttribute("ct-data-intentfilter-categorys").split(" ") : []
}, this.pageReceiver);
}
/***
* 获取page中注册的返回按钮
*/
this._pDom.addEventListener("click", function (e) {
if (e.target.getAttribute("ct-data-rel") && e.target.getAttribute("ct-data-rel") === "back") {
go.call(self, -1);
}
}, false);
/***
* 渲染
*/
window.document.body.appendChild(this._pDom);
/***
* 触发pageCreate事件
*/
this.ctmobile.fireEvent(this._pDom, "pageCreate");
}
/**
* 布局
* @access private
*/
function layout() {
/***
* 复位
*/
slideByTransition.call(this, this.transition, "reset", 0);
}
/**
* 注册Page的事件
* @access private
*/
function addEventListeners() {
const self = this;
/***
* 注册Page的缺省事件
*/
self.getPageJO().on({
"pageCreate": self.pageCreate.bind(self),
"pageBeforeShow": self.pageBeforeShow.bind(self),
"pageShow": self.pageShow.bind(self),
"pageAfterShow": self.pageAfterShow.bind(self),
"pageBeforePause": self.pageBeforePause.bind(self),
"pageAfterPause": self.pageAfterPause.bind(self),
"pageBeforeRestore": self.pageBeforeRestore.bind(self),
"pageRestore": self.pageRestore.bind(self),
"pageAfterRestore": self.pageAfterRestore.bind(self),
"pageBeforeDestroy": self.pageBeforeDestroy.bind(self),
"pageResult": self.pageResult.bind(self),
"pageReceiver": self.pageReceiver.bind(self)
});
/**
* onTransitionAndAnimationEnd
* @access private
* @param {Object} e
*/
function onTransitionAndAnimationEnd(e) {
console.log(Constant._debugger, "------------------------------onTransitionAndAnimationEnd");
/***
* 如果当前页面的transition为material则肯定不会执行webkitTransitionEnd事件
*/
if (e.type === "webkitTransitionEnd" && e.target.getAttribute("ct-data-role") !== "page") {
return;
}
if (self.pageTransitionType) {
if (self.pageTransitionType === "start") {
delete self.pageTransitionType;
pageStartTransitionEndCallback.call(self, e);
} else if (self.pageTransitionType === "finish") {
delete self.pageTransitionType;
pageFinishTransitioneEndCallback.call(self, e);
}
}
}
/***
* 注册Page的transitionEnd和animationEnd事件
*/
self.getPageDOM().addEventListener("webkitTransitionEnd", onTransitionAndAnimationEnd, false);
self.getPageDOM().addEventListener("webkitAnimationEnd", onTransitionAndAnimationEnd, false);
}
/**
* 调用finish方法后的transitionEnd
* @access private
* @callback
*/
function pageFinishTransitioneEndCallback(e) {
const self = this;
/***
* 如果当前页面的transition为material则肯定不会执行webkitTransitionEnd事件
*/
if ((e.type === "webkitTransitionEnd" && e.target.getAttribute("ct-data-role") !== "page") ||
(e.type === "webkitAnimationEnd" && e.target.getAttribute("ct-data-role") !== "page")) {
return;
}
e.stopPropagation();
e.preventDefault();
/***
*/
self.getPageDOM().classList.remove("materialHide");
/***
* 当前页隐藏
*/
self.getPageDOM().classList.remove("active");
self.ctmobile.maskDOM.style.display = "none";
self.changeKey = false;
/***
* 删除DOM
*/
const ctDataMode = self.ctmobile.getTemplateConfig(self.getPageId(), "ct-data-mode");
if (ctDataMode.toLowerCase().indexOf("singleinstance") === -1) {
self.getPageDOM().parentNode.removeChild(self.getPageDOM());
}
if (self.ctmobile.getHistoryLength() > 1) {
const lastPrePageIndex = self.ctmobile.getHistoryLength() - 2;
/***
* 当前页之前页恢复之后事件
*/
self.ctmobile.fireEvent(self.ctmobile.getPageByIndex(lastPrePageIndex).getPageDOM(), "pageAfterRestore");
/***
* 如果当前页是带有返回值的页
*/
if (
(ctDataMode.toLowerCase().lastIndexOf("result") !== -1) &&
self.resultIntent &&
self.resultIntent.resultCode
) {
self.ctmobile.fireEvent(
self.ctmobile.getPageByIndex(lastPrePageIndex).getPageDOM(),
"pageResult",
[self.resultIntent.resultCode, self.resultIntent.bundle]
);
}
}
/***
* 出stack
*/
self.ctmobile.router.removeLastPage();
if (self.pageTransitionEndCallback) {
self.pageTransitionEndCallback();
}
}
/**
* 调用start方法后的transitionEnd
* @access private
* @callback
* @param {Object} e
*/
function pageStartTransitionEndCallback(e) {
const self = this;
/***
* 如果当前页面的transition为material则肯定不会执行webkitTransitionEnd事件
*/
if ((e.type === "webkitTransitionEnd" && e.target.getAttribute("ct-data-role") !== "page") ||
(e.type === "webkitAnimationEnd" && e.target.getAttribute("ct-data-role") !== "page")) {
return;
}
e.stopPropagation();
e.preventDefault();
/***
* 当前页移除materialShow
*/
self.getPageDOM().classList.remove("materialShow");
/***
* 最后一页隐藏
*/
self.ctmobile.getLastPage().getPageDOM().classList.remove("active");
self.ctmobile.maskDOM.style.display = "none";
self.changeKey = false;
/***
* 当前页入历史
*/
self.ctmobile.router.addPage(self);
/***
* 当前页显示之后事件
*/
self.ctmobile.fireEvent(self.getPageDOM(), "pageAfterShow");
/***
* 当前页之前的页面暂停之后
*/
if (self.ctmobile.getHistoryLength() >= 2) {
self.ctmobile.fireEvent(self.ctmobile.getPageByIndex(self.ctmobile.getHistoryLength() - 2).getPageDOM(), "pageAfterPause");
}
if (self.pageTransitionEndCallback) {
self.pageTransitionEndCallback();
}
}
/**
* 根据transition进行平移
* @access private
* @param {string} transition - 类型
* @param {string} type - 重置还是显示
* @param {number} duration - 动画持续的时间
* @param {Function} beforeCallback - 回调函数
*/
function slideByTransition(transition, type, duration, beforeCallback) {
const self = this;
let x = 0, y = 0;
/****
* 重置
*/
function reset() {
/***
* 从右到左
*/
if (new RegExp(/.+left/g).exec(transition)) {
x = "100%";
}
/***
* 从左到右
*/
else if (new RegExp(/.+right/g).exec(transition)) {
x = "-100%";
}
/***
* 从下到上
*/
else if (new RegExp(/.+up/g).exec(transition)) {
y = "100%";
}
/***
* 从上到下
*/
else if (new RegExp(/.+down/g).exec(transition)) {
y = "-100%";
}
/***
* 如果过渡类型是微信类型且过渡时间为零则重置之前的面板位置
*/
if (transition.indexOf("wx") === 0 && duration !== 0) {
delete self.ctmobile.getPageByIndex(self.ctmobile.getHistoryLength() - 2).pageTransitionType;
slide.call(self.ctmobile.getPageByIndex(self.ctmobile.getHistoryLength() - 2), 0, 0, duration / 2);
}
/***
* 如果过渡类型是push且过渡时间为零则重置之前的面板位置
*/
else if (transition.indexOf("push") === 0 && duration !== 0) {
delete self.ctmobile.getPageByIndex(self.ctmobile.getHistoryLength() - 2).pageTransitionType;
slide.call(self.ctmobile.getPageByIndex(self.ctmobile.getHistoryLength() - 2), 0, 0, duration);
}
/***
* 当前页firstExecute
*/
if (transition.indexOf("material") === 0) {
if (duration !== 0) {
if (beforeCallback) {
beforeCallback();
}
this.getPageDOM().classList.add("materialHide");
}
} else {
slide.call(this, x, y, duration, beforeCallback);
}
}
/***
* 显示
*/
function show() {
/***
* 如果过渡类型是微信类型
*/
if (transition.indexOf("wx") === 0 && duration !== 0) {
delete self.ctmobile.getLastPage().pageTransitionType;
if (new RegExp(/.+left/g).exec(transition)) {
slide.call(self.ctmobile.getLastPage(), "-20%", y, duration / 2);
} else if (new RegExp(/.+right/g).exec(transition)) {
slide.call(self.ctmobile.getLastPage(), "20%", y, duration / 2);
} else if (new RegExp(/.+up/g).exec(transition)) {
slide.call(self.ctmobile.getLastPage(), x, "-20%", duration / 2);
} else if (new RegExp(/.+down/g).exec(transition)) {
slide.call(self.ctmobile.getLastPage(), x, "20%", duration / 2);
}
}
/***
* 如果过度类型是push
*/
else if (transition.indexOf("push") === 0 && duration !== 0) {
delete self.ctmobile.getLastPage().pageTransitionType;
if (new RegExp(/.+left/g).exec(transition)) {
slide.call(self.ctmobile.getLastPage(), "-100%", y, duration);
} else if (new RegExp(/.+right/g).exec(transition)) {
slide.call(self.ctmobile.getLastPage(), "100%", y, duration);
} else if (new RegExp(/.+up/g).exec(transition)) {
slide.call(self.ctmobile.getLastPage(), x, "-100%", duration);
} else if (new RegExp(/.+down/g).exec(transition)) {
slide.call(self.ctmobile.getLastPage(), x, "100%", duration);
}
}
/***
* 当前页firstExecute
*/
if (transition.indexOf("material") === 0) {
if (duration !== 0) {
if (beforeCallback) {
beforeCallback();
}
self.getPageDOM().classList.add("materialShow");
}
} else {
slide.call(this, x, y, duration, beforeCallback);
}
}
/***
* 重置
*/
if (type === "reset") {
reset.call(self);
}
/***
* 显示
*/
else {
show.call(self);
}
}
/**
* 平移 core
* @access private
* @param {number} x - x的平移值
* @param {number} y - y的平移值
* @param {number} duration - 平移经过的时间
* @param {Function} beforeCallback - 平移之前的回调函数
*/
function slide(x, y, duration, beforeCallback) {
const self = this;
if (beforeCallback) {
beforeCallback();
}
/***
* 滑动内部实现
*/
function slideSub() {
self.getPageJO().css({
"transform": "translate3d(" + x + "," + y + ",0)",
"-webkit-transform": "translate3d(" + x + "," + y + ",0)",
"transition": "transform " + duration + "ms cubic-bezier(0.1,0.25,0.1,1)",
"-webkit-transition": "transform " + duration + "ms cubic-bezier(0.1,0.25,0.1,1)"
});
}
if (duration === 0) {
slideSub();
} else {
window.setTimeout(slideSub, 100);
}
}
/**
* Page
* @class Page
* @classdesc 管理所有和页面相关的操作
*/
class Page {
/**
* @constructor
* @param {CtMobile} ctmobile
* @param {string} id
*/
constructor(ctmobile, id) {
Object.assign(this, {
ctmobile,
id,
pageId: id.substring(0, id.lastIndexOf("_")),
/*** 默认的页面过渡类型 */
transition: "material",
/*** 页面切换时的锁 */
changeKey: false,
/*** Page的transition类型[start|finish] */
pageTransitionType: null,
/*** Page的transitionEnd后的回调函数 */
pageTransitionEndCallback: null
});
createPageDOM.call(this);
layout.call(this);
return this;
}
/**
* 页面创建调用
* @callback
* @override
* @param {Object} e
*/
pageCreate(e) {
// console.log(Constant._debugger, "pageCreateParentByParent");
}
/***
* 页面显示之前
* @callback
* @override
* @param {Object} e
*/
pageBeforeShow(e) {
// console.log(Constant._debugger, "pageBeforeShowByParent");
}
/***
* 页面显示
* @callback
* @override
* @param {Object} e
*/
pageShow(e) {
// console.log(Constant._debugger, "pageShowByParent");
}
/***
* 页面显示之后
* @callback
* @override
* @param {Object} e
*/
pageAfterShow(e) {
// console.log(Constant._debugger, "pageAfterShowByParent");
}
/***
* 页面暂停之前
* @callback
* @override
* @param {Object} e
*/
pageBeforePause(e) {
// console.log(Constant._debugger, "pageBeforePauseByParent");
}
/***
* 页面暂停之后
* @callback
* @override
* @param {Object} e
*/
pageAfterPause(e) {
// console.log(Constant._debugger, "pageAfterPauseByParent");
}
/***
* 页面恢复之前
* @callback
* @override
* @param {Object} e
*/
pageBeforeRestore(e) {
// console.log(Constant._debugger, "pageBeforeRestoreByParent");
}
/***
* 页面恢复
* @callback
* @override
* @param {Object} e
*/
pageRestore(e) {
// console.log(Constant._debugger, "pageRestoreByParent");
}
/***
* 页面恢复之后
* @callback
* @override
* @param {Object} e
*/
pageAfterRestore(e) {
// console.log(Constant._debugger, "pageAfterRestoreByParent");
}
/***
* 页面DOM销毁之前
* @callback
* @override
* @param {Object} e
*/
pageBeforeDestroy(e) {
// console.log(Constant._debugger, "pageBeforeDestroyByParent");
}
/***
* pageResult
* @callback
* @override
* @param {Object} e - jQuery的event
* @param {string} resultCode - 返回的code
* @param {Object} bundle - 返回的参数
*/
pageResult(e, resultCode, bundle) {
// console.log(Constant._debugger, "pageResult");
}
/***
* 如果添加了ct-data-intentfilter-action属性,满足条件后触发
* @callback
* @override
* @param {Object} bundle
* @param {Object} functions
*/
pageReceiver(bundle, functions) {
// console.log(Constant._debugger, "pageReceiver");
}
/**
* 显示
* @param {string} duration - 完成显示的时间
* @param {Function} callback - 结束时的回调函数
*/
start(duration, callback) {
const self = this;
/***
* 如果操作锁定则不进行
*/
if (self.changeKey) return;
/***
* 操作加锁
* @type {boolean}
*/
self.changeKey = true;
/***
* 修改transitionEnd的类型
* @type {string}
*/
self.pageTransitionType = "start";
/***
* 修改transitionEnd的回调函数
*/
self.pageTransitionEndCallback = callback;
/***
* 最后一页暂停之前事件
*/
if (self.ctmobile.getHistoryLength() !== 0) {
self.ctmobile.fireEvent(self.ctmobile.getLastPage().getPageDOM(), "pageBeforePause");
}
/***
* 当前页显示前事件
*/
self.ctmobile.fireEvent(self.getPageDOM(), "pageBeforeShow");
if (duration !== 0) {
self.ctmobile.maskDOM.style.display = "block";
}
/***
* 当前页面显示
*/
self.getPageDOM().classList.add("active");
self.getPageDOM().style.zIndex = ++self.ctmobile.zIndex;
/***
* 当前页显示事件
*/
self.ctmobile.fireEvent(self.getPageDOM(), "pageShow");
/***
* 当前页移动
*/
slideByTransition.call(self, self.transition, "show", duration === 0 ? 0 : Constant._SLIDEDURATION);
/***
* 只有一个页的时候
*/
if (duration === 0) {
self.ctmobile.router.addPage(self);
self.changeKey = false;
self.ctmobile.fireEvent(self.getPageDOM(), "pageAfterShow");
if (callback) {
callback();
}
}
}
/**
* 销毁
* @params {string} duration - 完成显示的时间
* @param {Function} callback - 结束时的回调函数
* @param {Object} option - 调用startPage的option
*/
finish(duration, callback, option) {
const self = this;
/***
* 如果操作锁定则不进行
*/
if (self.changeKey) return;
/***
* 操作加锁
* @type {boolean}
*/
self.changeKey = true;
/***
* 修改transitionEnd的类型
* @type {string}
*/
self.pageTransitionType = "finish";
/***
* 层级减
*/
self.ctmobile.zIndex--;
/***
* 修改transitionEnd的回调函数
*/
self.pageTransitionEndCallback = callback;
/***
* 当前页销毁之前事件
*/
const ctDataMode = self.ctmobile.getTemplateConfig(self.getPageId(), "ct-data-mode");
if (ctDataMode.toLowerCase().indexOf("singleinstance") === -1) {
self.ctmobile.fireEvent(self.getPageDOM(), "pageBeforeDestroy");
self.ctmobile.unregisterReceiverByDom(self.getPageDOM());
} else {
// 页面暂停之前
self.ctmobile.fireEvent(self.getPageDOM(), "pageAfterPause");
}
// _history中最后一个元素之前的索引
const lastPrePageIndex = self.ctmobile.getHistoryLength() - 2;
// (多于一个元素)且(改变浏览器历史)
if (self.ctmobile.getHistoryLength() > 1 && (!option || !option.reload)) {
/***
* 最后一个页之前的页触发恢复之前事件
*/
self.ctmobile.fireEvent(self.ctmobile.getPageByIndex(lastPrePageIndex).getPageDOM(), "pageBeforeRestore");
}
if (duration !== 0) {
self.ctmobile.maskDOM.style.display = "block";
}
// (多于一个元素)且(改变浏览器历史)
if (self.ctmobile.getHistoryLength() > 1 && (!option || !option.reload)) {
/***
* 恢复最后一个页之前的页
*/
self.ctmobile.getPageByIndex(lastPrePageIndex).getPageDOM().classList.add("active");
/***
* 最后一个页之前的页恢复事件
*/
self.ctmobile.fireEvent(self.ctmobile.getPageByIndex(lastPrePageIndex).getPageDOM(), "pageRestore");
}
/***
* 重置当前页
*/
slideByTransition.call(this, this.transition, "reset", duration === 0 ? 0 : Constant._SLIDEDURATION);
/***
* 只有一个页的时候
*/
if (duration === 0) {
self.getPageDOM().classList.remove("active");
self.changeKey = false;
/***
* 删除DOM
*/
if (ctDataMode.toLowerCase().indexOf("singleinstance") === -1) {
self.getPageDOM().parentNode.removeChild(self.getPageDOM());
}
// (多于一个元素)且(改变浏览器历史)
if (self.ctmobile.getHistoryLength() > 1 && (!option || !option.reload)) {
self.ctmobile.fireEvent(self.ctmobile.getPageByIndex(lastPrePageIndex).getPageDOM(), "pageAfterRestore");
if (
(ctDataMode.toLowerCase().lastIndexOf("result") !== -1) &&
self.resultIntent &&
self.resultIntent.resultCode) {
self.ctmobile.fireEvent(
self.ctmobile.getPageByIndex(lastPrePageIndex).getPageDOM(),
"pageResult",
[self.resultIntent.resultCode, self.resultIntent.bundle]
);
}
}
// (多于一个元素)且(不改变浏览器历史)
if (self.ctmobile.getHistoryLength() > 1 && option && option.reload) {
self.ctmobile.router.removePageByIndex(lastPrePageIndex, 1);
} else {
// 出stack
self.ctmobile.router.removeLastPage();
}
if (callback) {
callback();
}
}
}
/**
* 获取page的DOM对象
* @returns {HtmlElement}
*/
getPageDOM() {
return this._pDom;
}
/**
* 获取当前页面的jQuery对象
* @returns {*|jQuery|HTMLElement}
*/
getPageJO() {
if (!this._pJO) {
this._pJO = $(this.getPageDOM());
}
return this._pJO;
}
/**
* 获取page的实际id
* @returns {*}
*/
getId() {
return this.id;
}
/**
* 获取克隆的pageId
* @returns {*}
*/
getPageId() {
return this.pageId;
}
/**
* 设置请求参数
* 页面之前传递参数的另一种形式(类似于android的intent)
* @param {String} requestCode
* @param {Object} bundle
*/
setRequest(requestCode = "", bundle = {}) {
this.requestIntent = {
requestCode: requestCode,
bundle: bundle
};
}
/**
* 获取父页面的请求参数
* 只有在页面的pageAfterShow中才可以调用此方法获取上一页面调用setRequest传递的参数
* @param {Function} callback
* @return {Object} - {
* requestCode:String
* bundle:Object
* }
*/
getRequest(callback) {
return this.ctmobile.getRequest(this);
}
/**
* 设置返回值
* 设置返回父页面的数据
* @param {String} resultCode
* @param {Object} bundle
*/
setResult(resultCode = "", bundle = {}) {
this.resultIntent = {
resultCode: resultCode,
bundle: bundle,
};
}
/**
* 获取resultIntent
* @param {Function} callback
* @return {Object}
*/
getResult(callback) {
return this.resultIntent;
}
/**
* 当前页面ct-data-mode设置为result或singleInstanceResult时,向父页面返回参数时调用over
* 只有设置了setRequest后在调用over父页面才能触发pageResult事件
*/
over() {
go.call(this, -1);
}
/**
* 获取CtMobile实例
* @return {CtMobile|*}
*/
getCtMobile() {
return this.ctmobile;
}
}
export default Page;