0
0
mirror of https://github.com/wagtail/wagtail.git synced 2024-11-25 05:02:57 +01:00

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)
This commit is contained in:
Tommaso Amici 2022-05-10 16:28:10 +02:00 committed by LB (Ben Johnston)
parent 0ec50f0d82
commit a4c18b4957
13 changed files with 136 additions and 10 deletions

View File

@ -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)

View File

@ -782,6 +782,7 @@
* Badr Fourane
* Vaishnav Dasari
* Aditya
* Tommaso Amici
## Translators

View File

@ -28,7 +28,7 @@ exports[`telepath: wagtail.widgets.TableInput it renders correctly 1`] = `
<label class="w-field__label" for="the-id-handsontable-col-caption">Table caption</label>
<div class="w-field w-field--char_field w-field--text_input" data-field="">
<div class="w-field__help" id="the-id-handsontable-col-caption-helptext" data-field-help="">
<div class="help">A heading that identifies the overall topic of the table, and is useful for screen reader users</div>
<div class="help">A heading that identifies the overall topic of the table, and is useful for screen reader users.</div>
</div>
<div class="w-field__input" data-field-input="">
<input type="text" id="the-id-handsontable-col-caption" name="handsontable-col-caption" aria-describedby="the-id-handsontable-col-caption-helptext">

View File

@ -263,7 +263,7 @@ class TableInput {
<label class="w-field__label" for="${id}-handsontable-col-caption">${this.strings['Table caption']}</label>
<div class="w-field w-field--char_field w-field--text_input" data-field>
<div class="w-field__help" id="${id}-handsontable-col-caption-helptext" data-field-help>
<div class="help">${this.strings['A heading that identifies the overall topic of the table, and is useful for screen reader users']}</div>
<div class="help">${this.strings['A heading that identifies the overall topic of the table, and is useful for screen reader users.']}</div>
</div>
<div class="w-field__input" data-field-input>
<input type="text" id="${id}-handsontable-col-caption" name="handsontable-col-caption" aria-describedby="${id}-handsontable-col-caption-helptext" />

View File

@ -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',
};

View File

@ -2,6 +2,22 @@
exports[`wagtail.contrib.typed_table_block.blocks.TypedTableBlock it renders correctly 1`] = `
"<div class="typed-table-block ">
<div class="w-field__wrapper" data-field-wrapper="">
<label class="w-field__label" for="mytable-caption">
Caption
</label>
<div class="w-field w-field--char_field w-field--text_input" data-field="">
<div class="w-field__help" data-field-help="">
<div class="help">
A heading that identifies the overall topic of the table, and is useful for screen reader users.
</div>
</div>
<div class="w-field__input" data-field-input="">
<input type="text" id="mytable-caption" name="mytable-caption" value="">
<span></span>
</div>
</div>
</div>
<input type="hidden" name="mytable-column-count" data-column-count="" value="2">
<input type="hidden" name="mytable-row-count" data-row-count="" value="2">
<div data-deleted-fields=""><input type="hidden" name="mytable-column-0-deleted" value=""><input type="hidden" name="mytable-column-1-deleted" value=""><input type="hidden" name="mytable-row-0-deleted" value=""><input type="hidden" name="mytable-row-1-deleted" value=""></div>
@ -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`] = `
"<div class="typed-table-block ">
<div class="w-field__wrapper" data-field-wrapper="">
<label class="w-field__label" for="mytable-caption">
Caption
</label>
<div class="w-field w-field--char_field w-field--text_input" data-field="">
<div class="w-field__help" data-field-help="">
<div class="help">
A heading that identifies the overall topic of the table, and is useful for screen reader users.
</div>
</div>
<div class="w-field__input" data-field-input="">
<input type="text" id="mytable-caption" name="mytable-caption" value="">
<span></span>
</div>
</div>
</div>
<input type="hidden" name="mytable-column-count" data-column-count="" value="2">
<input type="hidden" name="mytable-row-count" data-row-count="" value="2">
<div data-deleted-fields=""><input type="hidden" name="mytable-column-0-deleted" value=""><input type="hidden" name="mytable-column-1-deleted" value=""><input type="hidden" name="mytable-row-0-deleted" value=""><input type="hidden" name="mytable-row-1-deleted" value=""></div>
@ -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`] = `
"<div class="typed-table-block "><p class="help-block help-critical">This is just generally wrong</p>
<div class="w-field__wrapper" data-field-wrapper="">
<label class="w-field__label" for="mytable-caption">
Caption
</label>
<div class="w-field w-field--char_field w-field--text_input" data-field="">
<div class="w-field__help" data-field-help="">
<div class="help">
A heading that identifies the overall topic of the table, and is useful for screen reader users.
</div>
</div>
<div class="w-field__input" data-field-input="">
<input type="text" id="mytable-caption" name="mytable-caption" value="">
<span></span>
</div>
</div>
</div>
<input type="hidden" name="mytable-column-count" data-column-count="" value="2">
<input type="hidden" name="mytable-row-count" data-row-count="" value="2">
<div data-deleted-fields=""><input type="hidden" name="mytable-column-0-deleted" value=""><input type="hidden" name="mytable-column-1-deleted" value=""><input type="hidden" name="mytable-row-0-deleted" value=""><input type="hidden" name="mytable-row-1-deleted" value=""></div>

View File

@ -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 = $(`
<div class="typed-table-block ${h(this.blockDef.meta.classname || '')}">
<div class="w-field__wrapper" data-field-wrapper>
<label class="w-field__label" for="${captionID}">
${strings.CAPTION}
</label>
<div class="w-field w-field--char_field w-field--text_input" data-field>
<div class="w-field__help" data-field-help>
<div class="help">
${strings.CAPTION_HELP_TEXT}
</div>
</div>
<div class="w-field__input" data-field-input>
<input type="text" id="${captionID}" name="${captionID}" value="" />
<span></span>
</div>
</div>
</div>
<input type="hidden" name="${h(
prefix,
)}-column-count" data-column-count value="0">
@ -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;
}

View File

@ -101,6 +101,9 @@ describe('wagtail.contrib.typed_table_block.blocks.TypedTableBlock', () => {
helpText: 'use <strong>plenty</strong> of these',
helpIcon: '<svg></svg>',
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', () => {

View File

@ -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

View File

@ -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"),
}

View File

@ -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"),

View File

@ -1,5 +1,8 @@
{% load wagtailcore_tags %}
<table>
{% if value.caption %}
<caption>{{ value.caption }}</caption>
{% endif %}
<thead>
<tr>
{% for col in value.columns %}

View File

@ -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("<caption>Countries and their food</caption>", html)
self.assertIn('<th scope="col">Country</th>', html)
# rendering should use the block renderings of the child blocks ('FR' not 'fr')
self.assertIn("<td>FR</td>", html)