From aa1849c13af7f7188c4ed0936eb22796b56a3998 Mon Sep 17 00:00:00 2001 From: Harshal Dhumal Date: Tue, 27 Feb 2018 11:18:36 +0000 Subject: [PATCH] Support tab navigation in dialogs. Fixes #2898 --- docs/en_US/keyboard_shortcuts.rst | 75 +++++---- web/pgadmin/browser/__init__.py | 30 ++++ web/pgadmin/browser/static/js/keyboard.js | 93 +++++++----- web/pgadmin/browser/static/js/node.js | 15 +- web/pgadmin/browser/static/js/wizard.js | 12 +- web/pgadmin/static/js/backform.pgadmin.js | 4 +- web/pgadmin/static/js/dialog_tab_navigator.js | 143 ++++++++++++++++++ web/pgadmin/static/js/utils.js | 38 +++++ web/pgadmin/tools/backup/static/js/backup.js | 15 +- .../grant_wizard/static/js/grant_wizard.js | 6 +- .../import_export/static/js/import_export.js | 11 +- .../maintenance/static/js/maintenance.js | 8 +- .../tools/restore/static/js/restore.js | 11 +- .../javascript/dialog_tab_navigator_spec.js | 115 ++++++++++++++ web/webpack.shim.js | 1 + 15 files changed, 485 insertions(+), 92 deletions(-) create mode 100644 web/pgadmin/static/js/dialog_tab_navigator.js create mode 100644 web/pgadmin/static/js/utils.js create mode 100644 web/regression/javascript/dialog_tab_navigator_spec.js diff --git a/docs/en_US/keyboard_shortcuts.rst b/docs/en_US/keyboard_shortcuts.rst index 114297502..af840addb 100644 --- a/docs/en_US/keyboard_shortcuts.rst +++ b/docs/en_US/keyboard_shortcuts.rst @@ -10,36 +10,51 @@ desired.˝ When using main browser window, the following keyboard shortcuts are available: -+---------------------------+--------------------------------------------------------+ -| Shortcut for all platform | Function | -+===========================+========================================================+ -| Alt+Shift+F | Open the File menu | -+---------------------------+--------------------------------------------------------+ -| Alt+Shift+O | Open the Object menu | -+---------------------------+--------------------------------------------------------+ -| Alt+Shift+L | Open the Tools menu | -+---------------------------+--------------------------------------------------------+ -| Alt+Shift+H | Open the Help menu | -+---------------------------+--------------------------------------------------------+ -| Alt+Shift+B | Focus the browser tree | -+---------------------------+--------------------------------------------------------+ -| Alt+Shift+[ | Move tabbed panel backward/forward | -| Alt+Shift+] | | -+---------------------------+--------------------------------------------------------+ -| Alt+Shift+Q | Open the Query Tool in the current database | -+---------------------------+--------------------------------------------------------+ -| Alt+Shift+V | View Data in the selected table/view | -+---------------------------+--------------------------------------------------------+ -| Alt+Shift+C | Open the context menu | -+---------------------------+--------------------------------------------------------+ -| Alt+Shift+N | Create an object | -+---------------------------+--------------------------------------------------------+ -| Alt+Shift+E | Edit object properties | -+---------------------------+--------------------------------------------------------+ -| Alt+Shift+D | Delete the object | -+---------------------------+--------------------------------------------------------+ -| Alt+Shift+G | Direct debugging | -+---------------------------+--------------------------------------------------------+ ++----------------------------+-------------------------------------------------------+ +| Shortcut for all platforms | Function | ++============================+=======================================================+ +| Alt+Shift+F | Open the File menu | ++----------------------------+-------------------------------------------------------+ +| Alt+Shift+O | Open the Object menu | ++----------------------------+-------------------------------------------------------+ +| Alt+Shift+L | Open the Tools menu | ++----------------------------+-------------------------------------------------------+ +| Alt+Shift+H | Open the Help menu | ++----------------------------+-------------------------------------------------------+ +| Alt+Shift+B | Focus the browser tree | ++----------------------------+-------------------------------------------------------+ +| Alt+Shift+[ | Move tabbed panel backward | ++----------------------------+-------------------------------------------------------+ +| Alt+Shift+] | Move tabbed panel forward | ++----------------------------+-------------------------------------------------------+ +| Alt+Shift+Q | Open the Query Tool in the current database | ++----------------------------+-------------------------------------------------------+ +| Alt+Shift+V | View Data in the selected table/view | ++----------------------------+-------------------------------------------------------+ +| Alt+Shift+C | Open the context menu | ++----------------------------+-------------------------------------------------------+ +| Alt+Shift+N | Create an object | ++----------------------------+-------------------------------------------------------+ +| Alt+Shift+E | Edit object properties | ++----------------------------+-------------------------------------------------------+ +| Alt+Shift+D | Delete the object | ++----------------------------+-------------------------------------------------------+ +| Alt+Shift+G | Direct debugging | ++----------------------------+-------------------------------------------------------+ + + +**Dialog tab shortcuts** + +Use the shortcuts below to navigate the tabsets on dialogs: + ++----------------------------+-------------------------------------------------------+ +| Shortcut for all platforms | Function | ++============================+=======================================================+ +| Control+Shift+[ | Dialog tab backward | ++----------------------------+-------------------------------------------------------+ +| Control+Shift+] | Dialog tab forward | ++----------------------------+-------------------------------------------------------+ + **SQL Editors** diff --git a/web/pgadmin/browser/__init__.py b/web/pgadmin/browser/__init__.py index 9faa564bd..ca1edd02c 100644 --- a/web/pgadmin/browser/__init__.py +++ b/web/pgadmin/browser/__init__.py @@ -433,6 +433,36 @@ class BrowserModule(PgAdminModule): fields=fields ) + self.preference.register( + 'keyboard_shortcuts', + 'dialog_tab_forward', + gettext('Dialog tab forward'), + 'keyboardshortcut', + { + 'alt': False, + 'shift': True, + 'control': True, + 'key': {'key_code': 93, 'char': ']'} + }, + category_label=gettext('Keyboard shortcuts'), + fields=fields + ) + + self.preference.register( + 'keyboard_shortcuts', + 'dialog_tab_backward', + gettext('Dialog tab backward'), + 'keyboardshortcut', + { + 'alt': False, + 'shift': True, + 'control': True, + 'key': {'key_code': 91, 'char': '['} + }, + category_label=gettext('Keyboard shortcuts'), + fields=fields + ) + def get_exposed_url_endpoints(self): """ Returns: diff --git a/web/pgadmin/browser/static/js/keyboard.js b/web/pgadmin/browser/static/js/keyboard.js index 95b77dbf5..5c71b8070 100644 --- a/web/pgadmin/browser/static/js/keyboard.js +++ b/web/pgadmin/browser/static/js/keyboard.js @@ -1,7 +1,6 @@ -/* eslint-disable */ -define( - ['underscore', 'underscore.string', 'sources/pgadmin', 'jquery', 'mousetrap'], -function(_, S, pgAdmin, $, Mousetrap) { +define(['underscore', 'underscore.string', 'sources/pgadmin', 'jquery', 'mousetrap', + 'sources/utils', 'sources/dialog_tab_navigator'], +function(_, S, pgAdmin, $, Mousetrap, commonUtils, dialogTabNavigator) { 'use strict'; var pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {}; @@ -12,27 +11,26 @@ function(_, S, pgAdmin, $, Mousetrap) { init: function() { Mousetrap.reset(); if (pgBrowser.preferences_cache.length > 0) { - var getShortcut = this.parseShortcutValue; this.keyboardShortcut = { - 'file_shortcut': getShortcut(pgBrowser.get_preference('browser', 'main_menu_file').value), - 'object_shortcut': getShortcut(pgBrowser.get_preference('browser', 'main_menu_object').value), - 'tools_shortcut': getShortcut(pgBrowser.get_preference('browser', 'main_menu_tools').value), - 'help_shortcut': getShortcut(pgBrowser.get_preference('browser', 'main_menu_help').value), - 'left_tree_shortcut': getShortcut(pgBrowser.get_preference('browser', 'browser_tree').value), - 'tabbed_panel_backward': getShortcut(pgBrowser.get_preference('browser', 'tabbed_panel_backward').value), - 'tabbed_panel_forward': getShortcut(pgBrowser.get_preference('browser', 'tabbed_panel_forward').value), - 'sub_menu_query_tool': getShortcut(pgBrowser.get_preference('browser', 'sub_menu_query_tool').value), - 'sub_menu_view_data': getShortcut(pgBrowser.get_preference('browser', 'sub_menu_view_data').value), - 'sub_menu_properties': getShortcut(pgBrowser.get_preference('browser', 'sub_menu_properties').value), - 'sub_menu_create': getShortcut(pgBrowser.get_preference('browser', 'sub_menu_create').value), - 'sub_menu_delete': getShortcut(pgBrowser.get_preference('browser', 'sub_menu_delete').value), - 'context_menu': getShortcut(pgBrowser.get_preference('browser', 'context_menu').value), - 'direct_debugging': getShortcut(pgBrowser.get_preference('browser', 'direct_debugging').value) + 'file_shortcut': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'main_menu_file').value), + 'object_shortcut': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'main_menu_object').value), + 'tools_shortcut': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'main_menu_tools').value), + 'help_shortcut': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'main_menu_help').value), + 'left_tree_shortcut': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'browser_tree').value), + 'tabbed_panel_backward': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'tabbed_panel_backward').value), + 'tabbed_panel_forward': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'tabbed_panel_forward').value), + 'sub_menu_query_tool': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'sub_menu_query_tool').value), + 'sub_menu_view_data': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'sub_menu_view_data').value), + 'sub_menu_properties': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'sub_menu_properties').value), + 'sub_menu_create': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'sub_menu_create').value), + 'sub_menu_delete': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'sub_menu_delete').value), + 'context_menu': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'context_menu').value), + 'direct_debugging': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'direct_debugging').value), }; this.shortcutMethods = { 'bindMainMenu': {'shortcuts': [this.keyboardShortcut.file_shortcut, - this.keyboardShortcut.object_shortcut, this.keyboardShortcut.tools_shortcut, - this.keyboardShortcut.help_shortcut]}, // Main menu + this.keyboardShortcut.object_shortcut, this.keyboardShortcut.tools_shortcut, + this.keyboardShortcut.help_shortcut]}, // Main menu 'bindRightPanel': {'shortcuts': [this.keyboardShortcut.tabbed_panel_backward, this.keyboardShortcut.tabbed_panel_forward]}, // Main window panels 'bindMainMenuLeft': {'shortcuts': 'left', 'bindElem': '.pg-navbar'}, // Main menu 'bindMainMenuRight': {'shortcuts': 'right', 'bindElem': '.pg-navbar'}, // Main menu @@ -44,9 +42,9 @@ function(_, S, pgAdmin, $, Mousetrap) { 'bindSubMenuCreate': {'shortcuts': this.keyboardShortcut.sub_menu_create}, // Sub menu - Create Object, 'bindSubMenuDelete': {'shortcuts': this.keyboardShortcut.sub_menu_delete}, // Sub menu - Delete object, 'bindContextMenu': {'shortcuts': this.keyboardShortcut.context_menu}, // Sub menu - Open context menu, - 'bindDirectDebugging': {'shortcuts': this.keyboardShortcut.direct_debugging} // Sub menu - Direct Debugging + 'bindDirectDebugging': {'shortcuts': this.keyboardShortcut.direct_debugging}, // Sub menu - Direct Debugging }; - this.bindShortcuts(); + this.bindShortcuts(); } }, bindShortcuts: function() { @@ -71,6 +69,20 @@ function(_, S, pgAdmin, $, Mousetrap) { attachShortcut: function(shortcut, callback, bindElem) { this._bindWithMousetrap(shortcut, callback, bindElem); }, + attachDialogTabNavigatorShortcut: function(dialogTabNavigator, shortcuts) { + var callback = dialogTabNavigator.on_keyboard_event, + domElem = dialogTabNavigator.dialog.el; + + if (domElem) { + Mousetrap(domElem).bind(shortcuts, function() { + callback.apply(dialogTabNavigator, arguments); + }.bind(domElem)); + } else { + Mousetrap.bind(shortcuts, function() { + callback.apply(dialogTabNavigator, arguments); + }); + } + }, detachShortcut: function(shortcut, bindElem) { if (bindElem) Mousetrap(bindElem).unbind(shortcut); else Mousetrap.unbind(shortcut); @@ -211,18 +223,18 @@ function(_, S, pgAdmin, $, Mousetrap) { }, bindContextMenu: function(e) { var tree = this.getTreeDetails(), - e = window.event, - left = $(e.srcElement).find('.aciTreeEntry').position().left + 70, - top = $(e.srcElement).find('.aciTreeEntry').position().top + 70; + left = $(e.srcElement).find('.aciTreeEntry').position().left + 70, + top = $(e.srcElement).find('.aciTreeEntry').position().top + 70; + e = window.event; tree.t.blur(tree.i); $('#tree').blur(); // Call context menu and set position - var ctx = tree.i.children().contextMenu({x: left, y:top}); + tree.i.children().contextMenu({x: left, y:top}); }, - bindDirectDebugging: function(e) { + bindDirectDebugging: function() { var tree = this.getTreeDetails(), - type = tree.t.itemData(tree.i)._type; + type = tree.t.itemData(tree.i)._type; if (!tree.d || (type != 'function' && type != 'procedure')) return; @@ -232,26 +244,23 @@ function(_, S, pgAdmin, $, Mousetrap) { pgAdmin.Tools.Debugger.get_function_information(pgAdmin.Browser.Nodes[type]); } }, - parseShortcutValue: function(obj) { - var shortcut = ""; - if (obj.alt) { shortcut += 'alt+'; } - if (obj.shift) { shortcut += 'shift+'; } - if (obj.control) { shortcut += 'ctrl+'; } - shortcut += String.fromCharCode(obj.key.key_code).toLowerCase(); - return shortcut; - }, getTreeDetails: function() { var t = pgAdmin.Browser.tree, - i = t.selected().length > 0 ? t.selected() : t.first(), - d = i && i.length == 1 ? t.itemData(i) : undefined; + i = t.selected().length > 0 ? t.selected() : t.first(), + d = i && i.length == 1 ? t.itemData(i) : undefined; return { t: t, i: i, - d: d - } - } + d: d, + }; + }, + getDialogTabNavigator: function(dialog) { + var backward_shortcut = pgBrowser.get_preference('browser', 'dialog_tab_backward').value, + forward_shortcut = pgBrowser.get_preference('browser', 'dialog_tab_forward').value; + return new dialogTabNavigator.dialogTabNavigator(dialog, backward_shortcut, forward_shortcut); + }, }); return pgAdmin.keyboardNavigation; diff --git a/web/pgadmin/browser/static/js/node.js b/web/pgadmin/browser/static/js/node.js index 250bd5db0..ad5824839 100644 --- a/web/pgadmin/browser/static/js/node.js +++ b/web/pgadmin/browser/static/js/node.js @@ -1,8 +1,9 @@ define('pgadmin.browser.node', [ 'sources/gettext', 'jquery', 'underscore', 'underscore.string', 'sources/pgadmin', 'pgadmin.browser.menu', 'backbone', 'pgadmin.alertifyjs', 'pgadmin.browser.datamodel', - 'backform', 'sources/browser/generate_url', 'pgadmin.browser.utils', 'pgadmin.backform', -], function(gettext, $, _, S, pgAdmin, Menu, Backbone, Alertify, pgBrowser, Backform, generateUrl) { + 'backform', 'sources/browser/generate_url', 'sources/utils', 'pgadmin.browser.utils', + 'pgadmin.backform', +], function(gettext, $, _, S, pgAdmin, Menu, Backbone, Alertify, pgBrowser, Backform, generateUrl, commonUtils) { var wcDocker = window.wcDocker, keyCode = { @@ -365,9 +366,8 @@ define('pgadmin.browser.node', [ } var setFocusOnEl = function() { - setTimeout(function() { - $(el).find('.tab-pane.active:first').find('input:first').focus(); - }, 500); + var container = $(el).find('.tab-content:first > .tab-pane.active:first'); + commonUtils.findAndSetFocus(container); }; if (!newModel.isNew()) { @@ -394,6 +394,8 @@ define('pgadmin.browser.node', [ view.render(); setFocusOnEl(); newModel.startNewSession(); + // var dialogTabNavigator = pgBrowser.keyboardNavigation.getDialogTabNavigator(view); + pgBrowser.keyboardNavigation.getDialogTabNavigator(view); }, error: function(xhr, error, message) { var _label = that && item ? @@ -430,8 +432,11 @@ define('pgadmin.browser.node', [ view.render(); setFocusOnEl(); newModel.startNewSession(); + // var dialogTabNavigator = pgBrowser.keyboardNavigation.getDialogTabNavigator(view); + pgBrowser.keyboardNavigation.getDialogTabNavigator(view); } } + return view; } diff --git a/web/pgadmin/browser/static/js/wizard.js b/web/pgadmin/browser/static/js/wizard.js index 56b1edf29..ef59b0017 100644 --- a/web/pgadmin/browser/static/js/wizard.js +++ b/web/pgadmin/browser/static/js/wizard.js @@ -1,7 +1,7 @@ define([ 'underscore', 'jquery', 'backbone', 'sources/pgadmin', 'pgadmin.browser', - 'sources/gettext', -], function(_, $, Backbone, pgAdmin, pgBrowser, gettext) { + 'sources/gettext', 'sources/utils', +], function(_, $, Backbone, pgAdmin, pgBrowser, gettext, commonUtils) { var wcDocker = window.wcDocker; @@ -157,7 +157,8 @@ define([ this.currPage = this.collection.at(this.options.curr_page).toJSON(); }, render: function() { - var data = this.currPage; + var self = this, + data = this.currPage; /* Check Status of the buttons */ this.options.disable_next = (this.options.disable_next ? true : this.evalASFunc(this.currPage.disable_next)); @@ -179,6 +180,11 @@ define([ /* OnLoad Callback */ this.onLoad(); + setTimeout(function() { + var container = $(self.el); + commonUtils.findAndSetFocus(container); + }, 100); + return this; }, nextPage: function() { diff --git a/web/pgadmin/static/js/backform.pgadmin.js b/web/pgadmin/static/js/backform.pgadmin.js index 2bbaa174e..5bbbf9fc2 100644 --- a/web/pgadmin/static/js/backform.pgadmin.js +++ b/web/pgadmin/static/js/backform.pgadmin.js @@ -500,12 +500,12 @@ define([ template: { 'header': _.template([ '
  • >', - ' ', '<%=label%>
  • ', ].join(' ')), 'panel': _.template( - '
    ' + '
    ' ), }, render: function() { diff --git a/web/pgadmin/static/js/dialog_tab_navigator.js b/web/pgadmin/static/js/dialog_tab_navigator.js new file mode 100644 index 000000000..8b7f85313 --- /dev/null +++ b/web/pgadmin/static/js/dialog_tab_navigator.js @@ -0,0 +1,143 @@ +import $ from 'jquery'; +import Mousetrap from 'mousetrap'; +import { findAndSetFocus } from './utils'; +import { parseShortcutValue } from './utils'; + +class dialogTabNavigator { + constructor(dialog, backwardShortcut, forwardShortcut) { + + this.dialog = dialog; + + this.tabSwitching = false; + + this.tabs = this.dialog.$el.find('.nav-tabs'); + + if (this.tabs.length > 0 ) { + this.tabs = this.tabs[0]; + } + + this.dialogTabBackward = parseShortcutValue(backwardShortcut); + this.dialogTabForward = parseShortcutValue(forwardShortcut); + + Mousetrap(this.dialog.el).bind(this.dialogTabBackward, this.onKeyboardEvent.bind(this)); + Mousetrap(this.dialog.el).bind(this.dialogTabForward, this.onKeyboardEvent.bind(this)); + + } + + onKeyboardEvent(event, shortcut) { + var currentTabPane = this.dialog.$el + .find('.tab-content:first > .tab-pane.active:first'), + childTabData = this.isActivePaneHasChildTabs(currentTabPane); + + if (this.tabSwitching) { + return; + } + + this.tabSwitching = true; + + if(childTabData) { + var res = this.navigate(shortcut, childTabData.childTab, + childTabData.childTabPane); + + if (!res) { + this.navigate(shortcut, this.tabs, currentTabPane); + } + } else { + this.navigate(shortcut, this.tabs, currentTabPane); + } + } + + isActivePaneHasChildTabs(currentTabPane) { + var childTab = currentTabPane.find('.nav-tabs:first'), + childTabPane; + + if (childTab.length > 0) { + childTabPane = currentTabPane + .find('.tab-content:first > .tab-pane.active:first'); + + return { + 'childTab': childTab, + 'childTabPane': childTabPane, + }; + } + + return null; + } + + navigate(shortcut, tabs, tab_pane) { + if(shortcut == this.dialogTabBackward) { + return this.navigateBackward(tabs, tab_pane); + }else if (shortcut == this.dialogTabForward) { + return this.navigateForward(tabs, tab_pane); + } + return false; + } + + navigateBackward(tabs, tab_pane) { + var self = this, + nextTabPane, + innerTabContainer, + prevtab = $(tabs).find('li.active').prev('li'); + + if (prevtab.length > 0) { + prevtab.find('a').tab('show'); + + nextTabPane = tab_pane.prev(); + innerTabContainer = nextTabPane + .find('.tab-content:first > .tab-pane.active:first'); + + if (innerTabContainer.length > 0) { + findAndSetFocus(innerTabContainer); + } else { + findAndSetFocus(nextTabPane); + } + + setTimeout(function() { + self.tabSwitching = false; + }, 200); + + return true; + } + + this.tabSwitching = false; + return false; + } + + navigateForward(tabs, tab_pane) { + var self = this, + nextTabPane, + innerTabContainer, + nexttab = $(tabs).find('li.active').next('li'); + + if(nexttab.length > 0) { + nexttab.find('a').tab('show'); + + nextTabPane = tab_pane.next(); + innerTabContainer = nextTabPane + .find('.tab-content:first > .tab-pane.active:first'); + + if (innerTabContainer.length > 0) { + findAndSetFocus(innerTabContainer); + } else { + findAndSetFocus(nextTabPane); + } + + setTimeout(function() { + self.tabSwitching = false; + }, 200); + + return true; + } + this.tabSwitching = false; + return false; + } + + detach() { + Mousetrap(this.dialog.el).unbind(this.dialogTabBackward); + Mousetrap(this.dialog.el).unbind(this.dialogTabForward); + } +} + +module.exports = { + dialogTabNavigator: dialogTabNavigator, +}; \ No newline at end of file diff --git a/web/pgadmin/static/js/utils.js b/web/pgadmin/static/js/utils.js new file mode 100644 index 000000000..026297f0a --- /dev/null +++ b/web/pgadmin/static/js/utils.js @@ -0,0 +1,38 @@ +////////////////////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2018, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////////////////// + +export function parseShortcutValue(obj) { + var shortcut = ''; + if (obj.alt) { shortcut += 'alt+'; } + if (obj.shift) { shortcut += 'shift+'; } + if (obj.control) { shortcut += 'ctrl+'; } + shortcut += String.fromCharCode(obj.key.key_code).toLowerCase(); + return shortcut; +} + +export function findAndSetFocus(container) { + if (container.length == 0) { + return; + } + setTimeout(function() { + var first_el = container + .find('button.fa-plus:first'); + + if (first_el.length == 0) { + first_el = container + .find('.pgadmin-controls:first>input:enabled,.CodeMirror-scroll'); + } + + if(first_el.length > 0) { + first_el[0].focus(); + } else { + container[0].focus(); + } + }, 200); +} diff --git a/web/pgadmin/tools/backup/static/js/backup.js b/web/pgadmin/tools/backup/static/js/backup.js index 367f3548a..ddbfaee4c 100644 --- a/web/pgadmin/tools/backup/static/js/backup.js +++ b/web/pgadmin/tools/backup/static/js/backup.js @@ -2,9 +2,10 @@ define([ 'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'underscore.string', 'pgadmin.alertifyjs', 'backbone', 'pgadmin.backgrid', - 'pgadmin.backform', 'pgadmin.browser', + 'pgadmin.backform', 'pgadmin.browser', 'sources/utils', ], function( - gettext, url_for, $, _, S, alertify, Backbone, Backgrid, Backform, pgBrowser + gettext, url_for, $, _, S, alertify, Backbone, Backgrid, Backform, pgBrowser, +commonUtils ) { // if module is already initialized, refer to that. @@ -696,6 +697,9 @@ define([ this.elements.content.appendChild($container.get(0)); + var container = view.$el.find('.tab-content:first > .tab-pane.active:first'); + commonUtils.findAndSetFocus(container); + // Listen to model & if filename is provided then enable Backup button this.view.model.on('change', function() { if (!_.isUndefined(this.get('file')) && this.get('file') !== '') { @@ -940,6 +944,13 @@ define([ this.elements.content.appendChild($container.get(0)); + if(view) { + view.$el.attr('tabindex', -1); + // var dialogTabNavigator = pgBrowser.keyboardNavigation.getDialogTabNavigator(view); + pgBrowser.keyboardNavigation.getDialogTabNavigator(view); + var container = view.$el.find('.tab-content:first > .tab-pane.active:first'); + commonUtils.findAndSetFocus(container); + } // Listen to model & if filename is provided then enable Backup button this.view.model.on('change', function() { if (!_.isUndefined(this.get('file')) && this.get('file') !== '') { diff --git a/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.js b/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.js index c0ca2a735..750887ec0 100644 --- a/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.js +++ b/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.js @@ -89,8 +89,10 @@ define([ cell: Backgrid.Extension.SelectRowCell.extend({ render: function() { - // Use the Backform Control's render function - Backgrid.Extension.SelectRowCell.prototype.render.apply(this, arguments); + // Do not use parent's render function. It set's tabindex to -1 on + // checkboxes. + this.$el.empty().append(''); + this.delegateEvents(); var col = this.column.get('name'); if (this.model && this.model.has(col)) { diff --git a/web/pgadmin/tools/import_export/static/js/import_export.js b/web/pgadmin/tools/import_export/static/js/import_export.js index 070ba6c34..3058f122e 100644 --- a/web/pgadmin/tools/import_export/static/js/import_export.js +++ b/web/pgadmin/tools/import_export/static/js/import_export.js @@ -1,9 +1,10 @@ define([ 'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'underscore.string', 'pgadmin.alertifyjs', 'sources/pgadmin', 'pgadmin.browser', 'backbone', 'backgrid', 'backform', - 'pgadmin.backform', 'pgadmin.backgrid', 'pgadmin.browser.node.ui', + 'sources/utils', 'pgadmin.backform', 'pgadmin.backgrid', 'pgadmin.browser.node.ui', ], function( - gettext, url_for, $, _, S, Alertify, pgAdmin, pgBrowser, Backbone, Backgrid, Backform + gettext, url_for, $, _, S, Alertify, pgAdmin, pgBrowser, Backbone, Backgrid, +Backform, commonUtils ) { pgAdmin = pgAdmin || window.pgAdmin || {}; @@ -652,6 +653,12 @@ define([ // Give the dialog initial height & width this.elements.dialog.style.minHeight = '80%'; this.elements.dialog.style.minWidth = '70%'; + + view.$el.attr('tabindex', -1); + // var dialogTabNavigator = pgBrowser.keyboardNavigation.getDialogTabNavigator(view); + pgBrowser.keyboardNavigation.getDialogTabNavigator(view); + var container = view.$el.find('.tab-content:first > .tab-pane.active:first'); + commonUtils.findAndSetFocus(container); }, }; }); diff --git a/web/pgadmin/tools/maintenance/static/js/maintenance.js b/web/pgadmin/tools/maintenance/static/js/maintenance.js index 7f67dbfd0..81e459445 100644 --- a/web/pgadmin/tools/maintenance/static/js/maintenance.js +++ b/web/pgadmin/tools/maintenance/static/js/maintenance.js @@ -1,12 +1,12 @@ define([ 'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'underscore.string', 'pgadmin.alertifyjs', 'sources/pgadmin', 'pgadmin.browser', 'backbone', - 'backgrid', 'backform', + 'backgrid', 'backform', 'sources/utils', 'pgadmin.backform', 'pgadmin.backgrid', 'pgadmin.browser.node.ui', ], function( gettext, url_for, $, _, S, Alertify, pgAdmin, pgBrowser, Backbone, Backgrid, - Backform + Backform, commonUtils ) { pgAdmin = pgAdmin || window.pgAdmin || {}; @@ -468,6 +468,10 @@ define([ $(reindex_btn).addClass('active'); } + view.$el.attr('tabindex', -1); + var container = view.$el.find('.tab-content:first > .tab-pane.active:first'); + commonUtils.findAndSetFocus(container); + this.elements.content.appendChild($container.get(0)); }, }; diff --git a/web/pgadmin/tools/restore/static/js/restore.js b/web/pgadmin/tools/restore/static/js/restore.js index 2b44cf09a..5c082a9f7 100644 --- a/web/pgadmin/tools/restore/static/js/restore.js +++ b/web/pgadmin/tools/restore/static/js/restore.js @@ -2,9 +2,10 @@ define('tools.restore', [ 'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'backbone', 'underscore.string', 'pgadmin.alertifyjs', 'pgadmin.browser', - 'pgadmin.backgrid', 'pgadmin.backform', + 'pgadmin.backgrid', 'pgadmin.backform', 'sources/utils', ], function( - gettext, url_for, $, _, Backbone, S, alertify, pgBrowser, Backgrid, Backform + gettext, url_for, $, _, Backbone, S, alertify, pgBrowser, Backgrid, Backform, +commonUtils ) { // if module is already initialized, refer to that. @@ -572,6 +573,12 @@ define('tools.restore', [ this.elements.content.appendChild($container.get(0)); + view.$el.attr('tabindex', -1); + // var dialogTabNavigator = pgBrowser.keyboardNavigation.getDialogTabNavigator(view); + pgBrowser.keyboardNavigation.getDialogTabNavigator(view); + var container = view.$el.find('.tab-content:first > .tab-pane.active:first'); + commonUtils.findAndSetFocus(container); + // Listen to model & if filename is provided then enable Backup button this.view.model.on('change', function() { if (!_.isUndefined(this.get('file')) && this.get('file') !== '') { diff --git a/web/regression/javascript/dialog_tab_navigator_spec.js b/web/regression/javascript/dialog_tab_navigator_spec.js new file mode 100644 index 000000000..d4082c87e --- /dev/null +++ b/web/regression/javascript/dialog_tab_navigator_spec.js @@ -0,0 +1,115 @@ +////////////////////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2018, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////////////////// +import dialogTabNavigator from 'sources/dialog_tab_navigator'; +import $ from 'jquery'; +import 'bootstrap'; + + describe('dialogTabNavigator', function () { + let dialog, tabNavigator, backward_shortcut, forward_shortcut; + + beforeEach(() => { + let dialogHtml =$('
    '+ + ' '+ + ' '+ + '
    '); + + dialog = {}; + + dialog.el = dialogHtml[0]; + dialog.$el = dialogHtml; + + backward_shortcut = { + 'alt': false, + 'shift': true, + 'control': true, + 'key': {'key_code': 91, 'char': '['} + }; + + forward_shortcut = { + 'alt': false, + 'shift': true, + 'control': true, + 'key': {'key_code': 93, 'char': ']'} + }; + + tabNavigator = new dialogTabNavigator.dialogTabNavigator( + dialog, backward_shortcut, forward_shortcut); + }); + + describe('navigate', function () { + + beforeEach(() => { + spyOn(tabNavigator, 'navigateBackward').and.callThrough(); + + spyOn(tabNavigator, 'navigateForward').and.callThrough(); + }); + + it('navigate backward', function () { + tabNavigator.onKeyboardEvent({}, 'shift+ctrl+['); + + expect(tabNavigator.navigateBackward).toHaveBeenCalled(); + + expect(tabNavigator.navigateForward).not.toHaveBeenCalled(); + + }); + + it('navigate forward', function () { + tabNavigator.onKeyboardEvent({}, 'shift+ctrl+]'); + + expect(tabNavigator.navigateForward).toHaveBeenCalled(); + + expect(tabNavigator.navigateBackward).not.toHaveBeenCalled(); + + }); + + it('should not navigate', function () { + tabNavigator.onKeyboardEvent({}, 'shift+ctrl+a'); + + expect(tabNavigator.navigateForward).not.toHaveBeenCalled(); + + expect(tabNavigator.navigateBackward).not.toHaveBeenCalled(); + + }); + + }); + + }); \ No newline at end of file diff --git a/web/webpack.shim.js b/web/webpack.shim.js index b0cf5fd6e..d36fe6134 100644 --- a/web/webpack.shim.js +++ b/web/webpack.shim.js @@ -126,6 +126,7 @@ var webpackShimConfig = { 'pgadmin': path.join(__dirname, './pgadmin/static/js/pgadmin'), 'translations': path.join(__dirname, './pgadmin/tools/templates/js/translations'), 'sources/gettext': path.join(__dirname, './pgadmin/static/js/gettext'), + 'sources/utils': path.join(__dirname, './pgadmin/static/js/utils'), 'babel-polyfill': path.join(__dirname, './node_modules/babel-polyfill/dist/polyfill'), // Vendor JS