Preventing tab to cycle through address bar - browser

I realize this is probably an accessibility issue that may best be left alone, but I'd like to figure out if it possible to prevent the tab from visiting the address bar in the tabbing cycle.
My application has another method of cycling through input areas, but many new users instinctively try to use the tab, and it doesn't work as expected.

Here's a generic jquery implementation where you don't have to find the max tab index. Note that this code will also work if you add or remove elements in your DOM.
$('body').on('keydown', function (e) {
var jqTarget = $(e.target);
if (e.keyCode == 9) {
var jqVisibleInputs = $(':input:visible');
var jqFirst = jqVisibleInputs.first();
var jqLast = jqVisibleInputs.last();
if (!e.shiftKey && jqTarget.is(jqLast)) {
e.preventDefault();
jqFirst.focus();
} else if (e.shiftKey && jqTarget.is(jqFirst)) {
e.preventDefault();
jqLast.focus();
}
}
});
However, you should note that the code above will work only with visible inputs. Some elements may become the document's activeElement even if they're not input so if it's your case, you should consider adding them to the $(':input:visible') selector.
I didn't add code to scroll to the focus element as this may not be the wanted behavior for everyone... if you need it, just add it after the call to focus()

You can control the tabbing order (and which elements should be able to get focus at all) with the global tabindex attribute.
However, you can't prevent users to tab into another context not under control of the page (e.g. the browser's address bar) with this attribute. (It might be possible in combination with JavaScript, though.)
For such a (evil!) use case, you'd have to look into keyboard traps.
WCAG 2.0 has the guideline: 2.1.2 No Keyboard Trap. In Understanding SC 2.1.2 you can find "Techniques and Failures" for this guideline:
F10: Failure of Success Criterion 2.1.2 and Conformance Requirement 5 due to combining multiple content formats in a way that traps users inside one format type
FLASH17: Providing keyboard access to a Flash object and avoiding a keyboard trap
G21: Ensuring that users are not trapped in content
So maybe you get some ideas by that how such a trap would be possible.

I used to add two tiny, invisible elements on tabindex 1 and on the last tabindex. Add a onFocus for these two: The element with tabindex 1 should focus the last real element, the one with the max tabindex should focus the first real element. Make sure that you focus the first real element on Dom:loaded.

You could use Javascript and capture the "keydown" event on the element with the highest "tabindex". If the user presses the "TAB" key (event.keyCode==9) without the "Shift" key (event.shiftKey == false) then simply set the focus on the element with the lowest tabindex.
You would then also need to do the same thing in reverse for the element with the lowest tabindex. Capture the "keydown" event for this element. If the user presses the "TAB" key (event.keyCode==9) WITH the "Shift" key (event.shiftKey == true) then set the focus on the element with the highest tabindex.
This would effectively prevent the address bar from ever being focused using the TAB key. I am using this technique in my current project.
Dont forget to cancel the keydown event if the proper key-combination is pressed! With JQuery it's "event.preventDefault()". In standard Javascript, I believe you simply "return false".
Here's a JQuery-laden snippet I'm using...
$('#dos-area [tabindex=' + maxTabIndex + ']').on('keydown', function (e) {
if (e.keyCode == 9 && e.shiftKey == false) {
e.preventDefault();
$('#dos-area [tabindex=1]').focus();
}
});
$('#dos-area [tabindex=1]').on('keydown', function (e) {
if (e.keyCode == 9 && e.shiftKey == true) {
e.preventDefault();
$('#dos-area [tabindex=' + maxTabIndex + ']').focus();
}
});
Also keep in mind that setting tabindex=0 has undesirable results on the order in which things are focused. I always remember (for my purposes) that tabindex is a 1-based index.

Hi i have an easy solution. just place an empty span on the end of the page. Give it an id and tabindex = 0, give this span an onfocus event, when triggered let your focus jump to the first element on your page you want to cycle trough. This way you won't lose focus on the document, because if you do your events don't work anymore.

I used m-albert solution and it works. But in my case I do not control the tabindex properties. My intention is set the focus on a toolbar at the top of the page (first control) when user leaves the last control on the page.
$(':input:visible').last().on('keydown', function (e) {
if (e.keyCode == 9 && e.shiftKey == false) {
e.preventDefault();
$('html, body').animate({
scrollTop: 0
}, 500);
$(':input:visible', context).first().focus();
}
});
Where context can be any Jquery object, selector, or even document, or you can omit it.
The scrolling animation, of course, is optional.

Not sure if this is still a issue, but I implemented my own solution that does not use jQuery, can be used in the case when all the elements have tabindex="0", and when the DOM is subject to change. I added an extra argument for a context if you want to limit the tabcycling to a specific element containing the tabindex elements.
Some brief notes on the arguments:
min must be less than or equal to max, and contextSelector is an optional string that if used should be a valid selector. If contextSelector is an invalid selector or a selector that doesn't match with any elements, then the document object is used as the context.
function PreventAddressBarTabCyle(min, max, contextSelector) {
if( isNaN(min) ) throw new Error('Invalid argument: first argument needs to be a number type.')
if( isNaN(max) ) throw new Error('Invalid argument: second argument needs to be a number type.')
if( max < min ) throw new Error('Invalid arguments: first argument needs to be less than or equal to the second argument.')
min = min |0;
max = max |0;
var isDocumentContext = typeof(contextSelector) != 'string' || contextSelector == '';
if( min == max ) {
var tabCycleListener = function(e) {
if( e.keyCode != 9 ) return;
var context = isDocumentContext ? document : document.querySelector(contextSelector);
if( !context && !('querySelectorAll' in context) ) {
context = document;
}
var tabindexElements = context.querySelectorAll('[tabindex]');
if( tabindexElements.length <= 0 ) return;
var targetIndex = -1;
for(var i = 0; i < tabindexElements.length; i++) {
if( e.target == tabindexElements[i] ) {
targetIndex = i;
break;
}
}
// Check if tabbing backward and reached first element
if( e.shiftKey == true && targetIndex == 0 ) {
e.preventDefault();
tabindexElements[tabindexElements.length-1].focus();
}
// Check if tabbing forward and reached last element
if( e.shiftKey == false && targetIndex == tabindexElements.length-1 ) {
e.preventDefault();
tabindexElements[0].focus();
}
};
} else {
var tabCycleListener = function(e) {
if( e.keyCode != 9 ) return;
var context = isDocumentContext ? document : document.querySelector(contextSelector);
if( !context && !('querySelectorAll' in context) ) {
context = document;
}
var tabindex = parseInt(e.target.getAttribute('tabindex'));
if( isNaN(tabindex) ) return;
// Check if tabbing backward and reached first element
if (e.shiftKey == true && tabindex == min) {
e.preventDefault();
context.querySelector('[tabindex="' + max + '"]').focus();
}
// Check if tabbing forward and reached last element
else if (e.shiftKey == false && tabindex == max) {
e.preventDefault();
context.querySelector('[tabindex="' + min + '"]').focus();
}
};
}
document.body.addEventListener('keydown', tabCycleListener, true);
}
More notes:
If min is equal to max, then tab cycling will occur naturally until the last element in the context is reached. If min is strictly less than max, then tabbing will cycle naturally until either the min or the max is reached. If tabbing backwards and the min is reached, or tabbing forward and the max is reached, then the tabbing will cycle to the min element or max element respectively.
Example usage:
// For all tabindex elements inside the document
PreventAddressBarTabCyle(0,0);
// Same as above
PreventAddressBarTabCyle(1,1);
// NOTE: if min == max, the tabindex value doesn't matter
// it matches all elements with the tabindex attribute
// For all tabindex elements between 1 and 15
PreventAddressBarTabCyle(1,15);
// For tabindex elements between 1 and 15 inside
// the first element that matches the selector: .some-form
PreventAddressBarTabCyle(1,15, '.some-form');
// For all tabindex elements inside the element
// that matches the selector: .some-form2
PreventAddressBarTabCyle(1,1, '.some-form2');

Salinan solution worked for me
Put this in the start of your html page:
<span tabindex="0" id="prevent-outside-tab"></span>
and this at the end of your html page.:
<span tabindex="0" onfocus="foucsFirstElement()"></span>
foucsFirstElement() :
foucsFirstElement() {
document.getElementById("prevent-outside-tab").focus();
},

Related

get the document url in chrome.webRequest.onBeforeRequest

In chrome.webRequest.onBeforeRequest we get all sorts of url's -- javascript, css etc.
For every url I want to know the main tab url.
What is the simplest way to get it synchronously?
Right now I'm using this way:
if details.frameId == 0 then details.url contains the main tab url for this tab id
chrome.webRequest.onBeforeRequest.addListener(
function (details) {
if (details.tabId == -1)
{
return;
}
if ("type" in details && ['main_frame', 'sub_frame'].indexOf(details.type) !== -1)
{
if (details.frameId == 0) {
all_tabs_info.add_tab_info(details.tabId, details.url);
}
}
},
{
urls: ['<all_urls>']
},
["blocking"]);
So now onwards if any request comes for this tab id we already have the tab url. This crude logic seems to be working.
A couple of enhancements:
use filters as much as possible, in this case for type.
main_frame type by definition corresponds to the top-level frame, which in turn means it has frameId of 0, so by not listening to sub_frame you can omit the check altogether.
The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (type is main_frame or sub_frame), frameId indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab.
type is not optional as you can see in the documentation, no need to doubt its presence.
So the simplified code is just this:
chrome.webRequest.onBeforeRequest.addListener(
function(details) {
if (details.tabId >= 0) {
all_tabs_info.add_tab_info(details.tabId, details.url);
}
},
{
urls: ['<all_urls>'],
types: ['main_frame'],
},
["blocking"]
);

How do I change the order of jPlayer playlist without affecting playback?

Setting a new playlist object causes my jPlayer to stop playing music. I have also tried to re-order the playlist array but it seems jPlayer keeps two arrays (current and original) and it doesn't seem to correct its play index. Meaning that if you change the array in JavaScript, the playlist will no longer behave correctly.
Is there a way to change the playlist's order of items without resetting the playlist object?
jPlayer uses a JavaScript object to keep track of your playlist. But it also represents it in the DOM. If you re-order it in the DOM (for example, using jQuery UI Sortable), it's not synchronized with that JavaScript object.
But, it doesn't have to, since you can use the DOM to tell jPlayer about the new situation. You will need to add some extra information about each playlist item to make this work, so this solution is a bit of a hack.
Change the next and previous methods of the myPlaylist object
jplayer.playlist.min.js
Change
next:function(){var a=this.current+1<this.playlist.length?this.current+1:0;this.loop?0===a&&this.shuffled&&this.options.playlistOptions.shuffleOnLoop&&this.playlist.length>1?this.shuffle(!0,!0):this.play(a):a>0&&this.play(a)}
Into
next:function(){$(this).trigger("playlist_next")}
Change
previous:function(){var a=this.current-1>=0?this.current-1:this.playlist.length-1;(this.loop&&this.options.playlistOptions.loopOnPrevious||a<this.playlist.length-1)&&this.play(a)}
Into
previous:function(){$(this).trigger("playlist_previous")}
Inject a script tag for each playlist item
Change
_createListItem:function(b){var c=this,d="<li><div>";
Into
_createListItem:function(b){var c=this,d='<li><script class="jp-json" type="application/json">'+JSON.stringify(b)+'</script><div>';
Track your DOM playlist changes
For example, with jQuery UI Sortable plugin:
var playlistChanged = false;
$('.jp-playlist ul').sortable({
update : function( e, ui ) {
playlistChanged = true;
}
});
Create handlers for playlist behavior
function getCurrentPlaylistIndex() {
var index = 0;
$( '.jp-playlist ul li' ).each( function(){
if( $( this ).hasClass( 'jp-playlist-current' ) ) {
return false;
}
++index;
});
return index;
}
function playNextJpItem() {
var index = getCurrentPlaylistIndex();
if( index == $( '.jp-playlist ul li' ).length ) {
return false; // end of playlist reached, use index = 0 to cycle to beginning
}
if( playlistChanged ) {
resetJpPlaylist();
}
myPlaylist.play( ++index );
}
function playPreviousJpItem() {
var index = getCurrentPlaylistIndex();
if( index == 0 ) {
return false; // beginning of playlist reached, use index = length to cycle to end
}
if( playlistChanged ) {
resetJpPlaylist();
}
myPlaylist.play( --index );
}
Bind the handlers to previous and next events
$( myPlaylist ).bind( 'playlist_next', function(e){
playNextJpItem();
});
$( myPlaylist ).bind( 'playlist_previous', function(e){
playPreviousJpItem();
});
If playlist DOM has changed, re-set myPlaylist's playlist array
Since this only occurs when the previous or next methods are called, the user expects the audio to stop playing anyways. This is your chance to rebuild the array to match your DOM again and instruct jPlayer to start playing from the previously known index, plus 1 or minus 1. Note: This function only rebuilds the array, the new index is to play is calculated in the previous and next handlers as shown earlier.
function resetJpPlaylist() {
var playlist = new Array();
$( '.jp-playlist ul li .jp-json' ).each( function( index, element ){
playlist[ index ] = JSON.parse( $( element ).html() );
});
myPlaylist.setPlaylist( playlist );
playlistChanged = false;
}
I've written a fork of jPlayer 2.9.2 that enables sortable playlists without it affecting playback functionality.
jQuery UI Sortable support for jPlayer Playlists add-on

How to specify different delays between slides in bxslider

Ran across the following problem in bxslider- how can you apply different delays between slides in the auto show?
I came up with the following solution which I will show here:
in the jquery.bxslider.js replace:
el.startAuto = function(preventControlUpdate){
// if an interval already exists, disregard call
if(slider.interval) return;
// create an interval
slider.interval = setInterval(function(){
slider.settings.autoDirection == 'next' ? el.goToNextSlide() : el.goToPrevSlide();
}, slider.settings.pause);
// if auto controls are displayed and preventControlUpdate is not true
if (slider.settings.autoControls && preventControlUpdate != true) updateAutoControls('stop');
}
With
/**EDITS: By CRB - techdude **/
el.startAuto = function(preventControlUpdate){
el.continueAuto();
}
el.continueAuto = function(){
//get how long the current slide should stay
var duration = slider.children.eq(parseInt(slider.active.index)).attr("duration");
if(duration == ""){
duration = slider.settings.pause;
} else {
duration = parseInt(duration);
}
console.log(duration);
// create a timeout
slider.timer = setTimeout(function(){
slider.settings.autoDirection == 'next' ? el.goToNextSlide() : el.goToPrevSlide();
el.continueAuto();
}, duration);
// if auto controls are displayed and preventControlUpdate is not true
if (slider.settings.autoControls && preventControlUpdate != true) updateAutoControls('stop');
}
//*End Edits*/
Then to change the duration of a slide, simply give its li tag a duration attribute like this:
where duration is the number of milliseconds for the slide to pause.
To set the default duration, simply use the pause: option in the settings:
$("element").bxSlider({
auto:true,
pause: 4000
};
Hope this helps. Maybe bx slider will even add it to a future version. :)
What are the you're using to pick this up? Any way you can put up a gist of it working?
Perhaps this will help clarify:
In principle, the way this works is I change the setInterval with a setTimeout so the interval can be changed each time.
The key to getting multiple elements to work on a page is to not use the slider.timer object, but probably to use the el.timer object so the line would read something like,
el.timer = setTimeout(function(){
slider.settings.autoDirection == 'next' ? el.goToNextSlide() : el.goToPrevSlide();
el.continueAuto();
}, duration);
Instead of
slider.timer = setTimeout(function(){
slider.settings.autoDirection == 'next' ? el.goToNextSlide() : el.goToPrevSlide();
el.continueAuto();
}, duration);
I haven't tested it with multiple sliders, but let me know if this works. That is the principle anyway. The only problem with this, however, is that I believe that you would need to modify the el.pause method to use the el.timer as well, otherwise the slideshow can't be paused. I think that was the reason I did it the way I did. However, it was a long time ago.

jqGrid filterToolbar search

In trying to implement a filterToolbar search in jquery, but when I write in the textbox it doesnt send the value, the search field nor the operator: I used an example, here is the code in html file
jQuery(document).ready(function () {
var grid = $("#list");
$("#list").jqGrid({
url:'grid.php',
datatype: 'xml',
mtype: 'GET',
deepempty:true ,
colNames:['Id','Buscar','Desccripcion'],
colModel:[
{name:'id',index:'id', width:65, sorttype: 'int', hidden:true, search:false},
{name:'examen',index:'nombre', width:500, align:'left', search:true},
{name:'descripcion',index:'descripcion', width:100, sortable:false, hidden:true, search:false}
],
pager: jQuery('#pager'),
rowNum:25,
sortname: 'nombre',
sortorder: 'asc',
viewrecords: true,
gridview: true,
height: 'auto',
caption: 'Examenes',
height: "100%",
loadComplete: function() {
var ids = grid.jqGrid('getDataIDs');
for (var i=0;i<ids.length;i++) {
var id=ids[i];
$("#"+id+ " td:eq(1)", grid[0]).tooltip({
content: function(response) {
var rowData = grid.jqGrid('getRowData',this.parentNode.id);
return rowData.descripcion;
},
open: function() {
$(this).tooltip("widget").stop(false, true).hide().slideDown("fast");
},
close: function() {
$(this).tooltip("widget").stop(false, true).show().slideUp("fast");
}
}).tooltip("widget").addClass("ui-state-highlight");
}
}
});
$("#list").jqGrid('navGrid','#pager',{edit:false,add:false,del:false});
$("#list").jqGrid('filterToolbar', {stringResult: true, searchOnEnter: false,
defaultSearch: 'cn', ignoreCase: true});
});
and in the php file
$ops = array(
'eq'=>'=', //equal
'ne'=>'<>',//not equal
'lt'=>'<', //less than
'le'=>'<=',//less than or equal
'gt'=>'>', //greater than
'ge'=>'>=',//greater than or equal
'bw'=>'LIKE', //begins with
'bn'=>'NOT LIKE', //doesn't begin with
'in'=>'LIKE', //is in
'ni'=>'NOT LIKE', //is not in
'ew'=>'LIKE', //ends with
'en'=>'NOT LIKE', //doesn't end with
'cn'=>'LIKE', // contains
'nc'=>'NOT LIKE' //doesn't contain
);
function getWhereClause($col, $oper, $val){
global $ops;
if($oper == 'bw' || $oper == 'bn') $val .= '%';
if($oper == 'ew' || $oper == 'en' ) $val = '%'.$val;
if($oper == 'cn' || $oper == 'nc' || $oper == 'in' || $oper == 'ni') $val = '%'.$val.'%';
return " WHERE $col {$ops[$oper]} '$val' ";
}
$where = ""; //if there is no search request sent by jqgrid, $where should be empty
$searchField = isset($_GET['searchField']) ? $_GET['searchField'] : false;
$searchOper = isset($_GET['searchOper']) ? $_GET['searchOper']: false;
$searchString = isset($_GET['searchString']) ? $_GET['searchString'] : false;
if ($_GET['_search'] == 'true') {
$where = getWhereClause($searchField,$searchOper,$searchString);
}
I saw the query, and $searchField,$searchOper,$searchString have no value
But when I use the button search on the navigation bar it works! I dont konw what is happening with the toolbarfilter
Thank You
You use the option stringResult: true of the toolbarfilter. In the case the full filter will be encoded in filters option (see here). Additionally there are no ignoreCase option of toolbarfilter method. There are ignoreCase option of jqGrid, but it works only in case of local searching.
So you have to change the server code to use filters parameter or to remove stringResult: true option. The removing of stringResult: true could be probably the best way in your case because you have only one searchable column. In the case you will get one additional parameter on the server side: examen. For example if the user would type physic in the only searching field the parameter examen=physic will be send without of any information about the searching operation. If you would need to implement filter searching in more as one column and if you would use different searching operation in different columns you will have to implement searching by filters parameter.
UPDATED: I wanted to include some general remarks to the code which you posted. You will have bad performance because of the usage
$("#"+id+ " td:eq(1)", grid[0])
The problem is that the web browser create internally index of elements by id. So the code $("#"+id+ " td:eq(1)") can use the id index and will work quickly. On the other side if you use grid[0] as the context of jQuery operation the web browser will be unable to use the index in the case and the finding of the corresponding <td> element will be much more slowly in case of large number rows.
To write the most effective code you should remind, that jQuery is the wrapper of the DOM which represent the page elements. jQuery is designed to support common DOM interface. On the other side there are many helpful specific DOM method for different HTML elements. For example DOM of the <table> element contain very helpful rows property which is supported by all (even very old) web browsers. In the same way DOM of <tr> contains property cells which you can use directly. You can find more information about the subject here. In your case the only thing which you need to know is that jqGrid create additional hidden row as the first row only to have fixed width of the grid columns. So you can either just start the enumeration of the rows from the index 1 (skipping the index 0) or verify whether the class of every row is jqgrow. If you don't use subgrids or grouping you can use the following simple code which is equivalent your original code
loadComplete: function() {
var i, rows = this.rows, l = rows.length;
for (i = 1; i < l; i++) { // we skip the first dummy hidden row
$(rows[i].cells(1)).tooltip({
content: function(response) {
var rowData = grid.jqGrid('getRowData',this.parentNode.id);
return rowData.descripcion;
},
open: function() {
$(this).tooltip("widget").stop(false, true).hide().slideDown("fast");
},
close: function() {
$(this).tooltip("widget").stop(false, true).show().slideUp("fast");
}
}).tooltip("widget").addClass("ui-state-highlight");
}
}

recognize multple lines on info.selectionText from Context Menu

My extension adds a context menu whenever a user selects some text on the page.
Then, using info.selectionText, I use the selected text on a function executed whenever the user selects one of the items from my context menu. (from http://code.google.com/chrome/extensions/contextMenus.html)
So far, all works ok.
Now, I got this cool request from one of the extension users, to execute that same function once per line of the selected text.
A user would select, for example, 3 lines of text, and my function would be called 3 times, once per line, with the corresponding line of text.
I haven't been able to split the info.selectionText so far, in order to recognize each line...
info.selectionText returns a single line of text, and could not find a way to split it.
Anyone knows if there's a way to do so? is there any "hidden" character to use for the split?
Thanks in advance... in case you're interested, here's the link to the extension
https://chrome.google.com/webstore/detail/aagminaekdpcfimcbhknlgjmpnnnmooo
Ok, as OnClickData's selectionText is only ever going to be text you'll never be able to do it using this approach.
What I would do then is inject a content script into each page and use something similar to the below example (as inspired by reading this SO post - get selected text's html in div)
You could still use the context menu OnClickData hook like you do now but when you receive it instead of reading selectionText you use the event notification to then trigger your context script to read the selection using x.Selector.getSelected() instead. That should give you what you want. The text stays selected in your extension after using the context menu so you should have no problem reading the selected text.
if (!window.x) {
x = {};
}
// https://stackoverflow.com/questions/5669448/get-selected-texts-html-in-div
x.Selector = {};
x.Selector.getSelected = function() {
var html = "";
if (typeof window.getSelection != "undefined") {
var sel = window.getSelection();
if (sel.rangeCount) {
var container = document.createElement("div");
for (var i = 0, len = sel.rangeCount; i < len; ++i) {
container.appendChild(sel.getRangeAt(i).cloneContents());
}
html = container.innerHTML;
}
} else if (typeof document.selection != "undefined") {
if (document.selection.type == "Text") {
html = document.selection.createRange().htmlText;
}
}
return html;
}
$(document).ready(function() {
$(document).bind("mouseup", function() {
var mytext = x.Selector.getSelected();
alert(mytext);
console.log(mytext);
});
});​
http://jsfiddle.net/richhollis/vfBGJ/4/
See also: Chrome Extension: how to capture selected text and send to a web service

Resources