Hi,
Please find the updated patch.
On Mon, Feb 26, 2018 at 8:03 PM, Dave Page <[email protected]> wrote:
> Hi
>
> On Mon, Feb 26, 2018 at 12:03 PM, Harshal Dhumal <
> [email protected]> wrote:
>
>> Hi,
>>
>> Please find the updated patch for keyboard navigation.
>>
>> In this patch I have reduced delay which is required until current tab
>> navigation is completed.
>> Extracted class dialogTabNavigator and put it in new file.
>> Added jasmine test cases.
>> Fixed linting issues, variable naming convention issues.
>>
>
> This is still getting stuck on the Connection tab when I test on the
> server dialog.
>
The disabled input field (Host name/address) on connection tab was causing
issue.
>
> BTW, I've updated the documentation a little - please find the attached
> version.
>
Thanks for this. I have included revised version of documentation in
current patch.
>
> Thanks.
>
>
>
>>
>>
>>
>> --
>> *Harshal Dhumal*
>> *Sr. Software Engineer*
>>
>> EnterpriseDB India: http://www.enterprisedb.com
>> The Enterprise PostgreSQL Company
>>
>> On Wed, Feb 21, 2018 at 11:39 PM, Harshal Dhumal <
>> [email protected]> wrote:
>>
>>> Hi,
>>>
>>> On Wed, Feb 21, 2018 at 10:59 PM, Joao De Almeida Pereira <
>>> [email protected]> wrote:
>>>
>>>> Yep I installed the V2 file
>>>>
>>>> On Wed, Feb 21, 2018 at 11:31 AM Dave Page <[email protected]> wrote:
>>>>
>>>>> On Wed, Feb 21, 2018 at 4:22 PM, Joao De Almeida Pereira <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Hello Harshal,
>>>>>>
>>>>>> I passed the patch through our CI and all the tests passed. The
>>>>>> changes do not break previous behavior but because there are no tests on
>>>>>> the new feature we could not be sure it was really working. So we did
>>>>>> some
>>>>>> manual testing and sometimes it doesn't work, like it gets stuck in a
>>>>>> place
>>>>>> and you need to press the shortcut again in order for it to move.
>>>>>>
>>>>>
>>>>> It stuck because I have to wait until next tab is completely visible
>>> (fade in effect is completed).
>>> The fade in or fade out transition duration is 150 ms (set by
>>> bootstrap). So I can not set focus back to tab pane
>>> until fade in or fade out transition is completed. May be one
>>> improvement I can do is to reduce wait time to
>>> something 200 ms (currently it's 500 ms).
>>>
>>> Note that the original issue reported by Dave is already fixed in
>>> updated patch.
>>>
>>>
>>>> Was that with the updated patch? It sounds like the issue I saw with
>>>>> the original one.
>>>>>
>>>>>
>>>>>>
>>>>>> Codewise I have some suggestions:
>>>>>> - dialogTabNavigator looks a nice candidate for a class with its own
>>>>>> file. This way we can test the behavior
>>>>>>
>>>>> Ok I'll move dialogTabNavigator to new file and will add test cases.
>>>
>>>> - There is no difference between a variable called e and a variable
>>>>>> error so for sake of clarity I would love to see variable names that
>>>>>> we can easily read
>>>>>> - We are also using 2 different types of variable naming camelCase
>>>>>> and snake_case, if we could use only camelCase on Javascript it would
>>>>>> make
>>>>>> the code more uniform
>>>>>>
>>>>> Ok I'll do this.
>>>
>>>
>>>> - I noticed that there are some linting issues in the Javascript code
>>>>>>
>>>>> I just found that linter was disabled for this file by adding comment
>>> /* eslint-disable */ at first line. (not sure why we did that)
>>>
>>>
>>>>>> Summing it up I believe that despite the feature not working 100% of
>>>>>> the time, looks like the code is almost there but needs some refactoring
>>>>>> to
>>>>>> make it more readable, instead of comments we could have function calls
>>>>>> that more clearly state what we are looking for something like
>>>>>> DialogTabNavigator.isLastTabOfChild
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>> Thanks
>>>>>> Joao
>>>>>>
>>>>>> On Wed, Feb 21, 2018 at 4:32 AM Harshal Dhumal <
>>>>>> [email protected]> wrote:
>>>>>>
>>>>>>> Hi,
>>>>>>>
>>>>>>> --
>>>>>>> *Harshal Dhumal*
>>>>>>> *Sr. Software Engineer*
>>>>>>>
>>>>>>> EnterpriseDB India: http://www.enterprisedb.com
>>>>>>> The Enterprise PostgreSQL Company
>>>>>>>
>>>>>>> On Tue, Feb 20, 2018 at 10:34 PM, Dave Page <[email protected]>
>>>>>>> wrote:
>>>>>>>
>>>>>>>> Hi
>>>>>>>>
>>>>>>>> On Tue, Feb 20, 2018 at 7:22 AM, Harshal Dhumal <
>>>>>>>> [email protected]> wrote:
>>>>>>>>
>>>>>>>>> Hi,
>>>>>>>>>
>>>>>>>>> Please find attached patch to enable keyboard navigation in dialog.
>>>>>>>>>
>>>>>>>>> To allow navigation from one tab pane (bootstrap tab pane) to
>>>>>>>>> another one
>>>>>>>>> I have added two new shortcut preferences *Dialog tab previous *(
>>>>>>>>> shift+control+[ ) and *Dialog tab next* ( shift+control+] ) for
>>>>>>>>> backward
>>>>>>>>> and forward tab navigation.
>>>>>>>>>
>>>>>>>>> Also all dialog controls (within same tab pane) can be navigated
>>>>>>>>> using TAB key.
>>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>
>>>>>>>> This seems unreliable to me - for example, it keeps getting stuck
>>>>>>>> on the connection tab on the server properties dialog.
>>>>>>>>
>>>>>>>>
>>>>>>>
>>>>>>>> Also, can we use the same wording as for the tabbed panel
>>>>>>>> navigation please? E.g. Next/Previous instead of Forward/Back.
>>>>>>>>
>>>>>>>
>>>>>>> I have fixed all of above issues. Please find updated patch.
>>>>>>>
>>>>>>> Thanks,
>>>>>>>
>>>>>>>
>>>>>>>>
>>>>>>>> --
>>>>>>>> Dave Page
>>>>>>>> Blog: http://pgsnake.blogspot.com
>>>>>>>> Twitter: @pgsnake
>>>>>>>>
>>>>>>>> EnterpriseDB UK: http://www.enterprisedb.com
>>>>>>>> The Enterprise PostgreSQL Company
>>>>>>>>
>>>>>>>
>>>>>
>>>>>
>>>>> --
>>>>> Dave Page
>>>>> Blog: http://pgsnake.blogspot.com
>>>>> Twitter: @pgsnake
>>>>>
>>>>> EnterpriseDB UK: http://www.enterprisedb.com
>>>>> The Enterprise PostgreSQL Company
>>>>>
>>>>
>>>
>>
>
>
> --
> Dave Page
> Blog: http://pgsnake.blogspot.com
> Twitter: @pgsnake
>
> EnterpriseDB UK: http://www.enterprisedb.com
> The Enterprise PostgreSQL Company
>
diff --git a/docs/en_US/keyboard_shortcuts.rst b/docs/en_US/keyboard_shortcuts.rst
index 1142975..af840ad 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 9faa564..ca1edd0 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 95b77db..5c71b80 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 250bd5d..ad58248 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 56b1edf..ef59b00 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 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([
'<li role="presentation" <%=disabled ? "disabled" : ""%>>',
- ' <a data-toggle="tab" data-tab-index="<%=tabIndex%>" href="#<%=cId%>"',
+ ' <a data-toggle="tab" tabindex="-1" data-tab-index="<%=tabIndex%>" href="#<%=cId%>"',
' id="<%=hId%>" aria-controls="<%=cId%>">',
'<%=label%></a></li>',
].join(' ')),
'panel': _.template(
- '<div role="tabpanel" class="tab-pane <%=label%> pg-el-sm-12 pg-el-md-12 pg-el-lg-12 pg-el-xs-12 fade" id="<%=cId%>" aria-labelledby="<%=hId%>"></div>'
+ '<div role="tabpanel" tabindex="-1" class="tab-pane <%=label%> pg-el-sm-12 pg-el-md-12 pg-el-lg-12 pg-el-xs-12 fade" id="<%=cId%>" aria-labelledby="<%=hId%>"></div>'
),
},
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 0000000..8dcf266
--- /dev/null
+++ b/web/pgadmin/static/js/dialog_tab_navigator.js
@@ -0,0 +1,119 @@
+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.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(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 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);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ navigateForward(tabs, tab_pane) {
+ var 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);
+ }
+ return true;
+ }
+ 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 0000000..026297f
--- /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 367f354..ddbfaee 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 c0ca2a7..750887e 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('<input type="checkbox" />');
+ 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 070ba6c..3058f12 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 7f67dbf..81e4594 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 2b44cf0..5c082a9 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 0000000..d4082c8
--- /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 =$('<div tabindex="1" class="backform-tab" role="tabpanel">'+
+ ' <ul class="nav nav-tabs" role="tablist">'+
+ ' <li role="presentation" class="active">'+
+ ' <a data-toggle="tab" tabindex="-1" data-tab-index="1" href="#1" aria-controls="1"> General</a>'+
+ ' </li>'+
+ ' <li role="presentation">'+
+ ' <a data-toggle="tab" tabindex="-1" data-tab-index="5" href="#2" aria-controls="2"> Default Privileges</a>'+
+ ' </li>'+
+ ' <li role="presentation">'+
+ ' <a data-toggle="tab" tabindex="-1" data-tab-index="6" href="#3" aria-controls="3"> SQL</a>'+
+ ' </li>'+
+ ' </ul>'+
+ ' <ul class="tab-content">'+
+ ' <div role="tabpanel" tabindex="-1" class="tab-pane fade collapse in active" id="1">'+
+ ' </div>'+
+ ' <div role="tabpanel" tabindex="-1" class="tab-pane fade collapse" id="2">'+
+ ' <div class="inline-tab-panel" role="tabpanel">'+
+ ' <ul class="nav nav-tabs" role="tablist">'+
+ ' <li role="presentation" class="active">'+
+ ' <a data-toggle="tab" tabindex="-1" data-tab-index="601" href="#11" aria-controls="11"> Tables</a>'+
+ ' </li>'+
+ ' <li role="presentation">'+
+ ' <a data-toggle="tab" tabindex="-1" data-tab-index="602" href="#22" aria-controls="22"> Sequences</a>'+
+ ' </li>'+
+ ' </ul>'+
+ ' <ul class="tab-content">'+
+ ' <div role="tabpanel" tabindex="-1" class="tab-pane fade collapse in active" id="11" >'+
+ ' </div>'+
+ ' <div role="tabpanel" tabindex="-1" class="tab-pane fade collapse" id="22">'+
+ ' </div>'+
+ ' </ul>'+
+ ' </div>'+
+ ' </div>'+
+ ' <div role="tabpanel" tabindex="-1" class="tab-pane fade collapse" id="3">'+
+ ' </div>'+
+ ' </ul>'+
+ '</div>');
+
+ 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 b0cf5fd..d36fe61 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