重写IE的showModalDialog模态框以兼容现代浏览器

背景

之前有个项目是 jsp 的,之前都是在 IE 浏览器上运行,现在要将这个项目做兼容性改造(信创),需要兼容谷歌。所以需要将项目中的公共弹框给改掉,而项目中模态框基本上都是用的 showModalDialog

介绍 showModalDialog

showModalDialog 是微软在早期版本的(IE)中引入的一个方法,用于创建模态对话框。不过在现代浏览器中已经不再支持这个方法了。

用法(参考MDN):

ini 复制代码
returnVal = window.showModalDialog(uri[, arguments][, options]);
  • returnVal 模态框的返回值。
  • uri 要在模态对话框中打开的页面 URI。
  • arguments 可选变量。可以通过该参数将需要的值传入对话框。
  • options 可选字符串参数。用于设置对话框打开的样式,使用一个或多个逗号分隔。

缺点:

  • 现代浏览器不支持
  • 模态框没有遮罩层,无法做到处理完模态框再处理父级页面

重写

步骤
  • 使用 iframe 代替模态框中的内容包裹层(传递给模态框的uri是后台一个接口,接口返回一个jsp页面,模态框展示的就是这个页面,而 iframe 可以用来嵌入内容)
  • 仍然支持 showModalDialog 的三个参数以及返回值给父页面
  • 添加遮罩层,关闭模态框之前不能点击并处理父级页面
  • 重写关闭模态框的方法(之前模态框关闭方法是 window.close
编写函数

1、定义默认样式

js 复制代码
//定义默认样式
var DIALOG_CLASS = "modernShowDialog";  //弹框类名
var SUB_STYLE =
"position:absolute;top:50%;left:50%;margin-1eft:-50%;margin-top:150px;background:#fff;border-radius: 4px;";
var IFAME_STYLE = "width:100%;border: none;height: 70%;";
var dialog_header_style =
"width:100%;height:32px;font-size:14px;line-height:34px;";

2、获取项目顶层的 document,弹框要覆盖所有页面,必须添加到顶层 window

js 复制代码
function showModalDialog(pageUrl, comeInParams, iframeStyle) {
    //获取传进来的宽高
    iframeStyle = iframeStyle.replace(/dialogwidth/g, "width");
    iframeStyle = iframeStyle.replace(/dialogHeight/g, "height");

    //获取项目顶层的 document
    var topDoc = window.top.document;
    var topDocBody = window.top.document.body;
    var mainFrameSet = topDoc.querySelector("#setMain");
    
    // 最终生成的模态框会插入到 mainFrameSetParent 中
    var mainFrameSetParent = mainFrameSet.parentNode; 
    
    return new Promise((resolve, reject) => {
        //···
    })
}

这里返回一个 promise ,返回值也会通过 resolve 返回给父级页面,父页面通过 .then 或者 await 接收。

3、创建弹框盒子,弹框headeriframe区域,footer,添加遮罩层

js 复制代码
return new Promise((resolve, reject) => {
    //创建弹框盒子,添加遮罩层
    var dialog = document.createElement("div");
    dialog.className = DIALOG_CLASS + Date.now();
    dialog.style = DIALOG_STYLE + ";width:" + topDocBody.clientwidth + "px;";

    //创建弹框里面用来包裹iframe的 div
    var dialogBody = document.createElement("div");
    dialogBody.className = "dialogBody";
    var marginLeft = parseFloat(
      iframeStyle.match(/width\:(\d)+px;/g)[0].replace("width:", "")
    );
    var marginTop = parseFloat(
      iframeStyle.match(/height\:(\d)+px;/g)[0].replace("height:", "")
    );
    dialogBody.style =
      SUB_STYLE +
      ";margin-left:-" +
      marginLeft / 2 +
      "px;margin-top:-" +
      marginTop / 2 +
      "px;";

    //创建 header
    var header = document.createElement("div");
    header.className = "dialog_header";
    header.style = dialog_header_style;
    var headerBtn = document.createElement("div");
    headerBtn.style =
      "cursor:pointer;text-align:right;padding-right:10px;font-size: 20px;user-select: none;";
    headerBtn.textContent = "x";
    headerBtn.onclick = function () {
      mainFrameSetParent.removeChild(dialog);
    };
    header.appendChild(headerBtn);

    //创建 iframe 包裹层
    var iframe = document.createElement("iframe");
    iframe.src = pageUrl;
    iframe.className = "modernDialogIframe";
    iframe.style = IFAME_STYLE + iframeStyle;
    iframe.destroy = function (returnValue) {
      resolve(returnValue);
      mainFrameSetParent.removeChild(dialog);
      removeStorageClass();
    };
    dialogBody.appendChild(header);
    dialogBody.appendChild(iframe);
    dialog.appendChild(dialogBody);
    
    var removeStorageClass = function () {
      var existClass = sessionStorage.getItem("dialogClass");
      if (existClass) {
        sessionStorage.setItem(
          "dialogClass",
          existClass.split(",").pop().join(",")
        );
      }
    };
    
    //将创建好的弹框插入顶层 document
    mainFrameSetParent.appendChild(dialog);
    
    // 通过sessionStorage 存储当前显示的弹框的类名,
    //给某些特定页面(通过windbw.close 关闭不了的页面,因为里面的window可能表单操作刷新了window)使用
    var session = sessionStorage.getItem('dialogClass')
    sessionStorage.setItem('dialogClass',session ? (session + ',' + dialog.className) : dialog.className)

需要注意的是,上面定义了 iframe.destroyremoveStorageClass 方法用来给某些特殊页面用的,那些特殊页面因为表单操作刷新了当前 window,导致调用不到了我们的重写的 close 方法。所以只能那些页面中处理完业务后手动调用 destroy 方法关闭模态框。

4、iframe加载完毕后,重写模态框的关闭方法

js 复制代码
var modernDialogIframe = topDoc.querySelector(
  "." + dialog.className + " .modernDialogIframe"
);
var tempValue = null;
//监听 iframe 包裹的目标页面的加载情况
modernDialogIframe.contentwindow.addEventListener("load", function () {
  this.dialogArguments = comeInParams;
  this.oldClose = this.close;
  //重写当前页面的 window.close
  this.close = function () {
    // returnValue 是业务页面中定义的全局变量
    tempValue = this.returnValue;
    setTimeout(function () {
      resolve(tempValue);
    }, 10);
    mainFrameSetParent.removeChild(dialog);
    removeStorageClass();
    this.oldClose();
  };
});
完整代码
js 复制代码
//定义默认样式
var DIALOG_CLASS = "modernShowDialog";
var SUB_STYLE =
"position:absolute;top:50%;left:50%;margin-1eft:-50%;margin-top:150px;background:#fff;border-radius: 4px;";
var IFAME_STYLE = "width:100%;border: none;height: 70%;";
var dialog_header_style =
"width:100%;height:32px;font-size:14px;line-height:34px;";

/**
 * 模拟 IE 的 showModalDialog 方法,同样接收三个参数;
 * pageUrl 表示要显示的页面地址
 * comeInParams 表示传进目标页面的参数
 * iframeStyle 表示自定义页面样式,比如宽高
**/

function showModalDialog(pageUrl, comeInParams, iframeStyle) {
  iframeStyle = iframeStyle.replace(/dialogwidth/g, "width");
  iframeStyle = iframeStyle.replace(/dialogHeight/g, "height");

  //获取项目顶层的 document,弹框显示需要覆盖顶层页面
  var topDoc = window.top.document;
  var mainFrameSet = topDoc.querySelector("#setMain");
  var mainFrameSetParent = mainFrameSet.parentNode;
  var topDocBody = window.top.document.body;

  return new Promise((resolve, reject) => {
    //创建弹框盒子,添加遮罩层
    var dialog = document.createElement("div");
    dialog.className = DIALOG_CLASS + Date.now();
    dialog.style = DIALOG_STYLE + ";width:" + topDocBody.clientwidth + "px;";

    //创建弹框里面用来包裹iframe的 div
    var dialogBody = document.createElement("div");
    dialogBody.className = "dialogBody";
    var marginLeft = parseFloat(
      iframeStyle.match(/width\:(\d)+px;/g)[0].replace("width:", "")
    );
    var marginTop = parseFloat(
      iframeStyle.match(/height\:(\d)+px;/g)[0].replace("height:", "")
    );
    dialogBody.style =
      SUB_STYLE +
      ";margin-left:-" +
      marginLeft / 2 +
      "px;margin-top:-" +
      marginTop / 2 +
      "px;";

    //创建 header
    var header = document.createElement("div");
    header.className = "dialog_header";
    header.style = dialog_header_style;
    var headerBtn = document.createElement("div");
    headerBtn.style =
      "cursor:pointer;text-align:right;padding-right:10px;font-size: 20px;user-select: none;";
    headerBtn.textContent = "x";
    headerBtn.onclick = function () {
      mainFrameSetParent.removeChild(dialog);
    };
    header.appendChild(headerBtn);

    //创建 iframe 包裹层
    var iframe = document.createElement("iframe");
    iframe.src = pageUrl;
    iframe.className = "modernDialogIframe";
    iframe.style = IFAME_STYLE + iframeStyle;
    iframe.destroy = function (returnValue) {
      resolve(returnValue);
      mainFrameSetParent.removeChild(dialog);
      removeStorageClass();
    };
    dialogBody.appendChild(header);
    dialogBody.appendChild(iframe);
    dialog.appendChild(dialogBody);
    var removeStorageClass = function () {
      var existClass = sessionStorage.getItem("dialogClass");
      if (existClass) {
        sessionStorage.setItem(
          "dialogClass",
          existClass.split(",").pop().join(",")
        );
      }
    };

    //将创建好的弹框插入顶层 document
    mainFrameSetParent.appendChild(dialog);
    // 通过sessionStorage 存储当前显示的弹框的类名,给某些特定页面(通过windbw.close 关闭不了的页面,因为里面的window可能表单操作刷新了window)使用
    var session = sessionStorage.getItem('dialogClass')
    sessionStorage.setItem('dialogClass',session ? (session + ',' + dialog.className) : dialog.className)
		
    var modernDialogIframe = topDoc.querySelector(
      "." + dialog.className + " .modernDialogIframe"
    );

    var tempValue = null;
    //监听 iframe 包裹的目标页面的加载情况
    modernDialogIframe.contentwindow.addEventListener("load", function () {
      this.dialogArguments = comeInParams;
      this.oldClose = this.close;
      //重写当前页面的 window.close
      this.close = function () {
        tempValue = this.returnValue;
        setTimeout(function () {
          resolve(tempValue);
        }, 10);
        mainFrameSetParent.removeChild(dialog);
        removeStorageClass();
        this.oldClose();
      };
    });
  });
}
相关推荐
kite01211 分钟前
浏览器工作原理06 [#]渲染流程(下):HTML、CSS和JavaScript是如何变成页面的
javascript·css·html
крон3 分钟前
【Auto.js例程】华为备忘录导出到其他手机
开发语言·javascript·智能手机
coding随想2 小时前
JavaScript ES6 解构:优雅提取数据的艺术
前端·javascript·es6
年老体衰按不动键盘2 小时前
快速部署和启动Vue3项目
java·javascript·vue
小小小小宇2 小时前
一个小小的柯里化函数
前端
灵感__idea3 小时前
JavaScript高级程序设计(第5版):无处不在的集合
前端·javascript·程序员
小小小小宇3 小时前
前端双Token机制无感刷新
前端
小小小小宇3 小时前
重提React闭包陷阱
前端
小小小小宇3 小时前
前端XSS和CSRF以及CSP
前端
UFIT3 小时前
NoSQL之redis哨兵
java·前端·算法