In the accepted answer to this question Multiple submit buttons in an HTML form the comment is raised:
Please don't do this without also changing the tab order, so that
hitting the tab button will cycle through the buttons as they appear
on screen.
My question is: is there a way to set the tabindex on those two buttons to accomplish this ordering without having to assign a specific tabindex to every other tabable element on the page?
My understanding of tabindex values is that specified positive values proceed elements w/o a specified value, so I am at a loss to figure out a way to do this w/o going through and assigning everything else a value.
If indeed, assigning a specific tabindex to every item is the only way, is there some (hopefully short and hopefully jQuery) magic to assign every appropriate element on the page a tabindex of, say, 1?
EDIT
As it looks like the solution is going to involve applying a specific tabindex to every other tabable object -- it seems like an important part of the solution is going to be: is there a convenient way in jQuery to select every tabable object? All s s s and ???
According to the specification:
positiv values assigned to tabindex orders the elements according to their tabindex values
negative values make elements "unfocusable"
a value of 0 makes the element focusable but its order dependents on the platform
mdn-html specification of tabindex
So if you want to have a specific order in your page you have to assign a value to each element.
But here comes jquery:
Say the elements which should be in order are in a div with id="myDiv"
You can then do:
$("#myDiv").find("*").prop("tabindex", 1);
This would make every child/subchild element of myDiv have a tabindex of 1.
Then your two buttons could have a css class assigned (e.g: class="highTabIndex").
Then you can call jquery again:
var idx = 2;
$("#myDiv").find(".highTabIndex").each(function(idx, element) {
element.prop("tabindex", idx++);
});
and your buttons with class highTabIndexwould be orderd according to "position" in the page.
Using Adding tabindex dynamically and fixing button indecies:
$(":input:not(:hidden)")
.each(function (i) {
$(this).attr('tabindex', i + 1);
});
var r = $('input.r').attr('tabindex');
$('input.r').attr('tabindex', $('input.l').attr('tabindex'));
$('input.l').attr('tabindex', r);
html:
<input type="submit" value="Next" class="r" />
<input type="submit" value="Previous" class="l" />
Plunk
Update - fixed query to select not only inputs (check link in John's comment below):
$("a[href],area[href],input:not([disabled]),select:not([disabled]),\
textarea:not([disabled]),button:not([disabled]),iframe,[tabindex],\
[contentEditable=true]")
Related
How do I check a checkbox element according to the value in cypress, while the value attribute does not exist.
I want to check the checkbox according to the value, not the id, because the id is different for each page.
Because in some pages, the id starts with c1, and some page ids start with c2, and some start with c5.
How would I check the element according to value?
I got the selector for all 3 checkboxes, but it checked all the 3 elements. I want specific element according to value attribute, but there there is no value attribute.
i tried this
cy.get('.x-overlay__wrapper--right input[type="checkbox"]').check()
but it checked all the element
and this checked the first element
cy.get('.x-overlay__wrapper--right input[type="checkbox"]').first().check()
but I want the checked according to value, but here in attributes there is no value attribute.
the value that i want is just written next to checkbox
and here in html
Looking at the html, perhaps search for the text then use parent and sibling commands to shift the subject to the checkbox, something like
cy.contains('span', 'Free Shipping') // find your text
.parent('div') // move to parent div
.siblings('span.checkbox') // move to checkbox span
.find('input') // select it's input
.check();
Example with comments explaining the code:
cy.get('.x-overlay__wrapper--right input[type="checkbox"]').each((checkbox, i) => { // Get the elements and run .each() on them
if (cy.get('.cbx.x-refine__multi-select-cbx')[i].innerHTML === "value") { // If the value is something, perform an action. Replace "value" with the value that you need to test it for
checkbox.check()
}
})
I currently have made a way so the user can add another text field to the form by pressing on a 'add_another' div, this uses basic JS so when the user presses on the div 'add_another' the div 'author_2' is toggled.
I would like to make it so that when the user presses on the 'add_another' div for a second time it shows 'author_3' div, and when they press 'add_another' again, it then shows 'author_4'. I have put all the CSS and HTML divs in place to support this, I am just trying to adapt my code so it shows one div after another, rather then toggling a single div.
Here is my JS:
<script>
$(document).ready(function() {
$('.add_another').on('click', function(){
$('.author_2').toggle();
});
});
</script>
I have tried altering this code, however with no luck.
I haven't added my HTML as it is just 4 divs, 'author_1' 'author_2' ... 3...4
Thankyou for your help
There are two solutions to Your problem.
First one - use static code
It means the max author count is 4 and if user gets to 4, this is it.
If so - You need to store the number of authors already shown.
var authors_shown = 1;
$(document).ready(function() {
$('.add_another').on('click', function(){
authors_shown++;
if (!$('.author_'+authors_shown).is(":visible")) {
$('.author_'+authors_shown).toggle();
}
});
});
But there is also a second - more dynamic option.
What if user wants to input 10 or 20 authors? You don't want to pre render all that html code and hide it. You should clone the div and change its id or if the (HTML) code (for another author) is not too long, you can render it within JS code.
var div = document.getElementById('div_id'),
clone = div.cloneNode(true); // true means clone all childNodes and all event handlers
clone.id = "some_id";
document.body.appendChild(clone);
If it's a form, then change names of input fields to array as author_firstname[]
Also You can store number of added authors in another hidden field (so you know how long to loop the form fields on the server side.
The second option is a bit more complex and longer, but way more dynamic.
You should make another div when clicked on add_another:
something like this:
<script>
$(document).ready(function() {
$('.add_another').on('click', function(){
$('<div><input type="text" name="name[]" /></div>').appendTo('.your_container');
});
});
</script>
as you see, input's name has [] which means you should treat with the inputs as an array.
let me know if you got any further questions
good luck.
The below implementation is for a set of three radio buttons, with the buttons having display :none in their CSS paired with labels.
Now I've been trying to add some accessibility options to the page so that you can use the arrow keys to navigate across the radio buttons, changing the focus of the labels on each keypress. But I can't, for the life of me get it to change focus, the focus remains on the initial label that was tabbed to.
I've tried a number of options that I've seen here in regards to timeout and enabling tabIndex as I go (want it to act as a group), none have worked.
JS:
$(".radio-type label").keydown(function (e) {
// On Right Key press
if (e.which == 39) {
//Get the list of all buttons and labels
var parentList = $(".radio-type").children();
//Get index of currently focussed button
var indexThis = $(this).index();
//If not at the end of the list
if (indexThis < parentList.length) {
//Get the next in the list and its label
var next = parentList.get(indexThis + 1);
var nextLabel = parentList.get(indexThis + 2);
$(next).change(); //Custom method that changes the selection
//Remove the tab index from old and assign to nextLabel
$(this).removeAttr("tabIndex");
$(nextLabel).attr("tabIndex", "0");
//Change the focus to the new selected label
$(nextLabel).focus();
}
}
e.preventDefault();
});
EDIT: HTML Markup (names modified)
<div class="small-12 medium-12 large-4 columns radio-type #(!Model.ExtendedFilteringEnabled ? "medium-push-4" : "")">
<input type="radio" name="tt" id="ttAll" value="#Model.CurrentBlock.TTAll" checked="checked" />
<label for="ttAll" tabindex="0">All</label>
<input type="radio" name="tt" id="tt1" value="#Model.CurrentBlock.TT1" />
<label for="tt1">1</label>
<input type="radio" name="tt" id="tt2" value="#Model.CurrentBlock.TT2" />
<label for="tt2">2</label>
</div>
Updated Cory's jfiddle: http://jsfiddle.net/n99o3upp/10/ (Sorry keep buggering it up...)
I have updated your fiddle with a few things:
Changed markup for easier DOM traversal. Labels now contain their inputs.
Changed events to fire off inputs rather than labels
Changed your set method to use prop instead of attr as this will set the actual value in the DOM
Made the comparison on the if statement look for an index smaller that the length -1 as index is 0 based and length is 1 based.
Made the CSS hide the input by positioning rather than display. I commented this out in the fiddle so that you can see the input change
Added e.preventDefault() to stop the default browser action interfering with your js
The new fiddle is here: http://jsfiddle.net/n99o3upp/16/
However, I am not sure any of this code is necessary, as without it the browser will do the same thing anyway. The right and left key will automatically move the focus to the next input in the group when the DOM is marked up like this.
I think you could remove the keydown event function and just leave the change event function and this would do the same thing that you want. You can test it by moving right through the inputs, and then moving back to the left. Both work, but moving right is controlled by your script and moving left is automatic.
I've come up with a rough solution that doesn't fix all cases.
I've dropped the requirement of having it navigable by the arrow keys. All the labels have tabindex="0" on them so they can be tabbed through and activated using space or enter. If they want to move back the default Shift+Tab can do that.
Unfortunately, this solution will not work on Chrome as the browser's behaviour won't let you focus on the label if the control is hidden.
Running into a bit of a brick wall with jquery, I currently have the following code:
http://jsfiddle.net/uSLhb/1/
The clone method being:
$("#addMore").click(function() {
$('.user_restriction:last').clone().insertAfter(".user_restriction:last");
return false;
});
As you can see I have it set up so you can easily clone the row.
The problem I am facing is showing the correct select list (In the 'Field' column) in the new cloned elements, depending on what field they select from the first select (In the 'table' column) field in the row.
Wondering if anyone can help me find a solution, thanks!
The problem you are facing occurs, because you are using IDs for your selects.
IDs must be unique in a page, otherwise you'll run into trouble. If you're cloning a select with an id, the id will be cloned too, thus producing an invalid document.
Have a look at the example I created on JsBin
Markup:
<tr class="user_restriction">
<td>
<select name="table[]" class="userselect">
<option value="" selected>---</option>
<option value="members">Members</option>
<option value="staff_positions">Staff Positions</option>
</select>
</td>
<!-- and so on, basically just changed ids to classes -->
</tr>
I changed all the IDs to classes and altered the change-handler, because this was your second problem.
An event-handler gets bound to the elements that are selected, not to those you add afterwards. This is a proper use-case for event-delegation, when the handler is bound to a parent and catches, in this example, the change event from a child select-element, no matter when it was added to the DOM.
Using jQuery, this is a way to achieve this:
$('table.table').on('change', '.userselect', function() {
var activeRow = $(this).parents('tr');
activeRow.find('td').eq(1).find('select').hide();
if(this.value.length === 0){
activeRow.find('td').eq(1).find('select').eq(0).show();
}else{
activeRow.find("." + this.value).show();
}
});
The handler is bound to your table - element, .userselect is a class I added to the first select in a row. So every change on an element that was added later would be handled too. In the handler, I changed the behaviour to affect only the actual table-row, not the whole table.
Working example here - I had to use jsbin as fiddle was playing up!
Clone does not maintain the selected state, so you'll need to grab the value before cloning and set it after.
Your click now becomes:
$("#addMore").click(function() {
var value = $('.user_restriction:last').find('select').val();
$('.user_restriction:last').clone().insertAfter(".user_restriction:last");
//alert(value);
$('.user_restriction:last').find('select').val(value);
return false;
});
I using an HTML / Javascript combination to add fields dynamically.
Here is the jsfiddle for it: http://jsfiddle.net/kM9Yg/2/
My problem is, if I input values in a field, and then click on the Add More button, a new field gets added but the previous fields' values get reset.
The button to add more is of type <input type="button"/> and not <input type="reset" />
Any way to prevent this?
Use DOM methods, not innerHTML. DOM methods are standardised, innerHTML is not. Some browsers will reflect the current value as the default value, others will not.
You can do:
var el, oEl = document.getElementById('divToClone');
if (oEl) {
el = oEl.cloneNode(true);
// code here to fix duplicate ids and
// set style.display = '' so it's visible
oEl.parentNode.appendChild(el);
}