public inbox for [email protected]help / color / mirror / Atom feed
[pgAdmin][RM4772][Accessibility] Provide aria-label to an invisible label where a visible label cannot be used 4+ messages / 2 participants [nested] [flat]
* [pgAdmin][RM4772][Accessibility] Provide aria-label to an invisible label where a visible label cannot be used @ 2019-12-03 06:08 Aditya Toshniwal <[email protected]> 0 siblings, 1 reply; 4+ messages in thread From: Aditya Toshniwal @ 2019-12-03 06:08 UTC (permalink / raw) To: pgadmin-hackers Hi Hackers, Attached is the patch to add label and aria-label wherever applicable. The patch also fixes few other accessibility related errors raised by "WAVE" extension of Chrome. Kindly review. -- Thanks and Regards, Aditya Toshniwal Sr. Software Engineer | EnterpriseDB India | Pune "Don't Complain about Heat, Plant a TREE" Attachments: [application/octet-stream] RM4772.patch (35.6K, 3-RM4772.patch) download | inline diff: diff --git a/web/pgadmin/browser/server_groups/servers/templates/servers/password.html b/web/pgadmin/browser/server_groups/servers/templates/servers/password.html index 7086c383e..3bae7c1da 100644 --- a/web/pgadmin/browser/server_groups/servers/templates/servers/password.html +++ b/web/pgadmin/browser/server_groups/servers/templates/servers/password.html @@ -1,9 +1,9 @@ <form name="frmPassword" id="frmPassword" style="height: 100%; width: 100%" onsubmit="return false;"> <div> - <div><b>{{ _('Please enter the password for the user \'{0}\' to connect the server - "{1}"').format(username, - server_label) }}</b></div> + <div><label class="font-weight-bold" for="password">{{ _('Please enter the password for the user \'{0}\' to connect the server - "{1}"').format(username, + server_label) }}</label></div> <div class="input-group row py-2"> - <label for="password" class="col-sm-2 col-form-label">Password</label> + <label class="col-sm-2 col-form-label" aria-hidden="true">Password</label> <div class="col-sm-10"> <input id="password" class="form-control" name="password" type="password"> </div> @@ -24,7 +24,7 @@ <div class="pr-2"> <i class="fa fa-exclamation-triangle text-danger" aria-hidden="true"></i> </div> - <div class="alert-text">{{ errmsg }}</div> + <div class="alert-text" role="status">{{ errmsg }}</div> </div> </div> </div> diff --git a/web/pgadmin/browser/server_groups/servers/templates/servers/tunnel_password.html b/web/pgadmin/browser/server_groups/servers/templates/servers/tunnel_password.html index 1ad1a1138..ed7df8664 100644 --- a/web/pgadmin/browser/server_groups/servers/templates/servers/tunnel_password.html +++ b/web/pgadmin/browser/server_groups/servers/templates/servers/tunnel_password.html @@ -2,9 +2,9 @@ <div class="m-1"> {% if prompt_tunnel_password %} {% if tunnel_identity_file %} - <div><b>{{ _('SSH Tunnel password for the identity file \'{0}\' to connect the server "{1}"').format(tunnel_identity_file, tunnel_host) }}</b></div> + <div><label class="font-weight-bold" for="tunnel_password">{{ _('SSH Tunnel password for the identity file \'{0}\' to connect the server "{1}"').format(tunnel_identity_file, tunnel_host) }}</label></div> {% else %} - <div><b>{{ _('SSH Tunnel password for the user \'{0}\' to connect the server "{1}"').format(tunnel_username, tunnel_host) }}</b></div> + <div><label class="font-weight-bold" for="tunnel_password">{{ _('SSH Tunnel password for the user \'{0}\' to connect the server "{1}"').format(tunnel_username, tunnel_host) }}</label></div> {% endif %} <div class="input-group py-2"> <div class="w-100"> @@ -21,7 +21,7 @@ </div> {% endif %} {% if prompt_password %} - <div><b>{{ _('Database server password for the user \'{0}\' to connect the server "{1}"').format(username, server_label) }}</b></div> + <div><label class="font-weight-bold" for="password">{{ _('Database server password for the user \'{0}\' to connect the server "{1}"').format(username, server_label) }}</label></div> <div class="input-group py-2"> <div class="w-100"> <input id="password" class="form-control" name="password" type="password"> @@ -44,7 +44,7 @@ <div class="pr-2"> <i class="fa fa-exclamation-triangle text-danger" aria-hidden="true"></i> </div> - <div class="alert-text">{{ errmsg }}</div> + <div class="alert-text" role="status">{{ errmsg }}</div> </div> </div> </div> diff --git a/web/pgadmin/browser/static/js/browser.js b/web/pgadmin/browser/static/js/browser.js index 205f2c122..1dc9c24e1 100644 --- a/web/pgadmin/browser/static/js/browser.js +++ b/web/pgadmin/browser/static/js/browser.js @@ -226,7 +226,7 @@ define('pgadmin.browser', [ width: 500, isCloseable: false, isPrivate: true, - content: '<div class="sql_textarea"><textarea id="sql-textarea" name="sql-textarea"></textarea></div>', + content: '<div class="sql_textarea"><textarea id="sql-textarea" name="sql-textarea" title="'+gettext('SQL Code')+'"></textarea></div>', }), // Dependencies of the object 'dependencies': new pgAdmin.Browser.Panel({ @@ -357,7 +357,6 @@ define('pgadmin.browser', [ // enabled/disabled. _.each([ {m: 'file', id: '#mnu_file'}, - {m: 'edit', id: '#mnu_edit'}, {m: 'management', id: '#mnu_management'}, {m: 'tools', id: '#mnu_tools'}, {m: 'help', id:'#mnu_help'}], function(o) { @@ -827,7 +826,6 @@ define('pgadmin.browser', [ _.each([ {menu: 'file', id: '#mnu_file'}, - {menu: 'edit', id: '#mnu_edit'}, {menu: 'management', id: '#mnu_management'}, {menu: 'tools', id: '#mnu_tools'}, {menu: 'help', id:'#mnu_help'}], diff --git a/web/pgadmin/browser/static/js/menu.js b/web/pgadmin/browser/static/js/menu.js index de95490bc..3f3dba411 100644 --- a/web/pgadmin/browser/static/js/menu.js +++ b/web/pgadmin/browser/static/js/menu.js @@ -72,6 +72,7 @@ define([ 'href': this.url, 'target': this.target, 'data-toggle': 'pg-menu', + 'role': 'menuitem', }).data('pgMenu', { module: this.module || pgAdmin.Browser, cb: this.callback, diff --git a/web/pgadmin/browser/static/js/node.js b/web/pgadmin/browser/static/js/node.js index fbce346f0..a1493cd20 100644 --- a/web/pgadmin/browser/static/js/node.js +++ b/web/pgadmin/browser/static/js/node.js @@ -1129,7 +1129,8 @@ define('pgadmin.browser.node', [ tmpl = _.template([ '<button tabindex="0" type="<%= type %>" ', 'class="btn <%=extraClasses.join(\' \')%>"', - '<% if (disabled) { %> disabled="disabled"<% } %> title="<%-tooltip%>">', + '<% if (disabled) { %> disabled="disabled"<% } %> title="<%-tooltip%>"', + '<% if (label != "") {} else { %> aria-label="<%-tooltip%>"<% } %> >', '<span class="<%= icon %>"></span><% if (label != "") { %> <%-label%><% } %></button>', ].join(' ')); if (location == 'header') { diff --git a/web/pgadmin/browser/templates/browser/index.html b/web/pgadmin/browser/templates/browser/index.html index 76e222f5e..b2e0a8151 100644 --- a/web/pgadmin/browser/templates/browser/index.html +++ b/web/pgadmin/browser/templates/browser/index.html @@ -100,8 +100,8 @@ window.onload = function(e){ </div> <nav class="navbar fixed-top navbar-expand-lg navbar-dark pg-navbar"> <a class="navbar-brand pgadmin_header_logo" onClick="return false;" href="{{ '#' }}" - title="{{ config.APP_NAME }} {{ _('logo') }}"> - <i class="app-icon {{ config.APP_ICON }}"></i> + title="{{ config.APP_NAME }} {{ _('logo') }}" aria-label="{ config.APP_NAME }} {{ _('logo') }}"> + <i class="app-icon {{ config.APP_ICON }}" aria-hidden="true"></i> </a> <button type="button" class="navbar-toggler collapsed" data-toggle="collapse" data-target="#navbar-menu" aria-controls="navbar-menu"> <span class="sr-only">{{ _('Toggle navigation') }}</span> @@ -115,11 +115,6 @@ window.onload = function(e){ _('File') }} <span class="caret"></span></a> <ul class="dropdown-menu" role="menu"></ul> </li> - <li id="mnu_edit" class="nav-item active dropdown d-none"> - <a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">{{ - _('Edit') }} <span class="caret"></span></a> - <ul class="dropdown-menu" role="menu"></ul> - </li> <li id="mnu_obj" class="nav-item active dropdown "> <a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">{{ _('Object') }} <span class="caret"></span></a> diff --git a/web/pgadmin/dashboard/static/js/dashboard.js b/web/pgadmin/dashboard/static/js/dashboard.js index 6e466052a..c412a5c68 100644 --- a/web/pgadmin/dashboard/static/js/dashboard.js +++ b/web/pgadmin/dashboard/static/js/dashboard.js @@ -43,13 +43,13 @@ define('pgadmin.dashboard', [ this.$el.html( '<i class=\'fa fa-stop\' data-toggle=\'tooltip\' ' + 'title=\'' + gettext('Cancel the active query') + - '\'></i>' + '\' aria-label=\''+ gettext('Cancel the active query') +'\'></i>' ); } else { this.$el.html( '<i class=\'fa fa-times-circle text-danger\' data-toggle=\'tooltip\' ' + 'title=\'' + gettext('Terminate the session') + - '\'></i>' + '\' aria-label=\''+ gettext('Terminate the session') +'\'></i>' ); } this.$el.attr('tabindex', 0); @@ -147,7 +147,7 @@ define('pgadmin.dashboard', [ this.$el.html( '<i class=\'fa fa-caret-right\' data-toggle=\'tooltip\' ' + 'title=\'' + gettext('View the active session details') + - '\'></i>' + '\' aria-label=\''+ gettext('View the active session details') +'\'></i>' ); this.delegateEvents(); if (this.grabFocus) diff --git a/web/pgadmin/dashboard/templates/dashboard/database_dashboard.html b/web/pgadmin/dashboard/templates/dashboard/database_dashboard.html index d27a43a08..96aa90f06 100644 --- a/web/pgadmin/dashboard/templates/dashboard/database_dashboard.html +++ b/web/pgadmin/dashboard/templates/dashboard/database_dashboard.html @@ -81,12 +81,12 @@ <div class="navtab-inline-controls"> <div class="input-group"> <div class="input-group-prepend"> - <span class="input-group-text fa fa-search" id="labelSearch"></span> + <span class="input-group-text fa fa-search" id="labelSearch" aria-label="{{ _('Search') }}"></span> </div> - <input type="search" class="form-control" id="txtGridSearch" placeholder="Search" aria-label="Search" aria-describedby="labelSearch"> + <input type="search" class="form-control" id="txtGridSearch" placeholder="{{ _('Search') }}" aria-describedby="labelSearch" aria-labelledby="labelSearch"> </div> - <button id="btn_refresh" type="button" class="btn btn-secondary btn-navtab-inline" title="{{ _('Refresh') }}"> - <span class="fa fa-refresh"></span> + <button id="btn_refresh" type="button" class="btn btn-secondary btn-navtab-inline" title="{{ _('Refresh') }}" aria-label="{{ _('Refresh') }}"> + <span class="fa fa-refresh" aria-hidden="true"></span> </button> </div> </div> diff --git a/web/pgadmin/dashboard/templates/dashboard/server_dashboard.html b/web/pgadmin/dashboard/templates/dashboard/server_dashboard.html index a7fe75a68..e1c68b2f7 100644 --- a/web/pgadmin/dashboard/templates/dashboard/server_dashboard.html +++ b/web/pgadmin/dashboard/templates/dashboard/server_dashboard.html @@ -89,8 +89,8 @@ </div> <input type="search" class="form-control" id="txtGridSearch" placeholder="Search" aria-label="Search" aria-describedby="labelSearch"> </div> - <button id="btn_refresh" type="button" class="btn btn-secondary btn-navtab-inline" title="{{ _('Refresh') }}"> - <span class="fa fa-refresh"></span> + <button id="btn_refresh" type="button" class="btn btn-secondary btn-navtab-inline" title="{{ _('Refresh') }}" aria-label="{{ _('Refresh') }}"> + <span class="fa fa-refresh" aria-hidden="true"></span> </button> </div> </div> diff --git a/web/pgadmin/dashboard/templates/dashboard/welcome_dashboard.html b/web/pgadmin/dashboard/templates/dashboard/welcome_dashboard.html index 0c9a82749..fc798ab19 100644 --- a/web/pgadmin/dashboard/templates/dashboard/welcome_dashboard.html +++ b/web/pgadmin/dashboard/templates/dashboard/welcome_dashboard.html @@ -5,7 +5,7 @@ <div class="card"> <div class="card-header">{{ _('Welcome') }}</div> <div class="card-body p-2"> - <div class="welcome-logo"> + <div class="welcome-logo" aria-hidden="true"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 130"> <defs> <style>.cls-1{stroke:#000;stroke-width:10.19px;}.cls-2{fill:#336791;}.cls-3,.cls-4,.cls-9{fill:none;}.cls-3,.cls-4,.cls-5,.cls-6{stroke:#fff;}.cls-3,.cls-4{stroke-linecap:round;stroke-width:3.4px;}.cls-3{stroke-linejoin:round;}.cls-4{stroke-linejoin:bevel;}.cls-5,.cls-6{fill:#fff;}.cls-5{stroke-width:1.13px;}.cls-6{stroke-width:0.57px;}.cls-7{fill:#2775b6;}.cls-8{fill:#333;}.cls-9{stroke:#333;stroke-width:3px;}</style> diff --git a/web/pgadmin/static/js/backform.pgadmin.js b/web/pgadmin/static/js/backform.pgadmin.js index 8fe989126..b620ab73c 100644 --- a/web/pgadmin/static/js/backform.pgadmin.js +++ b/web/pgadmin/static/js/backform.pgadmin.js @@ -173,11 +173,9 @@ define([ }, template: _.template([ - '<label class="<%=Backform.controlLabelClassName%>"><%=label%></label>', + '<label class="<%=Backform.controlLabelClassName%>" for="<%=cId%>"><%=label%></label>', '<div class="<%=Backform.controlsClassName%>">', - ' <span class="<%=Backform.controlClassName%> uneditable-input" <%=disabled ? "disabled readonly" : ""%>>', - ' <%-value%>', - ' </span>', + ' <input class="<%=Backform.controlClassName%> uneditable-input" <%=disabled ? "disabled readonly" : ""%> id="<%=cId%>" value="<%-value%>" />', ' <% if (helpMessage && helpMessage.length) { %>', ' <span class="<%=Backform.helpMessageClassName%>"><%=helpMessage%></span>', ' <% } %>', @@ -239,6 +237,7 @@ define([ required: evalF(data.required, data, this.model), }); + data.cId = data.cId || _.uniqueId('pgC_'); // Clean up first this.$el.removeClass(Backform.hiddenClassName); @@ -258,6 +257,15 @@ define([ */ _.extend( Backform.InputControl.prototype, { + template: _.template([ + '<label class="<%=Backform.controlLabelClassName%>" for="<%=cId%>"><%=label%></label>', + '<div class="<%=Backform.controlContainerClassName%>">', + ' <input type="<%=type%>" id="<%=cId%>" class="<%=Backform.controlClassName%> <%=extraClasses.join(\' \')%>" name="<%=name%>" maxlength="<%=maxlength%>" value="<%-value%>" placeholder="<%-placeholder%>" <%=disabled ? "disabled" : ""%> <%=required ? "required" : ""%> />', + ' <% if (helpMessage && helpMessage.length) { %>', + ' <span class="<%=Backform.helpMessageClassName%>"><%=helpMessage%></span>', + ' <% } %>', + '</div>', + ].join('\n')), events: { 'change input': 'onChange', 'blur input': 'onChange', @@ -300,9 +308,9 @@ define([ 'focus textarea': 'clearInvalid', }, template: _.template([ - '<label class="<%=Backform.controlLabelClassName%>"><%=label%></label>', + '<label class="<%=Backform.controlLabelClassName%>" for="<%=cId%>"><%=label%></label>', '<div class="<%=Backform.controlsClassName%>">', - ' <textarea ', + ' <textarea id="<%=cId%>"', ' class="<%=Backform.controlClassName%> <%=extraClasses.join(\' \')%>" name="<%=name%>"', ' <% if (maxlength) { %>', ' maxlength="<%=maxlength%>"', @@ -365,6 +373,7 @@ define([ } } + data.cId = data.cId || _.uniqueId('pgC_'); // Clean up first this.$el.removeClass(Backform.hiddenClassName); @@ -376,18 +385,31 @@ define([ return this; }; + + Backform.SelectControl.prototype.template = _.template([ + '<label class="<%=Backform.controlLabelClassName%>" for="<%=cId%>"><%=label%></label>', + '<div class="<%=Backform.controlContainerClassName%>">', + ' <select id="<%=cId%>" class="<%=Backform.controlClassName%> <%=extraClasses.join(\' \')%>" name="<%=name%>" value="<%-value%>" <%=disabled ? "disabled" : ""%> <%=required ? "required" : ""%> >', + ' <% for (var i=0; i < options.length; i++) { %>', + ' <% var option = options[i]; %>', + ' <option value="<%-formatter.fromRaw(option.value)%>" <%=option.value === rawValue ? "selected=\'selected\'" : ""%> <%=option.disabled ? "disabled=\'disabled\'" : ""%>><%-option.label%></option>', + ' <% } %>', + ' </select>', + '</div>', + ].join('\n')); + _.extend(Backform.SelectControl.prototype.defaults, { helpMessage: null, }); Backform.ReadonlyOptionControl = Backform.SelectControl.extend({ template: _.template([ - '<label class="<%=Backform.controlLabelClassName%>"><%=label%></label>', + '<label class="<%=Backform.controlLabelClassName%>" for="<%=cId%>"><%=label%></label>', '<div class="<%=Backform.controlsClassName%>">', '<% for (var i=0; i < options.length; i++) { %>', ' <% var option = options[i]; %>', ' <% if (option.value === rawValue) { %>', - ' <span class="<%=Backform.controlClassName%> uneditable-input" disabled readonly><%-option.label%></span>', + ' <input id="<%=cId%>" class="<%=Backform.controlClassName%> uneditable-input" disabled readonly value="<%-option.label%>"></span>', ' <% } %>', '<% } %>', '<% if (helpMessage && helpMessage.length) { %>', @@ -529,8 +551,9 @@ define([ }, template: _.template([ '<label class="<%=controlLabelClassName%>"><%=label%></label>', + '<label class="sr-value sr-only" for="<%=cId%>"></label>', '<div class="<%=controlsClassName%> <%=extraClasses.join(\' \')%>">', - ' <input tabindex="-1" type="checkbox" data-style="quick" data-toggle="toggle"', + ' <input tabindex="-1" type="checkbox" aria-hidden="true" aria-label="Toggle button" data-style="quick" data-toggle="toggle"', ' data-size="<%=options.size%>" data-height="<%=options.height%>" ', ' data-on="<%=options.onText%>" data-off="<%=options.offText%>" ', ' data-onstyle="<%=options.onColor%>" data-offstyle="<%=options.offColor%>" data-width="<%=options.width%>" ', @@ -550,10 +573,29 @@ define([ 'change input': 'onChange', 'keyup': 'toggleSwitch', }, + setSrValue: function() { + let {onText, offText} = _.defaults(this.field.get('options'), this.defaults.options); + let label = this.field.get('label'); + + if(this.$el.find('.toggle.btn').hasClass('off')) { + this.$el.find('.sr-value').text(` + ${label}. ${offText}. ${gettext('Toggle button')} + `); + } else { + this.$el.find('.sr-value').text(` + ${label}. ${onText}. ${gettext('Toggle button')} + `); + } + }, + onChange: function() { + Backform.InputControl.prototype.onChange.apply(this, arguments); + this.setSrValue(); + }, toggleSwitch: function(e) { if (e.keyCode == 32) { this.$el.find('input[type=checkbox]').bootstrapToggle('toggle'); e.preventDefault(); + this.setSrValue(); } }, render: function() { @@ -580,6 +622,7 @@ define([ required: evalF(data.required, field, this.model), }); + data.cId = data.cId || _.uniqueId('pgC_'); // Clean up first this.$el.removeClass(Backform.hiddenClassName); @@ -607,7 +650,13 @@ define([ this.$input = this.$el.find('input[type=checkbox]').first(); this.$input.bootstrapToggle(); // When disable then set tabindex value to -1 - this.$el.find('.toggle.btn').attr('tabindex', data.options.disabled ? '-1' : '0'); + this.$el.find('.toggle.btn') + .attr('tabindex', data.options.disabled ? '-1' : '0') + .attr('id', data.cId); + + this.$el.find('.toggle.btn .toggle-group .btn').attr('aria-hidden', true); + this.setSrValue(); + this.updateInvalid(); return this; @@ -1834,9 +1883,9 @@ define([ helpMessage: null, }, template: _.template([ - '<label class="<%=Backform.controlLabelClassName%>"><%=label%></label>', + '<label for="<%=cId%>" class="<%=Backform.controlLabelClassName%>"><%=label%></label>', '<div class="<%=Backform.controlsClassName%>">', - ' <input type="<%=type%>" class="<%=Backform.controlClassName%> <%=extraClasses.join(\' \')%>" name="<%=name%>" min="<%=min%>" max="<%=max%>"maxlength="<%=maxlength%>" value="<%-value%>" placeholder="<%-placeholder%>" <%=disabled ? "disabled" : ""%> <%=required ? "required" : ""%> />', + ' <input type="<%=type%>" id="<%=cId%>" class="<%=Backform.controlClassName%> <%=extraClasses.join(\' \')%>" name="<%=name%>" min="<%=min%>" max="<%=max%>"maxlength="<%=maxlength%>" value="<%-value%>" placeholder="<%-placeholder%>" <%=disabled ? "disabled" : ""%> <%=required ? "required" : ""%> />', ' <% if (helpMessage && helpMessage.length) { %>', ' <span class="<%=Backform.helpMessageClassName%>"><%=helpMessage%></span>', ' <% } %>', @@ -2075,9 +2124,9 @@ define([ formatter: Select2Formatter, template: _.template([ - '<label class="<%=Backform.controlLabelClassName%>"><%=label%></label>', + '<label class="<%=Backform.controlLabelClassName%>" for="<%=cId%>"><%=label%></label>', '<div class="<%=Backform.controlsClassName%>">', - ' <select class="<%=Backform.controlClassName%> <%=extraClasses.join(\' \')%>"', + ' <select id="<%=cId%>" class="<%=Backform.controlClassName%> <%=extraClasses.join(\' \')%>"', ' name="<%=name%>" value="<%-value%>" <%=disabled ? "disabled" : ""%>', ' <%=required ? "required" : ""%><%= select2.multiple ? " multiple>" : ">" %>', ' <%=select2.first_empty ? " <option></option>" : ""%>', @@ -2158,6 +2207,7 @@ define([ } } + data.cId = data.cId || _.uniqueId('pgC_'); // Clean up first this.$el.removeClass(Backform.hiddenClassName); @@ -2599,12 +2649,12 @@ define([ Backform.InputControl.prototype.initialize.apply(this, arguments); }, template: _.template([ - '<label class="<%=Backform.controlLabelClassName%>"><%=label%></label>', + '<label class="<%=Backform.controlLabelClassName%>" for="<%=cId%>"><%=label%></label>', '<div class="<%=Backform.controlsClassName%>">', '<div class="input-group">', - '<input type="<%=type%>" class="form-control <%=extraClasses.join(\' \')%>" name="<%=name%>" min="<%=min%>" max="<%=max%>"maxlength="<%=maxlength%>" value="<%-value%>" placeholder="<%-placeholder%>" <%=disabled ? "disabled" : ""%> <%=required ? "required" : ""%> />', + '<input type="<%=type%>" id="<%=cId%>" class="form-control <%=extraClasses.join(\' \')%>" name="<%=name%>" min="<%=min%>" max="<%=max%>"maxlength="<%=maxlength%>" value="<%-value%>" placeholder="<%-placeholder%>" <%=disabled ? "disabled" : ""%> <%=required ? "required" : ""%> />', '<div class="input-group-append">', - '<button class="btn btn-secondary fa fa-ellipsis-h select_item" <%=disabled ? "disabled" : ""%> ></button>', + '<button class="btn btn-secondary fa fa-ellipsis-h select_item" <%=disabled ? "disabled" : ""%> aria-hidden="true" aria-label="Select file" title="Select file"></button>', '</div>', '</div>', '<% if (helpMessage && helpMessage.length) { %>', @@ -2853,9 +2903,9 @@ define([ defaultColor: '', }, template: _.template([ - '<label class="<%=Backform.controlLabelClassName%>"><%=label%></label>', + '<label class="<%=Backform.controlLabelClassName%>" for="<%=cId%>"><%=label%></label>', '<div class="<%=Backform.controlsClassName%>">', - ' <input class="<%=Backform.controlClassName%> <%=extraClasses.join(\' \')%>" name="<%=name%>" value="<%-value%>" <%=disabled ? "disabled" : ""%> <%=required ? "required" : ""%> />', + ' <input id="<%=cId%>" class="<%=Backform.controlClassName%> <%=extraClasses.join(\' \')%>" name="<%=name%>" value="<%-value%>" <%=disabled ? "disabled" : ""%> <%=required ? "required" : ""%> />', ' <% if (helpMessage && helpMessage.length) { %>', ' <span class="<%=Backform.helpMessageClassName%>"><%=helpMessage%></span>', ' <% } %>', @@ -2890,6 +2940,7 @@ define([ required: evalF(data.required, data, this.model), }); + data.cId = data.cId || _.uniqueId('pgC_'); // Clean up first this.$el.empty(); diff --git a/web/pgadmin/static/js/sqleditor/query_tool_preferences.js b/web/pgadmin/static/js/sqleditor/query_tool_preferences.js index 8e74e65d3..b430d654a 100644 --- a/web/pgadmin/static/js/sqleditor/query_tool_preferences.js +++ b/web/pgadmin/static/js/sqleditor/query_tool_preferences.js @@ -11,6 +11,7 @@ import {shortcut_key, shortcut_accesskey_title, shortcut_title} from 'sources/keyboard_shortcuts'; import * as SqlEditorUtils from 'sources/sqleditor_utils'; import $ from 'jquery'; +import gettext from 'sources/gettext'; function updateUIPreferences(sqlEditor) { let $el = sqlEditor.$el, @@ -26,51 +27,63 @@ function updateUIPreferences(sqlEditor) { /* Accessed using accesskey direct w/o ctrl,atl,shift */ $el.find('#btn-load-file') - .attr('title', shortcut_accesskey_title('Open File',preferences.btn_open_file)) + .attr('title', shortcut_accesskey_title(gettext('Open File'),preferences.btn_open_file)) + .attr('aria-label', shortcut_accesskey_title(gettext('Open File'),preferences.btn_open_file)) .attr('accesskey', shortcut_key(preferences.btn_open_file)); $el.find('#btn-save-file') - .attr('title', shortcut_accesskey_title('Save File',preferences.btn_save_file)) + .attr('title', shortcut_accesskey_title(gettext('Save File'),preferences.btn_save_file)) + .attr('aria-label', shortcut_accesskey_title(gettext('Save File'),preferences.btn_save_file)) .attr('accesskey', shortcut_key(preferences.btn_save_file)); $el.find('#btn-find-menu-dropdown') - .attr('title', shortcut_accesskey_title('Find',preferences.btn_find_options)) + .attr('title', shortcut_accesskey_title(gettext('Find'),preferences.btn_find_options)) + .attr('aria-label',shortcut_accesskey_title(gettext('Find'),preferences.btn_find_options)) .attr('accesskey', shortcut_key(preferences.btn_find_options)); $el.find('#btn-copy-row') - .attr('title', shortcut_accesskey_title('Copy',preferences.btn_copy_row)) + .attr('title', shortcut_accesskey_title(gettext('Copy'),preferences.btn_copy_row)) + .attr('aria-label', shortcut_accesskey_title(gettext('Copy'),preferences.btn_copy_row)) .attr('accesskey', shortcut_key(preferences.btn_copy_row)); $el.find('#btn-paste-row') - .attr('title', shortcut_accesskey_title('Paste',preferences.btn_paste_row)) + .attr('title', shortcut_accesskey_title(gettext('Paste'),preferences.btn_paste_row)) + .attr('aria-label', shortcut_accesskey_title(gettext('Paste'),preferences.btn_paste_row)) .attr('accesskey', shortcut_key(preferences.btn_paste_row)); $el.find('#btn-delete-row') - .attr('title', shortcut_accesskey_title('Delete',preferences.btn_delete_row)) + .attr('title', shortcut_accesskey_title(gettext('Delete'),preferences.btn_delete_row)) + .attr('aria-label', shortcut_accesskey_title(gettext('Delete'),preferences.btn_delete_row)) .attr('accesskey', shortcut_key(preferences.btn_delete_row)); $el.find('#btn-filter') - .attr('title', shortcut_accesskey_title('Filter',preferences.btn_filter_dialog)) + .attr('title', shortcut_accesskey_title(gettext('Filter'),preferences.btn_filter_dialog)) + .attr('aria-label', shortcut_accesskey_title(gettext('Filter'),preferences.btn_filter_dialog)) .attr('accesskey', shortcut_key(preferences.btn_filter_dialog)); $el.find('#btn-filter-dropdown') - .attr('title', shortcut_accesskey_title('Filter options',preferences.btn_filter_options)) + .attr('title', shortcut_accesskey_title(gettext('Filter options'),preferences.btn_filter_options)) + .attr('aria-label', shortcut_accesskey_title(gettext('Filter options'),preferences.btn_filter_options)) .attr('accesskey', shortcut_key(preferences.btn_filter_options)); $el.find('#btn-rows-limit') - .attr('title', shortcut_accesskey_title('Rows limit',preferences.btn_rows_limit)) + .attr('title', shortcut_accesskey_title(gettext('Rows limit'),preferences.btn_rows_limit)) + .attr('aria-label', shortcut_accesskey_title(gettext('Rows limit'),preferences.btn_rows_limit)) .attr('accesskey', shortcut_key(preferences.btn_rows_limit)); $el.find('#btn-query-dropdown') - .attr('title', shortcut_accesskey_title('Execute options',preferences.btn_execute_options)) + .attr('title', shortcut_accesskey_title(gettext('Execute options'),preferences.btn_execute_options)) + .attr('aria-label', shortcut_accesskey_title(gettext('Execute options'),preferences.btn_execute_options)) .attr('accesskey', shortcut_key(preferences.btn_execute_options)); $el.find('#btn-cancel-query') - .attr('title', shortcut_accesskey_title('Cancel query',preferences.btn_cancel_query)) + .attr('title', shortcut_accesskey_title(gettext('Cancel query'),preferences.btn_cancel_query)) + .attr('aria-label', shortcut_accesskey_title(gettext('Cancel query'),preferences.btn_cancel_query)) .attr('accesskey', shortcut_key(preferences.btn_cancel_query)); $el.find('#btn-clear-dropdown') - .attr('title', shortcut_accesskey_title('Clear',preferences.btn_clear_options)) + .attr('title', shortcut_accesskey_title(gettext('Clear'),preferences.btn_clear_options)) + .attr('aria-label', shortcut_accesskey_title(gettext('Clear'),preferences.btn_clear_options)) .attr('accesskey', shortcut_key(preferences.btn_clear_options)); $el.find('#btn-conn-status') @@ -83,31 +96,45 @@ function updateUIPreferences(sqlEditor) { /* Accessed using ctrl,atl,shift and key */ $el.find('#btn-flash') .attr('title', - shortcut_title('Execute/Refresh',preferences.execute_query)); + shortcut_title(gettext('Execute/Refresh'),preferences.execute_query)) + .attr('aria-label', + shortcut_title(gettext('Execute/Refresh'),preferences.execute_query)); $el.find('#btn-explain') .attr('title', - shortcut_title('Explain',preferences.explain_query)); + shortcut_title(gettext('Explain'),preferences.explain_query)) + .attr('aria-label', + shortcut_title(gettext('Explain'),preferences.explain_query)); $el.find('#btn-explain-analyze') .attr('title', - shortcut_title('Explain Analyze',preferences.explain_analyze_query)); + shortcut_title(gettext('Explain Analyze'),preferences.explain_analyze_query)) + .attr('aria-label', + shortcut_title(gettext('Explain Analyze'),preferences.explain_analyze_query)); $el.find('#btn-download') .attr('title', - shortcut_title('Download as CSV',preferences.download_csv)); + shortcut_title(gettext('Download as CSV'),preferences.download_csv)) + .attr('aria-label', + shortcut_title(gettext('Download as CSV'),preferences.download_csv)); $el.find('#btn-save-data') .attr('title', - shortcut_title('Save Data Changes',preferences.save_data)); + shortcut_title(gettext('Save Data Changes'),preferences.save_data)) + .attr('aria-label', + shortcut_title(gettext('Save Data Changes'),preferences.save_data)); $el.find('#btn-commit') .attr('title', - shortcut_title('Commit',preferences.commit_transaction)); + shortcut_title(gettext('Commit'),preferences.commit_transaction)) + .attr('aria-label', + shortcut_title(gettext('Commit'),preferences.commit_transaction)); $el.find('#btn-rollback') .attr('title', - shortcut_title('Rollback',preferences.rollback_transaction)); + shortcut_title(gettext('Rollback'),preferences.rollback_transaction)) + .attr('aria-label', + shortcut_title(gettext('Rollback'),preferences.rollback_transaction)); /* Set explain options on query editor */ if (preferences.explain_verbose){ diff --git a/web/pgadmin/static/scss/_bootstrap.overrides.scss b/web/pgadmin/static/scss/_bootstrap.overrides.scss index 0b61787e2..b9aa5fea9 100644 --- a/web/pgadmin/static/scss/_bootstrap.overrides.scss +++ b/web/pgadmin/static/scss/_bootstrap.overrides.scss @@ -195,9 +195,23 @@ legend { } } - & thead, & tbody { + & thead { + & tr { + & th { + &:first-of-type { + border-left: none; + } + + &:last-of-type { + border-right: none; + } + } + } + } + + & tbody { & tr { - & td, & th { + & td { &:first-of-type { border-left: none; } diff --git a/web/pgadmin/static/vendor/backform/backform.js b/web/pgadmin/static/vendor/backform/backform.js index 1dfa5c5cb..c4d44987b 100644 --- a/web/pgadmin/static/vendor/backform/backform.js +++ b/web/pgadmin/static/vendor/backform/backform.js @@ -541,11 +541,13 @@ id: _.uniqueId('bf_') }, template: _.template([ - '<label class="<%=Backform.controlLabelClassName%>"><%=controlLabel%></label>', + '<label class="<%=Backform.controlLabelClassName%>" for="<%=id%>"><%=controlLabel%></label>', '<div class="<%=Backform.controlContainerClassName%>">', ' <div class="form-check">', ' <input type="<%=type%>" class="form-check-input <%=extraClasses.join(\' \')%>" id="<%=id%>" name="<%=name%>" <%=value ? "checked=\'checked\'" : ""%> <%=disabled ? "disabled" : ""%> <%=required ? "required" : ""%> />', - ' <label class="form-check-label" for="<%=id%>"><%=label%></label>', + ' <% if (label && label.length) { %>', + ' <label class="form-check-label" for="<%=id%>"><%=label%></label>', + ' <% } %>', ' </div>', '</div>' ].join("\n")), diff --git a/web/pgadmin/static/vendor/backgrid/backgrid.js b/web/pgadmin/static/vendor/backgrid/backgrid.js index b250ebeee..b51635bc2 100644 --- a/web/pgadmin/static/vendor/backgrid/backgrid.js +++ b/web/pgadmin/static/vendor/backgrid/backgrid.js @@ -2190,6 +2190,39 @@ var HeaderCell = Backgrid.HeaderCell = Backbone.View.extend({ }); +/** + EmptyHeaderCell is a special cell class that renders a column header cell. + The text is empty here and it is not sortable. + + @class Backgrid.EmptyHeaderCell + @extends Backbone.View + */ +var EmptyHeaderCell = Backgrid.EmptyHeaderCell = Backbone.View.extend({ + + /** @property */ + tagName: "td", + + /** + Initializer. + + @param {Object} options + @param {Backgrid.Column|Object} options.column + */ + initialize: function (options) { + this.column = options.column; + }, + + /** + Renders a empty header cell with no events + */ + render: function () { + this.$el.empty(); + this.$el.addClass(this.column.get("name")); + this.$el.addClass("renderable"); + return this; + } +}); + /** HeaderRow is a controller for a row of header cells. @@ -2215,7 +2248,13 @@ var HeaderRow = Backgrid.HeaderRow = Backgrid.Row.extend({ }, makeCell: function (column, options) { - var headerCell = column.get("headerCell") || options.headerCell || HeaderCell; + var headerCell = null; + if(column.get("label") === "" || !column.get("label")) { + headerCell = column.get("headerCell") || options.headerCell || EmptyHeaderCell; + } else { + headerCell = column.get("headerCell") || options.headerCell || HeaderCell; + } + headerCell = new headerCell({ column: column, collection: this.collection ^ permalink raw reply [nested|flat] 4+ messages in thread
* Re: [pgAdmin][RM4772][Accessibility] Provide aria-label to an invisible label where a visible label cannot be used @ 2019-12-03 07:18 Akshay Joshi <[email protected]> parent: Aditya Toshniwal <[email protected]> 0 siblings, 1 reply; 4+ messages in thread From: Akshay Joshi @ 2019-12-03 07:18 UTC (permalink / raw) To: Aditya Toshniwal <[email protected]>; +Cc: pgadmin-hackers Thanks, patch applied. On Tue, Dec 3, 2019 at 11:39 AM Aditya Toshniwal < [email protected]> wrote: > Hi Hackers, > > Attached is the patch to add label and aria-label wherever applicable. The > patch also fixes few other accessibility related errors raised by "WAVE" > extension of Chrome. > > Kindly review. > > -- > Thanks and Regards, > Aditya Toshniwal > Sr. Software Engineer | EnterpriseDB India | Pune > "Don't Complain about Heat, Plant a TREE" > -- *Thanks & Regards* *Akshay Joshi* *Sr. Software Architect* *EnterpriseDB Software India Private Limited* *Mobile: +91 976-788-8246* ^ permalink raw reply [nested|flat] 4+ messages in thread
* Re: [pgAdmin][RM4772][Accessibility] Provide aria-label to an invisible label where a visible label cannot be used @ 2019-12-04 09:30 Aditya Toshniwal <[email protected]> parent: Akshay Joshi <[email protected]> 0 siblings, 1 reply; 4+ messages in thread From: Aditya Toshniwal @ 2019-12-04 09:30 UTC (permalink / raw) To: Akshay Joshi <[email protected]>; +Cc: pgadmin-hackers Hi Hackers, Attached is the patch to fix the feature tests which were failing after the commit. The reason being, the span elements which was used to show properties tab data is changed to input now as per accessibility requirements. Also, there is no way now to check XSS of input because the input tags returns the original value on extracting the value attribute and not the escaped value. Previously, the value was HTML element under the span tag. Not to worry, input is escaped and is XSS free, handled in underscore templates. On Tue, Dec 3, 2019 at 12:48 PM Akshay Joshi <[email protected]> wrote: > Thanks, patch applied. > > On Tue, Dec 3, 2019 at 11:39 AM Aditya Toshniwal < > [email protected]> wrote: > >> Hi Hackers, >> >> Attached is the patch to add label and aria-label wherever applicable. >> The patch also fixes few other accessibility related errors raised by >> "WAVE" extension of Chrome. >> >> Kindly review. >> >> -- >> Thanks and Regards, >> Aditya Toshniwal >> Sr. Software Engineer | EnterpriseDB India | Pune >> "Don't Complain about Heat, Plant a TREE" >> > > > -- > *Thanks & Regards* > *Akshay Joshi* > > *Sr. Software Architect* > *EnterpriseDB Software India Private Limited* > *Mobile: +91 976-788-8246* > -- Thanks and Regards, Aditya Toshniwal Sr. Software Engineer | EnterpriseDB India | Pune "Don't Complain about Heat, Plant a TREE" Attachments: [application/octet-stream] RM4772.feature.patch (2.0K, 3-RM4772.feature.patch) download | inline diff: diff --git a/web/pgadmin/feature_tests/xss_checks_panels_and_query_tool_test.py b/web/pgadmin/feature_tests/xss_checks_panels_and_query_tool_test.py index 15aad7be9..71b58c013 100644 --- a/web/pgadmin/feature_tests/xss_checks_panels_and_query_tool_test.py +++ b/web/pgadmin/feature_tests/xss_checks_panels_and_query_tool_test.py @@ -64,7 +64,6 @@ class CheckForXssFeatureTest(BaseFeatureTest): self.page.add_server(self.server) self._tables_node_expandable() self._check_xss_in_browser_tree() - self._check_xss_in_properties_tab() self._check_xss_in_sql_tab() # sometime the tab for dependent does not show info, so refreshing @@ -121,21 +120,6 @@ class CheckForXssFeatureTest(BaseFeatureTest): "Browser tree" ) - def _check_xss_in_properties_tab(self): - print( - "\n\tChecking the Properties tab for XSS vulnerabilities", - file=sys.stderr, end="" - ) - self.page.click_tab("Properties") - source_code = self.page.find_by_xpath( - "//span[contains(@class,'uneditable-input')]" - ).get_attribute('innerHTML') - self._check_escaped_characters( - source_code, - "<h1>X", - "Properties tab (Backform Control)" - ) - def _check_xss_in_sql_tab(self): print( "\n\tChecking the SQL tab for for XSS vulnerabilities", diff --git a/web/regression/feature_utils/pgadmin_page.py b/web/regression/feature_utils/pgadmin_page.py index 09fc30ac8..d3b089832 100644 --- a/web/regression/feature_utils/pgadmin_page.py +++ b/web/regression/feature_utils/pgadmin_page.py @@ -977,7 +977,7 @@ class PgadminPage: def element_if_it_exists(driver): try: element = find_method_with_args(driver) - if element.is_displayed() and element.is_enabled(): + if element.is_displayed(): return True except (NoSuchElementException, StaleElementReferenceException): return False ^ permalink raw reply [nested|flat] 4+ messages in thread
* Re: [pgAdmin][RM4772][Accessibility] Provide aria-label to an invisible label where a visible label cannot be used @ 2019-12-04 12:49 Akshay Joshi <[email protected]> parent: Aditya Toshniwal <[email protected]> 0 siblings, 0 replies; 4+ messages in thread From: Akshay Joshi @ 2019-12-04 12:49 UTC (permalink / raw) To: Aditya Toshniwal <[email protected]>; +Cc: pgadmin-hackers Thanks, patch applied. On Wed, Dec 4, 2019 at 3:00 PM Aditya Toshniwal < [email protected]> wrote: > Hi Hackers, > > Attached is the patch to fix the feature tests which were failing after > the commit. The reason being, the span elements which was used to show > properties tab data is changed to input now as per accessibility > requirements. Also, there is no way now to check XSS of input because the > input tags returns the original value on extracting the value attribute and > not the escaped value. Previously, the value was HTML element under the > span tag. > Not to worry, input is escaped and is XSS free, handled in underscore > templates. > > On Tue, Dec 3, 2019 at 12:48 PM Akshay Joshi < > [email protected]> wrote: > >> Thanks, patch applied. >> >> On Tue, Dec 3, 2019 at 11:39 AM Aditya Toshniwal < >> [email protected]> wrote: >> >>> Hi Hackers, >>> >>> Attached is the patch to add label and aria-label wherever applicable. >>> The patch also fixes few other accessibility related errors raised by >>> "WAVE" extension of Chrome. >>> >>> Kindly review. >>> >>> -- >>> Thanks and Regards, >>> Aditya Toshniwal >>> Sr. Software Engineer | EnterpriseDB India | Pune >>> "Don't Complain about Heat, Plant a TREE" >>> >> >> >> -- >> *Thanks & Regards* >> *Akshay Joshi* >> >> *Sr. Software Architect* >> *EnterpriseDB Software India Private Limited* >> *Mobile: +91 976-788-8246* >> > > > -- > Thanks and Regards, > Aditya Toshniwal > Sr. Software Engineer | EnterpriseDB India | Pune > "Don't Complain about Heat, Plant a TREE" > -- *Thanks & Regards* *Akshay Joshi* *Sr. Software Architect* *EnterpriseDB Software India Private Limited* *Mobile: +91 976-788-8246* ^ permalink raw reply [nested|flat] 4+ messages in thread
end of thread, other threads:[~2019-12-04 12:49 UTC | newest] Thread overview: 4+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2019-12-03 06:08 [pgAdmin][RM4772][Accessibility] Provide aria-label to an invisible label where a visible label cannot be used Aditya Toshniwal <[email protected]> 2019-12-03 07:18 ` Akshay Joshi <[email protected]> 2019-12-04 09:30 ` Aditya Toshniwal <[email protected]> 2019-12-04 12:49 ` Akshay Joshi <[email protected]>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox