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.
+
+ 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 %}
+
{{ 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("
Countries and their food
", html)
self.assertIn('
Country
', html)
# rendering should use the block renderings of the child blocks ('FR' not 'fr')
self.assertIn("