Skip to content

Commit

Permalink
Show preview of imported sheet when selecting a sheet (#34)
Browse files Browse the repository at this point in the history

On the sheet selection page, shows a preview of each sheet as the radio button is pressed, allowing users to check that it is the sheet that they want, which will be useful given the prevalence of both empty sheets and metadata sheets.

One issue we will need to work around is sheets with too many columns, currently we are using overflow-x to show a scrollbar but we might want to find a better way (such as limiting the number of columns etc).

It also removes the skips the review page until we are ready to re-instate it.
  • Loading branch information
rossjones authored Sep 9, 2024
1 parent 876f0a5 commit 94bdb70
Show file tree
Hide file tree
Showing 9 changed files with 125 additions and 48 deletions.
15 changes: 15 additions & 0 deletions lib/importer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,21 @@ exports.Initialise = (config, router, prototypeKit) => {
}, {})
}

//--------------------------------------------------------------------
// Allows the templates to get a list of viable sheet names, and for
// each one also obtain a preview of the data for display when selecting
// a sheet.
//--------------------------------------------------------------------
prototypeKit.views.addFunction("importSheetPreview", (data) => {
let session = data[IMPORTER_SESSION_KEY];
return session.sheets.map((sheetName) => {
return {
name: sheetName,
data: {rows: sheets_lib.GetPreview(session, sheetName)}
}
})
}, {})

//--------------------------------------------------------------------
// Adds a function which can be called to display an error message if
// any have been raised by the most recent post request. This allows
Expand Down
107 changes: 74 additions & 33 deletions lib/importer/nunjucks/importer/macros/sheet_selector.njk
Original file line number Diff line number Diff line change
@@ -1,53 +1,94 @@

{#
importerSheetSelector generates a radio button list which allows the
user to choose which sheet is to be used to upload data. If there is
importerSheetSelector generates a radio button list which allows the
user to choose which sheet is to be used to upload data. If there is
only a single sheet then it is selected by default.
The list of sheet names is retrieved from the current spreadsheet
The list of sheet names is retrieved from the current spreadsheet
being uploaded, and will always contain at least one sheet.
It accepts a data object which is taken from the prototype kit's
session data which is made available on every page, and contains the
data submitted from forms to the backend, and also the current
It accepts a data object which is taken from the prototype kit's
session data which is made available on every page, and contains the
data submitted from forms to the backend, and also the current
data import session.
legend is the text that should be used for the legend part of the
legend is the text that should be used for the legend part of the
radio buttons, if none is supplied, then a legend is not added.
#}
{% from "importer/macros/table_view.njk" import importerTableView %}

{% macro importerSheetSelector(data, legend) %}
{% set selectedSheet = data['importer.session'].sheet %}
{% set sheets = data['importer.session'].sheets %}
{% set sheets = importSheetPreview(data) %}
{% set tableRowIndex = sheets.length + 2 %}
{% set error = importerError(data) %}

<div class="govuk-form-group {% if error %}govuk-form-group--error{% endif %}">
<fieldset class="govuk-fieldset" aria-describedby="upload-error">

{% if legend %}
<legend class="govuk-fieldset__legend govuk-fieldset__legend--l">
<h1 class="govuk-fieldset__heading">
{{ legend }}
</h1>
</legend>
{% endif %}

{% if error %}
<p id="upload-error" class="govuk-error-message">
<span class="govuk-visually-hidden">Error:</span> {{ error.text }}
</p>
{% endif %}

<div class="govuk-radios" data-module="govuk-radios">
<style>
div.selectable { display: none; overflow-x: auto}
</style>

<div class="govuk-form {% if error %}govuk-form-group--error{% endif %}">
<div class="govuk-form-group ">
{% if legend %}
<legend class="govuk-fieldset__legend govuk-fieldset__legend--l">
<h1 class="govuk-fieldset__heading">
{{ legend }}
</h1>
</legend>
{% endif %}

{% if error %}
<p id="upload-error" class="govuk-error-message">
<span class="govuk-visually-hidden">Error:</span> {{ error.text }}
</p>
{% endif %}

<div class="govuk-radios rd-sheet-preview" data-module="govuk-radios">
{% for sheet in sheets %}
<div class="govuk-radios__item">
<input class="govuk-radios__input" id="{{sheet}}" name="sheet" type="radio" value="{{ sheet }}" {% if
selectedSheet==sheet %}checked{% endif %}>
<label class="govuk-label govuk-radios__label" for="{{sheet}}">
{{sheet}}
</label>
</div>
<input class="govuk-radios__input" id="{{sheet.name}}" name="sheet" type="radio" value="{{sheet.name}}" {% if selectedSheet==sheet.name %}checked="checked"{% endif %} data-preview-index="{{loop.index0}}" onclick="javascript:preview(this);">
<label class="govuk-label govuk-radios__label" for="{{sheet.name}}">
{{sheet.name}}
</label>
</div> <!-- .govuk-radios__item -->
{% endfor %}
</div> <!-- .govuk-radios -->
</div>
</div>

<div id="previews" class="govuk-!-margin-top-6">
{% for sheet in sheets %}
<div class="selectable">
{% if sheet.data.rows == null %}
<div class="govuk-body">
Sheet '{{ sheet.name }}' is empty
</div>
{% else %}
<div class="govuk-hint">
First {{sheet.data.rows | length}} rows of '{{sheet.name}}'
</div>
{{ importerTableView(sheet.data, hideHeader=true) }}
{% endif %}
</div>
</fieldset>
{% endfor %}
</div>

<script>
const previews = Array.from(document.getElementById("previews").children);
const preview = (elem) => {
let index = parseInt(elem.dataset.previewIndex ?? -1);
if (index < 0) {
return;
}
previews.forEach((p) => {p.style.display = "none"});
previews[index].style.display = "block";
}
{# Show the preview any default selected options when the page loads #}
const currentlySelected = document.querySelector('input[type="radio"]:checked');
preview(currentlySelected);
</script>
{% endmacro %}
9 changes: 4 additions & 5 deletions lib/importer/nunjucks/importer/macros/table_view.njk
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@


{% macro importerTableView(data, hideHeader=false) %}
{% set obj = importerMappedData(data) %}
{% set headers = obj.headers %}
{% set rows = obj.rows %}
{% set moreRowsAvailable = obj.extraRecordCount > 0 %}
{% set moreRowsCount = obj.extraRecordCount %}
{% set headers = data.headers %}
{% set rows = data.rows %}
{% set moreRowsAvailable = data.extraRecordCount > 0 %}
{% set moreRowsCount = data.extraRecordCount %}

<table class="selectable govuk-body" data-persist-selection="true">
{% if not hideHeader %}
Expand Down
26 changes: 26 additions & 0 deletions lib/importer/sheets.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,32 @@ exports.GetHeader = (session) => {
};


// Given a session and a sheet name, gets a preview of the data held there
// returning either a 2dim array of rows/cells or null if that specific sheet
// is empty.
exports.GetPreview = (session, sheet) => {
const dimensions = backend
.SessionGetInputDimensions(session.backendSid)
.sheetDimensions.get(sheet);

const preview = backend.SessionGetInputSampleRows(session.backendSid, {
sheet: sheet,
start: {row: 0, column: 0},
end: {row: dimensions.rows, column: dimensions.columns > 0 ? dimensions.columns - 1 : 0 }
}, 10, 0, 0);

// TODO: Is there a better way to tell if the sheet is empty than iterating all the cells?
let cellCount = preview[0].reduce((acc, row) => {
return acc + row.reduce((innerAcc, cell) => {
if (cell && cell.length > 0) return innerAcc + 1;
return innerAcc;
},0)
}, 0)

return cellCount == 0 ? null : preview[0];
};


// Returns the total number of columns from the input
exports.GetTotalColumns = (session) => {
return backend
Expand Down
4 changes: 1 addition & 3 deletions lib/importer/templates/review.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,11 @@
<div class="govuk-grid-column-two-thirds">
<h1 class="govuk-heading-l">Review your data</h1>

{{ importerTableView(data) }}

<form action="{{ importerReviewDataPath('/success') }}" method="post">
<div class="govuk-button-group">
{{ govukButton({ text: "Submit" }) }}
</div>
</form>
</div>
</div>
{% endblock %}
{% endblock %}
2 changes: 1 addition & 1 deletion lib/importer/templates/select_sheet.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

{% block content %}
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<div class="govuk-grid-column-full">
<form action="{{ importerSelectSheetPath('/select_header_row') }}" method="post">
<div class="govuk-form-group">
<fieldset class="govuk-fieldset">
Expand Down
2 changes: 1 addition & 1 deletion prototypes/basic/app/views/mapping.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ <h1 class="govuk-heading-l">Map columns</h1>

<h2 class="govuk-heading-m">{{sheet}}</h2>

<form action="{{ importerMapDataPath('/review') }}" method="post">
<form action="{{ importerMapDataPath('/success') }}" method="post">

{{ importerFieldMapper(data) }}

Expand Down
6 changes: 2 additions & 4 deletions prototypes/basic/app/views/review.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,19 @@

{% block pageTitle %} – {{ serviceName }} – GOV.UK Prototype Kit {% endblock %}
{% block beforeContent %}
{{ govukBackLink({ text: "Back", href: "javascript:window.history.back()" }) }}
{{ govukBackLink({ text: "Back", href: "javascript:window.history.back()" }) }}
{% endblock %}

{% block content %}
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<h1 class="govuk-heading-l">Review your data</h1>

{{ importerTableView(data) }}

<form action="{{ importerReviewDataPath('/success') }}" method="post">
<div class="govuk-button-group">
{{ govukButton({ text: "Submit" }) }}
</div>
</form>
</div>
</div>
{% endblock %}
{% endblock %}
2 changes: 1 addition & 1 deletion prototypes/basic/app/views/select_sheet.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

{% block content %}
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<div class="govuk-grid-column-full">
<form action="{{ importerSelectSheetPath('/select_header_row') }}" method="post">
<div class="govuk-form-group">
<fieldset class="govuk-fieldset">
Expand Down

0 comments on commit 94bdb70

Please sign in to comment.