Support for cancelling DHTML dialog box with escape - javascript

Imagine a DHTML dialog box with the following markup:
<div id="someDialog" class="dialog">
<h2>Title of dialog</h2>
Lots: <input ...>
of: <select ...>
controls: <textarea ...>
<input type="submit" value="OK">
<input type="reset" value="Cancel">
</div>
The user will expect hitting escape to cancel the dialog. This in itself isn't hard -- just add a keydown event handler to document.documentElement to check for ev.keyCode == 27, and use that to close the topmost dialog on the page.
The problem is this – there are certain circumstances where it's important that the browser sees the escape key first. For example, if the browser prompts with an autocomplete menu for <input type="text">, pressing escape should cancel that, not cancel the dialog. If you bring up the dropdown/popup menu for a <select>, pressing escape should close that, not the dialog.
How do you arrange to handle the escape key for a window, if and only if the browser doesn't need escape keypresses for something?
Edit: Stack Exchange itself has this very fault. If I click the "Would you like to have responses to your questions sent to you via email?" link, which opens a DHTML dialog box, then tab to the frequency dropdown, press alt-down to open the dropdown menu, then escape to close the dropdown menu, the whole dialog closes. This should not happen. The browser's control implementation should have first pickings on the escape key under these circumstances.

After some decent researching and trial/error, the best/only solution here seems to be creating your own custom form controls.
The following is a failed attempt to solve the problem.
http://jsfiddle.net/CoryDanielson/4jBgs/10/
Here's basically how it works.
First, there's an activeInput variable which stores a DOMElement of the input that the user is focused on. (only if the input is escapable)
var activeInput = false;
In order to populate this variable, I created an array of the DOMElements that you mentioned can be escaped (textboxes with autocomplete, select elements)
var escapableElements = [];
escapableElements = escapableElements.concat(
Array.prototype.slice.call(document.getElementsByTagName('select')),
Array.prototype.slice.call(document.getElementsByTagName('input'))
//add more elements here
);
and then looped through the array and attached eventListeners for the focus and blur (lose focus) events. (i included the for each function at the bottom of this post)
forEach(escapableElements, function() {
this.addEventListener('focus', registerActiveElement);
this.addEventListener('blur', deregisterActiveElement);
});
function registerActiveElement() {
if (!activeInput)
activeInput = this;
//console.log('registered'); //testing only
}
function deregisterActiveElement() {
if (activeInput)
activeInput = false;
//console.log('deregistered'); //testing only
}
After that, I wired up an eventListener for the keydown event. Inside of it, I checked to see if there is an activeInput if there is, I just return true; which will let the browser do what it wants (escape from autocomplete, etc) if there IS NOT an activeInput, I checked if ESC was pressed and call hide_dialog_box(event.keyCode);
The only difference from the paragraph in your question about handling the ESC keypress is that I checked to see if there was an activeInput beforehand. If there is an activeInput, I did nothing (let the browser handle ESC natively) if there's no activeInput I called event.preventDefault() which will cancel the browser's native handling of ESC and then called the function hide_dialog_box(keyCode) and then did return false; which also helps to prevent the browser from handling the ESC keypress.
document.addEventListener('keydown', function(event) {
if (!activeInput) {
if (event.keyCode == 27) { //esc
event.preventDefault();
hide_dialog_box(event.keyCode);
return false;
}
} else {
return true; //if active input, let browser function
}
/*
if the browser prompts with an autocomplete menu for
<input type="text">, or options on a <select> drop down
pressing escape will cancel that, not cancel the dialog.
*/
});
The last 2 snippits of code are the function hide_dialog_box(keyCode) and the function I wrote to wrote to loop through the NodeList called escapableElements
function hide_dialog_box(keyCode) {
var dialog_box = document.getElementById('dialog_box');
dialog_box.style.display = 'none';
}
function forEach(list, callback) {
for (var i = 0; i < list.length; i++)
{
//calls the callback function, but places list[i] as the 'this'
callback.call(list[i]);
}
}

Related

How to prevent the html details element from toggling when the summary has an embedded text input and user presses space bar

I have a text input within the summary tag of a details element which is in the open state. The intention is to capture user input which will eventually be displayed as a details element (see below). However the details element toggles when the user presses the space bar while entering text. I want to prevent this. I expected that this could be done using stopPropagation in the keypress event but it does not seem to be working. How do I prevent the element from toggling?
window.onload = function() {
var summary = document.getElementById("x");
var fn = function(e) {
e.stopPropagation();
/*if(e.keyCode == 32){
e.preventDefault();
e.target.value += " "
} */
};
summary.onkeypress = fn;
//summary.onkeydown = fn;
};
<details open>
<summary><input id="x" type="text" /></summary>
Some content
</details>
I need to do this within a React component but I'm posting a js/html example here for simplicity.
One alternative is to use preventDefault onkeypress when space is entered and manually concatenate the space char but this seems inelegant
You can catch the event on summary object and then preventDefault() will not toggle details element state but also will not block space addition.
window.onload = function() {
var summary = document.getElementById("x");
var fn = function(e) {
if(e.keyCode == 32){
e.preventDefault();
}
};
summary.onkeyup = fn;
};
<details open>
<summary id="x"><input type="text" /></summary>
Some content
</details>
Pay attention i moved id="x" to summary instead of input
Having seen the other answer, unfortunately it appeared it didn't work for me. Instead I used the following (you'll need jQuery for this one):
$("details").on("keyup", function(event) {
if ($(document.activeElement).is("input") && $(document.activeElement).parent().is("summary") && event.keyCode == 32) {
if ($(this).attr("open")) {
$(this).removeAttr("open");
} else {
$(this).attr("open", "");
}
}
});
When the space key is pressed, the code will detect if you're in an input, and if you are, the open attribute will be toggled (as it was firstly toggled before the event was run, so this creates an effect similar to event.preventDefault(); which doesn't work here) before the screen has a chance to redraw. The space character will of course still show in the input, and the space action will of course be working again when the input is not focused but the summary element is, as to not break accessibility.
The code may need altering (specifically the if on line 2) if you've got an input nested inside something else rather than the summary element, but it shouldn't be too hard to modify.
Also, I have tested the script for accessibility ─ it works well with keyboard-only navigation, and of course with ChromeVox too (the readouts make sense, as well as with the keyboard).

Safari issue with text inputs, text is selected as user enters it causing text to be lost

I have the following input element on my page:
<input class="input" name="custom_fields[new]" placeholder="Enter placeholder" type="text">
I have a Twitter Flight event listener on this element that looks like this:
this.on('keyup', {
inputsSelector: this.updateViewInputs
});
Which triggers this method:
this.updateViewInputs = function(ev) {
var isDeletionKeycode = (ev.keyCode == 8 || ev.keyCode == 46);
// Remove field is user is trying to delete it
if (isDeletionKeycode && this.shouldDeleteInput(ev.target.value, this.select('inputsSelector').length)) {
$(ev.target.parentNode).remove();
}
// Add another field dynamically
if (this.select('lastInputsSelector')[0] && (ev.target == this.select('lastInputSelector')[0]) && !isDeletionKeycode) {
this.select('inputsContainer').append(InputTemplate());
}
// Render fields
that.trigger('uiUpdateInputs', {
inputs: that.collectInputs()
});
}
And finally triggers uiUpdateInputs:
this.after('initialize', function() {
this.on(document, 'uiUpdateInputs', this.updateInputs)
});
this.updateInputs = function(ev, data) {
// Render all inputs provided by user
this.select('inputListSelector').html(InputsTemplate({ inputs: data.inputs }));
}
All of this functionality works as expected on Chrome and Firefox. Users can type into the input and see the page change in 'real time'. Users also get additional fields that they can enter text into and see the page change.
The issue in question arises when using Safari, as a user enters text into the described input field the text in the input field becomes highlighted (selected) and when they enter the next character all the content is replaced with that single character. This results in the user not being able to enter more than 1 or 2 characters before having them all replaced by the next entered character.
I have tried several approaches to fix this problem but none have worked, they include:
Using a setTimeout to delay the code run on the keyup event
Using Selection to try to disable the selection of the text using collapseToEnd.
Using click,focus,blur events to try to remove the selection from the entered text
Triggering a right arrow key event to try to simply move the cursor forward so they user does not delete the selected text
Using setInterval to routinely remove selections made by the window
I am very confused why this is happening and I am wondering if this is a bug in webkit with Flight. I see no issue with the Firefox or Chrome versions of this page. Thanks for any help!
This seems to be an issue with certain versions of Safari. When listening for the keyup function in javascript it will automatically select all of the text in the box and subsequently delete it all when the next key is typed. To prevent this from happening call preventDefault on the event object that is passed to the keyup function.
this.on('keyup', function(e) {
e.preventDefault()
});

How to disable tab key globally except for all forms in a page with JavaScript?

I'm making a single page app that is launching next week, for a pretty huge client, and going live for a pretty big event and well, there's still a ton to finish before then.
There's 100+ 'pages' which are all loaded within a single 700px x 600px window, and I had learned not long ago you could tab through the page/sections, which in-turn would break the app because it would bring focus to hidden off-screen elements, so for this reason, I disabled the tab key for the entire app.
But now there are a couple places where we have a form with a handful of input fields which you are not able to tab through as you fill in the form. It's a pain in the ass.
I need to make it so you can tab through the form fields, but only the form fields. I have the tabindex attribute set for the form, and have tried to make inputs tab enabled but was not able to make it work without causing the app to jump to hidden sections.
Here's the function I need to change so it will disable tab key except from input to input fields in a form.
window.onkeydown = function(e) {
if (e.keyCode === tab) {
return false;
}
}
I tried to do this, which obv didnt work lol
$('input').keydown(function(e) {
if (e.keyCode === tab) {
return true;
}
});
Thanks :)
I made some fixes to what #Joseph posted for an answer to this that handle being able to shift + tab through inputs of a form so you can reverse direction. It was a very annoying thing for me before when I first had to find a way to do this, and didn't have time to waste anymore trying to find a complete solution for this until now. Here it is.
$(function() {
// gather all inputs of selected types
var inputs = $('input, textarea, select, button'), inputTo;
// bind on keydown
inputs.on('keydown', function(e) {
// if we pressed the tab
if (e.keyCode == 9 || e.which == 9) {
// prevent default tab action
e.preventDefault();
if (e.shiftKey) {
// get previous input based on the current input
inputTo = inputs.get(inputs.index(this) - 1);
} else {
// get next input based on the current input
inputTo = inputs.get(inputs.index(this) + 1);
}
// move focus to inputTo, otherwise focus first input
if (inputTo) {
inputTo.focus();
} else {
inputs[0].focus();
}
}
});
});
Demo of it working http://jsfiddle.net/jaredwilli/JdJPs/
Have you tried setting tabIndex="-1" on all elements that you don't want to be able to tab to? I think that's a much better solution.
Otherwise, within your key handler function test event.target (or event.srcElement in IE) to see if the event originated with a form element. You seem to be using jQuery, so you could assign an "allowTab" class just to the fields in your form and then do this:
$(document).keydown(function(e) {
if (!$(e.target).hasClass("allowTab"))
return false;
});
Or
if (e.target.tagName !== "input")
// etc
what we do is to determine what input is next in line and skip to it!:
http://jsfiddle.net/qXDvd/
$(document).ready(function() {
//gather all inputs of selected types
var inputs = $('input, textarea, select, button');
//bind on keydown
inputs.on('keydown', function(e) {
//if we pressed the tab
if (e.keyCode == 9 || e.which == 9) {
//prevent default tab action
e.preventDefault();
//get next input based on the current input we left off
var nextInput = inputs.get(inputs.index(this) + 1);
//if we have a next input, go to it. or go back
if (nextInput) {
nextInput.focus();
}
else{
inputs[0].focus();
}
}
});
});​
may need some optimization but it works. this was originally meant to skip non-form elements. you can add selectors not to skip if you like. additionally, you can add logic for the Shift+Tab behavior (maybe before the tab logic)
obviously, it will still go through some elements according to how they appear in the source. however, why not just remove those hidden elements from the DOM but still keep track of them using the methods found in this question. that way, you won't have the pain of having to cycle back and forth through off-screen elements.

Exit confirmation in Javascript with Telerik controls

I'm using Telerik controls for my project. On my page, there are some links to another page, several radtextboxes and a radbutton (it causes a postback). When the textbox values are changed, the button becomes enabled. Then, in my window.onbeforeunload, I check if the button is enabled or disabled. If it is enabled, then the confirm dialog appears. The code looks like this :
window.onbeforeunload = function (evt) {
var ClientGeneral_btnSave = $find('<%=btnSave.ClientID %>');
if (ClientGeneral_btnSave.get_enabled() == true) {
var message = 'You will lose unsaved data?';
if (typeof evt == 'undefined') {
evt = window.event;
}
if (evt) {
evt.returnValue = message;
}
return message;
}
This code works well. When I close the tab, the confirm dialog appears. When I click on the links, it does. But, when I click on the btnSave itself, the dialog appears too, which is unsual. I want the btnSave NOT to cause onbeforeunload event
Please tell me how to do this.
Thank you in advance
It will fire onbeforeunload this if the btnSave posts back which it looks as if it is. Therefore you have a couple of choices
Prevent the btnSave from posting back if you don't need it to. Easiest way to do it is put this attribute in the asp:Button markup
OnClientClick="return false"
Wire up a javaScript/jQuery method to disable the onbeforeunload event. I did this before by stashing an value in a hidden field and using this to signal that the onbeforeunload event should fire.
for instance
$('#<%= btnSave.ClientID %>').bind('click', function(e){
$('#myHiddenFieldId').val('1');
});
and change your on beforeunload handler to check that the hidden field is not equal to 1 (make 0 the default i.e.
window.onbeforeunload = function (evt) {
if( $('#myHiddenFieldId').val() != '1')
{
//your logic here
}
}
You could probably do something better by unbinding the onbeforeunload handler in the btnSave click event using JQuery rather than using a hidden field to override.
Option 2 can get fiddly though - so best of luck

Javascript virtual keyboard: how to indentify text fields?

I'm developing a Javascript virtual keyboard, and I would like it to appear everytime a user press enter on a text fields. But how can I know if a text (or any input) field is selected?
--clarification
I have to information or control over the page that will be loaded. I just want that, if a input field is selected and the user press enter, my virtual keyboard shows up.
--update
Does it makes any difference if what I'm trying to do is a firefox extension? (I think it shouldn't)
use jQuery and add the following
$(document).ready(function() {
//apply action to input elements by class
//$("#.input_class").keypress(function(e) {
//apply action to all input elements ( input, textarea, select and button )
$(':input').keypress(function(e) {
if(e.keyCode==13){
// Enter pressed... do anything here...
alert($(this).val());
} else {
//make shure you get the desired action for other keys pressed
xTriggered++;
}
//do not submit the form
return false;
});
});
bind it to the onfocus event. That event is triggered when the input element gets the focus. You could remove the keyboard again on the onblur event if you want to hide it again.
To get notified that a text field is selected, you could attach an event handler to onfocus of the fields you're interested in.
Example in jQuery (jQ chosen for brevity, the event works in plain JS):
$('input[type="text"]').focus(function(event){
// do something here
});
If you only care to capture the "enter" key, you don't need to worry about focus, just attach to the onkeypress event of the textfields (see #poelinca's answer).
Despite of what jquery apologetes say, there is no hassle to instrument all fields without resorting to large and slow external library:
for (var i = 0; i < document.forms.length; i++)
for (var j = 0; j < document.forms[i].elements.length; j++)
if (document.forms[i].elements[j].tagName.match(/^INPUT$/i))
if (document.forms[i].elements[j].type.match(/^TEXT$/i))
document.forms[i].elements[j].addEventListener('focus', function(){/* your stuff here */}, false);
My solution, for now, was use a specified key just to open the virtual keyboard when the user request.

Categories