重写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();
      };
    });
  });
}
相关推荐
翻滚吧键盘20 分钟前
js代码09
开发语言·javascript·ecmascript
万少1 小时前
第五款 HarmonyOS 上架作品 奇趣故事匣 来了
前端·harmonyos·客户端
OpenGL1 小时前
Android targetSdkVersion升级至35(Android15)相关问题
前端
rzl021 小时前
java web5(黑马)
java·开发语言·前端
Amy.Wang1 小时前
前端如何实现电子签名
前端·javascript·html5
海天胜景1 小时前
vue3 el-table 行筛选 设置为单选
javascript·vue.js·elementui
今天又在摸鱼1 小时前
Vue3-组件化-Vue核心思想之一
前端·javascript·vue.js
蓝婷儿1 小时前
每天一个前端小知识 Day 21 - 浏览器兼容性与 Polyfill 策略
前端
百锦再1 小时前
Vue中对象赋值问题:对象引用被保留,仅部分属性被覆盖
前端·javascript·vue.js·vue·web·reactive·ref
jingling5551 小时前
面试版-前端开发核心知识
开发语言·前端·javascript·vue.js·面试·前端框架