mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-07-08 18:34:24 -04:00
Merge pull request #1686 from alphagov/full-screen-row-errors-3
Allow horizontal scrolling for big spreadsheets
This commit is contained in:
107
app/assets/javascripts/fullscreenTable.js
Normal file
107
app/assets/javascripts/fullscreenTable.js
Normal 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);
|
||||
134
app/assets/stylesheets/components/fullscreen-table.scss
Normal file
134
app/assets/stylesheets/components/fullscreen-table.scss
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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())
|
||||
|
||||
Reference in New Issue
Block a user