限制可调焦控件到当前对话框的最佳方法是什么?

时间:2021-05-24 15:53:19

I've a web application with dialogs. A dialog is a simple div-container appended to the body. There is also an overlay for the whole page to prevent clicks to other controls. But: Currently the user can focus controls that are under the overlay (for example an input). Is there any way to limit the tabbable controls to those which are in the dialog?

我有一个有对话框的web应用程序。对话框是附加在主体上的一个简单的div-container。整个页面也有一个覆盖层,以防止对其他控件的单击。但是:目前用户可以对覆盖层下的控件进行聚焦(例如输入)。是否有办法将可制表控件限制为对话框中的控件?

I'm using jQuery (but now jQueryUI). In jQueryUi dialogs it's working (but I don't want to use jQueryUI). I failed to figure out, how this is accomplished there.

我使用jQuery(现在是jQueryUI)。在jQueryUi对话框中,它正在工作(但我不想使用jQueryUi)。我没搞清楚,这是怎么实现的。

Here is the jQueryUI example: http://jqueryui.com/resources/demos/dialog/modal-confirmation.html - The link on the webpage is not focusable. The focus is kept inside the dialog (the user cannot focus the urlbar of the browser using tab).

下面是jQueryUI示例:http://jqueryui.com/resources/demos/dialog/modal- confirm.html——网页上的链接无法锁定。焦点保持在对话框中(用户不能使用选项卡对浏览器的urlbar进行焦点)。

HTML:

HTML:

<a href="#test" onclick="alert('Oh no!');">I should not receive any focus</a>
<input type="text" value="No focus please" />
<div class="overlay">
    <div class="dialog">
        Here is my dialog<br />
        TAB out with Shift+Tab after focusing "focus #1"<br />
        <input type="text" value="focus #1" /><br />
        <input type="text" value="focus #1" /><br />
    </div>
</div>    

CSS:

CSS:

.overlay {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-color: rgba(0, 0, 0, 0.3);
    text-align: center;
}

.dialog {
    display: inline-block;
    margin-top: 30%;
    padding: 10px;
    outline: 1px solid black;
    background-color: #cccccc;
    text-align: left;
}

Here is my fiddle: http://jsfiddle.net/SuperNova3000/weY4L/

这是我的小提琴:http://jsfiddle.net/SuperNova3000/weY4L/

Does anybody have an idea? I repeat: I don't want to use jQueryUI for this. I'd like to understand the underlying technique.

有人知道吗?我重复一遍:我不想使用jQueryUI。我想了解一下潜在的技术。

2 个解决方案

#1


4  

I've found an easy solution for this issue after hours of trying. I think the best way is adding 2 pseudo elements. One before and one after the dialog (inside the overlay). I'm using <a>-Tags which are 0x0 pixels. When reaching the first <a>, I'm focusing the last control in the dialog. When focusing the last <a>, I'm focusing the first control in the dialog.

经过数小时的努力,我找到了解决这个问题的简单方法。我认为最好的方法是添加两个伪元素。对话框前后各一个(覆盖层内)。我使用的是-Tags,即0x0像素。当到达第一个时,我正在对焦对话框中的最后一个控件。当聚焦最后一个时,我将聚焦对话框中的第一个控件。

I've adapted the answer of this post: Is there a jQuery selector to get all elements that can get focus? - to find the first and last focusable control.

我改编了这篇文章的答案:是否有一个jQuery选择器来获取所有可以获得焦点的元素?-查找第一个和最后一个可调焦控制。

HTML:

HTML:

<div class="overlay">
    <a href="#" class="focusKeeper">
    <div class="dialog">
        Here is my dialog<br />
        TAB out with Shift+Tab after focusing "focus #1"<br />
        <input type="text" value="focus #1" /><br />
        <input type="text" value="focus #1" /><br />
    </div>
    <a href="#" class="focusKeeper">
</div>    

Extra CSS:

额外的CSS:

.focusKeeper {
    width: 0;
    height: 0;
    overflow: hidden;
}

My Javascript:

我的Javascript:

$.fn.getFocusableChilds = function() {
  return $(this)
    .find('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object:not([disabled]), embed, *[tabindex], *[contenteditable]')
    .filter(':visible');
};

[...]

$('.focusKeeper:first').on('focus', function(event) {
    event.preventDefault();
    $('.dialog').getFocusableChilds().filter(':last').focus();
});

$('.focusKeeper:last').on('focus', function(event) {
    event.preventDefault();
    $('.dialog').getFocusableChilds().filter(':first').focus();
});

May be I'll add a fiddle later, no more time for now. :(

可能以后我再加一把小提琴,现在没时间了。:(


EDIT: As KingKing noted below the focus is lost, when clicking outside the control. This may be covered by adding an mousedown handler for the .overlay:

编辑:正如KingKing在下面所指出的,当点击控件外时,焦点将丢失。这可以通过为.overlay添加一个mousedown处理程序来覆盖:

$('.overlay').on('mousedown', function(event) {
    event.preventDefault();
    event.stopImmediatePropagation();
});

EDIT #2: There's another thing missing: Going outside the document with the focus (for example the titlebar) and than tabbing back. So we need another handler for document which puts back the focus on the first focusable element:

编辑#2:还有另外一件事没做:用焦点(例如titlebar)跳出文档,而不是返回标签。所以我们需要另一个文档处理程序它将焦点放在第一个可聚焦元素上:

$(document).on('focus', function(event) {
    event.preventDefault();
    $('.dialog').getFocusableChilds().filter(':first').focus();
});

#2


2  

You can try handling the focusout event for the .dialog element, check the e.target. Note about the e.relatedTarget here, it refers to the element which receives focus while e.target refers to the element lossing focus:

您可以尝试为.dialog元素处理focusout事件,检查e.target。请注意关于e。这里的relatedTarget是指在e时接收焦点的元素。目标为元素缺失焦点:

var tabbingForward = true;
//The body should have at least 2 input fields outside of the dialog to trap focusing,
//otherwise  focusing may be outside of the document 
//and we will loss control in such a case.
//So we create 2 dummy text fields with width = 0 (or opacity = 0)
var dummy = "<input style='width:0; opacity:0'/>";
var clickedOutside = false;
$('body').append(dummy).prepend(dummy);
$('.dialog').focusout(function(e){     
  if(clickedOutside) { 
    e.target.focus();
    clickedOutside = false;
  }
  else if(!e.relatedTarget||!$('.dialog').has(e.relatedTarget).length) {   
    var inputs = $('.dialog :input');
    var input = tabbingForward ? inputs.first() : inputs.last();
    input.focus();        
  }
}); 
$('.dialog').keydown(function(e){
  if(e.which == 9) {
    tabbingForward = !e.shiftKey;
  }
});
$('body').mousedown(function(e){
  if(!$('.dialog').has(e.target).length) {        
    clickedOutside = true;        
  }
});

Demo.

#1


4  

I've found an easy solution for this issue after hours of trying. I think the best way is adding 2 pseudo elements. One before and one after the dialog (inside the overlay). I'm using <a>-Tags which are 0x0 pixels. When reaching the first <a>, I'm focusing the last control in the dialog. When focusing the last <a>, I'm focusing the first control in the dialog.

经过数小时的努力,我找到了解决这个问题的简单方法。我认为最好的方法是添加两个伪元素。对话框前后各一个(覆盖层内)。我使用的是-Tags,即0x0像素。当到达第一个时,我正在对焦对话框中的最后一个控件。当聚焦最后一个时,我将聚焦对话框中的第一个控件。

I've adapted the answer of this post: Is there a jQuery selector to get all elements that can get focus? - to find the first and last focusable control.

我改编了这篇文章的答案:是否有一个jQuery选择器来获取所有可以获得焦点的元素?-查找第一个和最后一个可调焦控制。

HTML:

HTML:

<div class="overlay">
    <a href="#" class="focusKeeper">
    <div class="dialog">
        Here is my dialog<br />
        TAB out with Shift+Tab after focusing "focus #1"<br />
        <input type="text" value="focus #1" /><br />
        <input type="text" value="focus #1" /><br />
    </div>
    <a href="#" class="focusKeeper">
</div>    

Extra CSS:

额外的CSS:

.focusKeeper {
    width: 0;
    height: 0;
    overflow: hidden;
}

My Javascript:

我的Javascript:

$.fn.getFocusableChilds = function() {
  return $(this)
    .find('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object:not([disabled]), embed, *[tabindex], *[contenteditable]')
    .filter(':visible');
};

[...]

$('.focusKeeper:first').on('focus', function(event) {
    event.preventDefault();
    $('.dialog').getFocusableChilds().filter(':last').focus();
});

$('.focusKeeper:last').on('focus', function(event) {
    event.preventDefault();
    $('.dialog').getFocusableChilds().filter(':first').focus();
});

May be I'll add a fiddle later, no more time for now. :(

可能以后我再加一把小提琴,现在没时间了。:(


EDIT: As KingKing noted below the focus is lost, when clicking outside the control. This may be covered by adding an mousedown handler for the .overlay:

编辑:正如KingKing在下面所指出的,当点击控件外时,焦点将丢失。这可以通过为.overlay添加一个mousedown处理程序来覆盖:

$('.overlay').on('mousedown', function(event) {
    event.preventDefault();
    event.stopImmediatePropagation();
});

EDIT #2: There's another thing missing: Going outside the document with the focus (for example the titlebar) and than tabbing back. So we need another handler for document which puts back the focus on the first focusable element:

编辑#2:还有另外一件事没做:用焦点(例如titlebar)跳出文档,而不是返回标签。所以我们需要另一个文档处理程序它将焦点放在第一个可聚焦元素上:

$(document).on('focus', function(event) {
    event.preventDefault();
    $('.dialog').getFocusableChilds().filter(':first').focus();
});

#2


2  

You can try handling the focusout event for the .dialog element, check the e.target. Note about the e.relatedTarget here, it refers to the element which receives focus while e.target refers to the element lossing focus:

您可以尝试为.dialog元素处理focusout事件,检查e.target。请注意关于e。这里的relatedTarget是指在e时接收焦点的元素。目标为元素缺失焦点:

var tabbingForward = true;
//The body should have at least 2 input fields outside of the dialog to trap focusing,
//otherwise  focusing may be outside of the document 
//and we will loss control in such a case.
//So we create 2 dummy text fields with width = 0 (or opacity = 0)
var dummy = "<input style='width:0; opacity:0'/>";
var clickedOutside = false;
$('body').append(dummy).prepend(dummy);
$('.dialog').focusout(function(e){     
  if(clickedOutside) { 
    e.target.focus();
    clickedOutside = false;
  }
  else if(!e.relatedTarget||!$('.dialog').has(e.relatedTarget).length) {   
    var inputs = $('.dialog :input');
    var input = tabbingForward ? inputs.first() : inputs.last();
    input.focus();        
  }
}); 
$('.dialog').keydown(function(e){
  if(e.which == 9) {
    tabbingForward = !e.shiftKey;
  }
});
$('body').mousedown(function(e){
  if(!$('.dialog').has(e.target).length) {        
    clickedOutside = true;        
  }
});

Demo.