Making a clickable <div> accessible through tab structure? - html

So I am working on a project that requires a <div> with an onclick event. I've got the main functionality working, but there is one problem. I need the onclick event to happen when the user tabs to the <div> and presses enter. I added a tabindex to the <div> which allows it to gain focus, but nothing happens when the user presses enter (or any other key).
Can anyone help me with this? Or is what I want not even possible?
Here is a jsfiddle demonstrating my problem.
http://jsfiddle.net/logiwan992/suwq7r09/
Thank you in advance for any help.

I note the question is tagged WCAG and "accessibility".
The answer to your question is therefore "don't do that." The other answers on this page will all work fine, for everyone except the people who need it to work, i.e. those using screenreaders or other assistive technology. None of the javascript-based solutions here are WCAG compliant.
What you want is a <button>. That gives you your tabindex and keyboard control for free.
You can also force a <div> to work like a <button> by adding ARIA markup (though it's better and easier to just use the tag that already does what you need it to do.)
If absolutely necessary, a good introduction to using ARIA for pseudo-buttons is here:
https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_button_role
The gist of it is, you need to add the role="button" attribute, and manage the state of the aria-pressed attribute manually (by capturing key events and clicks in javascript; other answers have covered this pretty thoroughly so I won't repeat the details)

It is perfectly fine to have a <div> work like a button provided you specify the right ARIA tags, roles, and keyboard events. That's the whole point of ARIA.
I do agree, though, that when possible you should use the native html elements. That's the first rule of ARIA use - http://www.w3.org/TR/aria-in-html/#notes-on-aria-use-in-html. But I understand that's not always possible.
There was a mention of using focus(). That's incorrect. Focus() is used to move the focus to the object. It's not used to handle an event. Now perhaps they meant onFocus(), which is an event triggered when the object receives focus, but that's still not the right event to trap for. You don't want a button (whether implemented as a <div> or a <button>) to perform its function just because you tabbed to it. The user has to click or press enter/space on it.
Please look at the authoring practices which define the keyboard behavior for a button, http://www.w3.org/TR/wai-aria-practices/#button, as well as the section that talks about keyboard events, http://www.w3.org/TR/wai-aria-practices/#focus_tabindex. In particular, note that you should not rely on keypress. Not all browsers send that event.
Anytime you press a key, three possible events might happen: Keydown, keypress, keyup. Keydown and keyup are supported on all browsers and have access to event.keyCode. Keypress is supported on some browser and has access to event.charCode.
There's a significant different between keyCode and charCode, especially if you're trying to implement shortcut keys such as Ctrl+/. Many non-US keyboards have special keys in different places on the keyboard and you get different keyCodes for them. But that's a topic for another discussion.

The "onclick" attribute has a specific behavior on links, because it can be triggered with the enter key.
See the following WCAG failure:
http://www.w3.org/TR/WCAG20-TECHS/F59.html
You have to take care of the "role" of the element.
The following page illustrates how you can make an "accessible link" from a "span":
https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_link_role
Also, as already said, best way is relying on a button/a element when possible.

Try to bind the keypress event on the said div and detect if Enter key was pressed (which has code number 13).
var div = document.getElementsByTagName('div');
div[0].addEventListener('keypress', function(e) {
if(e.keyCode == 13) {
alert('div focused');
}
});
Working JSFiddle
You can, alternatively, use jQuery also:
jQuery(function($) {
$('div').on('keypress', function(e) {
if(e.keyCode == 13) {
alert('div focused');
}
});
});
Working JSFiddle

Use the same event handler for both events. If the event is keypress verify the key pressed is the Enter key before executing.
var divButton = document.querySelector('#div-button');
divButton.addEventListener('click', activate);
divButton.addEventListener('keypress', activate);
function activate(e) {
if (e.type === 'keypress' && e.keyCode == 13) {
alert('activated the div');
}
};
div {
outline: 1px solid black;
}
div:focus {
outline: 1px solid red;
}
<div id="div-button" tabindex="0">
<h1>This is my div!</h1>
</div>

Related

How to not allow user to enable input editing [duplicate]

I have a button as follows:
<input type="submit" class="button" value="FooBar" name="FooBar" id="FooBar" disabled="disabled">
I am enabling this button only when certain parameters are met. To test whether it was secure, I pressed F12 (or right click -> Inspect Element) and edited out the text disabled="disabled". Doing this overrides my code and that is scary. How can I prevent someone from changing it in this manner?
I am using php and jquery in this page and using the latter to enable or disable the button. I have checked the jquery click function and it not only executes, but shows the button as not disabled. The alert below reads Disabled: false
$("#FooBar").click(function(){
alert('Disabled: ' + $(this).is('[disabled=disabled]'));
})
So how can I prevent a user from changing the button disabled state and thus overriding my logic?
You can disable the right-click by doing:
document.addEventListener('contextmenu', event => event.preventDefault());
but it is not recommended. Why? It achieves nothing other than the annoying user
OR
Alternatively, a better approach is to recheck your validations in submit action and simply returns if it fails, in this case, if user inspects and changed the state of a button, the button stays idle and will not allow to proceed
$("#FooBar").click(function() {
if (!this.acceptenceCriteria()) {
return;
}
alert('Disabled: ' + $(this).is('[disabled=disabled]'));
})
You can't stop people from using dev tools.
You can try a couple of tricks to disable right clicking (like the one below) which will stop some number of people but ultimately the solution to your problem is to use html and http properly.
$(document).bind("contextmenu",function(e) {
e.preventDefault();
});

How to access more than 2 DOM elements "The AngularJS way"?

I'm starting to learn angularJS better, and I've noticed that AngularJS tries to make strong emphasis on separating the view from the controller and encapsulation. One example of this is people telling me DOM manipulation should go in directives. I kinda got the hang of it now, and how using link functions that inject the current element allow for great behavior functionality, but this doesn't explain a problem I always encounter.
Example:
I have a sidebar I want to open by clicking a button. There is no way to do this in button's directive link function without using a hard-coded javascript/jquery selector to grab the sidebar, something I've seen very frowned upon in angularJS (hard-coding dom selectors) since it breaks separation of concerns. I guess one way of getting around this is making each element I wish to manipulate an attribute directive and on it's link function, saving a reference it's element property into a dom-factory so that whenever a directive needs to access an element other than itself, it can call the dom-factory which returns the element, even if it knows nothing where it came from. But is this the "Angular way"?
I say this because in my current project I'm using hard-coded selectors which are already a pain to mantain because I'm constantly changing my css. There must be a better way to access multiple DOM elements. Any ideas?
There are a number of ways to approach this.
One approach, is to create a create a sidebar directive that responds to "well-defined" broadcasted messages to open/close the sidebar.
.directive("sidebar", function(){
return {
templateUrl: "sidebar.template.html",
link: function(scope, element){
scope.$root.$on("openSidebar", function(){
// whatever you do to actually show the sidebar DOM content
// e.x. element.show();
});
}
}
});
Then, a button could invoke a function in some controller to open a sidebar:
$scope.openSidebar = function(){
$scope.$root.$emit("openSidebar");
}
Another approach is to use a $sidebar service - this is somewhat similar to how $modal works in angularui-bootstrap, but could be more simplified.
Well, if you have a directive on a button and the element you need is outside the directive, you could pass the class of the element you need to toggle as an attribute
<button my-directive data-toggle-class="sidebar">open</button>
Then in your directive
App.directive('myDirective', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
angular.element('.' + attrs.toggleClass).toggleClass('active');
}
};
}
You won't always have the link element argument match up with what you need to manipulate unfortunately. There are many "angular ways" to solve this though.
You could even do something like:
<div ng-init="isOpen = false" class="sidebar" ng-class="{'active': isOpen}" ng-click="isOpen = !isOpen">
...
</div>
The best way for directive to communicate with each other is through events. It also keeps with the separation of concerns. Your button could $broadcast on the $rootScope so that all scopes hear it. You would emit and event such as sidebar.open. Then the sidebar directive would listen for that event and act upon it.

TabIndex - hitting tab moves me to Address Bar - unable to work around this using Focus or +tab indexes

I read several threads that talk about how the Address Bar in IE is basically the first one to get focus when using TAB (MSDN's own docs talk about this).
Yet, I have seen situations where this doesn't always have to be the case....
I have a master page and inside my content area is a formView.
It defaults to INSERT view and can never leave it (they can only insert not edit and reading is handled elsewhere)
So on my page load for the page I have:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If fvwLogEntry.CurrentMode = FormViewMode.Insert = True Then
'Set the default field to position the cursor there...hopefully
Dim FCtxtHrEmployeeId As TextBox
FCtxtHrEmployeeId = CType(fvwLogEntry.FindControl("txtHrEmployeeId"), TextBox)
Page.SetFocus(FCtxtHrEmployeeId.ClientID.ToString)
End If
Now that works, when the page loads it sets the cursor to the employeeID text box inside the formview's INSERT template.
HOWEVER, when I hit TAB it takes me to the address bar and THEN if I hit tab again it takes me through the rest of the items on the page.
I set the tab index of the first item to 11 and then incrimented from there (I had read that IE's toolbars have tab indexes too so I thought perhaps using a higher number would bypass those, but again that doesn't REALLY make sense since it would still start at the lowest number, but I gave it a shot thinking it would move forward from where the focus was set.) If I click on the textbox and then hit TAB it DOES move through the page like I would expect.
It is just when the page loads and gets the focus set to the employeeID textbox that hitting tab moves it to the address bar.
I also tried setting the other controls to -1 (those I didn't want it to tab to), still no luck there.
So... what can I do to get around this?
There MUST be a simple way to set the focus to the employeeID textbox and ensure that pressing TAB after that moves to the next control in the formview's insert template and does NOT jump up to the address bar?
The following jquery code seems to be working fine for me..
$(window).load(function () {
$('.myClass :visible:input:enabled:first').focus();
});
$('body').on('keydown', '.myClass :visible:input:enabled:first', function (e) {
if ((e.which == 9) || (e.keyCode == 9)) {
$('.myClass :visible:input:enabled:first').focus();
}
});
I found another better option which is fastest as of what I tried.
Here's the code for that
function handleTabOrder() {
$('.myClass :visible:input:enabled').each(function (index) {
$(this).attr('tabindex', index + 10);
});
$('.myClass :visible:input:enabled:first').keydown(function (e) {
if (e.keyCode == 9 || e.which == 9) {
$("[tabindex=10]").focus();
}
});
}
What I have done here is to assign Tab order to all the visible controls on the page, then I have handled the key down event of only first control(that shifts the control to address bar) and now it shifts the control to next visible input item on the screen..
Its just a work around but works faster than any of the other things mentioned in the thread.
Just write the above function and all it in on-load event of page.
I was having this issue as well. For me, it was being caused by the use of the .select() method in order to bring focus automatically on a text field as soon as the page loaded. I changed my code to instead use JQuery's .focus() method and that resolved the issue.
I faced similar problem in IE. After some analysis I found that, this problem occurs if there is any HTML content outside form.
for example:
<html>
<div id="1">
</div>
<form>
//other code
</form>
</html>
It worked for me, after I moved all HTML inside form tag.
<html>
<form>
<div id="1">
</div>
//other code
</form>
</html>
Have a look at: http://www.w3schools.com/tags/att_global_tabindex.asp
Your txtHrEmployeeId element should have tabindex 1 and all other elements should have higher values.
-1 is not valid
Also verify that the tabindex are correct in the html that gets rendered (right-click in page and "view source").
I realize this is an old post, but an even simpler method is to add a "tab-stop" attribute to the form element with the last tabindex. Then bind a keydown listener and force focus to the first tabindex when the tab-stop is encountered.
Here's a simple example:
<input type="text" tab-stop />
$document.bind("keydown", function(event) {
var attrs = event.currentTarget.activeElement.attributes;
if (attrs['tab-stop']) {
angular.element.find('select')[0].focus();
event.preventDefault();
}
});
};
The answer mentioned in my other post works fine but it made the page take a huge performance hit because with every key press on the page the whole DOM was being searched for the elements.
So I found a new more optimized solution
var myNameSpace = function(){
this.selector = '.myClass :visible:input:enabled:first';
this.myElement = $(selector);
this._body = $('body');
var _self= this;
this._body.on('keydown',_self.selector,function(e){
if ((e.which == 9) || (e.keyCode == 9)) {
_self.myElement.focus();
}
});
};
The general idea being to 'cache' the node to be accessed. No need to traverse the DOM again and again for just selecting.
I had this same problem. It turns out mine was related to the ajax modal popup extenders. a modal popup was being shown, even though technically i could not see it because it was wrapped inside a parent div that was hidden. if you are using modal popup extenders, this could be causing an issue like this.
If you are using JSF or Primefaces, you can make use of:
<p:focus for"formname"></p:focus>

Difference between HTML event and JavaScript events

There are many ways by which we can attach an event on an HTML element.
The first way is: HTML attribute
<div id="foo" onclick="print2()> My event is attached as HTML attribute</div>
The second way is using some library (say jQuery)
<div id="bar"> My event is attached using jQuery </div>
<script>
$("#bar").click(function() {
alert("Hi this is Bar");
}
</script>
I earlier thought that jQuery might be internally converting the event to corresponding HTML attribute but this does not happen. Check this http://jsfiddle.net/blunderboy/wp4RU/3/
I am logging all the attributes of foo and bar and see bar does not have onclick attribute.
Please explain.
There is nothing called HTML Event! The two types of events you have described are, inline events and unobtrusive events, and both are javascript events.
Inline Events
When you declare javascript code on the elements itself, then it becomes an inline event. You have a few common events (as attributes to HTML Elements) like onclick, onkeydown, onkeypress, onkeyup, and all of them start with on. One such example is:
Click Me!
Unobtrusive Events
We need to assign something to be performed when the event is triggered. The = symbol is always used in JavaScript to assign the value on the right to the method or property on the left.
The window is not the only object we can attach events to. We can attach events to any object within the web page provided that we have a way of uniquely identifying that object. One way of identifying an object is by giving it an ID and referencing it by document.getElementById("id_of_the_element").
Lets take the same example.
Click Me!
Instead of the onclick attribute, I have an ID in the same place, which uniquely identifies the HTML element <a>. Now I can get the ID inside javascript this way:
document.getElementById('clickme');
For this, I can attach an event handler, which doesn't differ much from the way we use the attributes. It just doesn't have the on in the front. In our previous example, we used onclick, but now we are just going to use click.
document.getElementById('clickme').click = functionName;
Here, the functionName refers to any javascript's function name, or an anonymous function. So, for the alert, if we create a function named alertme(), we can define this way:
function alertme()
{
alert('You clicked me!');
}
Now to attach the function to the element can be done this way:
document.getElementById('clickme').click = alertme;
Still feeling lazy, you can do it using the anonymous function way, which takes no name:
document.getElementById('clickme').click = function () {
alert('You clicked me!');
}
Hope you understood. :) Let me know for further clarification.

Which HTML elements can receive focus?

I'm looking for a definitive list of HTML elements which are allowed to take focus, i.e. which elements will be put into focus when focus() is called on them?
I'm writing a jQuery extension which works on elements that can be brought into focus. I hope the answer to this question will allow me to be specific about the elements I target.
There isn't a definite list, it's up to the browser. The only standard we have is DOM Level 2 HTML, according to which the only elements that have a focus() method are
HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement and HTMLAnchorElement. This notably omits HTMLButtonElement and HTMLAreaElement.
Today's browsers define focus() on HTMLElement, but an element won't actually take focus unless it's one of:
HTMLAnchorElement/HTMLAreaElement with an href
HTMLInputElement/HTMLSelectElement/HTMLTextAreaElement/HTMLButtonElement but not with disabled (IE actually gives you an error if you try), and file uploads have unusual behaviour for security reasons
HTMLIFrameElement (though focusing it doesn't do anything useful). Other embedding elements also, maybe, I haven't tested them all.
Any element with a tabindex
There are likely to be other subtle exceptions and additions to this behaviour depending on browser.
Here I have a CSS-selector based on bobince's answer to select any focusable HTML element:
a[href]:not([tabindex='-1']),
area[href]:not([tabindex='-1']),
input:not([disabled]):not([tabindex='-1']),
select:not([disabled]):not([tabindex='-1']),
textarea:not([disabled]):not([tabindex='-1']),
button:not([disabled]):not([tabindex='-1']),
iframe:not([tabindex='-1']),
[tabindex]:not([tabindex='-1']),
[contentEditable=true]:not([tabindex='-1'])
{
/* your CSS for focusable elements goes here */
}
or a little more beautiful in SASS:
a[href],
area[href],
input:not([disabled]),
select:not([disabled]),
textarea:not([disabled]),
button:not([disabled]),
iframe,
[tabindex],
[contentEditable=true]
{
&:not([tabindex='-1'])
{
/* your SCSS for focusable elements goes here */
}
}
I've added it as an answer, because that was, what I was looking for, when Google redirected me to this Stackoverflow question.
EDIT: There is one more selector, which is focusable:
[contentEditable=true]
However, this is used very rarely.
$focusable:
'a[href]',
'area[href]',
'button',
'details',
'input',
'iframe',
'select',
'textarea',
// these are actually case sensitive but i'm not listing out all the possible variants
'[contentEditable=""]',
'[contentEditable="true"]',
'[contentEditable="TRUE"]',
'[tabindex]:not([tabindex^="-"])',
':not([disabled])';
I'm creating a SCSS list of all focusable elements and I thought this might help someone due to this question's Google rank.
A few things to note:
I changed :not([tabindex="-1"]) to :not([tabindex^="-"]) because it's perfectly plausible to generate -2 somehow. Better safe than sorry right?
Adding :not([tabindex^="-"]) to all the other focusable selectors is completely pointless. When using [tabindex]:not([tabindex^="-"]) it already includes all elements that you'd be negating with :not!
I included :not([disabled]) because disabled elements can never be focusable. So again it's useless to add it to every single element.
The ally.js accessibility library provides an unofficial, test-based list here:
https://allyjs.io/data-tables/focusable.html
(NB: Their page doesn't say how often tests were performed.)
Maybe this one can help:
function focus(el){
el.focus();
return el==document.activeElement;
}
return value: true = success, false = failed
Reff:
https://developer.mozilla.org/en-US/docs/Web/API/DocumentOrShadowRoot/activeElement
https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus
There is a much more elegant way to handle this:
Extend the element prototype like the sample below.
Then you can use it like:
element.isFocusable()
*Returns true if "element" is focusable and false if not.
/**
* Determining if an element can be focused on
* #return {Boolean}
*/
HTMLElement.prototype.isFocusable = function () {
var current = document.activeElement
if (current === this) return true
var protectEvent = (e) => e.stopImmediatePropagation()
this.addEventListener("focus", protectEvent, true)
this.addEventListener("blur", protectEvent, true)
this.focus({preventScroll:true})
var result = document.activeElement === this
this.blur()
if (current) current.focus({preventScroll:true})
this.removeEventListener("focus", protectEvent, true)
this.removeEventListener("blur", protectEvent, true)
return result
}
// A SIMPLE TEST
console.log(document.querySelector('a').isFocusable())
console.log(document.querySelector('a[href]').isFocusable())
<a>Not focusable</a>
Focusable