lg_frontend/static/libs/mars3d/thirdParty/es5-widget/es5-widget.js

1836 lines
52 KiB
JavaScript
Raw Permalink Normal View History

/**
* 原生js项目widget模块化框架
*/
;(function () {
const Loader = window.Loader //loader.js中定义
const jQuery = window.jQuery
if (!jQuery) {
throw new Error("请引入 jQuery 库")
}
const layer = window.layer // 请引入layer弹窗插件
if (!window.layer) {
throw new Error("请引入 layer.js弹窗 库")
}
// 内部参数
let thismap
let basePath = ""
let widgetsdata = []
let defoptions
let cacheVersion
let isdebuger
const removeKeys = ["_class"]
/**
* widget事件类型枚举
* @enum {string}
*/
const WidgetEventType = {
/**
* 在实例初始化之后创建之前执行
*/
beforeCreate: "beforeCreate",
/**
* 实例创建后执行
*/
created: "created",
/**
* 在activat挂载开始之前调用
*/
beforeActivate: "beforeActivate",
/**
* activate方法调用后
*/
activated: "activated",
/**
*view弹窗构造完成后后调用
*/
openView: "openView",
/**
* 实例销毁之前调用
*/
beforeDisable: "beforeDisable",
/**
*实例销毁完成调用
*/
disabled: "disabled",
/**
*加载完成 未做任何其他处理前
*/
loadBefore: "loadBefore",
/**
*加载完成执行所有内部处理后
*/
load: "load"
}
/**
* 初始化widget管理器在构造完成map后调用一次即可
*
* @param {Map} map 地图对象
* @param {object} [widgetcfg={}] 全局配置(一般存放在widget.json)包括
* @param {BaseWidget.widgetOptions} [widgetcfg.defaultOptions] 所有widget的默认参数值可以系统内所有widget相同配置统一在此处传入额外的个性化的再配置到各widget中
* @param {BaseWidget.widgetOptions[]} [widgetcfg.openAtStart] 默认自启动并不可释放的插件其中autoDisable和openAtStart固定设置无效
* @param {BaseWidget.widgetOptions[]} [widgetcfg.widgets] 所有插件配置传入后后续激活时只用传入uri即可
* @param {string} [widgetcfg.version] 加载资源时附加的参数主要为了清理浏览器缓存可选值"time"实时时间戳或固定的字符串值每次发布新版本换下固定值
* @param {boolean} [widgetcfg.debugger] 是否显示插件测试栏true时会在地图下侧显示所有插件测试按钮方便测试
*
* @param {string} [_basePath=''] widgets目录所在的主路径(统一前缀), 如果widgets目录不在主页面一起或存在路由时可以传入自定义主目录值为 widgets目录相对于当前html页面的相对路径
* @return {void}
* @example
let widgetCfg ={
"version": "2017",
"defaultOptions": {
"style": "dark",
"windowOptions": {
"skin": "layer-mars-dialog animation-scale-up",
"position": {
"top": 50,
"right": 10
},
"maxmin": false,
"resize": true
},
"autoReset": false,
"autoDisable": true,
"disableOther": true
},
"openAtStart": [
{
"name": "放大缩小按钮",
"uri": "widgets/toolButton/zoom.js"
}
],
"widgets": [
{
"name": "模板-div弹窗",
"uri": "widgets/_example_divwin/widget.js"
},
{
"name": "模板-append模板",
"uri": "widgets/_example_append/widget.js"
}
]
}
es5widget.init(map, widgetCfg, './')
*/
function init(map, widgetcfg = {}, _basePath = "") {
thismap = map
basePath = _basePath
widgetsdata = []
defoptions = mars3d.Util.merge(
{
windowOptions: { position: "rt", maxmin: false, resize: true },
autoDisable: true,
disableOther: true
},
widgetcfg.defaultOptions
)
cacheVersion = widgetcfg.version
if (cacheVersion === "time") {
cacheVersion = new Date().getTime()
}
// 将自启动的加入
let arrtemp = widgetcfg.openAtStart
if (arrtemp && arrtemp.length > 0) {
for (let i = 0; i < arrtemp.length; i++) {
const item = arrtemp[i]
if (!item.hasOwnProperty("uri") || item.uri === "") {
// eslint-disable-next-line no-console
console.error("widget未配置uri", item)
continue
}
if (item.hasOwnProperty("visible") && !item.visible) {
continue
}
item.autoDisable = false
item.openAtStart = true
item._nodebug = true
bindDefOptions(item)
item._firstConfigBak = { ...item }
widgetsdata.push(item)
}
}
// 显示测试栏
// 为了方便测试所有widget会在页面下侧生成一排按钮每个按钮对应一个widget单击后激活对应widget
isdebuger = widgetcfg.debugger
if (isdebuger) {
const inhtml =
'<div id="widget-testbar" class="mars3d-widgetbar animation-slide-bottom no-print-view" > ' +
' <div style="height: 30px; line-height:30px;"><b style="color: #4db3ff;">widget测试栏</b>&nbsp;&nbsp;<button id="widget-testbar-remove" type="button" class="btn btn-link btn-xs">关闭</button> </div>' +
' <button id="widget-testbar-disableAll" type="button" class="btn btn-info" ><i class="fa fa-globe"></i>漫游</button>' +
"</div>"
jQuery("body").append(inhtml)
jQuery("#widget-testbar-remove").click(function (e) {
removeDebugeBar()
})
jQuery("#widget-testbar-disableAll").click(function (e) {
disableAll()
})
}
// 将配置的加入
arrtemp = widgetcfg.widgets
if (arrtemp && arrtemp.length > 0) {
for (let i = 0; i < arrtemp.length; i++) {
const item = arrtemp[i]
if (item.type === "group") {
let inhtml =
' <div class="btn-group dropup"> <button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-expanded="false"><i class="fa fa-align-justify"></i>' +
item.name +
' <span class="caret"></span></button> <ul class="dropdown-menu">'
for (let j = 0; j < item.children.length; j++) {
const childItem = item.children[j]
if (!childItem.hasOwnProperty("uri") || childItem.uri === "") {
// eslint-disable-next-line no-console
console.error("widget未配置uri", childItem)
continue
}
inhtml +=
' <li data-widget="' + childItem.uri + '" class="widget-btn" ><a href="#"><i class="fa fa-star"></i>' + childItem.name + "</a></li>"
bindDefOptions(childItem)
childItem._firstConfigBak = { ...childItem }
widgetsdata.push(childItem) // 将配置的加入
}
inhtml += "</ul></div>"
if (isdebuger && !item._nodebug) {
jQuery("#widget-testbar").append(inhtml)
}
} else {
if (!item.hasOwnProperty("uri") || item.uri === "") {
// eslint-disable-next-line no-console
console.error("widget未配置uri", item)
continue
}
// 显示测试栏
if (isdebuger && !item._nodebug) {
const inhtml =
'<button type="button" class="btn btn-primary widget-btn" data-widget="' +
item.uri +
'" > <i class="fa fa-globe"></i>' +
item.name +
" </button>"
jQuery("#widget-testbar").append(inhtml)
}
bindDefOptions(item)
item._firstConfigBak = { ...item }
widgetsdata.push(item) // 将配置的加入
}
}
if (isdebuger) {
jQuery("#widget-testbar .widget-btn").each(function () {
jQuery(this).click(function (e) {
const uri = jQuery(this).attr("data-widget")
if (!uri || uri === "") {
return
}
if (isActivate(uri)) {
disable(uri)
} else {
activate(uri)
}
})
})
}
}
for (let i = 0; i < widgetsdata.length; i++) {
const item = widgetsdata[i]
if (item.openAtStart || item.createAtStart) {
_arrLoadWidget.push(item)
}
}
jQuery(window).resize(function () {
for (let i = 0; i < widgetsdata.length; i++) {
const item = widgetsdata[i]
if (item._class) {
item._class.indexResize() // BaseWidget: indexResize
}
}
})
if (isdebuger) {
const hash = getLocationParam()
if (hash) {
activate(hash)
}
}
loadWidgetJs()
}
/**
* 获取默认init时中传入配置的 windowOptions 参数
* @return {object} windowOptions参数默认值
* @see BaseWidget.widgetOptions
*/
function getDefWindowOptions() {
return mars3d.Util.clone(defoptions.windowOptions, removeKeys)
}
function getLocationParam() {
let param = window.location.toString()
if (param.indexOf("#") === -1) {
return ""
}
param = param.split("#")
if (param && param.length > 0) {
return param[1]
}
}
function bindDefOptions(item) {
// 赋默认值至options跳过已存在设置值
if (defoptions) {
for (const aa in defoptions) {
if (aa === "windowOptions") {
// for (var jj in defoptions['windowOptions']) {
// if (!item['windowOptions'].hasOwnProperty(jj)) {
// item['windowOptions'][jj] = defoptions['windowOptions'][jj];
// }
// }
} else if (!item.hasOwnProperty(aa)) {
item[aa] = defoptions[aa]
}
}
}
// 赋值内部使用属性
item.path = getFilePath(basePath + item.uri)
item.name = item.name || item.label // 兼容name和label命名
}
/**
* 激活指定 widget模块
*
* @param {string|BaseWidget.widgetOptions} item 指widget模块的uri 指模块的配置参数,当有配置参数时参数优先级是
* activate方法传入的配置 > init方法传入的配置(widget.json) > widget.js内部配置的
* @param {Map} [item.map] 当单页面简单场景没有init时也可以传入map来使用单个widget
* @param {boolean} [noDisableOther=false] 不释放其他已激活的widget
* @return {BaseWidget.widgetOptions} 指widget模块对象
* @example
//常用方式直接使用uri
es5widget.activate("widgets/bookmark/widget.js");
//使用对象,可以传入更多参数,具体参数参看配置项手册,。
es5widget.activate({
name:"视角书签"
uri: "widgets/bookmark/widget.js",
autoDisable: true,
testdata:'测试数据1987', //传数据进widget内部widget内部使用this.config.testdata获取到传的数据
success:function(thisWidget){
//创建完成的回调方法
}
});
*/
function activate(item, noDisableOther) {
if (!thismap && item.map) {
init(item.map, {}, item.basePath)
}
// 参数是字符串id或uri时
if (typeof item === "string") {
item = { uri: item }
if (noDisableOther != null) {
// 是否释放其他已激活的widget
item.disableOther = !noDisableOther
}
} else {
if (!item.uri) {
// eslint-disable-next-line no-console
console.error("activate激活widget时需要uri参数", item)
}
}
let thisItem
for (let i = 0; i < widgetsdata.length; i++) {
const othitem = widgetsdata[i]
if (item.uri === othitem.uri || (othitem.id && item.uri === othitem.id)) {
thisItem = othitem
if (thisItem.isloading) {
return thisItem
} // 激活了正在loading的widget 防止快速双击了菜单
// 赋值
for (const aa in item) {
if (aa === "uri") {
continue
}
thisItem[aa] = item[aa]
}
break
}
}
if (!thisItem) {
bindDefOptions(item)
thisItem = item
// 非config中配置的外部传入首次激活
if (!item._firstConfigBak) {
item._firstConfigBak = { ...item }
}
widgetsdata.push(item)
}
if (isdebuger) {
// eslint-disable-next-line no-console
console.log("开始激活widget" + thisItem.uri)
window.location.hash = "#" + thisItem.uri
}
// 释放其他已激活的widget
if (thisItem.disableOther) {
if (Array.isArray(thisItem.disableOther)) {
disable(thisItem.disableOther)
} else {
disableAll(thisItem.uri, thisItem.group)
}
}
if (thisItem.group) {
disableGroup(thisItem.group, thisItem.uri)
}
// 激活本widget
if (thisItem._class) {
if (thisItem._class.isActivate) {
// 已激活时
if (thisItem._class.update) {
// 刷新
thisItem._class.update()
} else {
// 重启
_reStart(thisItem)
}
} else {
thisItem._class.activateBase() // BaseWidget: activateBase
}
} else {
for (let i = 0; i < _arrLoadWidget.length; i++) {
if (_arrLoadWidget[i].uri === thisItem.uri) {
// 如果已在加载列表中的直接跳出
return _arrLoadWidget[i]
}
}
_arrLoadWidget.push(thisItem)
if (_arrLoadWidget.length === 1) {
loadWidgetJs()
}
}
return thisItem
}
let timetemp
// 重启
function _reStart(thisItem) {
clearInterval(timetemp)
thisItem._class.disableBase()
timetemp = setInterval(function () {
if (thisItem._class.isActivate) {
return
}
thisItem._class.activateBase()
clearInterval(timetemp)
}, 200)
}
/**
* 获取指定的widget配置信息
*
* @param {string} uri widget的uri id
* @return {BaseWidget.widgetOptions} widget配置信息
*/
function getWidget(uri) {
for (let i = 0; i < widgetsdata.length; i++) {
const item = widgetsdata[i]
if (uri === item.uri || uri === item.id) {
return item
}
}
}
/**
* 获取指定的widget 对应的实例化对象
*
* @param {string} uri widget的uri id
* @return {BaseWidget} widget对应的实例化对象
*/
function getClass(uri) {
const item = getWidget(uri)
if (item) {
return item._class
} else {
return null
}
}
/**
* 获取widget的当前激活状态
*
* @param {string} uri widget的uri id
* @return {boolean} 是否激活
*/
function isActivate(uri) {
const _class = getClass(uri)
if (!_class) {
return false
}
return _class.isActivate
}
/**
* 设置view弹窗的显示和隐藏基于修改css实现
*
* @param {string} uri widget的uri id
* @param {boolean} show 是否显示
* @param {number} [index] 当有多个view时可以指定单个操作的view的index
* @return {boolean} 是否成功设置
*/
function setViewShow(uri, show, index) {
const _class = getClass(uri)
if (_class) {
_class.setViewShow(show, index)
}
return _class?.isActivate
}
/**
* 释放指定的widget
*
* @param {string|string[]} uri widget的uri id
* @return {boolean} 是否成功调用了释放
*/
function disable(uri) {
if (!uri) {
return false
}
if (Array.isArray(uri)) {
const arrUri = uri
for (let i = 0; i < widgetsdata.length; i++) {
const item = widgetsdata[i]
for (let j = 0; j < arrUri.length; j++) {
const uri = arrUri[j]
if (item._class && (uri === item.uri || uri === item.id)) {
item._class.disableBase()
arrUri.splice(j, 1)
break
}
}
}
} else {
if (typeof uri === "object") {
uri = uri.uri
}
for (let i = 0; i < widgetsdata.length; i++) {
const item = widgetsdata[i]
if (item._class && (uri === item.uri || uri === item.id)) {
item._class.disableBase()
return true
}
}
}
return false
}
/**
* 关闭释放所有widget
*
* @export
* @param {string|boolean} [nodisable] 传string时 指定不释放的widget的uri或id 传true值强制释放所有widget(默认autoDisable为false的widet不会释放)
* @param {string} [group] 指定强制释放的group名(默认autoDisable为false的widet不会释放)传入group值后会强制释放所有同group组的widget
* @return {void}
*/
function disableAll(nodisable, group) {
for (let i = 0; i < widgetsdata.length; i++) {
const item = widgetsdata[i]
if (group && item.group === group) {
// 同组别的全部释放
} else {
if (nodisable !== true && !item.autoDisable) {
continue
}
}
// 指定不释放的跳过
if (nodisable && (nodisable === item.uri || nodisable === item.id)) {
continue
}
if (item._class) {
item._class.disableBase() // BaseWidget: disableBase
}
}
}
/**
* 关闭释放同组widget
*
* @param {string} group 指定强制释放的group名
* @param {string} [nodisable] 指定不释放的widget的uri或id
* @return {void}
*/
function disableGroup(group, nodisable) {
if (!group) {
return
}
for (let i = 0; i < widgetsdata.length; i++) {
const item = widgetsdata[i]
if (item.group === group) {
// 指定不释放的跳过
if (nodisable && (nodisable === item.uri || nodisable === item.id)) {
continue
}
if (item._class) {
item._class.disableBase() /// /BaseWidget: disableBase
}
}
}
}
/**
* 遍历所有widget
* @param {Function} method 回调方法
* @return {void}
*/
function eachWidget(method) {
for (let i = 0; i < widgetsdata.length; i++) {
const item = widgetsdata[i]
method(item)
}
}
const _arrLoadWidget = []
let loadItem
let isloading
function loadWidgetJs() {
if (_arrLoadWidget.length === 0) {
return
}
if (isloading) {
setTimeout(loadWidgetJs, 500)
return
}
isloading = true
loadItem = _arrLoadWidget[0]
loadItem.isloading = true
let _uri = loadItem.uri
if (cacheVersion) {
if (_uri.indexOf("?") === -1) {
_uri += "?cache=" + cacheVersion
} else {
_uri += "&cache=" + cacheVersion
}
}
if (window.NProgress) {
window.NProgress.start()
}
fire(WidgetEventType.loadBefore, {
sourceTarget: loadItem
})
Loader.async([basePath + _uri], function () {
isloading = false
loadItem.isloading = false
if (window.NProgress) {
window.NProgress.done(true)
}
_arrLoadWidget.shift()
loadWidgetJs()
})
}
/**
* 绑定类到当前对应js的widget中
*
* @param {BaseWidget} _class 定义的BaseWidget子类
* @return {object} 实例化后的对象
*/
function bindClass(_class) {
fire(WidgetEventType.load, {
sourceTarget: _class
})
if (!loadItem) {
const _jspath = getThisJSPath()
for (let i = 0; i < widgetsdata.length; i++) {
const item = widgetsdata[i]
if (_jspath.endsWith(item.uri)) {
item.isloading = false
item._class = new _class(thismap, item)
item._class.activateBase() // BaseWidget: activateBase
return item._class
}
}
} else {
loadItem.isloading = false
loadItem._class = new _class(thismap, loadItem)
loadItem._class.activateBase() // BaseWidget: activateBase
return loadItem._class
}
}
function getThisJSPath() {
let jsPath
const js = document.scripts
for (let i = js.length - 1; i >= 0; i--) {
jsPath = js[i].src
if (!jsPath) {
continue
}
if (jsPath.indexOf("widgets") === -1) {
continue
}
// jsPath = jsPath.substring(0, jsPath.lastIndexOf("/") + 1);
return jsPath
}
return ""
}
// 获取路径
function getFilePath(file) {
const pos = file.lastIndexOf("/")
return file.substring(0, pos + 1)
}
/**
* 移除Widget测试栏当有开启debugger时
* @return {void}
*/
function removeDebugeBar() {
jQuery("#widget-testbar").remove()
}
/**
* 获取配置的version配置参数用于附加清除浏览器缓存
* @return {string} 配置的version参数
*/
function getCacheVersion() {
return cacheVersion
}
/**
* 获取init方法传入的主目录配置参数
* @return {string} 主目录配置参数
*/
function getBasePath() {
return basePath
}
/**
* 销毁对象
* @return {void}
*/
function destroy() {
for (let i = 0; i < widgetsdata.length; i++) {
const item = widgetsdata[i]
if (item._class) {
item._class.disableBase() // BaseWidget: disableBase
if (item._class.destroy) {
item._class.destroy()
}
delete item._class
}
}
thismap = null
}
// 事件相关
const eventTarget = new mars3d.BaseClass()
/**
* 绑定指定类型事件监听器
*
* @param {WidgetEventType|WidgetEventType[]} types 事件类型
* @param {Function} [fn] 绑定的监听器回调方法
* @param {object} [context] 侦听器的上下文(this关键字将指向的对象)
* @return {void}
*/
function on(types, fn, context) {
return eventTarget.on(types, fn, context)
}
/**
* 解除绑定指定类型事件监听器
*
* @param {WidgetEventType|WidgetEventType[]} types 事件类型
* @param {Function} [fn] 绑定的监听器回调方法
* @param {object} [context] 侦听器的上下文(this关键字将指向的对象)
* @return {void}
*/
function off(types, fn, context) {
return eventTarget.off(types, fn, context)
}
/**
* 触发指定类型的事件
*
* @param {WidgetEventType} type 事件类型
* @param {object} data 传输的数据或对象可在事件回调方法中event对象中获取进行使用
* @param {BaseClass|object} [propagate] 将事件传播给父类 (用addEventParent设置)
* @return {void}
*/
function fire(type, data, propagate) {
return eventTarget.fire(type, data, propagate)
}
/**
* 绑定一次性执行的指定类型事件监听器
* 与on类似监听器只会被触发一次然后被删除
*
* @param {WidgetEventType|WidgetEventType[]} types 事件类型
* @param {Function} [fn] 绑定的监听器回调方法
* @param {object} [context] 侦听器的上下文(this关键字将指向的对象)
* @return {void}
*/
function once(types, fn, context) {
return eventTarget.once(types, fn, context)
}
/**
* 是否有绑定指定的事件
*
* @param {WidgetEventType} type 事件类型
* @param {BaseClass} [propagate] 是否判断指定的父类 (用addEventParent设置的)
* @return {boolean} 是否存在
*/
function listens(type, propagate) {
return eventTarget.listens(type, propagate)
}
let _resources_cache = []
/**
* widget 配置参数
*
* @typedef {object} BaseWidget.widgetOptions
*
* @property {string} name 必须中文名称用于标识和弹窗标题
* @property {string} uri 必须JS文件路径路径是相对于widgets目录的路径"widgets/bookmark/widget.js"
* @property {string} [id] 定义该插件的唯一标识,方便后续判断
* @property {boolean} [autoDisable=true] 激活其他新插件时是否自动释放本插件
* @property {boolean} [disableOther=true] 激活本插件时是否释放其它已激活的插件
* @property {string} [group] 配置group后同group下的widget互斥打开任意一个会自动释放其他的
* @property {object} [windowOptions] 存在弹窗的插件的弹窗相关参数配置更多参数请参考 [layer弹窗API]{@link https://layui.gitee.io/v2/docs/modules/layer.html} 包括:
* @property {number|string} [windowOptions.width] 窗口宽度可以是 像素数字(像素值) 或者 字符串(屏幕宽度百分比)示例200 "20%"
* @property {number|string} [windowOptions.height] 窗口高度可以是 像素数字(像素值) 或者 字符串(屏幕高度百分比)示例600 "50%"
* @property {string|object} [windowOptions.position='auto'] 窗口所在位置坐标配置字符串可选值auto垂直水平居中t顶部,b底部,r右边缘,l左边缘,lt左上角,lb左下角,rt右上角,rb右下角也可以配置对象
* @property {number|string} [windowOptions.position.top] 位置css的top值可以是 像素数字(像素值) 或者 字符串(屏幕高度百分比)示例10 "5%"
* @property {number|string} [windowOptions.position.bottom] 位置css的top值可以是 像素数字(像素值) 或者 字符串(屏幕高度百分比)示例10 "5%"
* @property {number|string} [windowOptions.position.left] 位置css的top值可以是 像素数字(像素值) 或者 字符串(屏幕宽度百分比)示例10 "5%"
* @property {number|string} [windowOptions.position.right] 位置css的top值可以是 像素数字(像素值) 或者 字符串(屏幕宽度百分比)示例10 "5%"
* @property {number} [windowOptions.minHeight] 限定的窗口最小高度(像素值)默认不限制
* @property {number} [windowOptions.maxHeight] 限定的窗口最大高度(像素值)默认不限制
* @property {number} [windowOptions.minWidth] 限定的窗口最小宽度(像素值)默认不限制
* @property {number} [windowOptions.maxWidth] 限定的窗口最大宽度(像素值)默认不限制
*
* @property {boolean} [windowOptions.maxmin=true] 是否可以在弹层右下角拖动来拉伸尺寸
* @property {number|Array} [windowOptions.shade=0] 遮罩默认为0不显示可配置数字0.3透明度的黑色背景'#000'其他颜色可以shade: [0.8, '#393D49']
* @property {boolean} [windowOptions.shadeClose=false] 当shade是存在的点击弹层外区域后是否关闭弹窗
* @property {number} [windowOptions.closeBtn=1] 当为0时不显示关闭按钮配置1和2来展示两种风格的关闭按钮
* @property {number} [windowOptions.noTitle=false] 是否不显示标题为true是不显示标题
* @property {boolean} [windowOptions.show=true] 激活后是否显示弹窗false时激活后自动隐藏弹窗
*
* @property {boolean} [openAtStart=false] 打开系统后是否自动启动本插件
* @property {string} [style] 添加到widget的view中的class样式名
* @property {object} [css] 添加到widget的css值
* @property {*} [多个参数] 传入数据等定义的任意参数在widget内部方法中都可以通过this.config获取到
*/
/**
* 原生JS技术栈下widget基础类,
* 需要继承后使用不用手动实例化框架内部自动实例化及相关处理
*
* @param {Map} map 地图对象
* @param {BaseWidget.widgetOptions} options 配置参数
* @class BaseWidget
* @extends {BaseClass}
* @see [支持的事件类型]{@link WidgetEventType}
* @example
//使用示例
class MyWidget extends es5widget.BaseWidget {
//外部资源配置
get resources() {
return [
'js/test.js', //当前同目录下
'./lib/dom2img/dom-to-image.js', //主页面相同目录下
]
}
//弹窗配置
get view() {
return {
type: 'window',
url: 'view.html',
windowOptions: { width: 250 },
}
}
//初始化[仅执行1次]
create() {}
//每个窗口创建完成后调用
winCreateOK(opt, result) {
this.viewWindow = result
}
//打开激活
activate() {}
//关闭释放
disable() {
this.viewWindow = null
}
}
//注册到widget管理器中。
es5widget.bindClass(MyWidget)
*/
class BaseWidget extends mars3d.BaseClass {
constructor(map, options) {
super(options)
/**
* 获取当前地图
* @type {Map}
* @readonly
*/
this.map = map
/**
* 获取当前配置参数
* @type {BaseWidget.widgetOptions}
* @readonly
*/
this.options = options // 配置的config信息
/**
* 获取当前配置参数别名同options
* @type {BaseWidget.widgetOptions}
* @readonly
*/
this.config = options
/**
* 获取当前widget的目录路径
* @type {string}
* @readonly
*/
this.path = options.path || "" // 当前widget目录相对路径
/**
* 是否激活状态
* @type {boolean}
* @readonly
*/
this.isActivate = false
/**
* 是否已创建
* @type {boolean}
* @readonly
*/
this.isCreate = false
this._viewcreate_allcount = 0
this._viewcreate_okcount = 0
this._viewConfig = this.view
this.init()
}
/**
* 该模块依赖的外部jscss资源文件会在实例化之前加入的页面中
* 默认引用是当前widget所在同path目录的资源
* 相当于html主页面的资源 外部资源 / . http 开始的url
* @type {string[]}
* @readonly
* @abstract
*/
get resources() {
return null
}
/**
* 定义关联的view弹窗或页面配置信息目前支持3种类型
* 1type:'window'iframe模式弹窗 ,参考_example示例 独立的html子页面比较自由简单粗暴无任何限制可以每个页面用不同的UI和第三方插件不用考虑冲突问题任何水平的开发人员均容易快速开发
* 2type:'divwindow'div元素模式弹窗 参考_example_divwin示例可直接互相访问这种模式弊端是易引起模块间id命名冲突在css和html中命名时需注意
* 3type:'append'任意html元素 参考_example_append示例任意div节点比较自由
* 为空时表示当前模块无关联的view页面
* 其中url地址规则参考resources说明
* @type {object|object[]}
* @readonly
* @abstract
*/
get view() {
return null
}
//= =============激活插件=================
/**
* 激活widget es5widget.activate方法
* @return {void}
*/
activateBase() {
const that = this
if (this.isActivate) {
// 已激活状态时跳出
this.eachView(function (viewopt) {
if (viewopt._dom) {
// 将层置顶
jQuery(".layui-layer").each(function () {
jQuery(this).css("z-index", 19891000)
})
jQuery(viewopt._dom).css("z-index", 19891014)
}
})
return
}
eventTarget.fire(WidgetEventType.beforeActivate, {
sourceTarget: this
})
this.beforeActivate()
this.isActivate = true
if (!this.isCreate) {
eventTarget.fire(WidgetEventType.beforeCreate, {
sourceTarget: this
})
// 首次进行创建
if (this.resources && this.resources.length > 0) {
const resources = []
for (let i = 0; i < this.resources.length; i++) {
let _resource = this.resources[i]
_resource = this._getUrl(_resource)
if (_resources_cache.indexOf(_resource) !== -1) {
continue
} // 不加重复资源
resources.push(_resource)
}
_resources_cache = _resources_cache.concat(resources) // 不加重复资源
Loader.async(resources, function () {
const result = that.create(function () {
that._createWidgetView()
that.isCreate = true
})
eventTarget.fire(WidgetEventType.created, {
sourceTarget: that
})
if (result) {
return
}
if (that.options.createAtStart) {
that.options.createAtStart = false
that.isActivate = false
that.isCreate = true
return
}
that._createWidgetView()
that.isCreate = true
})
return
} else {
const result = this.create(function () {
that._createWidgetView()
this.isCreate = true
})
eventTarget.fire(WidgetEventType.created, {
sourceTarget: this
})
if (result) {
return
}
if (that.options.createAtStart) {
that.options.createAtStart = false
that.isActivate = false
that.isCreate = true
return
}
}
this.isCreate = true
}
this._createWidgetView()
}
/**
* 构造方法完成后的钩子方法子类继承后按需使用
* @return {void}
* @abstract
*/
init() {}
/**
* 模块初始化仅首次初始化执行1次
* @param {Function} [endfun] 当create内存在异步时可以异步后调用下endfun
* @return {void}
* @abstract
*/
create() {}
// 创建插件的view
_createWidgetView() {
const viewopt = this._viewConfig
if (viewopt === undefined || viewopt == null) {
this._startActivate()
} else if (Array.isArray(viewopt)) {
this._viewcreate_allcount = viewopt.length
this._viewcreate_okcount = 0
for (let i = 0; i < viewopt.length; i++) {
this.createItemView(viewopt[i])
}
} else {
this._viewcreate_allcount = 1
this._viewcreate_okcount = 0
this.createItemView(viewopt)
}
}
/**
* 遍历所有view配置
*
* @param {Function} callback 回调方法
* @param {number} [index] 当有多个view时可以指定单个操作的view的index
* @return {*} callback执行的返回结果
*/
eachView(callback, index) {
const viewopt = this._viewConfig
if (viewopt === undefined || viewopt == null) {
return false
} else if (Array.isArray(viewopt)) {
let hascal = false
if (index != null) {
return callback(viewopt[index])
}
for (let i = 0; i < viewopt.length; i++) {
hascal = callback(viewopt[i])
}
return hascal
} else {
return callback(viewopt)
}
}
createItemView(viewopt) {
const that = this
switch (viewopt.type) {
case "divwindow":
this._openDivWindow(viewopt)
break
case "append":
that.getHtml(this._getUrl(viewopt.url), function (html) {
that._appendView(viewopt, html)
})
break
case "custom": // 自定义
viewopt.open(
this._getUrl(viewopt.url),
function (html) {
that.winCreateOK(viewopt, html)
eventTarget.fire(WidgetEventType.openView, {
sourceTarget: that,
view: viewopt,
dom: html
})
that._viewcreate_okcount++
if (that._viewcreate_okcount >= that._viewcreate_allcount) {
that._startActivate(html)
}
},
this
)
break
case "window":
default:
this._openWindow(viewopt)
break
}
}
//= =============layer弹窗=================
_openWindow(viewopt) {
const view_url = this._getUrl(viewopt.url)
const opts = {
type: 2,
content: [view_url, "no"],
success: (layero, index) => {
if (!this.isActivate) {
layer.close(index)
return
}
if (viewopt._layerIdx !== index) {
layer.close(viewopt._layerIdx)
viewopt._layerIdx = index
}
viewopt._layerOpening = false
viewopt._dom = layero
// 得到iframe页的窗口对象执行iframe页的方法viewWindow.method();
const viewWindow = window[layero.find("iframe")[0].name]
// 绑定常用对象到子页面,方便直接使用
viewWindow.map = this.map
viewWindow.mars3d = mars3d
viewWindow.Cesium = mars3d.Cesium
viewWindow.es5widget = window.es5widget
// 设置css
if (this.options.css) {
jQuery("#layui-layer" + viewopt._layerIdx).css(this.options.css)
}
// 隐藏弹窗
if (viewopt.windowOptions.hasOwnProperty("show") && !viewopt.windowOptions.show) {
jQuery(layero).hide()
}
layer.setTop(layero)
this.winCreateOK(viewopt, viewWindow)
eventTarget.fire(WidgetEventType.openView, {
sourceTarget: this,
view: viewopt,
dom: layero
})
this._viewcreate_okcount++
if (this._viewcreate_okcount >= this._viewcreate_allcount) {
this._startActivate(layero)
}
// 通知页面,页面需要定义initWidgetView方法
if (viewWindow && viewWindow.initWidgetView) {
if (this.config?.style) {
jQuery(viewWindow.document.body).addClass(this.config.style)
}
viewWindow.initWidgetView(this)
} else {
mars3d.Log.logError(view_url + "页面没有定义function initWidgetView(widget)方法无法初始化widget页面!")
}
}
}
if (viewopt._layerIdx && viewopt._layerIdx > 0) {
layer.close(viewopt._layerIdx)
viewopt._layerIdx = -1
}
viewopt._layerOpening = true
viewopt._layerIdx = layer.open(this._getWinOpt(viewopt, opts))
}
_openDivWindow(viewopt) {
const view_url = this._getUrl(viewopt.url)
// div弹窗
this.getHtml(view_url, (data) => {
const opts = {
type: 1,
content: data,
success: (layero, index) => {
if (!this.isActivate) {
layer.close(index)
return
}
if (viewopt._layerIdx !== index) {
layer.close(viewopt._layerIdx)
viewopt._layerIdx = index
}
viewopt._layerOpening = false
viewopt._dom = layero
// 隐藏弹窗
if (viewopt.windowOptions.hasOwnProperty("show") && !viewopt.windowOptions.show) {
jQuery(layero).hide()
}
layer.setTop(layero)
this.winCreateOK(viewopt, layero)
eventTarget.fire(WidgetEventType.openView, {
sourceTarget: this,
view: viewopt,
dom: layero
})
this._viewcreate_okcount++
if (this._viewcreate_okcount >= this._viewcreate_allcount) {
this._startActivate(layero)
}
}
}
viewopt._layerOpening = true
viewopt._layerIdx = layer.open(this._getWinOpt(viewopt, opts))
})
}
_getUrl(url) {
url = this.addCacheVersion(url)
if (url.startsWith("/") || url.startsWith(".") || url.startsWith("http")) {
return url
} else {
return this.path + url
}
}
_getWinOpt(viewopt, opts) {
// 优先使用cofig中配置覆盖js中的定义
const def = getDefWindowOptions()
const windowOptions = { ...def, ...viewopt.windowOptions, ...this.options.windowOptions }
viewopt.windowOptions = windowOptions // 赋值
const that = this
const _size = this._getWinSize(windowOptions)
let title = false
if (!windowOptions.noTitle) {
title = this.options.name || " "
if (this.options.icon) {
title = '<i class="' + this.options.icon + '" ></i>&nbsp;' + title
}
}
// 默认值
const defOpts = {
title: title,
area: _size.area,
offset: _size.offset,
shade: 0,
maxmin: false,
beforeEnd: function () {
that.beforeDisable()
},
end: function () {
// 销毁后触发的回调
viewopt._layerIdx = -1
viewopt._dom = null
that.disableBase()
},
full: function (dom) {
// 最大化后触发的回调
that.winFull(dom)
},
min: function (dom) {
// 最小化后触发的回调
that.winMin(dom)
},
restore: function (dom) {
// 还原 后触发的回调
that.winRestore(dom)
}
}
return { ...defOpts, ...windowOptions, ...opts }
}
// 计算弹窗大小和位置
_getWinSize(windowOptions) {
// 获取高宽
let _width = this.bfb2Number(windowOptions.width, document.documentElement.clientWidth, windowOptions)
let _height = this.bfb2Number(windowOptions.height, document.documentElement.clientHeight, windowOptions)
// 计算位置offset
let offset = ""
const position = windowOptions.position
if (position) {
if (typeof position === "string") {
// t顶部,b底部,r右边缘,l左边缘,lt左上角,lb左下角,rt右上角,rb右下角
offset = position
} else if (typeof position === "object") {
let _top
let _left
if (position.hasOwnProperty("top") && position.top != null) {
_top = this.bfb2Number(position.top, document.documentElement.clientHeight, windowOptions)
}
if (position.hasOwnProperty("bottom") && position.bottom != null) {
windowOptions._hasresize = true
const _bottom = this.bfb2Number(position.bottom, document.documentElement.clientHeight, windowOptions)
if (_top != null) {
_height = document.documentElement.clientHeight - _top - _bottom
} else {
_top = document.documentElement.clientHeight - _height - _bottom
}
}
if (position.hasOwnProperty("left") && position.left != null) {
_left = this.bfb2Number(position.left, document.documentElement.clientWidth, windowOptions)
}
if (position.hasOwnProperty("right") && position.right != null) {
windowOptions._hasresize = true
const _right = this.bfb2Number(position.right, document.documentElement.clientWidth, windowOptions)
if (_left != null) {
_width = document.documentElement.clientWidth - _left - _right
} else {
_left = document.documentElement.clientWidth - _width - _right
}
}
if (_top == null || _top === undefined) {
_top = (document.documentElement.clientHeight - _height) / 2
}
if (_left == null || _left === undefined) {
_left = (document.documentElement.clientWidth - _width) / 2
}
offset = [_top + "px", _left + "px"]
}
}
// 最大最小高度判断
if (windowOptions.hasOwnProperty("minHeight") && _height < windowOptions.minHeight) {
windowOptions._hasresize = true
_height = windowOptions.minHeight
}
if (windowOptions.hasOwnProperty("maxHeight") && _height > windowOptions.maxHeight) {
windowOptions._hasresize = true
_height = windowOptions.maxHeight
}
// 最大最小宽度判断
if (windowOptions.hasOwnProperty("minWidth") && _width < windowOptions.minWidth) {
windowOptions._hasresize = true
_width = windowOptions.minWidth
}
if (windowOptions.hasOwnProperty("maxWidth") && _width > windowOptions.maxWidth) {
windowOptions._hasresize = true
_width = windowOptions.maxWidth
}
let area
if (_width && _height) {
area = [_width + "px", _height + "px"]
} else {
area = _width + "px"
}
return { area: area, offset: offset }
}
/**
* 更新窗口大小或位置改变了主页面尺寸后需要调用(内部已自动调用)
* @return {void}
*/
indexResize() {
if (!this.isActivate) {
return
}
const that = this
this.eachView(function (viewopt) {
if (!viewopt._layerIdx || viewopt._layerIdx === -1 || !viewopt.windowOptions || !viewopt.windowOptions._hasresize) {
return
}
const tag = viewopt._dom.attr("maxmin")
if (tag === "min") {
return
}
if (tag === "full") {
layer.full(viewopt._layerIdx, viewopt)
return
}
const _size = that._getWinSize(viewopt.windowOptions)
const _style = {}
if (Array.isArray(_size.area)) {
if (_size.area[0]) {
_style.width = _size.area[0]
}
if (_size.area[1]) {
_style.height = _size.area[1]
}
}
if (Array.isArray(_size.offset)) {
if (_size.offset[1]) {
_style.top = _size.offset[0]
}
if (_size.offset[1]) {
_style.left = _size.offset[1]
}
}
jQuery(viewopt._dom).attr("myTopLeft", true)
layer.style(viewopt._layerIdx, _style)
if (viewopt.type === "divwindow") {
layer.iframeAuto(viewopt._layerIdx)
}
})
}
//= =============直接添加dom节点=================
_appendView(viewopt, html) {
viewopt._dom = jQuery(html).appendTo(viewopt.parent || "body")
// 设置css
if (this.options.css) {
jQuery(viewopt._dom).css(this.options.css)
}
this.winCreateOK(viewopt, html)
this._viewcreate_okcount++
if (this._viewcreate_okcount >= this._viewcreate_allcount) {
this._startActivate(html)
}
}
/**
* 每个view窗口或页面创建完成后调用的钩子方法
*
* @param {object} opt 对应的view配置
* @param {object|string} result 得到iframe页的窗口对象 view的html内容
* @return {void}
* @abstract
*/
winCreateOK(opt, result) {}
/**
* 窗口最大化后触发后 的钩子方法
* @return {void}
* @abstract
*/
winFull() {}
/**
* 窗口最小化后触发 的钩子方法
* @return {void}
* @abstract
*/
winMin() {}
/**
* 最小化窗口
* @return {void}
*/
minView() {
this.eachView(function (viewopt) {
if (viewopt._layerIdx) {
layer.min(viewopt._layerIdx, viewopt)
}
})
}
/**
* 还原窗口
* @return {void}
*/
restoreView() {
this.eachView(function (viewopt) {
if (viewopt._layerIdx) {
layer.restore(viewopt._layerIdx)
}
})
}
/**
* 最大化窗口
* @return {void}
*/
fullView() {
this.eachView(function (viewopt) {
if (viewopt._layerIdx) {
layer.full(viewopt._layerIdx, viewopt)
}
})
}
/**
* 窗口还原后触发 的钩子方法
* @return {void}
* @abstract
*/
winRestore() {}
_startActivate(layero) {
this.activate(layero)
eventTarget.fire(WidgetEventType.activated, {
sourceTarget: this
})
if (this.options.success) {
this.options.success(this)
delete this.options.success // 一次性的
}
if (!this.isActivate) {
// 窗口打开中没加载完成时,被释放
this.disableBase()
}
}
/**
* 激活模块之前 的钩子方法
* @return {void}
* @abstract
*/
beforeActivate() {}
/**
* 激活模块类内部实现方法
* @return {void}
* @abstract
*/
activate() {}
//= =============释放插件=================
/**
* 释放插件 es5widget.disable方法
* @return {void}
*/
disableBase() {
if (!this.isActivate) {
return
}
this.isActivate = false
this.beforeDisable()
eventTarget.fire(WidgetEventType.beforeDisable, {
sourceTarget: this
})
// 关闭所有窗口
this.eachView(function (viewopt) {
if (viewopt._layerIdx && viewopt._layerIdx > 0) {
layer.close(viewopt._layerIdx)
if (viewopt._layerOpening) {
// 窗口还在加载中,success方法中去关闭
} else {
viewopt._layerIdx = -1
}
return true
} else {
if (viewopt.type === "append" && viewopt._dom) {
viewopt._dom.remove()
viewopt._dom = null
}
if (viewopt.type === "custom" && viewopt.close) {
viewopt.close()
}
return false
}
})
this.disable()
// 还原配置为初始状态
if (this.options.autoReset) {
this.resetConfig()
}
eventTarget.fire(WidgetEventType.disabled, {
sourceTarget: this
})
}
/**
* 释放模块前
* @return {void}
* @abstract
*/
beforeDisable() {}
/**
* 释放模块类内部实现方法
* @return {void}
* @abstract
*/
disable() {}
//= =============其他方法=================
bfb2Number(str, allnum, windowOptions) {
if (typeof str === "string" && str.indexOf("%") !== -1) {
windowOptions._hasresize = true
return (allnum * Number(str.replace("%", ""))) / 100
}
return str
}
addCacheVersion(_resource) {
if (!_resource) {
return _resource
}
const cacheVersion = getCacheVersion()
if (cacheVersion) {
if (_resource.indexOf("?") === -1) {
_resource += "?cache=" + cacheVersion
} else if (_resource.indexOf("cache=" + cacheVersion) === -1) {
_resource += "&cache=" + cacheVersion
}
}
return _resource
}
/**
* 还原配置为初始状态
* @return {void}
*/
resetConfig() {
if (this.options._firstConfigBak) {
const _backData = this.options._firstConfigBak
for (const aa in _backData) {
if (aa === "uri") {
continue
}
this.options[aa] = _backData[aa]
}
}
}
/**
* 设置view弹窗的显示和隐藏基于修改css实现
*
* @param {boolean} show 是否显示
* @param {number} [index] 当有多个view时可以指定单个操作的view的index
* @return {void}
*/
setViewShow(show, index) {
this.eachView(function (viewopt) {
if (viewopt._layerIdx && viewopt._layerIdx > 0) {
if (show) {
jQuery("#layui-layer" + viewopt._layerIdx).show()
} else {
jQuery("#layui-layer" + viewopt._layerIdx).hide()
}
} else if (viewopt.type === "append" && viewopt._dom) {
if (show) {
jQuery(viewopt._dom).show()
} else {
jQuery(viewopt._dom).hide()
}
}
}, index)
}
/**
* 设置view弹窗的css
*
* @param {object} style css值
* @param {number} [index] 当有多个view时可以指定单个操作的view的index
* @return {void}
*/
setViewCss(style, index) {
this.eachView(function (viewopt) {
if (viewopt._layerIdx != null && viewopt._layerIdx > 0) {
layer.style(viewopt._layerIdx, style)
} else if (viewopt.type === "append" && viewopt._dom) {
jQuery(viewopt._dom).css(style)
}
}, index)
}
/**
* 设置view弹窗的标题
*
* @param {string} title css值
* @param {number} [index] 当有多个view时可以指定单个操作的view的index
* @return {void}
*/
setTitle(title, index) {
this.eachView(function (viewopt) {
if (viewopt._dom) {
viewopt._dom.find(".layui-layer-title").html(title)
}
}, index)
}
/**
* 读取html页面的内容
*
* @param {string} url html页面的url
* @param {Function} callback 读取完成后的回调方法
* @return {void}
*/
getHtml(url, callback) {
jQuery.ajax({
url: url,
type: "GET",
dataType: "html",
timeout: 0, // 永不超时
success: function (data) {
callback(data)
}
})
}
}
//对外接口
window.es5widget = {
BaseWidget: BaseWidget,
EventType: WidgetEventType,
WidgetEventType: WidgetEventType,
eventTarget,
activate,
bindClass,
destroy,
disable,
disableAll,
disableGroup,
eachWidget,
fire,
getBasePath,
getCacheVersion,
getClass,
getDefWindowOptions,
getWidget,
init,
isActivate,
listens,
off,
on,
once,
removeDebugeBar,
setViewShow
}
})()