diff --git a/docs/en_US/keyboard_shortcuts.rst b/docs/en_US/keyboard_shortcuts.rst index 1142975..7699798 100644 --- a/docs/en_US/keyboard_shortcuts.rst +++ b/docs/en_US/keyboard_shortcuts.rst @@ -41,6 +41,21 @@ When using main browser window, the following keyboard shortcuts are available: | Alt+Shift+G | Direct debugging | +---------------------------+--------------------------------------------------------+ + +**Dialog tab shortcuts** + +When any dialog which has bootstrap tabs (nav tabs) below shortcuts are +available to navigate within them: + ++---------------------------+--------------------------------------------------------+ +| Shortcut for all platform | Function | ++===========================+========================================================+ +| Control+Shift+[ | Dialog tab backward | ++---------------------------+--------------------------------------------------------+ +| Control+Shift+] | Dialog tab forward | ++---------------------------+--------------------------------------------------------+ + + **SQL Editors** When using the syntax-highlighting SQL editors, the following shortcuts are available: diff --git a/web/pgadmin/browser/__init__.py b/web/pgadmin/browser/__init__.py index 1d0374a..d827a49 100644 --- a/web/pgadmin/browser/__init__.py +++ b/web/pgadmin/browser/__init__.py @@ -430,6 +430,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/browser.js b/web/pgadmin/browser/static/js/browser.js index 13af4ea..cf739bb 100644 --- a/web/pgadmin/browser/static/js/browser.js +++ b/web/pgadmin/browser/static/js/browser.js @@ -1951,7 +1951,25 @@ define('pgadmin.browser', [ brace_matching: pgBrowser.utils.braceMatching, indent_with_tabs: pgBrowser.utils.is_indent_with_tabs, }, + find_and_set_focus: function(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,.CodeMirror-scroll'); + } + if(first_el.length > 0) { + first_el[0].focus(); + } else { + container[0].focus(); + } + }, 500); + }, }); /* Remove paste event mapping from CodeMirror's emacsy KeyMap binding diff --git a/web/pgadmin/browser/static/js/keyboard.js b/web/pgadmin/browser/static/js/keyboard.js index 95b77db..982ef48 100644 --- a/web/pgadmin/browser/static/js/keyboard.js +++ b/web/pgadmin/browser/static/js/keyboard.js @@ -27,7 +27,9 @@ function(_, S, pgAdmin, $, Mousetrap) { '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) + 'direct_debugging': getShortcut(pgBrowser.get_preference('browser', 'direct_debugging').value), + 'dialog_tab_backward': getShortcut(pgBrowser.get_preference('browser', 'dialog_tab_backward').value), + 'dialog_tab_forward': getShortcut(pgBrowser.get_preference('browser', 'dialog_tab_forward').value), }; this.shortcutMethods = { 'bindMainMenu': {'shortcuts': [this.keyboardShortcut.file_shortcut, @@ -71,6 +73,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); @@ -250,8 +266,113 @@ function(_, S, pgAdmin, $, Mousetrap) { i: i, d: d } - } + }, + getDialogTabNavigator: function(dialog) { + var self = this, + dialogTabNavigator = function() {}; + + _.extend(dialogTabNavigator, { + init: function() { + + this.dialog = dialog + + this.tabs = this.dialog.$el.find('.nav-tabs'); + + if (this.tabs.length > 0 ) { + this.tabs = this.tabs[0]; + } + + this.dialog_tab_backward = { + 'shortcuts': self.keyboardShortcut.dialog_tab_backward, + }; + + this.dialog_tab_forward = { + 'shortcuts': self.keyboardShortcut.dialog_tab_forward, + } + self.attachDialogTabNavigatorShortcut(this, this.dialog_tab_backward.shortcuts); + self.attachDialogTabNavigatorShortcut(this, this.dialog_tab_forward.shortcuts); + }, + on_keyboard_event: function(e, shortcut) { + var current_tab_pane = this.dialog.$el + .find('.tab-content:first > .tab-pane.active:first'), + child_tab_data = this.is_active_pane_has_child_tabs(current_tab_pane); + + if(child_tab_data) { + var res = this.navigate(shortcut, child_tab_data.child_tab, + child_tab_data.child_tab_pane); + + // child tab navigation was not successful because we reached + // to either of ends of tabs. + // so navigate parent tabs. + if (!res) { + this.navigate(shortcut, this.tabs, current_tab_pane); + } + } else { + this.navigate(shortcut, this.tabs, current_tab_pane); + } + }, + is_active_pane_has_child_tabs: function (current_tab_pane) { + var child_tab = current_tab_pane.find('.nav-tabs:first'), + child_tab_pane; + + if (child_tab.length > 0) { + child_tab_pane = current_tab_pane + .find('.tab-content:first > .tab-pane.active:first'); + + return { + 'child_tab': child_tab, + 'child_tab_pane': child_tab_pane, + } + } + + return null; + }, + navigate: function(shortcut, tabs, tab_pane) { + if(shortcut == this.dialog_tab_backward.shortcuts) { + var prevtab = $(tabs).find('li.active').prev('li'); + if (prevtab.length > 0) { + prevtab.find('a').tab('show'); + + var next_tab_pane = tab_pane.prev(), + inner_tab_container = next_tab_pane + .find('.tab-content:first > .tab-pane.active:first'); + + if (inner_tab_container.length > 0) { + pgBrowser.find_and_set_focus(inner_tab_container); + } else { + pgBrowser.find_and_set_focus(next_tab_pane); + } + return true; + } + }else if (shortcut == this.dialog_tab_forward.shortcuts) { + var nexttab = $(tabs).find('li.active').next('li'); + if(nexttab.length > 0) { + nexttab.find('a').tab('show'); + + var next_tab_pane = tab_pane.next(), + inner_tab_container = next_tab_pane + .find('.tab-content:first > .tab-pane.active:first'); + + if (inner_tab_container.length > 0) { + pgBrowser.find_and_set_focus(inner_tab_container); + } else { + pgBrowser.find_and_set_focus(next_tab_pane); + } + return true; + } + } + return false; + }, + detach: function() { + self.detachShortcut(this.dialog_tab_backward.shortcuts, this.dialog.el); + self.detachShortcut(this.dialog_tab_forward.shortcuts, this.dialog.el); + }, + }); + + return dialogTabNavigator; + + }, }); return pgAdmin.keyboardNavigation; diff --git a/web/pgadmin/browser/static/js/node.js b/web/pgadmin/browser/static/js/node.js index 250bd5d..f7ddf04 100644 --- a/web/pgadmin/browser/static/js/node.js +++ b/web/pgadmin/browser/static/js/node.js @@ -365,9 +365,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'); + pgBrowser.find_and_set_focus(container); }; if (!newModel.isNew()) { @@ -394,6 +393,8 @@ define('pgadmin.browser.node', [ view.render(); setFocusOnEl(); newModel.startNewSession(); + var dialogTabNavigator = pgBrowser.keyboardNavigation.getDialogTabNavigator(view); + dialogTabNavigator.init(); }, error: function(xhr, error, message) { var _label = that && item ? @@ -430,8 +431,11 @@ define('pgadmin.browser.node', [ view.render(); setFocusOnEl(); newModel.startNewSession(); + var dialogTabNavigator = pgBrowser.keyboardNavigation.getDialogTabNavigator(view); + dialogTabNavigator.init(); } } + return view; } diff --git a/web/pgadmin/browser/static/js/wizard.js b/web/pgadmin/browser/static/js/wizard.js index 56b1edf..32193c8 100644 --- a/web/pgadmin/browser/static/js/wizard.js +++ b/web/pgadmin/browser/static/js/wizard.js @@ -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); + pgBrowser.find_and_set_focus(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 2bbaa17..5bbbf9f 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([ '