Merge pull request #1686 from alphagov/full-screen-row-errors-3

Allow horizontal scrolling for big spreadsheets
This commit is contained in:
Chris Hill-Scott
2017-12-20 12:30:42 +00:00
committed by GitHub
9 changed files with 377 additions and 104 deletions

View File

@@ -0,0 +1,107 @@
(function(Modules) {
"use strict";
Modules.FullscreenTable = function() {
this.start = function(component) {
this.$component = $(component);
this.$table = this.$component.find('table');
this.nativeHeight = this.$component.innerHeight() + 20; // 20px to allow room for scrollbar
this.topOffset = this.$component.offset().top;
this.insertShims();
this.maintainWidth();
this.maintainHeight();
this.toggleShadows();
$(window)
.on('scroll resize', this.maintainHeight)
.on('resize', this.maintainWidth);
this.$scrollableTable
.on('scroll', this.toggleShadows)
.on('scroll', this.maintainHeight);
if (
window.GOVUK.stopScrollingAtFooter &&
window.GOVUK.stopScrollingAtFooter.updateFooterTop
) {
window.GOVUK.stopScrollingAtFooter.updateFooterTop();
}
};
this.insertShims = () => {
this.$table.wrap('<div class="fullscreen-scrollable-table"/>');
this.$component
.append(
this.$component.find('.fullscreen-scrollable-table')
.clone()
.addClass('fullscreen-fixed-table')
.removeClass('fullscreen-scrollable-table')
.attr('role', 'presentation')
)
.append(
'<div class="fullscreen-right-shadow" />'
)
.after(
$("<div class='fullscreen-shim'/>").css({
'height': this.nativeHeight,
'top': this.topOffset
})
);
this.$scrollableTable = this.$component.find('.fullscreen-scrollable-table');
this.$fixedTable = this.$component.find('.fullscreen-fixed-table');
};
this.maintainHeight = () => {
let height = Math.min(
$(window).height() - this.topOffset + $('html, body').scrollTop() + 5,
this.nativeHeight
);
this.$scrollableTable.outerHeight(height);
this.$fixedTable.outerHeight(height);
};
this.maintainWidth = () => {
let indexColumnWidth = this.$fixedTable.find('.table-field-index').outerWidth();
this.$scrollableTable
.css({
'width': this.$component.parent('main').width() - indexColumnWidth,
'margin-left': indexColumnWidth
});
this.$fixedTable
.width(indexColumnWidth + 4);
};
this.toggleShadows = () => {
this.$fixedTable
.toggleClass(
'fullscreen-scrolled-table',
this.$scrollableTable.scrollLeft() > 0
);
this.$component.find('.fullscreen-right-shadow')
.toggleClass(
'visible',
this.$scrollableTable.scrollLeft() < (this.$table.width() - this.$scrollableTable.width())
);
};
};
})(window.GOVUK.Modules);

View File

@@ -0,0 +1,134 @@
.fullscreen {
&-content {
background: $white;
z-index: 10;
overflow-y: hidden;
box-sizing: border-box;
position: absolute;
margin: 5px 0 $gutter 0;
padding: 0 0 0 0;
overflow: hidden;
border-bottom: 1px solid $border-colour;
.table {
margin-bottom: 0;
tr:last-child {
td {
border-bottom: 1px solid $white;
}
}
}
th,
.table-field-error-label,
.table-field-center-aligned {
white-space: nowrap;
}
}
&-right-shadow {
position: absolute;
top: 0;
right: 0;
width: 4px;
height: 100%;
z-index: 200;
&.visible {
transition: box-shadow 0.3s ease-in-out;
box-shadow: inset -1px 0 0 0 $border-colour, inset -3px 0 0 0 rgba($border-colour, 0.2);
}
}
&-scrollable-table {
overflow-x: auto;
overflow-y: hidden;
.table-field-heading-first,
.table-field-index {
display: none;
}
.table-field-center-aligned {
position: relative;
z-index: 150;
background: $white;
}
&::-webkit-scrollbar {
-webkit-appearance: none;
}
&::-webkit-scrollbar:horizontal {
height: 11px;
background-color: $white;
}
&::-webkit-scrollbar-thumb {
border-radius: 8px;
border: 2px solid $white;
background-color: rgba(0, 0, 0, .5);
}
&::-webkit-scrollbar-track {
background-color: $white;
border-radius: 8px;
}
}
&-fixed-table {
position: absolute;
top: 0;
overflow: hidden;
.table-field-heading {
visibility: hidden;
}
.table-field-center-aligned {
width: 0;
position: relative;
z-index: 100;
visibility: hidden;
}
.table-field-heading-first,
.table-field-index {
transition: none;
position: relative;
z-index: 200;
background: $white;
}
}
&-scrolled-table {
padding-bottom: 20px;
.table-field-heading-first,
.table-field-index {
transition: box-shadow 0.3s ease-in-out;
box-shadow: 1px 0 0 0 $border-colour, 3px 0 0 0 rgba($border-colour, 0.2);
}
}
&-shim {
width: 100%;
position: relative;
z-index: 9;
}
}

View File

@@ -16,9 +16,24 @@
margin-bottom: 20px;
}
.back-to-top-link {
position: absolute;
top: $gutter;
right: $gutter-half;
opacity: 0;
transition: opacity 0.1s ease-in-out;
@include ie-lte(8) {
display: none;
}
}
}
.content-fixed {
position: fixed;
top: 0;
background: $white;
@@ -28,6 +43,12 @@
border-bottom: 1px solid $border-colour;
box-shadow: 0 2px 0 0 rgba($border-colour, 0.2);
transition: background 0.6s ease-in-out, margin-top 0.4s ease-out;
.back-to-top-link {
opacity: 1;
transition: opacity 0.6s ease-in-out;
}
}
.shim {

View File

@@ -209,10 +209,9 @@
.table-show-more-link {
@include core-16;
color: $secondary-text-colour;
margin-top: -30px;
margin-bottom: $gutter * 1.3333;
border-bottom: 1px solid $border-colour;
padding: 0.75em 0 0.5625em 0;
padding: 10px 0 10px 0;
text-align: center;
}

View File

@@ -59,6 +59,7 @@ $path: '/static/images/';
@import 'components/letter';
@import 'components/live-search';
@import 'components/stick-at-top-when-scrolling';
@import 'components/fullscreen-table';
@import 'components/vendor/breadcrumbs';
@import 'components/vendor/responsive-embed';

View File

@@ -114,54 +114,58 @@
{% endcall %}
</div>
<div class="bottom-gutter-3-2">
{% if request.args.from_test %}
<a href="{{ back_link }}" class="page-footer-back-link">Back</a>
{% else %}
{{file_upload(form.file, button_text='Re-upload your file')}}
{% endif %}
<div class="js-stick-at-top-when-scrolling">
<div class="form-group">
{% if request.args.from_test %}
<a href="{{ back_link }}" class="page-footer-back-link">Back</a>
{% else %}
{{file_upload(form.file, button_text='Re-upload your file')}}
{% endif %}
</div>
<a href="#content" class="back-to-top-link">Back to top</a>
</div>
{% if not request.args.from_test %}
<h2 class="heading-medium" id="{{ file_contents_header_id }}">{{ original_file_name }}</h2>
{% call(item, row_number) list_table(
recipients.initial_annotated_rows_with_errors if row_errors and not recipients.missing_column_headers else recipients.initial_annotated_rows,
caption=original_file_name,
caption_visible=False,
field_headings=[
'<span class="visually-hidden">Row in file</span><span aria-hidden="true">1</span>'|safe
] + recipients.column_headers
) %}
{% call index_field() %}
<span class="{% if item.index in recipients.rows_with_errors %}table-field-error{% endif %}">
{{ item.index + 2 }}
</span>
{% endcall %}
{% for column in recipients.column_headers %}
{% if item['columns'][column].error and not recipients.missing_column_headers %}
{% call field() %}
<span>
<span class="table-field-error-label">{{ item['columns'][column].error }}</span>
{{ item['columns'][column].data if item['columns'][column].data != None }}
</span>
{% endcall %}
{% elif item['columns'][column].ignore %}
{{ text_field(item['columns'][column].data or '', status='default') }}
{% else %}
{{ text_field(item['columns'][column].data or '') }}
{% endif %}
{% endfor %}
{% if item['columns'].get(None) %}
{% for column in item['columns'][None].data %}
{{ text_field(column, status='default') }}
<div class="fullscreen-content" data-module="fullscreen-table">
{% call(item, row_number) list_table(
recipients.initial_annotated_rows_with_errors if row_errors and not recipients.missing_column_headers else recipients.initial_annotated_rows,
caption=original_file_name,
caption_visible=False,
field_headings=[
'<span class="visually-hidden">Row in file</span><span aria-hidden="true">1</span>'|safe
] + recipients.column_headers
) %}
{% call index_field() %}
<span>
{{ item.index + 2 }}
</span>
{% endcall %}
{% for column in recipients.column_headers %}
{% if item['columns'][column].error and not recipients.missing_column_headers %}
{% call field() %}
<span>
<span class="table-field-error-label">{{ item['columns'][column].error }}</span>
{{ item['columns'][column].data if item['columns'][column].data != None }}
</span>
{% endcall %}
{% elif item['columns'][column].ignore %}
{{ text_field(item['columns'][column].data or '', status='default') }}
{% else %}
{{ text_field(item['columns'][column].data or '') }}
{% endif %}
{% endfor %}
{% endif %}
{% endcall %}
{% endif %}
{% if item['columns'].get(None) %}
{% for column in item['columns'][None].data %}
{{ text_field(column, status='default') }}
{% endfor %}
{% endif %}
{% endcall %}
{% endif %}
</div>
{% if recipients.too_many_rows %}
<p class="table-show-more-link">

View File

@@ -49,32 +49,34 @@
<h2 class="heading-medium" id="{{ file_contents_header_id }}">{{ original_file_name }}</h2>
{% call(item, row_number) list_table(
recipients.initial_annotated_rows_with_errors if row_errors and not recipients.missing_column_headers else recipients.initial_annotated_rows,
caption=original_file_name,
caption_visible=False,
field_headings=[
'<span class="visually-hidden">Row in file</span><span aria-hidden="true">1</span>'|safe
] + recipients.column_headers
) %}
{% call index_field() %}
<span class="{% if item.index in recipients.rows_with_errors %}table-field-error{% endif %}">
{{ item.index + 2 }}
</span>
{% endcall %}
{% for column in recipients.column_headers %}
{% if item['columns'][column].ignore %}
{{ text_field(item['columns'][column].data or '', status='default') }}
{% else %}
{{ text_field(item['columns'][column].data or '') }}
{% endif %}
{% endfor %}
{% if item['columns'].get(None) %}
{% for column in item['columns'][None].data %}
{{ text_field(column, status='default') }}
<div class="fullscreen-content" data-module="fullscreen-table">
{% call(item, row_number) list_table(
recipients.initial_annotated_rows_with_errors if row_errors and not recipients.missing_column_headers else recipients.initial_annotated_rows,
caption=original_file_name,
caption_visible=False,
field_headings=[
'<span class="visually-hidden">Row in file</span><span aria-hidden="true">1</span>'|safe
] + recipients.column_headers
) %}
{% call index_field() %}
<span class="{% if item.index in recipients.rows_with_errors %}table-field-error{% endif %}">
{{ item.index + 2 }}
</span>
{% endcall %}
{% for column in recipients.column_headers %}
{% if item['columns'][column].ignore %}
{{ text_field(item['columns'][column].data or '', status='default') }}
{% else %}
{{ text_field(item['columns'][column].data or '') }}
{% endif %}
{% endfor %}
{% endif %}
{% endcall %}
{% if item['columns'].get(None) %}
{% for column in item['columns'][None].data %}
{{ text_field(column, status='default') }}
{% endfor %}
{% endif %}
{% endcall %}
</div>
{% endif %}

View File

@@ -19,7 +19,7 @@
{% block maincolumn_content %}
<div class="bottom-gutter">
<div class="bottom-gutter-1-2">
{% call banner_wrapper(type='dangerous') %}
{% if row_errors|length == 1 %}
<h1 class='banner-title' data-module="track-error" data-error-type="Bad rows" data-error-label="{{ upload_id }}">
@@ -45,44 +45,48 @@
{% endcall %}
</div>
<div class="bottom-gutter-3-2">
{{ file_upload(form.file, button_text='Re-upload your file') }}
<div class="js-stick-at-top-when-scrolling">
<div class="form-group">
{{ file_upload(form.file, button_text='Re-upload your file') }}
</div>
<a href="#content" class="back-to-top-link">Back to top</a>
</div>
{% call(item, row_number) list_table(
recipients.initial_annotated_rows_with_errors if row_errors and not recipients.missing_column_headers else recipients.initial_annotated_rows,
caption=original_file_name,
caption_visible=False,
field_headings=[
'<span class="visually-hidden">Row in file</span><span aria-hidden="true" class="table-field-invisible-error">1</span>'|safe
] + recipients.column_headers
) %}
{% call index_field() %}
<span class="{% if item.index in recipients.rows_with_errors %}table-field-error{% endif %}">
{{ item.index + 2 }}
</span>
{% endcall %}
{% for column in recipients.column_headers %}
{% if item['columns'][column].error and not recipients.missing_column_headers %}
{% call field() %}
<span>
<span class="table-field-error-label">{{ item['columns'][column].error }}</span>
{{ item['columns'][column].data if item['columns'][column].data != None }}
</span>
{% endcall %}
{% elif item['columns'][column].ignore %}
{{ text_field(item['columns'][column].data or '', status='default') }}
{% else %}
{{ text_field(item['columns'][column].data or '') }}
{% endif %}
{% endfor %}
{% if item['columns'].get(None) %}
{% for column in item['columns'][None].data %}
{{ text_field(column, status='default') }}
<div class="fullscreen-content" data-module="fullscreen-table">
{% call(item, row_number) list_table(
recipients.initial_annotated_rows_with_errors if row_errors and not recipients.missing_column_headers else recipients.initial_annotated_rows,
caption=original_file_name,
caption_visible=False,
field_headings=[
'<span class="visually-hidden">Row in file</span><span aria-hidden="true" class="table-field-invisible-error">1</span>'|safe
] + recipients.column_headers
) %}
{% call index_field() %}
<span class="{% if item.index in recipients.rows_with_errors %}table-field-error{% endif %}">
{{ item.index + 2 }}
</span>
{% endcall %}
{% for column in recipients.column_headers %}
{% if item['columns'][column].error and not recipients.missing_column_headers %}
{% call field() %}
<span>
<span class="table-field-error-label">{{ item['columns'][column].error }}</span>
{{ item['columns'][column].data if item['columns'][column].data != None }}
</span>
{% endcall %}
{% elif item['columns'][column].ignore %}
{{ text_field(item['columns'][column].data or '', status='default') }}
{% else %}
{{ text_field(item['columns'][column].data or '') }}
{% endif %}
{% endfor %}
{% endif %}
{% endcall %}
{% if item['columns'].get(None) %}
{% for column in item['columns'][None].data %}
{{ text_field(column, status='default') }}
{% endfor %}
{% endif %}
{% endcall %}
</div>
{% if count_of_displayed_recipients < count_of_recipients %}
<p class="table-show-more-link">
{% if row_errors and not recipients.missing_column_headers %}

View File

@@ -68,6 +68,7 @@ gulp.task('javascripts', () => gulp
paths.src + 'javascripts/liveSearch.js',
paths.src + 'javascripts/errorTracking.js',
paths.src + 'javascripts/preventDuplicateFormSubmissions.js',
paths.src + 'javascripts/fullscreenTable.js',
paths.src + 'javascripts/main.js'
])
.pipe(plugins.prettyerror())