65.9K
CodeProject 正在变化。 阅读更多。
Home

模态对话框 - 增强版

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.54/5 (32投票s)

2006年6月2日

CPOL

4分钟阅读

viewsIcon

240751

downloadIcon

1565

在本文中,我们将尝试生成一个可通过XMLHTTP连接从外部URL加载数据的可拖动DHTML层。这是我之前一篇可拖动层文章的增强版,因此它解决了前一篇文章中不存在的额外问题。

Sample Image - ModalDialogV2.gif

引言

本文可以看作是我之前文章的后续:《使用XML HTTP建模可拖动层并向其中加载动态内容》。如果您还没有看过,请先阅读一下,以便更快地跟上进度。

在本文中,我们将尝试:

  • 创建一个跨浏览器、可拖动的DHTML模态对话框。
  • 使用HTTP-POST发送AJAX请求。
  • 获取响应并在模态对话框内显示。

最棒的是,借助sardalya API,我们将在不到20行代码中完成所有这些工作。

请注意,本文源文件压缩包中包含的API是sardalya的简化版本。您可以访问sardalya网站获取其最新的完整版本。

在开始之前,您可能想先看看最终效果

创建视图 (View)

我们的ModalDialog的HTML非常简单。

<div id="ModalBG"></div>
 
<div id="DialogWindow">
  <div id="DialogHeader">
    <span id="DialogTitle">Title comes here</span>
    <img id="DialogActionBtn" src="icn_close.png" 
          alt="close icon" title="" />
  </div>
  <div id="DialogIcon"><img id="DialogIcon" 
     src="icn_alert.png" alt="alert icon" title="" /></div>
  <div id="DialogContent">...</div>
</div>

"ModalBG" 是一个放置在页面内容和ModalDialog窗口之间的层,这样可以防止意外点击其他页面元素,同时通过淡化背景页面来强调ModalDialog

CSS

为了使我们的“对话框窗口”层看起来像一个真正的模态对话框,我们需要一些CSS调整。

Master.css
#DialogWindow
{
 border: 1px #FFFFFF outset;
 width: 550px;
 display: none;
 background: #FFFFFF;
 z-index: 1000;
 position: absolute;
 top: 0;
 left: 0;
}
 
#DialogContent
{
 float: right;
 margin-right: 10px;
 margin-bottom: 10px;
 margin-top: 24px;
 width: 450px;
 display: block;
 font-size: 90%;
}
 
#DialogIcon
{
 padding: 10px;
 float: left;
}
 
#DialogHeader
{
 border-bottom: 1px #00449E outset;
 background: #00449E;
 text-align: right;
}
 
#DialogTitle
{
 float: left;
 padding: 8px;
 color: #FFFFFF;
}
 
#DialogActionBtn
{
 cursor:pointer;
}

我们还需要一个“Opacity.css”来实现透明度支持。

Opacity.css
#ModalBG
{
 width:100%;
 display:none;
 background-color:#333333;
 position:absolute;
 top:0;
 left:0;
 height:100%;
 z-index:999;
 opacity:.40;
 filter:alpha(opacity=40)
}

借助这些CSS,我们的框在大多数浏览器中看起来会像一个模态对话框,但某些浏览器除外。

善待Opera

我听到您说:“为什么我总是要善待Opera?Opera什么时候才能善待我?!” 我完全理解您:)。但让我们再次善待Opera一次。

为了让我们的透明背景在Opera中生效,我们需要更多的CSS调整。

Master.css
/* transparency support for Opera */
.modalOpera
{
 background-image: url("maskBg.png") !important;
}

就是这样!Opera不支持CSS透明度,但它完全支持.png透明度,因此将透明遮罩作为背景可以使我们的ModalDialog在Opera中同样出色地工作。

这里还有一个问题。我们需要选择性地应用此类当且仅当用户代理是Opera时。也就是说,其他浏览器如Mozilla不需要这种透明度技巧,如果我们的用户代理是其中之一,我们就不应该使用“modalOpera”类。我们稍后会解决这个问题。

脚本

首先,我们需要在页面加载时准备好模态对话框。

window.onload=function()
{
  /* Sweep unnecessary empty text nodes. */
  DOMManager.sweep();

  /*
   * Attach supporting css bind required
   * classes for transparency support in Opera.
   */ 
  addExtensionsForOpera();
 
  /* Attach opacity css. */
  attachOpacityCSS();
 
  /* Adjust height. */
  adjustHeight();

  /* Create the modal dialog */
  g_Modal=new ModalDialog("ModalBG","DialogWindow",
   "DialogContent","DialogActionBtn");
 

  /* Bind an event listener to double-click event. */
  EventHandler.addEventListener(document,"dblclick",document_dblclick);
  
  /* Re-adjust height on window resize */
  EventHandler.addEventListener(window,"resize",window_resize);
};

sweep是sardalya的DOMManager对象的一个实用方法。它会从DOM结构中移除空的文本节点。

现在,让我们逐个看看其他方法。

function addExtensionsForOpera()
{
  /* classes for opera */
  var ModalBG=new CBObject("ModalBG").getObject();
  if(typeof(window.opera)!="undefined")
  {
   ModalBG.className="modalOpera";
  }
}

如您所见,我们仅当用户代理是Opera时才将“modalOpera”类名附加到“ModalBG”。

请注意,我们不是通过嗅探用户代理(navigator.userAgent)来判断,而是通过“对象检测”(window.opera)。

浏览器喜欢通过发送伪造的用户代理字符串来欺骗脚本,因此对象检测才是正确的方法。尽管其细节是整篇文章的主题,但我可以这样说:浏览器嗅探是“90年代”的做法。经验法则:始终使用对象检测。

接下来是附加opacity CSS部分。

function attachOpacityCSS()
{
  /*
   * CSS for opacity support
   * Note that this can be directly added to the body.
   * If you do not care about blindly adhering to standards
   * you can directly include the rules into Master.css
   *
   * Do I care? Yes and No.(visit http://www.sarmal.com/Exceptions.aspx
   * to learn how I feel about it).
   */
    var opacityCSS = document.createElement("link");
    opacityCSS.type="text/css";
    opacityCSS.rel="stylesheet";
    opacityCSS.href="Opacity.css";
    document.getElementsByTagName("head")[0].appendChild(opacityCSS);
}

然后是高度调整。

function adjustHeight()
{
  /* get the available height of the viewport */
  var intWindowHeight=WindowObject.getInnerDimension().getY();
  var dynModalBG=new DynamicLayer("ModalBG");
  var intModalHeight=dynModalBG.getHeight();
 
  /*
   * if modal background's height is less than the viewport's 
   * available height, increase its height.
   */
  if(intModalHeight<intWindowHeight)
  {
   dynModalBG.setHeight(intWindowHeight);
  }
}

然后我们用一行代码创建模态对话框。

g_Modal=new ModalDialog("ModalBG","DialogWindow",
   "DialogContent","DialogActionBtn");

"ModalBG" 是透明背景的ID,"DialogWindow" 是模态对话框容器的ID,"DialogContent" 是调用ModalDialogshow方法时显示消息的地方,而"DialogActionBtn" 是关闭按钮的ID。

最后,我们向文档附加一个双击事件,该事件将触发一个AJAX操作。

/* Bind an event listener to double-click event. */
EventHandler.addEventListener(document,"dblclick",document_dblclick);

现在让我们看看document_dblclick方法。

function document_dblclick(evt)
{
  /* create an AJAX request */
  var ajax = _.ajax();

  /*
   * Note that _.ajax(); is a shorthand notation 
   * for new XHRequest();
   * Visit http://sardalya.pbwiki.com/Shortcuts for details.
   */

  /* 
   * You can add as many fields as you like to the post data. 
   * Normally the server will use this data to create an
   * output that makes sense which may be an XML, a JSON String
   * or an HTML String.
   */
  ajax.removeAllFields();
  ajax.addField("name","John");
  ajax.addField("surname","Doe");

  /* These events will be fired when server posts back a response. */
  ajax.oncomplete=ajax_complete;
  ajax.onerror=ajax_error;

  /* Set a default waiting message. */
  g_Modal.show("Fetching data... Please wait...");

  /*
   * Disable close action if you want to force the user 
   * to wait for the outcome of the AJAX request.
   * Although it is generally not recommended 
   * this may be necessary at certain times.
   */
  g_Modal.disableClose();

  /* Post data to the server. */
  ajax.get("externalScript.html");

  /* Stop event propagation. */
  new EventObject(evt).cancelDefaultAction();
}

注释应该能自明。

最后,是服务器回发后触发的两个方法。

/* Triggered when a successful AJAX response comes from the server.*/
function ajax_complete(strResponseText,objResponseXML)
{
  g_Modal.show(strResponseText);
  
  /* Re-activate close button. */
  g_Modal.enableClose();
}

/* Triggered when server generates an error. */
function ajax_error(intStatus,strStatusText)
{
  g_Modal.show("Error code: ["+ intStatus+ "] error message: [" + 
   strStatusText + "].");

  /* Re-activate close button. */
  g_Modal.enableClose();
}

就是这样!

那些讨厌的SELECT框怎么办?

ModalDialog对象内部会处理这个问题,当ModalDialog打开时,它会用带有"modalWrap"类的SPAN元素替换它们。这解决了众所周知的“SELECT框会穿透我的顶层”的问题。

为了完整起见,这是它的CSS。

.modalWrap
{
 border: 2px #ffffcc inset;
 background:#ffffcc;
 margin:5px;
}

您可以向其中添加任意多条规则。SPAN越像SELECT元素越好(您可以应用宽度和行高,设置display为inline-table...等,我没有过多更改以保持简单)。

对于那些想知道如何替换这些SPANSELECT的人,下面是相应的私有方法。您可以查看文章Zip文件的源代码以获取更多详细信息。

前一个版本中,我们只是通过将SELECT的CSS可见性设置为hidden来隐藏它们。在实际场景中进行测试后,我发现SELECT的“突然消失”让一些用户感到烦恼。

我认为,转换SELECT元素比完全隐藏它们要好得多。

……不,我不想使用IFRAMEs:)

代码如下:

_this._replaceCombos=function(blnReplaceBack)
{
 var arSelect = document.getElementsByTagName("select");
 var len=arSelect.length;
 var objSel=null;
 var strNodeValue="";
 var objSpan=null;
 var o=null;

 if(!blnReplaceBack)
 {
  blnReplaceBack=false;
 }
 
 for(var i=0;i<len;i++)
 {
  objSel=arSelect[i];
  strNodeValue=objSel.childNodes[objSel.selectedIndex
  ].childNodes[0].nodeValue;

  objSpan=new CBObject(objSel.id+"_ModalWrap");
  if(objSpan.exists())
  {
   o=objSpan.getObject();
   o.parentNode.removeChild(o);
  }

  objSpan=document.createElement("span");
  objSpan.id=objSel.id+"_ModalWrap";
  objSpan.appendChild(document.createTextNode(strNodeValue));
  objSpan.className="modalWrap";
  objSel.parentNode.insertBefore(objSpan,objSel);

  if(blnReplaceBack)
  {
   new DynamicLayer(objSpan).collapse();
   new DynamicLayer(objSel).expandInline();
  }
  else
  {
   new DynamicLayer(objSpan).expandInline();
   new DynamicLayer(objSel).collapse();
  }
 }
};

结论

总之,我们建模并创建了一个可拖动的DHTML模态对话框,并建立了一个到外部脚本的AJAX连接;我们进行了一些跨浏览器调整,以使我们的应用程序在尽可能多的浏览器上运行;并且我们进行了一些面向对象的编码。

一如既往,祝您编码愉快!

历史

  • 2006-06-02:文章创建。
© . All rights reserved.