Adding Additional Information Into Each Form Field
Solution 1:
My crude but effective solution
As I was unable to override the CharField model to add the additional parameter, width
, I thought it would be easier to utilise an already existing parameter to pass the width
value into the templates. For this approach, I will be using the attrs
parameter to pass the width
value into the HTML rendered instance of each form field, I will then use some jQuery to extract the value and add it to the class of the container div.
Starting with the singleline CharField, we need to override the field to allow the user to input a width
value, my field override class looks like this:
@register('singleline')classColumn_SingleLineTextField(BaseField):
field_class = CustomSinglelineField
icon = "pilcrow"
label = "Text field (single line)"defget_options(self, block_value):
options = super().get_options(block_value)
options.update({'width': block_value.get('width')})
return options
defget_form_block(self):
return blocks.StructBlock([
('label', blocks.CharBlock()),
('help_text', blocks.CharBlock(required=False)),
('required', blocks.BooleanBlock(required=False)),
('default_value', blocks.CharBlock(required=False)),
('width', blocks.IntegerBlock(help_text="Width of field, 1 to 12, 12 is full width.", max_value=12, min_value=1, default=12, required=True)),
], icon=self.icon, label=self.label)
Now we need to override the CharField class to set the width
value as one of the attributes in attrs
:
classCustomSinglelineField(forms.CharField):def__init__(self,*args,**kwargs):
self.width = kwargs.pop('width')
self.widget = forms.widgets.TextInput(attrs={'col_width': self.width})
super().__init__(*args,**kwargs)
The whole code (wagtailstreamforms_fields.py
)
I have also added a placeholder
option to some of the applicable fields, a rows
attribute option for the multiline field, as well as the width
option:
from django import forms
from wagtailstreamforms.fields import BaseField, register
from wagtail.core import blocks
classCustomSinglelineField(forms.CharField):
def__init__(self,*args,**kwargs):
self.width = kwargs.pop('width')
self.placeholder = kwargs.pop('placeholder')
self.widget = forms.widgets.TextInput(attrs={'col_width': self.width, 'placeholder': self.placeholder})
super().__init__(*args,**kwargs)
classCustomMultilineField(forms.CharField):
def__init__(self,*args,**kwargs):
self.width = kwargs.pop('width')
self.rows = kwargs.pop('rows')
self.placeholder = kwargs.pop('placeholder')
self.widget = forms.widgets.Textarea(attrs={'col_width': self.width, 'rows': self.rows, 'placeholder': self.placeholder})
super().__init__(*args,**kwargs)
classCustomDateField(forms.DateField):
def__init__(self,*args,**kwargs):
self.width = kwargs.pop('width')
self.widget = forms.widgets.DateInput(attrs={'col_width': self.width})
super().__init__(*args,**kwargs)
classCustomDatetimeField(forms.DateField):
def__init__(self,*args,**kwargs):
self.width = kwargs.pop('width')
self.widget = forms.widgets.DateTimeInput(attrs={'col_width': self.width})
super().__init__(*args,**kwargs)
classCustomEmailField(forms.EmailField):
def__init__(self,*args,**kwargs):
self.width = kwargs.pop('width')
self.placeholder = kwargs.pop('placeholder')
self.widget = forms.widgets.EmailInput(attrs={'col_width': self.width, 'placeholder': self.placeholder})
super().__init__(*args,**kwargs)
classCustomURLField(forms.URLField):
def__init__(self,*args,**kwargs):
self.width = kwargs.pop('width')
self.placeholder = kwargs.pop('placeholder')
self.widget = forms.widgets.URLInput(attrs={'col_width': self.width, 'placeholder': self.placeholder})
super().__init__(*args,**kwargs)
classCustomNumberField(forms.DecimalField):
def__init__(self,*args,**kwargs):
self.width = kwargs.pop('width')
self.placeholder = kwargs.pop('placeholder')
self.widget = forms.widgets.URLInput(attrs={'col_width': self.width, 'placeholder': self.placeholder})
super().__init__(*args,**kwargs)
classCustomDropdownField(forms.ChoiceField):
def__init__(self,*args,**kwargs):
self.width = kwargs.pop('width')
self.widget = forms.widgets.Select(attrs={'col_width': self.width})
super().__init__(*args,**kwargs)
classCustomRadioField(forms.ChoiceField):
def__init__(self,*args,**kwargs):
self.width = kwargs.pop('width')
self.widget = forms.widgets.RadioSelect(attrs={'col_width': self.width})
super().__init__(*args,**kwargs)
classCustomCheckboxesField(forms.MultipleChoiceField):
def__init__(self,*args,**kwargs):
self.width = kwargs.pop('width')
self.widget = forms.widgets.SelectMultiple(attrs={'col_width': self.width})
super().__init__(*args,**kwargs)
classCustomCheckboxField(forms.BooleanField):
def__init__(self,*args,**kwargs):
self.width = kwargs.pop('width')
self.widget = forms.widgets.CheckboxInput(attrs={'col_width': self.width})
super().__init__(*args,**kwargs)
classCustomSinglefileField(forms.FileField):
def__init__(self,*args,**kwargs):
self.width = kwargs.pop('width')
self.widget = forms.widgets.ClearableFileInput(attrs={'col_width': self.width})
super().__init__(*args,**kwargs)
classCustomMultifileField(forms.FileField):
def__init__(self,*args,**kwargs):
self.width = kwargs.pop('width')
self.widget = forms.widgets.ClearableFileInput(attrs={'col_width': self.width,"multiple": True})
super().__init__(*args,**kwargs)
@register('singleline')classColumn_SingleLineTextField(BaseField):
field_class = CustomSinglelineField
icon = "pilcrow"
label = "Text field (single line)"defget_options(self, block_value):
options = super().get_options(block_value)
options.update({'placeholder': block_value.get('placeholder')})
options.update({'width': block_value.get('width')})
return options
defget_form_block(self):
return blocks.StructBlock([
('label', blocks.CharBlock()),
('help_text', blocks.CharBlock(required=False)),
('required', blocks.BooleanBlock(required=False)),
('default_value', blocks.CharBlock(required=False)),
('placeholder', blocks.CharBlock(required=False)),
('width', blocks.IntegerBlock(help_text="Width of field, 1 to 12, 12 is full width.", max_value=12, min_value=1, default=12, required=True)),
], icon=self.icon, label=self.label)
@register('multiline')classMultiLineTextField(BaseField):
field_class = CustomMultilineField
icon = "form"
label = "Text field (multiple lines)"defget_options(self, block_value):
options = super().get_options(block_value)
options.update({'width': block_value.get('width')})
options.update({'rows': block_value.get('rows')})
options.update({'placeholder': block_value.get('placeholder')})
return options
defget_form_block(self):
return blocks.StructBlock([
('label', blocks.CharBlock()),
('help_text', blocks.CharBlock(required=False)),
('required', blocks.BooleanBlock(required=False)),
('default_value', blocks.CharBlock(required=False)),
('placeholder', blocks.CharBlock(required=False)),
('width', blocks.IntegerBlock(help_text="Width of field, 1 to 12, 12 is full width.", max_value=12, min_value=1, default=12, required=True)),
('rows', blocks.IntegerBlock(help_text="Height of field", max_value=100, min_value=1, default=10, required=True)),
], icon=self.icon, label=self.label)
@register('date')classDateField(BaseField):
field_class = CustomDateField
icon = "date"
label = "Date field"defget_options(self, block_value):
options = super().get_options(block_value)
options.update({'width': block_value.get('width')})
return options
defget_form_block(self):
return blocks.StructBlock([
('label', blocks.CharBlock()),
('help_text', blocks.CharBlock(required=False)),
('required', blocks.BooleanBlock(required=False)),
('default_value', blocks.CharBlock(required=False)),
('width', blocks.IntegerBlock(help_text="Width of field, 1 to 12, 12 is full width.", max_value=12, min_value=1, default=12, required=True)),
], icon=self.icon, label=self.label)
@register('datetime')classDateTimeField(BaseField):
field_class = CustomDatetimeField
icon = "time"
label = "Time field"defget_options(self, block_value):
options = super().get_options(block_value)
options.update({'width': block_value.get('width')})
return options
defget_form_block(self):
return blocks.StructBlock([
('label', blocks.CharBlock()),
('help_text', blocks.CharBlock(required=False)),
('required', blocks.BooleanBlock(required=False)),
('default_value', blocks.CharBlock(required=False)),
('width', blocks.IntegerBlock(help_text="Width of field, 1 to 12, 12 is full width.", max_value=12, min_value=1, default=12, required=True)),
], icon=self.icon, label=self.label)
@register('email')classEmailField(BaseField):
field_class = CustomEmailField
icon = "mail"
label = "Email field"defget_options(self, block_value):
options = super().get_options(block_value)
options.update({'width': block_value.get('width')})
options.update({'placeholder': block_value.get('placeholder')})
return options
defget_form_block(self):
return blocks.StructBlock([
('label', blocks.CharBlock()),
('help_text', blocks.CharBlock(required=False)),
('required', blocks.BooleanBlock(required=False)),
('default_value', blocks.CharBlock(required=False)),
('placeholder', blocks.CharBlock(required=False)),
('width', blocks.IntegerBlock(help_text="Width of field, 1 to 12, 12 is full width.", max_value=12, min_value=1, default=12, required=True)),
], icon=self.icon, label=self.label)
@register('url')classURLField(BaseField):
field_class = CustomURLField
icon = "link"
label = "URL field"defget_options(self, block_value):
options = super().get_options(block_value)
options.update({'width': block_value.get('width')})
options.update({'placeholder': block_value.get('placeholder')})
return options
defget_form_block(self):
return blocks.StructBlock([
('label', blocks.CharBlock()),
('help_text', blocks.CharBlock(required=False)),
('required', blocks.BooleanBlock(required=False)),
('default_value', blocks.CharBlock(required=False)),
('placeholder', blocks.CharBlock(required=False)),
('width', blocks.IntegerBlock(help_text="Width of field, 1 to 12, 12 is full width.", max_value=12, min_value=1, default=12, required=True)),
], icon=self.icon, label=self.label)
@register('number')classNumberField(BaseField):
field_class = forms.DecimalField
label = "Number field"defget_options(self, block_value):
options = super().get_options(block_value)
options.update({'width': block_value.get('width')})
options.update({'placeholder': block_value.get('placeholder')})
return options
defget_form_block(self):
return blocks.StructBlock([
('label', blocks.CharBlock()),
('help_text', blocks.CharBlock(required=False)),
('required', blocks.BooleanBlock(required=False)),
('default_value', blocks.CharBlock(required=False)),
('placeholder', blocks.CharBlock(required=False)),
('width', blocks.IntegerBlock(help_text="Width of field, 1 to 12, 12 is full width.", max_value=12, min_value=1, default=12, required=True)),
], icon=self.icon, label=self.label)
@register('dropdown')classDropdownField(BaseField):
field_class = CustomDropdownField
icon = "arrow-down-big"
label = "Dropdown field"defget_options(self, block_value):
options = super().get_options(block_value)
options.update({'width': block_value.get('width')})
return options
defget_form_block(self):
return blocks.StructBlock(
[
("label", blocks.CharBlock()),
("help_text", blocks.CharBlock(required=False)),
("required", blocks.BooleanBlock(required=False)),
("empty_label", blocks.CharBlock(required=False)),
("choices", blocks.ListBlock(blocks.CharBlock(label="Option"))),
('width', blocks.IntegerBlock(help_text="Width of field, 1 to 12, 12 is full width.", max_value=12, min_value=1, default=12, required=True)),
],
icon=self.icon,
label=self.label,
)
@register('radio')classRadioField(BaseField):
field_class = CustomRadioField
icon = "radio-empty"
label = "Radio buttons"defget_options(self, block_value):
options = super().get_options(block_value)
options.update({'width': block_value.get('width')})
return options
defget_form_block(self):
return blocks.StructBlock(
[
("label", blocks.CharBlock()),
("help_text", blocks.CharBlock(required=False)),
("required", blocks.BooleanBlock(required=False)),
("choices", blocks.ListBlock(blocks.CharBlock(label="Option"))),
('width', blocks.IntegerBlock(help_text="Width of field, 1 to 12, 12 is full width.", max_value=12, min_value=1, default=12, required=True)),
],
icon=self.icon,
label=self.label,
)
@register('checkboxes')classCheckboxesField(BaseField):
field_class = CustomCheckboxesField
icon = "tick-inverse"
label = "Checkboxes"defget_options(self, block_value):
options = super().get_options(block_value)
options.update({'width': block_value.get('width')})
return options
defget_form_block(self):
return blocks.StructBlock(
[
("label", blocks.CharBlock()),
("help_text", blocks.CharBlock(required=False)),
("required", blocks.BooleanBlock(required=False)),
("choices", blocks.ListBlock(blocks.CharBlock(label="Option"))),
('width', blocks.IntegerBlock(help_text="Width of field, 1 to 12, 12 is full width.", max_value=12, min_value=1, default=12, required=True)),
],
icon=self.icon,
label=self.label,
)
@register('checkbox')classCheckboxField(BaseField):
field_class = forms.BooleanField
icon = "tick-inverse"
label = "Checkbox field"defget_options(self, block_value):
options = super().get_options(block_value)
options.update({'width': block_value.get('width')})
return options
defget_form_block(self):
return blocks.StructBlock(
[
("label", blocks.CharBlock()),
("help_text", blocks.CharBlock(required=False)),
("required", blocks.BooleanBlock(required=False)),
('width', blocks.IntegerBlock(help_text="Width of field, 1 to 12, 12 is full width.", max_value=12, min_value=1, default=12, required=True)),
],
icon=self.icon,
label=self.label,
)
@register('singlefile')classSingleFileField(BaseField):
field_class = CustomSinglefileField
icon = "doc-full-inverse"
label = "File field"defget_options(self, block_value):
options = super().get_options(block_value)
options.update({'width': block_value.get('width')})
return options
defget_form_block(self):
return blocks.StructBlock(
[
("label", blocks.CharBlock()),
("help_text", blocks.CharBlock(required=False)),
("required", blocks.BooleanBlock(required=False)),
('width', blocks.IntegerBlock(help_text="Width of field, 1 to 12, 12 is full width.", max_value=12, min_value=1, default=12, required=True)),
],
icon=self.icon,
label=self.label,
)
@register('multifile')classMultiFileField(BaseField):
field_class = forms.FileField
icon = "doc-full-inverse"
label = "Files field"defget_form_block(self):
return blocks.StructBlock(
[
("label", blocks.CharBlock()),
("help_text", blocks.CharBlock(required=False)),
("required", blocks.BooleanBlock(required=False)),
('width', blocks.IntegerBlock(help_text="Width of field, 1 to 12, 12 is full width.", max_value=12, min_value=1, default=12, required=True)),
],
icon=self.icon,
label=self.label,
)
Now the templates
The templates/custom_form_block.html
is unchanged and looks like the following:
<form{% if form.is_multipart %} enctype="multipart/form-data"{% endif %} class="row g-3 normal-form" action="{{ value.form_action }}" method="post" novalidate>
{{ form.media }}
{% csrf_token %}
{% for hidden in form.hidden_fields %}{{ hidden }}{% endfor %}
{% for field in form.visible_fields %}
{% include 'partials/custom_form_field.html' %}
{% endfor %}
<div class="col-12">
<buttonclass="btn btn-primary">{{ value.form.submit_button_text }}</button>
</div>
</form>
And the templates/partials/custom_form_field.html
has all the changes and looks like the following:
<divclass="field-column-id_{{ value.form.slug }}_{{ field.name }}">
{{ field.label_tag }}
{{ field }}
{% if field.help_text %}<pclass="help-text">{{ field.help_text }}</p>{% endif %}
{{ field.errors }}
</div><script>var col_width = $(".field-column-id_{{ value.form.slug }}_{{ field.name }} #id_{{ field.name }}").attr("col_width")
$(".field-column-id_{{ value.form.slug }}_{{ field.name }}").addClass("col-"+col_width);
// Set the "None" placeholders as ""var placeholder = $(".field-column-id_{{ value.form.slug }}_{{ field.name }} #id_{{ field.name }}").attr("placeholder")
if (placeholder == "None") {
$(".field-column-id_{{ value.form.slug }}_{{ field.name }} #id_{{ field.name }}").attr("placeholder", "")
}
</script>
I am setting the parent div for each field as field-column-id_{{ value.form.slug }}_{{ field.name }}
, this way if we have multiple forms with fields of the same name on the same page, there wouldn't be any conflicts as each form will have a unique slug.
The jQuery retrieved the col_width
HTML attribute that we set as the width
option and will append it to col-
and set it as the parent div's class.
By default, if we don't set a value in the 'placeholder' option, wagtailstreamforms sets this option as 'None'. We don't want an empty placeholder to be displayed as 'None' in the HTML so we have a jQuery script that replaces None
with an empty string. This will work for the most part but this functionality breaks when the user wants the placeholder to actually be set to None
.
This solution works for the most part, but in my honest opinion, I'm not very proud of it as I think this is inefficient and hacky.
Post a Comment for "Adding Additional Information Into Each Form Field"