From a4c18b49574c866db2c226c5aa83b9f7bf922129 Mon Sep 17 00:00:00 2001 From: Tommaso Amici Date: Tue, 10 May 2022 16:28:10 +0200 Subject: [PATCH] Add optional caption field to TypedTableBlock - Closes #8507 - Make caption default to "" for tables that predate adding the caption field - Update the html generated in JS so the rendered form matches our new styles - Use a consistent period (full stop) at the end of the help text sentence (including table fields) --- CHANGELOG.txt | 1 + CONTRIBUTORS.md | 1 + .../__snapshots__/table.test.js.snap | 2 +- .../entrypoints/contrib/table_block/table.js | 2 +- .../contrib/table_block/table.test.js | 6 +-- .../typed_table_block.test.js.snap | 48 +++++++++++++++++++ .../typed_table_block/typed_table_block.js | 29 +++++++++++ .../typed_table_block.test.js | 15 ++++++ docs/releases/6.0.md | 1 + wagtail/contrib/table_block/blocks.py | 4 +- wagtail/contrib/typed_table_block/blocks.py | 27 +++++++++-- .../typed_table_block/typed_table_block.html | 3 ++ wagtail/contrib/typed_table_block/tests.py | 7 +++ 13 files changed, 136 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index a80f093e3a..b4286d6323 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -37,6 +37,7 @@ Changelog * Show character counts on RichTextBlock with `max_length` (Elhussein Almasri) * Move locale selector in generic IndexView to a filter (Sage Abdullah) * Add ability to customise a page's copy form (Neeraj Yetheendran) + * Add optional caption field to `TypedTableBlock` (Tommaso Amici, Cynthia Kiser) * Fix: Update system check for overwriting storage backends to recognise the `STORAGES` setting introduced in Django 4.2 (phijma-leukeleu) * Fix: Prevent password change form from raising a validation error when browser autocomplete fills in the "Old password" field (Chiemezuo Akujobi) * Fix: Ensure that the legacy dropdown options, when closed, do not get accidentally clicked by other interactions wide viewports (CheesyPhoenix, Christer Jensen) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 161a659f4a..b1d56c02d2 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -782,6 +782,7 @@ * Badr Fourane * Vaishnav Dasari * Aditya +* Tommaso Amici ## Translators diff --git a/client/src/entrypoints/contrib/table_block/__snapshots__/table.test.js.snap b/client/src/entrypoints/contrib/table_block/__snapshots__/table.test.js.snap index 9079e38027..fe6ebd08db 100644 --- a/client/src/entrypoints/contrib/table_block/__snapshots__/table.test.js.snap +++ b/client/src/entrypoints/contrib/table_block/__snapshots__/table.test.js.snap @@ -28,7 +28,7 @@ exports[`telepath: wagtail.widgets.TableInput it renders correctly 1`] = `
-
A heading that identifies the overall topic of the table, and is useful for screen reader users
+
A heading that identifies the overall topic of the table, and is useful for screen reader users.
diff --git a/client/src/entrypoints/contrib/table_block/table.js b/client/src/entrypoints/contrib/table_block/table.js index 8743cc4e39..134b7edd40 100644 --- a/client/src/entrypoints/contrib/table_block/table.js +++ b/client/src/entrypoints/contrib/table_block/table.js @@ -263,7 +263,7 @@ class TableInput {
-
${this.strings['A heading that identifies the overall topic of the table, and is useful for screen reader users']}
+
${this.strings['A heading that identifies the overall topic of the table, and is useful for screen reader users.']}
diff --git a/client/src/entrypoints/contrib/table_block/table.test.js b/client/src/entrypoints/contrib/table_block/table.test.js index becbf79bb0..26feabba33 100644 --- a/client/src/entrypoints/contrib/table_block/table.test.js +++ b/client/src/entrypoints/contrib/table_block/table.test.js @@ -40,8 +40,8 @@ const TEST_STRINGS = { 'Display the first column as a header.', 'Table caption': 'Table caption', - 'A heading that identifies the overall topic of the table, and is useful for screen reader users': - 'A heading that identifies the overall topic of the table, and is useful for screen reader users', + 'A heading that identifies the overall topic of the table, and is useful for screen reader users.': + 'A heading that identifies the overall topic of the table, and is useful for screen reader users.', 'Table': 'Table', }; @@ -163,7 +163,7 @@ describe('telepath: wagtail.widgets.TableInput', () => { "Affichez la première colonne sous forme d'en-tête.", 'Table caption': 'Légende du tableau', - 'A heading that identifies the overall topic of the table, and is useful for screen reader users': + 'A heading that identifies the overall topic of the table, and is useful for screen reader users.': "Un en-tête qui identifie le sujet général du tableau et qui est utile pour les utilisateurs de lecteurs d'écran", 'Table': 'Tableau', }; diff --git a/client/src/entrypoints/contrib/typed_table_block/__snapshots__/typed_table_block.test.js.snap b/client/src/entrypoints/contrib/typed_table_block/__snapshots__/typed_table_block.test.js.snap index 9453f8a6e6..aa39d95ee6 100644 --- a/client/src/entrypoints/contrib/typed_table_block/__snapshots__/typed_table_block.test.js.snap +++ b/client/src/entrypoints/contrib/typed_table_block/__snapshots__/typed_table_block.test.js.snap @@ -2,6 +2,22 @@ exports[`wagtail.contrib.typed_table_block.blocks.TypedTableBlock it renders correctly 1`] = ` "
+
+ +
+
+
+ A heading that identifies the overall topic of the table, and is useful for screen reader users. +
+
+
+ + +
+
+
@@ -92,6 +108,22 @@ exports[`wagtail.contrib.typed_table_block.blocks.TypedTableBlock it renders cor exports[`wagtail.contrib.typed_table_block.blocks.TypedTableBlock setError passes error messages to children 1`] = ` "
+
+ +
+
+
+ A heading that identifies the overall topic of the table, and is useful for screen reader users. +
+
+
+ + +
+
+
@@ -182,6 +214,22 @@ exports[`wagtail.contrib.typed_table_block.blocks.TypedTableBlock setError passe exports[`wagtail.contrib.typed_table_block.blocks.TypedTableBlock setError shows non-block errors 1`] = ` "

This is just generally wrong

+
+ +
+
+
+ A heading that identifies the overall topic of the table, and is useful for screen reader users. +
+
+
+ + +
+
+
diff --git a/client/src/entrypoints/contrib/typed_table_block/typed_table_block.js b/client/src/entrypoints/contrib/typed_table_block/typed_table_block.js index e0cbbe0da0..16b4a8c894 100644 --- a/client/src/entrypoints/contrib/typed_table_block/typed_table_block.js +++ b/client/src/entrypoints/contrib/typed_table_block/typed_table_block.js @@ -13,6 +13,8 @@ export class TypedTableBlock { this.blockDef = blockDef; this.type = blockDef.name; + this.caption = ''; + // list of column definition objects, each consisting of fields: // * blockDef: the block definition object // * position: the 0-indexed position of this column within the list of columns @@ -44,8 +46,25 @@ export class TypedTableBlock { }); const strings = this.blockDef.meta.strings; + const captionID = `${h(prefix)}-caption`; const dom = $(`
+
+ +
+
+
+ ${strings.CAPTION_HELP_TEXT} +
+
+
+ + +
+
+
@@ -89,6 +108,7 @@ export class TypedTableBlock { `); $(placeholder).replaceWith(dom); this.container = dom; + this.captionInput = dom.find(`#${captionID}`).get(0); this.thead = dom.find('table > thead').get(0); this.tbody = dom.find('table > tbody').get(0); @@ -175,6 +195,7 @@ export class TypedTableBlock { clear() { // reset to initial empty state with no rows or columns + this.setCaption(''); this.columns = []; this.rows = []; this.columnCountIncludingDeleted = 0; @@ -202,6 +223,11 @@ export class TypedTableBlock { this.addRowButton.hide(); } + setCaption(caption) { + this.caption = caption; + this.captionInput.value = caption; + } + insertColumn(index, blockDef, opts) { const column = { blockDef, @@ -454,6 +480,7 @@ export class TypedTableBlock { state.rows.forEach((row, index) => { this.insertRow(index, row.values); }); + this.setCaption(state.caption); } } @@ -483,6 +510,7 @@ export class TypedTableBlock { rows: this.rows.map((row) => ({ values: row.blocks.map((block) => block.getState()), })), + caption: this.caption, }; return state; } @@ -506,6 +534,7 @@ export class TypedTableBlock { rows: this.rows.map((row) => ({ values: row.blocks.map((block) => block.getValue()), })), + caption: this.caption, }; return value; } diff --git a/client/src/entrypoints/contrib/typed_table_block/typed_table_block.test.js b/client/src/entrypoints/contrib/typed_table_block/typed_table_block.test.js index 751c44bec6..fd6c26ec3d 100644 --- a/client/src/entrypoints/contrib/typed_table_block/typed_table_block.test.js +++ b/client/src/entrypoints/contrib/typed_table_block/typed_table_block.test.js @@ -101,6 +101,9 @@ describe('wagtail.contrib.typed_table_block.blocks.TypedTableBlock', () => { helpText: 'use plenty of these', helpIcon: '', strings: { + CAPTION: 'Caption', + CAPTION_HELP_TEXT: + 'A heading that identifies the overall topic of the table, and is useful for screen reader users.', ADD_COLUMN: 'Add column', ADD_ROW: 'Add row', COLUMN_HEADING: 'Column heading', @@ -123,6 +126,7 @@ describe('wagtail.contrib.typed_table_block.blocks.TypedTableBlock', () => { { type: 'test_block_b', heading: 'Quantity' }, ], rows: [{ values: ['Cheese', 3] }, { values: ['Peas', 5] }], + caption: 'A shopping list', }, ); }); @@ -131,6 +135,9 @@ describe('wagtail.contrib.typed_table_block.blocks.TypedTableBlock', () => { expect(document.body.innerHTML).toMatchSnapshot(); expect(boundBlock.columns.length).toBe(2); expect(boundBlock.rows.length).toBe(2); + expect(document.getElementsByName('mytable-caption')[0].value).toBe( + 'A shopping list', + ); }); test('can be cleared', () => { @@ -141,6 +148,14 @@ describe('wagtail.contrib.typed_table_block.blocks.TypedTableBlock', () => { '0', ); expect(document.getElementsByName('mytable-row-count')[0].value).toBe('0'); + expect(document.getElementsByName('mytable-caption')[0].value).toBe(''); + }); + + test('supports adding a caption', () => { + boundBlock.setCaption('A shopping list'); + expect(document.getElementsByName('mytable-caption')[0].value).toBe( + 'A shopping list', + ); }); test('supports inserting columns', () => { diff --git a/docs/releases/6.0.md b/docs/releases/6.0.md index 9dc6ce6501..2016741296 100644 --- a/docs/releases/6.0.md +++ b/docs/releases/6.0.md @@ -59,6 +59,7 @@ Thank you to Thibaud Colas and Badr Fourane for their work on this feature. * Use SlugInput on all SlugFields by default (LB (Ben) Johnston) * Show character counts on RichTextBlock with `max_length` (Elhussein Almasri) * Move locale selector in generic IndexView to a filter (Sage Abdullah) + * Add optional caption field to `TypedTableBlock` (Tommaso Amici, Cynthia Kiser) ### Bug fixes diff --git a/wagtail/contrib/table_block/blocks.py b/wagtail/contrib/table_block/blocks.py index 39b415c54d..dcacd9b4f7 100644 --- a/wagtail/contrib/table_block/blocks.py +++ b/wagtail/contrib/table_block/blocks.py @@ -76,8 +76,8 @@ class TableInputAdapter(WidgetAdapter): "Display the first column as a header." ), "Table caption": _("Table caption"), - "A heading that identifies the overall topic of the table, and is useful for screen reader users": _( - "A heading that identifies the overall topic of the table, and is useful for screen reader users" + "A heading that identifies the overall topic of the table, and is useful for screen reader users.": _( + "A heading that identifies the overall topic of the table, and is useful for screen reader users." ), "Table": _("Table"), } diff --git a/wagtail/contrib/typed_table_block/blocks.py b/wagtail/contrib/typed_table_block/blocks.py index c875fd4883..d8d1a5ad35 100644 --- a/wagtail/contrib/typed_table_block/blocks.py +++ b/wagtail/contrib/typed_table_block/blocks.py @@ -40,13 +40,15 @@ class TypedTableBlockValidationError(ValidationError): class TypedTable: template = "typed_table_block/typed_table_block.html" - def __init__(self, columns, row_data): + def __init__(self, columns, row_data, caption: str): # a list of dicts, each with items 'block' (the block instance) and 'heading' self.columns = columns # a list of dicts, each with an item 'values' (the list of block values) self.row_data = row_data + self.caption = caption + @property def rows(self): """ @@ -86,6 +88,8 @@ class BaseTypedTableBlock(Block): self.child_blocks[name] = block def value_from_datadict(self, data, files, prefix): + caption = data["%s-caption" % prefix] + column_count = int(data["%s-column-count" % prefix]) columns = [ { @@ -123,6 +127,7 @@ class BaseTypedTableBlock(Block): {"block": col["block"], "heading": col["heading"]} for col in columns ], row_data=[{"values": row["values"]} for row in rows], + caption=caption, ) def get_prep_value(self, table): @@ -141,11 +146,13 @@ class BaseTypedTableBlock(Block): } for row in table.row_data ], + "caption": table.caption, } else: return { "columns": [], "rows": [], + "caption": "", } def to_python(self, value): @@ -170,11 +177,13 @@ class BaseTypedTableBlock(Block): {"values": [column_data[row_index] for column_data in columns_data]} for row_index in range(0, len(value["rows"])) ], + caption=value.get("caption", ""), ) else: return TypedTable( columns=[], row_data=[], + caption="", ) def get_form_state(self, table): @@ -193,11 +202,13 @@ class BaseTypedTableBlock(Block): } for row in table.row_data ], + "caption": table.caption, } else: return { "columns": [], "rows": [], + "caption": "", } def clean(self, table): @@ -223,10 +234,16 @@ class BaseTypedTableBlock(Block): if cell_errors: raise TypedTableBlockValidationError(cell_errors=cell_errors) else: - return TypedTable(columns=table.columns, row_data=cleaned_rows) + return TypedTable( + columns=table.columns, row_data=cleaned_rows, caption=table.caption + ) else: - return TypedTable(columns=[], row_data=[]) + return TypedTable( + columns=[], + row_data=[], + caption="", + ) def deconstruct(self): """ @@ -274,6 +291,10 @@ class TypedTableBlockAdapter(Adapter): "required": block.required, "icon": block.meta.icon, "strings": { + "CAPTION": _("Caption"), + "CAPTION_HELP_TEXT": _( + "A heading that identifies the overall topic of the table, and is useful for screen reader users." + ), "ADD_COLUMN": _("Add column"), "ADD_ROW": _("Add row"), "COLUMN_HEADING": _("Column heading"), diff --git a/wagtail/contrib/typed_table_block/templates/typed_table_block/typed_table_block.html b/wagtail/contrib/typed_table_block/templates/typed_table_block/typed_table_block.html index 66430f31b7..524af5ce56 100644 --- a/wagtail/contrib/typed_table_block/templates/typed_table_block/typed_table_block.html +++ b/wagtail/contrib/typed_table_block/templates/typed_table_block/typed_table_block.html @@ -1,5 +1,8 @@ {% load wagtailcore_tags %} + {% if value.caption %} + + {% endif %} {% for col in value.columns %} diff --git a/wagtail/contrib/typed_table_block/tests.py b/wagtail/contrib/typed_table_block/tests.py index f0ab668f81..cd8d5a7d4f 100644 --- a/wagtail/contrib/typed_table_block/tests.py +++ b/wagtail/contrib/typed_table_block/tests.py @@ -38,6 +38,7 @@ class TestTableBlock(TestCase): ) self.form_data = { + "table-caption": "Countries and their food", "table-column-count": "2", "table-row-count": "3", "table-column-0-type": "country", @@ -71,6 +72,7 @@ class TestTableBlock(TestCase): {"values": ["nl", "A small country with stroopwafels"]}, {"values": ["fr", "A large country with baguettes"]}, ], + "caption": "Countries and their food", } def test_value_from_datadict(self): @@ -81,6 +83,7 @@ class TestTableBlock(TestCase): table = self.block.value_from_datadict(self.form_data, {}, "table") self.assertIsInstance(table, TypedTable) + self.assertEqual(table.caption, "Countries and their food") self.assertEqual(len(table.columns), 2) self.assertEqual(table.columns[0]["heading"], "Country") self.assertEqual(table.columns[1]["heading"], "Description") @@ -103,6 +106,7 @@ class TestTableBlock(TestCase): # Column id 1 is a population column that was deleted before being replaced by the # current one with id 3. form_data = { + "table-caption": "Countries and their food", # table-column-count includes deleted columns, as it's telling the server code # the maximum column ID number it should consider "table-column-count": "4", @@ -129,6 +133,7 @@ class TestTableBlock(TestCase): table = self.block.value_from_datadict(form_data, {}, "table") self.assertIsInstance(table, TypedTable) + self.assertEqual(table.caption, "Countries and their food") self.assertEqual(len(table.columns), 3) self.assertEqual(table.columns[0]["heading"], "Country") self.assertEqual(table.columns[1]["heading"], "Population") @@ -145,6 +150,7 @@ class TestTableBlock(TestCase): Test that we can turn JSONish data from the database into a TypedTable instance """ table = self.block.to_python(self.db_data) + self.assertEqual(table.caption, "Countries and their food") self.assertIsInstance(table, TypedTable) self.assertEqual(len(table.columns), 2) self.assertEqual(table.columns[0]["heading"], "Country") @@ -188,6 +194,7 @@ class TestTableBlock(TestCase): table = self.block.value_from_datadict(self.form_data, {}, "table") html = self.block.render(table) + self.assertIn("", html) self.assertIn('', html) # rendering should use the block renderings of the child blocks ('FR' not 'fr') self.assertIn("", html)
{{ value.caption }}
Countries and their food
CountryFR