backoffice: group adjacent block subfield columns (#78318) #893

Merged
fpeters merged 1 commits from wip/78318-group-adjacent-subfield-columns into main 2023-12-18 13:19:42 +01:00
4 changed files with 107 additions and 18 deletions

View File

@ -533,12 +533,22 @@ def test_backoffice_block_columns(pub):
'Block / card field',
'Anonymised',
]
# enable columns for subfields
resp.forms['listing-settings']['8-123'].checked = True
resp.forms['listing-settings']['8-456'].checked = True
resp = resp.forms['listing-settings'].submit()
assert '<th><span>Block / Test</span></th>' in resp
assert '<th><span>Block / card field</span></th>' in resp
assert resp.text.count('<tr') == 2
# thead is now on two rows
assert resp.pyquery('thead tr').length == 2
assert [x.text_content() for x in resp.pyquery('thead tr:first-child th')] == [
'',
'Number',
'Created',
'Last Modified',
'User Label',
'Status',
'Block',
]
assert [x.text_content() for x in resp.pyquery('thead tr:last-child th')] == ['Test', 'card field']
assert '<td>blah</td>' in resp
assert '<td>Foo Bar</td>' in resp
@ -561,8 +571,16 @@ def test_backoffice_block_columns(pub):
resp.forms['listing-settings']['8-123'].checked = True
resp.forms['listing-settings']['8-456'].checked = True
resp = resp.forms['listing-settings'].submit()
assert '<th data-field-sort-key="f8-123"><span>Block / Test</span></th>' in resp
assert '<th data-field-sort-key="f8-456"><span>Block / card field</span></th>' in resp
assert resp.pyquery('th[data-field-sort-key="f8-123"]').text() == 'Test'
assert resp.pyquery('th[data-field-sort-key="f8-456"]').text() == 'card field'
# enable a single block subfield
resp.forms['listing-settings']['8-123'].checked = False
resp.forms['listing-settings']['8-456'].checked = True
resp = resp.forms['listing-settings'].submit()
# thead is now on a single row
assert resp.pyquery('thead tr').length == 1
assert resp.pyquery('th[data-field-sort-key="f8-456"]').text() == 'Block / card field'
def test_backoffice_block_email_column(pub):

View File

@ -1148,12 +1148,15 @@ def test_backoffice_custom_view_columns(pub):
app = login(get_app(pub))
resp = app.get('/backoffice/management/form-title/shared-custom-test-view/')
assert '<span>Number</span></th>' in resp
assert resp.pyquery('th').length == 2
assert resp.pyquery('th').text().strip() == 'Number'
custom_view.columns = {'list': [{'id': 'unknown'}]}
custom_view.store()
resp = app.get('/backoffice/management/form-title/shared-custom-test-view/')
assert '<th></th>' in resp # columns not found
# columns not found
assert resp.pyquery('th').length == 1
assert not resp.pyquery('th').text()
def test_backoffice_custom_view_sort_field(pub):

View File

@ -98,17 +98,52 @@ class FormDefUI:
r += htmltext('</colgroup>')
r += htmltext('<thead><tr>')
# group adjacent same-block columns
previous_block_subfield = None
grouped_columns = []
for f in fields:
if getattr(f, 'block_field', None):
if previous_block_subfield and f.block_field.id == previous_block_subfield.block_field.id:
previous_block_subfield.include_block_label = False
grouped_columns[-1]['count'] += 1
f.include_block_label = False
f.block_column = grouped_columns[-1]
Review

Si on est sur le cas de sous-champs de blocs qui se suivent, on n'inclut plus le libellé du bloc dans le libellé de la colonne, on incrément le nombre de colonnes qui seront réunies et on ajoute une référence à ces infos dans l'objet du champ, ça resservira plus tard.

Si on est sur le cas de sous-champs de blocs qui se suivent, on n'inclut plus le libellé du bloc dans le libellé de la colonne, on incrément le nombre de colonnes qui seront réunies et on ajoute une référence à ces infos dans l'objet du champ, ça resservira plus tard.
else:
grouped_columns.append({'label': f.block_field.label, 'count': 1})
f.include_block_label = True
f.block_column = grouped_columns[-1]
previous_block_subfield = f
else:
previous_block_subfield = None
grouped_columns.append({'count': 1})
thead_height = 1
if any(x.get('count') > 1 for x in grouped_columns):
# if there are adjacent same-block columns, the thead will have two rows
thead_height = 2
if self.formdef.workflow.criticality_levels:
r += htmltext(
'<th class="criticality-level-cell" data-field-sort-key="criticality_level"><span></span></th>'
f'<th rowspan="{thead_height}" class="criticality-level-cell" '
'data-field-sort-key="criticality_level"><span></span></th>'
)
else:
r += htmltext('<th></th>') # lock
r += htmltext(f'<th rowspan="{thead_height}"></th>') # lock
if include_checkboxes:
r += htmltext('<th class="select"><input type="checkbox" id="top-select"/>')
r += htmltext(
f'<th rowspan="{thead_height}" class="select"><input type="checkbox" id="top-select"/>'
)
r += htmltext(
' <span id="info-all-rows"><label><input type="checkbox" name="select[]" value="_all"/> %s</label></span></th>'
) % _('Run selected action on all pages')
def get_column_title(label):
if len(label) < 20:
return htmltext('<span>%s</span>') % label
else:
return htmltext('<span title="%s">%s</span>') % (label, misc.ellipsize(label, 20))
Review

Petite refacto de ça vu qu'on va l'utiliser plusieurs fois.

Petite refacto de ça vu qu'on va l'utiliser plusieurs fois.
for f in fields:
if getattr(f, 'fake', False):
field_sort_key = f.id
@ -124,17 +159,40 @@ class FormDefUI:
else:
field_sort_key = 'f%s' % f.contextual_id
if field_sort_key:
r += htmltext('<th data-field-sort-key="%s">') % field_sort_key
else:
r += htmltext('<th>')
th_rowspan = f' rowspan="{thead_height}"' if thead_height > 1 else ''
Review

Quand il y a une hauteur à tenir, ajout d'un attribut rowspan. (ça aurait pu être rowspan="1" dans les autres cas mais ça fait des tests qui cherchaient juste qui échouent, autant s'éviter ça).

Quand il y a une hauteur à tenir, ajout d'un attribut rowspan. (ça aurait pu être rowspan="1" dans les autres cas mais ça fait des tests qui cherchaient juste <th> qui échouent, autant s'éviter ça).
if getattr(f, 'block_field', None):
f.label = '%s / %s' % (f.block_field.label, f.label)
if len(f.label) < 20:
r += htmltext('<span>%s</span>') % f.label
if f.include_block_label:
# isolated block subfield column
f.label = '%s / %s' % (f.block_field.label, f.label)
Review

Le comportement actuel, libellé avec bloc / champ.

Le comportement actuel, libellé avec bloc / champ.
else:
# grouped subfields first row, block name
if not f.block_column.get('seen'):
f.block_column['seen'] = True
r += htmltext(f'<th class="col-group" colspan="{f.block_column["count"]}">')
r += get_column_title(f.block_field.label)
Review

Pour les colonnes groupées, si le th n'a pas encore été ajouté, on le met à la bonne largeur; sinon on zappe juste le champ, il apparaitra sur la deuxième ligne du thead.

Pour les colonnes groupées, si le th n'a pas encore été ajouté, on le met à la bonne largeur; sinon on zappe juste le champ, il apparaitra sur la deuxième ligne du thead.
r += htmltext('</th>')
f.field_sort_key = field_sort_key
continue
if field_sort_key:
r += htmltext(f'<th{th_rowspan} data-field-sort-key="{field_sort_key}">')
else:
r += htmltext('<span title="%s">%s</span>') % (f.label, misc.ellipsize(f.label, 20))
r += htmltext(f'<th{th_rowspan}>')
r += get_column_title(f.label)
r += htmltext('</th>')
if thead_height > 1:
# add individual columns for grouped subfields
r += htmltext('</tr><tr>')
for f in fields:
if getattr(f, 'block_field', None) and not f.include_block_label:
if f.field_sort_key:
Review

Deuxième ligne du thead, on ajoute des colonnes pour les champs des blocs.

Deuxième ligne du thead, on ajoute des colonnes pour les champs des blocs.
r += htmltext(f'<th class="col-subfield" data-field-sort-key="{f.field_sort_key}">')
else:
r += htmltext('<th class="col-subfield">')
r += get_column_title(f.label)
r += htmltext('</th>')
r += htmltext('</tr></thead>')
r += htmltext('<tbody>')
r += htmltext(self.tbody(fields, items, url_action, include_checkboxes=include_checkboxes))

View File

@ -465,6 +465,16 @@ table.main td {
table.main th {
cursor: pointer;
&.col-group,
&.col-subfield {
padding-top: 0;
padding-bottom: 0;
}
&.col-group {
background: #eee;
border: 1px solid #aaa;
border-width: 0px 2px;
}
}
table.main th.nosort {