Added aria-label to provide an invisible label where a visible label cannot be used. Fixes #4772.

This commit is contained in:
Aditya Toshniwal 2019-12-03 12:47:42 +05:30 committed by Akshay Joshi
parent d476343b99
commit 0a67b2ecb9
16 changed files with 201 additions and 72 deletions

View File

@ -12,6 +12,7 @@ New features
| `Issue #4396 <https://redmine.postgresql.org/issues/4396>`_ - Warn the user on changing the definition of Materialized View about the loss of data and its dependent objects.
| `Issue #4435 <https://redmine.postgresql.org/issues/4435>`_ - Allow drag and drop functionality for all the nodes under the database node, excluding collection nodes.
| `Issue #4711 <https://redmine.postgresql.org/issues/4711>`_ - Use a 'play' icon for the Execute Query button in the Query Tool for greater consistency with other applications.
| `Issue #4772 <https://redmine.postgresql.org/issues/4772>`_ - Added aria-label to provide an invisible label where a visible label cannot be used.
| `Issue #4773 <https://redmine.postgresql.org/issues/4773>`_ - Added role="status" attribute to all the status messages for accessibility.
| `Issue #4944 <https://redmine.postgresql.org/issues/4944>`_ - Allow Gunicorn logs in the container to be directed to a file specified through GUNICORN_ACCESS_LOGFILE.

View File

@ -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>

View File

@ -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>

View File

@ -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'}],

View File

@ -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,

View File

@ -1115,7 +1115,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 != "") { %>&nbsp;<%-label%><% } %></button>',
].join(' '));
if (location == 'header') {

View File

@ -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>

View File

@ -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)

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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();

View File

@ -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){

View File

@ -195,9 +195,23 @@ legend {
}
}
& thead, & tbody {
& thead {
& tr {
& td, & th {
& th {
&:first-of-type {
border-left: none;
}
&:last-of-type {
border-right: none;
}
}
}
}
& tbody {
& tr {
& td {
&:first-of-type {
border-left: none;
}

View File

@ -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" : ""%> />',
' <% if (label && label.length) { %>',
' <label class="form-check-label" for="<%=id%>"><%=label%></label>',
' <% } %>',
' </div>',
'</div>'
].join("\n")),

View File

@ -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