Browse Source

fixup! fixup! [ADD] Backport functionality from 12.0 to web_keyboard_navigation module.

pull/1119/head
George Daramouskas 5 years ago
parent
commit
4f2021cdb7
No known key found for this signature in database GPG Key ID: 5B4EF742F8CD859C
  1. 6
      web_keyboard_navigation/README.rst
  2. 10
      web_keyboard_navigation/static/src/js/browser_detection.js
  3. 231
      web_keyboard_navigation/static/src/js/web_keyboard_navigation.js

6
web_keyboard_navigation/README.rst

@ -30,8 +30,8 @@ To use this module, you need to:
#. go to ...
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/162
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/162
163
164
186
@ -43,7 +43,7 @@ To use this module, you need to:
Known issues / Roadmap
======================
* ...
* None
Bug Tracker
===========

10
web_keyboard_navigation/static/src/js/browser_detection.js

@ -3,17 +3,13 @@ odoo.define('web.BrowserDetection', function (require) {
var Class = require('web.Class');
var BrowserDetection = Class.extend({
init: function () {
},
isOsMac: function () {
return navigator.platform.toLowerCase().indexOf('mac') !== -1;
},
isBrowserChrome: function () {
return $.browser.chrome && // depends on jquery 1.x, removed in jquery 2 and above
navigator.userAgent.toLocaleLowerCase().indexOf('edge') === -1; // as far as jquery is concerned, Edge is chrome
}
return $.browser.chrome &&
navigator.userAgent.toLocaleLowerCase().indexOf('edge') === -1;
},
});
return BrowserDetection;
});

231
web_keyboard_navigation/static/src/js/web_keyboard_navigation.js

@ -1,26 +1,28 @@
odoo.define('web_keyboard_navigation.KeyboardNavigationMixin', function (require) {
odoo.define('web_keyboard_navigation.KeyboardNavigationMixin', function (
require) {
"use strict";
var BrowserDetection = require('web.BrowserDetection');
/**
* list of the key that should not be used as accesskeys. Either because we want to reserve them for a specific behavior in Odoo or
* because they will not work in certain browser/OS
* List of the key that should not be used as accesskeys.
* Either because we want to reserve them for a specific behavior in Odoo
* or because they will not work in certain browser/OS
*/
var knownUnusableAccessKeys = [' ',
'A', // reserved for Odoo Edit
'C', // reserved for Odoo Create
'H', // reserved for Odoo Home
'J', // reserved for Odoo Discard
'K', // reserved for Odoo Kanban view
'L', // reserved for Odoo List view
'N', // reserved for Odoo pager Next
'P', // reserved for Odoo pager Previous
'S', // reserved for Odoo Save
'Q', // reserved for Odoo Search
'E', // chrome does not support 'E' access key --> go to address bar to search google
'F', // chrome does not support 'F' access key --> go to menu
'D', // chrome does not support 'D' access key --> go to address bar
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9' // reserved for Odoo menus
'A',
'C',
'H',
'J',
'K',
'L',
'N',
'P',
'S',
'Q',
'E',
'F',
'D',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
];
var KeyboardNavigationMixin = {
@ -35,38 +37,26 @@ odoo.define('web_keyboard_navigation.KeyboardNavigationMixin', function (require
this.BrowserDetection = new BrowserDetection();
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @private
*/
_addAccessKeyOverlays: function () {
var accesskeyElements = $(document).find('[accesskey]').filter(':visible');
var accesskeyElements = $(document).find('[accesskey]').filter(
':visible');
_.each(accesskeyElements, function (elem) {
var overlay = $(_.str.sprintf("<div class='o_web_accesskey_overlay'>%s</div>", $(elem).attr('accesskey').toUpperCase()));
var overlay = $(_.str.sprintf(
"<div class='o_web_accesskey_overlay'>%s</div>",
$(elem).attr('accesskey').toUpperCase()));
var $overlayParent;
if (elem.tagName.toUpperCase() === "INPUT") {
// special case for the search input that has an access key
// defined. We cannot set the overlay on the input itself,
// only on its parent.
$overlayParent = $(elem).parent();
} else {
$overlayParent = $(elem);
}
if ($overlayParent.css('position') !== 'absolute') {
$overlayParent.css('position', 'relative');
}
overlay.appendTo($overlayParent);
});
},
/**
* @private
* @return {jQuery[]}
*/
_getAllUsedAccessKeys: function () {
var usedAccessKeys = knownUnusableAccessKeys.slice();
this.$el.find('[accesskey]').each(function (_, elem) {
@ -74,13 +64,7 @@ odoo.define('web_keyboard_navigation.KeyboardNavigationMixin', function (require
});
return usedAccessKeys;
},
/**
* hides the overlay that shows the access keys.
*
* @private
* @param $parent {jQueryElemen} the parent of the DOM element to which shorcuts overlay have been added
* @return {undefined|jQuery}
*/
_hideAccessKeyOverlay: function () {
this._areAccessKeyVisible = false;
var overlays = this.$el.find('.o_web_accesskey_overlay');
@ -88,140 +72,159 @@ odoo.define('web_keyboard_navigation.KeyboardNavigationMixin', function (require
return overlays.remove();
}
},
/**
* @private
*/
_setAccessKeyOnTopNavigation: function () {
this.$el.find('.o_menu_sections>li>a').each(function (number, item) {
this.$el.find(
'.o_menu_sections>li>a').each(function (number, item) {
item.accessKey = number + 1;
});
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* Assign access keys to all buttons inside $el and sets an overlay to show the access key
* The access keys will be assigned using first the name of the button, letter by letter until we find one available,
* after that we will assign any available letters.
* Not all letters should be used as access keys, some of the should be reserved for standard odoo behavior or browser behavior
*
* @private
* @param keyDownEvent {jQueryKeyboardEvent} the keyboard event triggered
* return {undefined|false}
*/
_onKeyDown: function (keyDownEvent) {
if ($('body.o_ui_blocked').length &&
(keyDownEvent.altKey || keyDownEvent.key === 'Alt') &&
!keyDownEvent.ctrlKey) {
if (keyDownEvent.preventDefault) keyDownEvent.preventDefault(); else keyDownEvent.returnValue = false;
if (keyDownEvent.stopPropagation) keyDownEvent.stopPropagation();
if (keyDownEvent.cancelBubble) keyDownEvent.cancelBubble = true;
if (keyDownEvent.preventDefault) {
keyDownEvent.preventDefault();
} else {
keyDownEvent.returnValue = false;
}
if (keyDownEvent.stopPropagation) {
keyDownEvent.stopPropagation();
}
if (keyDownEvent.cancelBubble) {
keyDownEvent.cancelBubble = true;
}
return false;
}
if (!this._areAccessKeyVisible &&
(keyDownEvent.altKey || keyDownEvent.key === 'Alt') &&
!keyDownEvent.ctrlKey) {
this._areAccessKeyVisible = true;
this._setAccessKeyOnTopNavigation();
var usedAccessKey = this._getAllUsedAccessKeys();
var buttonsWithoutAccessKey = this.$el.find('button.btn:visible')
var buttonsWithoutAccessKey = this.$el.find(
'button.btn:visible')
.not('[accesskey]')
.not('[disabled]')
.not('[tabindex="-1"]');
_.each(buttonsWithoutAccessKey, function (elem) {
var buttonString = [elem.innerText, elem.title, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"].join('');
for (var letterIndex = 0; letterIndex < buttonString.length; letterIndex++) {
var candidateAccessKey = buttonString[letterIndex].toUpperCase();
if (candidateAccessKey >= 'A' && candidateAccessKey <= 'Z' &&
!_.includes(usedAccessKey, candidateAccessKey)) {
var buttonString = [elem.innerText, elem.title,
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"].join('');
for (var letterIndex = 0;
letterIndex < buttonString.length;
letterIndex++) {
var candidateAccessKey = buttonString[
letterIndex].toUpperCase();
if (candidateAccessKey >= 'A' &&
candidateAccessKey <= 'Z' &&
!_.includes(usedAccessKey,
candidateAccessKey)) {
elem.accessKey = candidateAccessKey;
usedAccessKey.push(candidateAccessKey);
break;
}
}
});
var elementsWithoutAriaKeyshortcut = this.$el.find('[accesskey]').not('[aria-keyshortcuts]');
var elementsWithoutAriaKeyshortcut = this.$el.find(
'[accesskey]').not('[aria-keyshortcuts]');
_.each(elementsWithoutAriaKeyshortcut, function (elem) {
elem.setAttribute('aria-keyshortcuts', 'Alt+Shift+' + elem.accessKey);
elem.setAttribute(
'aria-keyshortcuts', 'Alt+Shift+' + elem.accessKey);
});
this._addAccessKeyOverlays();
}
// on mac, there are a number of keys that are only accessible though the usage of
// the ALT key (like the @ sign in most keyboards)
// for them we do not facilitate the access keys, so they will need to be activated classically
// though Control + Alt + key (case sensitive), see https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/accesskey
if (this.BrowserDetection.isOsMac())
return;
if (keyDownEvent.altKey && !keyDownEvent.ctrlKey && keyDownEvent.key.length === 1) { // we don't want to catch the Alt key down, only the characters A to Z and number keys
if (keyDownEvent.altKey &&
!keyDownEvent.ctrlKey &&
keyDownEvent.key.length === 1) {
var elementWithAccessKey = [];
if (keyDownEvent.keyCode >= 65 && keyDownEvent.keyCode <= 90 || keyDownEvent.keyCode >= 97 && keyDownEvent.keyCode <= 122) {
// 65 = A, 90 = Z, 97 = a, 122 = z
elementWithAccessKey = document.querySelectorAll('[accesskey="' + String.fromCharCode(keyDownEvent.keyCode).toLowerCase() +
'"], [accesskey="' + String.fromCharCode(keyDownEvent.keyCode).toUpperCase() + '"]');
if (keyDownEvent.keyCode >= 65 &&
keyDownEvent.keyCode <= 90 ||
keyDownEvent.keyCode >= 97 &&
keyDownEvent.keyCode <= 122) {
elementWithAccessKey = document.querySelectorAll(
'[accesskey="' + String.fromCharCode(
keyDownEvent.keyCode).toLowerCase() +
'"], [accesskey="' + String.fromCharCode(
keyDownEvent.keyCode).toUpperCase() + '"]');
if (elementWithAccessKey.length) {
if (this.BrowserDetection.isOsMac() ||
!this.BrowserDetection.isBrowserChrome()) { // on windows and linux, chrome does not prevent the default of the accesskeys
!this.BrowserDetection.isBrowserChrome()) {
elementWithAccessKey[0].focus();
elementWithAccessKey[0].click();
if (keyDownEvent.preventDefault) keyDownEvent.preventDefault(); else keyDownEvent.returnValue = false;
if (keyDownEvent.stopPropagation) keyDownEvent.stopPropagation();
if (keyDownEvent.cancelBubble) keyDownEvent.cancelBubble = true;
if (keyDownEvent.preventDefault) {
keyDownEvent.preventDefault();
} else {
keyDownEvent.returnValue = false;
}
if (keyDownEvent.stopPropagation) {
keyDownEvent.stopPropagation();
}
if (keyDownEvent.cancelBubble) {
keyDownEvent.cancelBubble = true;
}
return false;
}
}
}
else {
// identify if the user has tapped on the number keys above the text keys.
// this is not trivial because alt is a modifier and will not input the actual number in most keyboard layouts
var numberKey;
if (keyDownEvent.originalEvent.code && keyDownEvent.originalEvent.code.indexOf('Digit') === 0) {
//chrome & FF have the key Digit set correctly for the numbers
numberKey = keyDownEvent.originalEvent.code[keyDownEvent.originalEvent.code.length - 1];
if (keyDownEvent.originalEvent.code &&
keyDownEvent.originalEvent.code.indexOf(
'Digit') === 0) {
numberKey = keyDownEvent.originalEvent.code[
keyDownEvent.originalEvent.code.length - 1];
} else if (keyDownEvent.originalEvent.key &&
keyDownEvent.originalEvent.key.length === 1 &&
keyDownEvent.originalEvent.key >= '0' &&
keyDownEvent.originalEvent.key <= '9') {
//edge does not use 'code' on the original event, but the 'key' is set correctly
numberKey = keyDownEvent.originalEvent.key;
} else if (keyDownEvent.keyCode >= 48 && keyDownEvent.keyCode <= 57) {
//fallback on keyCode if both code and key are either not set or not digits
} else if (
keyDownEvent.keyCode >= 48 &&
keyDownEvent.keyCode <= 57) {
numberKey = keyDownEvent.keyCode - 48;
}
if (numberKey >= '0' && numberKey <= '9') {
elementWithAccessKey = document.querySelectorAll('[accesskey="' + numberKey + '"]');
elementWithAccessKey = document.querySelectorAll(
'[accesskey="' + numberKey + '"]');
if (elementWithAccessKey.length) {
elementWithAccessKey[0].click();
if (keyDownEvent.preventDefault) keyDownEvent.preventDefault(); else keyDownEvent.returnValue = false;
if (keyDownEvent.stopPropagation) keyDownEvent.stopPropagation();
if (keyDownEvent.cancelBubble) keyDownEvent.cancelBubble = true;
if (keyDownEvent.preventDefault) {
keyDownEvent.preventDefault();
} else {
keyDownEvent.returnValue = false;
}
if (keyDownEvent.stopPropagation) {
keyDownEvent.stopPropagation();
}
if (keyDownEvent.cancelBubble) {
keyDownEvent.cancelBubble = true;
}
return false;
}
}
}
}
},
/**
* hides the shortcut overlays when keyup event is triggered on the ALT key
*
* @private
* @param keyUpEvent {jQueryKeyboardEvent} the keyboard event triggered
* @return {undefined|false}
*/
_onKeyUp: function (keyUpEvent) {
if ((keyUpEvent.altKey || keyUpEvent.key === 'Alt') && !keyUpEvent.ctrlKey) {
if ((keyUpEvent.altKey || keyUpEvent.key === 'Alt') &&
!keyUpEvent.ctrlKey) {
this._hideAccessKeyOverlay();
if (keyUpEvent.preventDefault) keyUpEvent.preventDefault(); else keyUpEvent.returnValue = false;
if (keyUpEvent.stopPropagation) keyUpEvent.stopPropagation();
if (keyUpEvent.cancelBubble) keyUpEvent.cancelBubble = true;
if (keyUpEvent.preventDefault) {
keyUpEvent.preventDefault();
}
else {
keyUpEvent.returnValue = false;
}
if (keyUpEvent.stopPropagation) {
keyUpEvent.stopPropagation();
}
if (keyUpEvent.cancelBubble) {
keyUpEvent.cancelBubble = true;
}
return false;
}
},

Loading…
Cancel
Save