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
Related
I'm using the Tab Modifier plugin for Chrome to dynamically rename some tabs that I use daily. In the tab Title definition, it says the following:
You can inject any DOM content with {selector}. Examples: {title} for website title, {h1}, {#id}, {.class}, etc.
Here is an example of the element I want to use to name the tab:
<td class="portalTitleInfoVal">
PORTALNAME
<a class="portalLink">Change Portal</a>
</td>
This is what I'm currently using for the title:
{.portalTitleInfoVal:nth-of-type(4)}
But, of course, the tab is named PORTALNAMEChange Portal.
How can I modify the DOM selector so that the tab is just named "PORTALNAME"?
I know I'm really late to the party, but I found this post while searching for an answer.
I'm working with a lot of old systems and all the tabs just says {title}, which is.. not useful when having 15-20 tabs open at once, and it's tedious to hard code every tab.
So.. I brute forced tested until I found a solution:
Every page has a breadcrumb:
<div class="breadcrumb noPrint">
Home "»"
Materials
123123
</div>
So they might have updated the extension since, but your guess was very close. I don't know why you were putting in 4, but I assume you had more elements than posted.
Anyhow, the way I got it to work were by:
{.breadcrumb :nth-last-child(2)} : {.breadcrumb :last-child}
So, there has to be a space between the .class and the child element, which in my case returns Materials : 12312
I haven't tried nearly half, but DoFactorys list of CSS selectors were a big help for me.
The element's first child node is the plain text, before the HTML element (<a>).
$('. portalTitleInfoVal')[0].childNodes[0].nodeValue
It looks like this plugin will only allow for CSS style element selectors compatible with querySelector. It then grabs the text from that element. From their github repo:
/**
* Returns the text related to the given CSS selector
* #param selector
* #returns {string}
*/
getTextBySelector = function (selector) {
var el = document.querySelector(selector), value = '';
if (el !== null) {
value = el.innerText || el.textContent;
}
return value.trim();
};
If I have an HTML element <input type="submit" value="Search" /> a css selector needs to be case-sensitive:
input[value='Search'] matches
input[value='search'] does not match
I need a solution where the case-insensitive approach works too. I am using Selenium 2 and Jquery, so answers for both are welcome.
CSS4 (CSS Selector Level 4) adds support for it:
input[value='search' i]
It's the "i" at the end which does the trick.
Broader adoption started mid-2016: Chrome (since v49), Firefox (from v47?), Opera and some others have it. IE not and Edge since it uses Blink. See “Can I use”...
It now exists in CSS4, see this answer.
Otherwise, for jQuery, you can use...
$(':input[name]').filter(function() {
return this.value.toLowerCase() == 'search';
});
jsFiddle.
You could also make a custom selector...
$.expr[':'].valueCaseInsensitive = function(node, stackIndex, properties){
return node.value.toLowerCase() == properties[3];
};
var searchInputs = $(':input:valueCaseInsensitive("Search")');
jsFiddle.
The custom selector is a bit of overkill if doing this once, but if you need to use it many times in your application, it may be a good idea.
Update
Is it possible to have that kind of custom selector for any attribute?
Sure, check out the following example. It's a little convoluted (syntax such as :input[value:toLowerCase="search"] may have been more intuitive), but it works :)
$.expr[':'].attrCaseInsensitive = function(node, stackIndex, properties){
var args = properties[3].split(',').map(function(arg) {
return arg.replace(/^\s*["']|["']\s*$/g, '');
});
return $(node).attr(args[0]).toLowerCase() == args[1];
};
var searchInputs = $('input:attrCaseInsensitive(value, "search")');
jsFiddle.
You could probably use eval() to make that string an array, but I find doing it this way more comfortable (and you won't accidentally execute any code you place in your selector).
Instead, I am splitting the string on , delimiter, and then stripping whitespace, ' and " either side of each array member. Note that a , inside a quote won't be treated literally. There is no reason one should be required literally, but you could always code against this possibility. I'll leave that up to you. :)
I don't think map() has the best browser support, so you can explictly iterate over the args array or augment the Array object.
input[value='Search'] matches
input[value='search' i] Also matches in latest browsers
Support:
version : Chrome >= 49.0, Firefox (Gecko) >= 47.0, Safari >= 9
You can't do it with selectors alone, try:
$('input').filter(function() {
return $(this).attr('value').toLowerCase() == 'search';
});
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.
If I create an HTML anchor tag and set the disabled attribute to true, I get different behaviors in different browsers (surprise! surprise!).
I created a fiddle to demonstrate.
In IE9, the link is grayed out and does not transfer to the HREF location.
In Chrome/FF/Safari, the link is the normal color and will transfer to the HREF location.
What should the correct behavior be? Is IE9 rendering this incorrectly and I should implement some CSS and javascript to fix it; or is Chrome/FF/Safari not correct and will eventually catch up?
Thanks in advance.
IE appears to be acting incorrectly in this instance.
See the HTML5 spec
The IDL attribute disabled only applies to style sheet links. When the
link element defines a style sheet link, then the disabled attribute
behaves as defined for the alternative style sheets DOM. For all other
link elements it always return false and does nothing on setting.
http://dev.w3.org/html5/spec/Overview.html#the-link-element
The HTML4 spec doesn't even mention disabled
http://www.w3.org/TR/html401/struct/links.html#h-12.2
EDIT
I think the only way to get this effect cross-browser is js/css as follows:
#link{
text-decoration:none;
color: #ccc;
}
js
$('#link').click(function(e){
e.preventDefault();
});
Example: http://jsfiddle.net/jasongennaro/QGWcn/
I had to fix this behavior in a site with a lot of anchors that were being enabled/disabled with this attribute according to other conditions, etc. Maybe not ideal, but in a situation like that, if you prefer not to fix each anchor's code individually, this will do the trick for all the anchors:
$('a').each(function () {
$(this).click(function (e) {
if ($(this).attr('disabled')) {
e.preventDefault();
e.stopImmediatePropagation();
}
});
var events = $._data ? $._data(this, 'events') : $(this).data('events');
events.click.splice(0, 0, events.click.pop());
});
And:
a[disabled] {
color: gray;
text-decoration: none;
}
disabled is an attribute that only applies to input elements per the standards. IE may support it on a, but you'll want to use CSS/JS instead if you want to be standards compliant.
The JQuery answer didn't work for me because my anchor tag is on a form and on my forms I use asp field validators and they just weren't playing nice. This led me to finding a pretty simple answer that doesn't require JQuery or CSS...
<a id="btnSubmit" href="GoSomePlace">Display Text</a>
You can disable the element and it should behave as input types do. No CSS needed. This worked for me in chrome and ff.
function DisableButton() {
var submitButton = document.getElementById("btnSubmit");
if (submitButton != null) {
submitButton.setAttribute('disabled', 'disabled');
}
}
Of course you'll be doing a loop to disable all anchor tags in the DOM but my example shows how to do it for just one specific element. You want to make sure you're getting the right client id of your element but this worked for me, on more than one occasion. This will also work on asp:LinkButtons which end up being anchor tag elements when rendered in the browser.
bangin' my head against this and it's starting to hurt.
I'm having trouble with adding an event to an element.
I'm able to add the event, and then call it immediately with element.fireEvent('click'), but once the element is attached to the DOM, it does not react to the click.
example code:
var el = new Element('strong').setStyle('cursor','pointer');
el.addEvent('click',function () { alert('hi!'); });
el.replaces(old_element); // you can assume old_element exists
el.fireEvent('click'); // alert fires
however, once I attach this to the DOM, the element is not reactive to the click. styles stick (cursor is pointer when I mouseover), but no event fires. tried mouseover as well, to no avail.
any clues here? am I missing something basic? I am doing this all over the place, but in this one instance it doesn't work.
EDIT----------------
ok here's some more code. unfortunately I can't expose the real code, as it's for a project that is still under tight wraps.
basically, the nodes all get picked up as "replaceable", then the json found in the rel="" attribute sets the stage for what it should be replaced by. In this particular instance, the replaced element is a user name that should pop up some info when clicked.
again, if I fire the event directly after attaching it, all is good, but the element does not react to the click once it's attached.
HTML-----------
<p>Example: <span class='_mootpl_' rel="{'text':'foo','tag':'strong','event':'click','action':'MyAction','params':{'var1': 'val1','var2': 'val2'}}"></span></p>
JAVASCRIPT-----
assumptions:
1. below two functions are part of a larger class
2. ROOTELEMENT is set at initialize()
3. MyAction is defined before any parsing takes place (and is properly handled on the .fireEvent() test)
parseTemplate: function() {
this.ROOTELEMENT.getElements('span._mootpl_').each(function(el) {
var _c = JSON.decode(el.get('rel'));
var new_el = this.get_replace_element(_c); // sets up the base element
if (_c.hasOwnProperty('event')) {
new_el = this.attach_event(new_el, _c);
}
});
},
attach_event: function(el, _c) {
el.store(_c.event+'-action',_c.action);
el.store('params',_c.params);
el.addEvent(_c.event, function() {
eval(this.retrieve('click-action') + '(this);');
}).setStyle('cursor','pointer');
return el;
},
Works just fine. Test case: http://jsfiddle.net/2GX66/
debugging this is not easy when you lack content / DOM.
first - do you use event delegation or have event handlers on a parent / the parent element that do event.stop()?
if so, replace with event.preventDefault()
second thing to do. do not replace an element but put it somewhere else in the DOM - like document.body's first node and see if it works there.
if it does work elsewhere, see #1
though I realsie you said 'example code', you should write this as:
new Element('strong', {
styles: {
cursor: "pointer"
},
events: {
click: function(event) {
console.log("hi");
}
}
}).replaces(old_element);
no point in doing 3 separate statements and saving a reference if you are not going to reuse it. you really ought to show the ACTUAL code if you need advice, though. in this snippet you don't even set content text so the element won't show if it's inline. could it be a styling issue, what is the display on the element, inline? inline-block?
can you assign it a class that changes it on a :hover pseudo and see it do it? mind you, you say the cursor sticks which means you can mouseover it - hence css works. this also eliminates the possibility of having any element shims above it / transparent els that can prevent the event from bubbling.
finally. assign it an id in the making. assign the event to a parent element via:
parentEl.addEvent("click:relay(strong#idhere)", fn);
and see if it works that way (you need Element.delegate from mootools-more)
good luck, gotta love the weird problems - makes our job worth doing. it wouldn't be the worst thing to post a url or JSFIDDLE too...