/***
* Created by playerljc on 2018/11/13.
* CtMobile.js
* CtMobie-React移动端开发框架,基于React
*/
import React from 'react';
import ReactDOM from 'react-dom';
import $ from "jquery";
import Page from "./Page";
import Router from "./Router";
import BorasdCast from "./BorasdCast";
/**
* 页面载入完成后,支持Promise
* @access private
* @callback
* @return {Promise}
*/
function readyPromise() {
return new Promise((resolve) => {
$(window.document).ready(() => {
resolve();
});
});
}
/**
* DOM载入完成后,支持Promise
* @access private
* @callback
* @return {Promise}
*/
function DOMContentLoadedPromise() {
return new Promise((resolve) => {
window.addEventListener("DOMContentLoaded", () => {
resolve();
});
});
}
/**
* cordova的设备载入完成后的回调函数,支持Promise
* @access private
* @callback
* @return {Promise}
*/
function devicereadyPromise() {
return new Promise((resolve) => {
window.document.addEventListener("deviceready", () => {
resolve();
});
});
}
/**
* 触发自定义的Html事件
* @access private
* @param {HtmlElement} dom - 触发的HTML对象
* @param {string} type - 触发的事件
* @param {Array} params - 参数
*/
function fireEvent(dom, type, params = []) {
$(dom).trigger(type, params);
}
/**
* 通过ID创建Page对象
* @access private
* @param {string} id
* @param {Function} callback ReactDOM.render后的回调函数
* @return {Promise}
*/
function createPage(id, callback) {
return new Promise((resolve, reject) => {
const pageId = id.substring(0, id.lastIndexOf("_"));
const ctDataMode = this.getPageConfigAttribute(pageId, 'mode');
const singleInstance = this.getSingleInstance(pageId);
if (ctDataMode.toLowerCase().indexOf("singleinstance") !== -1 && singleInstance) {
if (callback) {
callback(singleInstance);
}
resolve();
} else {
let ReactComponent;
const pageRouterConfig = this.config.router[pageId];
if (pageRouterConfig) {
// 路由中配置的ReactComponent
const component = pageRouterConfig.component;
if (component && component.then) {
component.then((ReactComponentWrap) => {
if (ReactComponentWrap) {
// 每个页的逻辑组件
ReactComponent = ReactComponentWrap.default;
// 顶层容器
const el = $(`<div data-ct-data-role="page"></div>`)[0];
document.body.appendChild(el);
// 包装逻辑组件
const WrappedComponent = Page.create(el)(ReactComponent);
// 包装逻辑组件放入顶层容器
ReactDOM.render(
<WrappedComponent
ctmobile={this}
id={id}
// ct-data的一系列配置
config={pageRouterConfig.config || {}}
// componentDidMount后的操作
callback={callback}
/>
,
el,
() => {
resolve();
}
);
} else {
reject();
}
}).catch((error) => {
reject(error);
});
} else {
reject();
}
} else {
reject();
}
}
});
}
/**
* 页面载入完成的回调函数
* @access private
* @callback
*/
function onReady() {
const self = this;
/***
* 判断页面是否已经ready
*/
if (this.hasInited) return;
this.hasInited = true;
/***
* window.document.body的jQuery
*/
this.bodyDOM = window.document.body;
/***
* 存放完全单例对象的容器
*/
this.singleInstances = null;
/***
* 创建page切换时的遮罩层
*/
this.maskDOM = $(
"<div class='ct-page-mask'>" +
" <div opt='animation' class='la-ball-circus la-dark' style='color:#3e98f0;'>" +
" <div></div>" +
" <div></div>" +
" <div></div>" +
" <div></div>" +
" <div></div>" +
" </div>" +
"</div>")[0];
this.bodyDOM.appendChild(this.maskDOM);
fireEvent(window.document, "pageBeforeChange", [CtMobileFactory.getUrlParam(window.location.hash)]);
/***
* 初始化第一页
* TODO:初始化第一页
*/
createPage.call(this, this.getFirstId(), (Component) => {
Component.start(0, () => {
/***
* if(有hash值) 加载的不是首页而是某一个指定的页面 {
* 调用startPage即可
* startPage需要三部分值
* 1.html的路径
* 2.pageId
* 3.parameter
* }
*/
const hash = window.location.hash;
if (!hash) return false;
const pageId = self.getPageIdByHash();
if (!pageId) return false;
const url = `#${pageId}`;
const parameter = self.getParameterByHash();
const searchObj = CtMobileFactory.getUrlParam(`${url}${parameter}`);
self.startPage(`${url}${parameter}${parameter ? (searchObj.pageId ? '' : `&pageId=${pageId}`) : `?pageId=${pageId}`}`, {
reload: self.config.linkCaptureReload
});
}
);
});
}
/**
* initCtMobile
* @access private
*/
function init() {
const {supportCordova = false} = this.config;
onReady = onReady.bind(this);
/***
* 如果开启了对cordova的支持
*/
if (supportCordova) {
/***
* 如果开启了对cordova的支持,那么页面完成事件和cordova的deviceReady事件必须同时完成后才能支持后续代码
*/
Promise.all([
readyPromise(),
DOMContentLoadedPromise(),
devicereadyPromise()
]).then(() => {
onReady();
fireEvent(window.document, "DOMContentAndDeviceReady");
}).catch((error) => {
});
}
else {
/***
* 页面载入完成事件
*/
$(window.document).ready(onReady);
/***
* 自动 init
*/
window.addEventListener("DOMContentLoaded", onReady);
}
}
/**
* CtMobile
* @class CtMobile
* @classdesc 管理所有的行为
*/
class CtMobile {
/**
* @constructor
* @param {Object} config -
* config {
* supportCordova: [true | false],是否支持cordova,默认为false
* linkCaptureReload: [true | false],<a>标签加载页面是否改变浏览器历史,默认为true
* router: Object {
* id (ct-data-role="page"的id属性): Object{
* component: Function (返回一个Prmise)
* config: Object {
* transition: {string} - 过度类型
* mode: {string} - 启动类型
* intentfilterAction: {string} - 通知的actioon
* intentfilterCategorys: {string} - 通知的categorys
* intentfilterPriority: {string} - 通知的proirity
* }
* }
* }
* }
*/
constructor(config) {
this.config = config;
// 是否初始化过
this.hasInited = false;
// page的zIndex
this.zIndex = 0;
// 路由对象
this.router = new Router(this);
// 广播对象
this.borasdcast = new BorasdCast();
init.call(this);
}
/**
* 页面跳转
* @param {string} href - (pageId = pageId + params) 如: page1?a=1&b=2;
* @param {Object} option {
* reload : [true | false]
* }
*/
startPage(href, option) {
this.router.startPage(href, option);
}
/**
* 通过ID创建Page对象
* @param {string} id
* @param {Function} callback
* @return {Page}
*/
createPage(id, callback) {
return createPage.call(this, id, callback);
}
/**
* 返回
*/
back() {
this.router.go(-1);
}
/**
* 获取Page的配置信息
* @param pageId {string} pageId
* @param property {string} property
* @return {string}
*
* transition: {string} - 过度类型
* mode: {string} - 启动类型
* intentfilterAction: {string} - 通知的actioon
* intentfilterCategorys: {string} - 通知的categorys
* intentfilterPriority: {string} - 通知的proirity
*/
getPageConfigAttribute(pageId, property) {
const router = this.config.router[pageId];
if (router.config) {
let value = router.config[property];
if (!value) {
switch (property) {
case 'mode':
value = 'standard';
break;
case 'transition':
value = 'material';
break;
}
}
return value;
} else {
let value = '';
switch (property) {
case 'mode':
value = 'standard';
break;
case 'transition':
value = 'material';
break;
}
return value;
}
}
/**
* 获取第一页真正的ID
* @return {string}
*/
getFirstId() {
return this.getId(this.getFirstPageId());
}
/**
* 根据模板page的ID获取真正page的ID
* 注释:pageId_时间戳?parameters
* @param {string} pageId
* @return {string}
*/
getId(pageId) {
let id = "";
const index = pageId.indexOf("?");
if (index !== -1) {
id = pageId.substring(0, index) + "_" + new Date().getTime() + pageId.substring(index);
} else {
id = pageId + "_" + new Date().getTime();
}
return id;
}
/**
* 通过hash值获取pageId
* 例子: "#info_1541214530597?id=1"
* @return {string}
*/
getPageIdByHash() {
let hash = window.location.hash;
if (!hash) return "";
if (hash.indexOf("?") !== -1) {
hash = hash.substring(0, hash.lastIndexOf("?"));
return hash.substring(1, hash.lastIndexOf("_"));
} else {
return hash.substring(1, hash.lastIndexOf("_"));
}
}
/**
* 通过hash获取参数Parameter
* @return {string}
*/
getParameterByHash() {
let hash = window.location.hash;
if (!hash) return "";
if (hash.indexOf("?") !== -1) {
return hash.substring(hash.lastIndexOf("?"));
} else {
return "";
}
}
/**
* 获取第一个页面的pageId
* @return {string}
*/
getFirstPageId() {
let id;
for (let p in this.config.router) {
if (this.config.router[p].isFirst) {
id = p;
break;
}
}
return id ? id : 'index';
}
/**
* 根据ID获取page对象
* @param {string} id
* @return {Page}
*/
getPageById(id) {
return this.router.getPageById(this.indexOfById(id));
}
/**
* 根据pageId获取单例对象
* @param {string} pageId
* @return {Page}
*/
getSingleInstance(pageId) {
if (!this.singleInstances) {
this.singleInstances = {};
}
return this.singleInstances[pageId];
}
/**
* 触发一个自定义事件
* @param {HtmlElement} dom
* @param {string} type
* @param {Object} params
*/
fireEvent(dom, type, params) {
fireEvent(dom, type, params);
}
/**
* 根据索引获取page对象
* @param {string} index
* @returns {Page}
*/
getPageByIndex(index) {
return this.router.getPageByIndex(index);
}
/**
* 根据id获取索引
* @param {string} id
* @returns {number}
*/
indexOfById(id) {
let index = -1;
for (let i = 0, len = this.getHistoryLength(); i < len; i++) {
if (this.getPageByIndex(i).getId() === id) {
index = i;
break;
}
}
return index;
}
/**
* 获取历史记录中的栈第一个元素
* @return {Page}
*/
getFirstPage() {
return this.router.getPageByIndex(0);
}
/**
* 获取历史记录中的栈顶的元素
* @returns {Page}
*/
getLastPage() {
return this.router.getLastPage();
}
/**
* 获取转场的参数
* @return {Object}
*/
getParameter() {
return this.router.getParameter();//$.extend({}, _parameter);
}
/**
* 获取历史栈长度
* @return {number}
*/
getHistoryLength() {
return this.router.getHistoryLength();
}
/**
* 获取父窗体的setRequest的值
* @param {Page} page
* @return {Object}
*/
getRequest(page) {
if (this.getHistoryLength() === 0 || this.getHistoryLength() === 1) {
return {};
} else {
const index = this.indexOfById(page.getId());
if (index <= 0 || index > this.getHistoryLength() - 1) {
return {};
} else {
return this.getPageByIndex(this.getHistoryLength() - 2).requestIntent || {};
}
}
}
/**
* 注册Receiver对象
* @params {Object} intentFilter -
* {
* el: HtmlElement
* action:[string] action
* priority:[number] 优先级
* categorys:[array] 分类
* }
* @params {Function} handler - receiver执行的handler
* @params {Object} context - 调用handler的上下文
*/
registerReceiver(intentFilter, handler, context) {
this.borasdcast.registerReceiver(intentFilter, handler, context);
}
/**
* 执行Receiver通过Id
* @param {string} id
* @param {string} jsonStr
*/
executeReceiverById(id, jsonStr) {
this.borasdcast.executeReceiverById(id, jsonStr);
}
/**
* 解除注册Receiver对象
* @params {Function} handler
*/
unregisterReceiver(action, handler) {
this.borasdcast.unregisterReceiver(action, handler);
}
/**
* 解除注册通过Page中的Dom
* @param {HtmlElement} el
*/
unregisterReceiverByDom(el) {
this.borasdcast.unregisterReceiverByDom(el);
}
/**
* 发送无序广播
* @param {Object} intent -
* {
* action:[string] action
* categorys:[array] 分类
* bundle:Object 参数
* }
*/
sendBroadcast(intent) {
this.borasdcast.sendBroadcast(intent);
}
/**
* 发送有序广播
* @param {Object} intent -
* {
* action:[string] action
* categorys:[array] 分类
* bundle:Object 参数
* }
*/
sendOrderedBroadcast(intent) {
this.borasdcast.sendOrderedBroadcast(intent);
}
}
/**
* CtMobileFactory
* @class
*/
const CtMobileFactory = {
/**
* 将转场参数转换为对象
* @param {string} url
* @return {Object}
*/
getUrlParam(url) {
const reg_url = /^[^\?]+\?([\w\W]+)$/,
reg_para = /([^&=]+)=([\w\W]*?)(&|$)/g,
arr_url = reg_url.exec(url),
ret = {};
if (arr_url && arr_url[1]) {
const str_para = arr_url[1];
let result;
while ((result = reg_para.exec(str_para)) != null) {
ret[result[1]] = decodeURI(result[2]);
}
}
return ret;
},
/**
* 创建CtMobile
* @param {object} config
* @return {CtMobile}
*/
create(config) {
return new CtMobile(config);
}
};
export default CtMobileFactory;