blocks: add option to have a remove button (#45368)
This commit is contained in:
parent
669b338187
commit
f59b73c7c6
|
@ -855,7 +855,7 @@ def test_block_repeated(pub, blocks_feature):
|
|||
resp = resp.form.submit('f1$add_element')
|
||||
assert resp.text.count('>Test<') == 3
|
||||
assert resp.text.count('>hintblock<') == 1
|
||||
assert 'Add another' not in resp
|
||||
assert resp.pyquery('.list-add').attr['style'] == 'display: none'
|
||||
|
||||
# fill items (1st and 3rd row)
|
||||
resp.form['f1$element0$f123'] = 'foo'
|
||||
|
@ -915,7 +915,7 @@ def test_block_repeated_over_limit(pub, blocks_feature):
|
|||
assert resp.text.count('>Test<') == 2
|
||||
resp = resp.form.submit('f1$add_element')
|
||||
assert resp.text.count('>Test<') == 3
|
||||
assert 'Add another' not in resp
|
||||
assert resp.pyquery('.list-add').attr['style'] == 'display: none'
|
||||
|
||||
# fill items
|
||||
resp.form['f1$element0$f123'] = 'foo'
|
||||
|
@ -963,7 +963,7 @@ def test_block_repeated_files(pub, blocks_feature):
|
|||
assert resp.text.count('>Test<') == 2
|
||||
resp = resp.form.submit('f1$add_element')
|
||||
assert resp.text.count('>Test<') == 3
|
||||
assert 'Add another' not in resp
|
||||
assert resp.pyquery('.list-add').attr['style'] == 'display: none'
|
||||
|
||||
# fill items (1st and 3rd row)
|
||||
resp.form['f1$element0$f123'] = 'foo'
|
||||
|
@ -991,6 +991,84 @@ def test_block_repeated_files(pub, blocks_feature):
|
|||
assert 'test2.txt' in resp
|
||||
|
||||
|
||||
@pytest.mark.parametrize('removed_line', [0, 1, 2])
|
||||
def test_block_repeated_remove_line(pub, blocks_feature, removed_line):
|
||||
FormDef.wipe()
|
||||
BlockDef.wipe()
|
||||
|
||||
block = BlockDef()
|
||||
block.name = 'foobar'
|
||||
block.fields = [
|
||||
fields.StringField(id='123', required=True, label='Test', type='string'),
|
||||
fields.StringField(id='234', required=True, label='Test2', type='string'),
|
||||
]
|
||||
block.store()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'form title'
|
||||
formdef.fields = [
|
||||
fields.PageField(id='0', label='1st page', type='page'),
|
||||
fields.BlockField(
|
||||
id='1', label='test', type='block:foobar', max_items=5, hint='hintblock', remove_button=True
|
||||
),
|
||||
fields.PageField(id='2', label='2nd page', type='page'),
|
||||
]
|
||||
formdef.store()
|
||||
formdef.data_class().wipe()
|
||||
|
||||
app = get_app(pub)
|
||||
resp = app.get(formdef.get_url())
|
||||
assert resp.text.count('>Test<') == 1
|
||||
resp = resp.form.submit('f1$add_element')
|
||||
assert resp.text.count('>Test<') == 2
|
||||
resp = resp.form.submit('f1$add_element')
|
||||
assert resp.text.count('>Test<') == 3
|
||||
|
||||
# fill items on three rows
|
||||
resp.form['f1$element0$f123'] = 'foo1'
|
||||
resp.form['f1$element0$f234'] = 'bar1'
|
||||
resp.form['f1$element1$f123'] = 'foo2'
|
||||
resp.form['f1$element1$f234'] = 'bar2'
|
||||
resp.form['f1$element2$f123'] = 'foo3'
|
||||
resp.form['f1$element2$f234'] = 'bar3'
|
||||
|
||||
resp = resp.form.submit('submit') # -> 2nd page
|
||||
resp = resp.form.submit('submit') # -> validation page
|
||||
assert 'Check values then click submit.' in resp.text
|
||||
assert resp.form['f1$element0$f123'].value == 'foo1'
|
||||
assert resp.form['f1$element0$f234'].value == 'bar1'
|
||||
assert resp.form['f1$element1$f123'].value == 'foo2'
|
||||
assert resp.form['f1$element1$f234'].value == 'bar2'
|
||||
assert resp.form['f1$element2$f123'].value == 'foo3'
|
||||
assert resp.form['f1$element2$f234'].value == 'bar3'
|
||||
|
||||
resp = resp.form.submit('previous') # -> 2nd page
|
||||
resp = resp.form.submit('previous') # -> 1st page
|
||||
# simulate javascript removing of block elements from DOM
|
||||
resp.form.field_order.remove(
|
||||
('f1$element%s$f123' % removed_line, resp.form.fields['f1$element%s$f123' % removed_line][0])
|
||||
)
|
||||
del resp.form.fields['f1$element%s$f123' % removed_line]
|
||||
resp.form.field_order.remove(
|
||||
('f1$element%s$f234' % removed_line, resp.form.fields['f1$element%s$f234' % removed_line][0])
|
||||
)
|
||||
del resp.form.fields['f1$element%s$f234' % removed_line]
|
||||
|
||||
resp = resp.form.submit('submit') # -> 2nd page
|
||||
resp = resp.form.submit('submit') # -> validation page
|
||||
values = ['1', '2', '3']
|
||||
del values[removed_line]
|
||||
assert resp.form['f1$element0$f123'].value == 'foo%s' % values[0]
|
||||
assert resp.form['f1$element0$f234'].value == 'bar%s' % values[0]
|
||||
assert resp.form['f1$element1$f123'].value == 'foo%s' % values[1]
|
||||
assert resp.form['f1$element1$f234'].value == 'bar%s' % values[1]
|
||||
assert 'f1$element2$f123' not in resp.form.fields
|
||||
assert 'f1$element2$f234' not in resp.form.fields
|
||||
|
||||
resp = resp.form.submit('submit') # -> submit
|
||||
assert len(formdef.data_class().select()[0].data['1']['data']) == 2
|
||||
|
||||
|
||||
@pytest.mark.parametrize('block_name', ['foobar', 'Foo bar'])
|
||||
def test_block_digest(pub, blocks_feature, block_name):
|
||||
FormDef.wipe()
|
||||
|
|
|
@ -192,9 +192,12 @@ class BlockDef(StorableObject):
|
|||
|
||||
|
||||
class BlockSubWidget(CompositeWidget):
|
||||
template_name = 'qommon/forms/widgets/block_sub.html'
|
||||
|
||||
def __init__(self, name, value=None, *args, **kwargs):
|
||||
self.block = kwargs.pop('block')
|
||||
self.readonly = kwargs.get('readonly')
|
||||
self.remove_button = kwargs.pop('remove_button', False)
|
||||
super().__init__(name, value, *args, **kwargs)
|
||||
for field in self.block.fields:
|
||||
if 'readonly' in kwargs:
|
||||
|
@ -240,19 +243,23 @@ class BlockSubWidget(CompositeWidget):
|
|||
|
||||
|
||||
class BlockWidget(WidgetList):
|
||||
template_name = 'qommon/forms/widgets/block.html'
|
||||
always_include_add_button = True
|
||||
|
||||
def __init__(
|
||||
self, name, value=None, title=None, block=None, max_items=None, add_element_label=None, **kwargs
|
||||
):
|
||||
self.block = block
|
||||
self.readonly = kwargs.get('readonly')
|
||||
self.label_display = kwargs.pop('label_display') or 'normal'
|
||||
self.remove_button = kwargs.pop('remove_button', False)
|
||||
element_values = None
|
||||
if value:
|
||||
element_values = value.get('data')
|
||||
if not max_items:
|
||||
max_items = 1
|
||||
hint = kwargs.pop('hint', None)
|
||||
element_kwargs = {'block': self.block, 'render_br': False}
|
||||
element_kwargs = {'block': self.block, 'render_br': False, 'remove_button': self.remove_button}
|
||||
element_kwargs.update(kwargs)
|
||||
super().__init__(
|
||||
name,
|
||||
|
@ -275,8 +282,9 @@ class BlockWidget(WidgetList):
|
|||
# (maybe this could be moved to WidgetList)
|
||||
prefix = '%s$element' % self.name
|
||||
known_prefixes = {x.split('$', 2)[1] for x in request.form.keys() if x.startswith(prefix)}
|
||||
for i in range(len(known_prefixes) - len(self.element_names)):
|
||||
self.add_element()
|
||||
for prefix in known_prefixes:
|
||||
if prefix not in self.element_names:
|
||||
self.add_element(element_name=prefix)
|
||||
super()._parse(request)
|
||||
if self.value:
|
||||
self.value = {'data': self.value}
|
||||
|
|
|
@ -3154,9 +3154,10 @@ class BlockField(WidgetField):
|
|||
|
||||
widget_class = BlockWidget
|
||||
max_items = 1
|
||||
extra_attributes = ['block', 'max_items', 'add_element_label', 'label_display']
|
||||
extra_attributes = ['block', 'max_items', 'add_element_label', 'label_display', 'remove_button']
|
||||
add_element_label = ''
|
||||
label_display = 'normal'
|
||||
remove_button = False
|
||||
|
||||
# cache
|
||||
_block = None
|
||||
|
@ -3191,9 +3192,15 @@ class BlockField(WidgetField):
|
|||
value=self.label_display or 'normal',
|
||||
options=display_options,
|
||||
)
|
||||
form.add(CheckboxWidget, 'remove_button', title=_('Include remove button'), value=self.remove_button)
|
||||
|
||||
def get_admin_attributes(self):
|
||||
return super().get_admin_attributes() + ['max_items', 'add_element_label', 'label_display']
|
||||
return super().get_admin_attributes() + [
|
||||
'max_items',
|
||||
'add_element_label',
|
||||
'label_display',
|
||||
'remove_button',
|
||||
]
|
||||
|
||||
def store_display_value(self, data, field_id):
|
||||
value = data.get(field_id)
|
||||
|
|
|
@ -1699,6 +1699,8 @@ class CaptchaWidget(CompositeWidget):
|
|||
|
||||
|
||||
class WidgetList(quixote.form.widget.WidgetList):
|
||||
always_include_add_button = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
|
@ -1737,9 +1739,9 @@ class WidgetList(quixote.form.widget.WidgetList):
|
|||
for i in range(len(known_prefixes) - len(self.element_names)):
|
||||
self.add_element()
|
||||
|
||||
# Add submit to add more element widgets
|
||||
current_len = len(self.element_names)
|
||||
if (not max_items) or current_len < max_items:
|
||||
# Add submit to add more element widgets
|
||||
if self.always_include_add_button or (not max_items) or current_len < max_items:
|
||||
self.add(
|
||||
SubmitWidget,
|
||||
'add_element',
|
||||
|
@ -1747,15 +1749,15 @@ class WidgetList(quixote.form.widget.WidgetList):
|
|||
render_br=False,
|
||||
extra_css_class='list-add',
|
||||
)
|
||||
if self.get('add_element'):
|
||||
self.add_element()
|
||||
current_len = len(self.element_names)
|
||||
if max_items and current_len >= max_items:
|
||||
self.widgets.remove(self.get_widget('add_element'))
|
||||
del self._names['add_element']
|
||||
if self.get('add_element') and (not max_items or current_len < max_items):
|
||||
# add an empty row
|
||||
self.add_element()
|
||||
|
||||
def add_element(self, value=None):
|
||||
name = "element%d" % len(self.element_names)
|
||||
def add_element(self, value=None, element_name=None):
|
||||
if element_name:
|
||||
name = element_name
|
||||
else:
|
||||
name = 'element%d' % len(self.element_names)
|
||||
self.add(self.element_type, name, value=value, **self.element_kwargs)
|
||||
self.element_names.append(name)
|
||||
|
||||
|
@ -1789,13 +1791,19 @@ class WidgetList(quixote.form.widget.WidgetList):
|
|||
# values in subwidgets either, clear them instead of filling the
|
||||
# screen with "required field" messages.
|
||||
clear_errors = True
|
||||
|
||||
count = 0
|
||||
for widget in self.get_widgets():
|
||||
if widget is add_element_widget:
|
||||
continue
|
||||
if clear_errors:
|
||||
widget.clear_error()
|
||||
r += widget.render()
|
||||
count += 1
|
||||
|
||||
if add_element_widget:
|
||||
if self.max_items and count >= self.max_items:
|
||||
add_element_widget.is_hidden = True
|
||||
r += add_element_widget.render()
|
||||
return r.getvalue()
|
||||
|
||||
|
|
|
@ -2111,3 +2111,22 @@ div.timetable-widget {
|
|||
padding-bottom: 1ex;
|
||||
}
|
||||
}
|
||||
|
||||
.wcs-block-with-remove-button {
|
||||
.BlockSubWidget {
|
||||
position: relative;
|
||||
}
|
||||
.remove-button {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0.5em;
|
||||
margin-right: 0;
|
||||
span {
|
||||
display: none;
|
||||
}
|
||||
&::after {
|
||||
font-family: FontAwesome;
|
||||
content: "\f1f8"; // trash
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -470,4 +470,22 @@ $(function() {
|
|||
const table = elem.querySelector('table');
|
||||
new Responsive_table_widget(table);
|
||||
});
|
||||
|
||||
function disable_single_block_remove_button() {
|
||||
$('.BlockSubWidget button.remove-button').each(function(i, elem) {
|
||||
if ($(this).parents('.BlockWidget').find('.BlockSubWidget').length == 1) {
|
||||
$(this).prop('disabled', true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
disable_single_block_remove_button();
|
||||
$('.BlockSubWidget button.remove-button').on('click', function() {
|
||||
if ($(this).parents('.BlockWidget').find('.BlockSubWidget').length > 1) {
|
||||
$(this).parents('.BlockWidget').find('.list-add').show();
|
||||
$(this).parents('.BlockSubWidget').remove();
|
||||
disable_single_block_remove_button();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
{% extends "qommon/forms/widget.html" %}
|
||||
|
||||
{% block widget-css-classes %}{{ block.super }} {% if widget.remove_button %}wcs-block-with-remove-button{% endif %}{% endblock %}
|
|
@ -0,0 +1,8 @@
|
|||
{% extends "qommon/forms/widget.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block widget-content %}
|
||||
{% for subwidget in widget.get_widgets %}
|
||||
{{ subwidget.render|safe }}
|
||||
{% endfor %}
|
||||
{% if not widget.readonly and widget.remove_button %}<button class="remove-button" title="{% trans "Remove" %}"><span>-</span></button>{% endif %} {% endblock %}
|
Loading…
Reference in New Issue