1+ {%- if _ is not defined -%}
2+ {% macro _(message) -%}
3+ {{ message }}
4+ {%- endmacro %}
5+ {%- endif -%}
6+
7+ {# Renders field for bootstrap 4 standards.
8+
9+ Params:
10+ field - WTForm field
11+ kwargs - pass any arguments you want in order to put them into the html attributes.
12+ There are few exceptions: for - for_, class - class_, class__ - class_
13+
14+ Example usage:
15+ {{ horz_form.field(form.email, placeholder='Input email', type='email') }}
16+ #}
17+
18+ {% macro div_form_group(field) -%}
19+ < div class ="row mb-3 {{ 'required' if field.flags.required }} {{ kwargs.get('class_', '') }} ">
20+ {{ caller(**kwargs) }}
21+ </ div >
22+ {%- endmacro %}
23+
24+ {% macro description(field) -%}
25+ < small class ="form-text text-muted description ">
26+ {% if field.description is callable %}
27+ {{ field.description()|safe }}
28+ {% else %}
29+ {{ field.description|safe }}
30+ {% endif %}
31+ </ small >
32+ {%- endmacro %}
33+
34+ {% macro field_errors(field) -%}
35+ {# You will need javascript similar to below for validations to work properly.
36+ Alternatively, remove .was-validated from the form element to disable native client side validations by default.
37+
38+ var forms = document.getElementsByClassName('needs-validation');
39+ var validation = Array.prototype.filter.call(forms, function(form) {
40+ form.addEventListener('submit', function(event) {
41+ if (form.checkValidity() === false) {
42+ event.preventDefault();
43+ event.stopPropagation();
44+ }
45+ form.classList.add('was-validated');
46+ }, false);
47+ });
48+
49+ var fields = document.getElementsByClassName('is-invalid');
50+ Array.prototype.filter.call(fields, function(field) {
51+ field.setCustomValidity(false);
52+ });
53+ #}
54+ < div class ="invalid-feedback ">
55+ {% if field.errors %}
56+ {% for e in field.errors %}
57+ < p > {{ e }}</ p >
58+ {% endfor %}
59+ {% elif field.flags.required %}
60+ < p > This field is required.</ p >
61+ {% endif %}
62+ </ div >
63+ {% endmacro %}
64+
65+ {% macro field(field, label_visible=true) -%}
66+ {% call div_form_group(field, **kwargs) %}
67+ {% if (field.type != 'HiddenField' and field.type != 'CSRFTokenField') and label_visible %}
68+ {{ field.label(class_='col-form-label col-3') }}
69+ {% endif %}
70+ < div class ="col-9 ">
71+ {{ field_widget(field, **kwargs) }}
72+ {% if field.description %}
73+ {{ description(field) }}
74+ {% endif %}
75+ </ div >
76+ {% endcall %}
77+ {%- endmacro %}
78+
79+
80+ {% macro custom_css() %}
81+ <!-- included from keg-elements custom-css macro -->
82+ < style >
83+ .multi-checkbox > * {
84+ flex : 1 1 48% ;
85+ margin : 0 1% ;
86+ }
87+
88+ .multi-checkbox-controls {
89+ padding : 0.5em 1em ;
90+ }
91+ </ style >
92+ {% endmacro %}
93+
94+ {% macro custom_js() %}
95+ <!-- included from keg-elements custom-js macro -->
96+ < script >
97+ document . querySelectorAll ( '[multi-checkbox-data^="select-"]' ) . forEach ( item => {
98+ const target_value = item . attributes [ 'multi-checkbox-data' ] . value . indexOf ( 'select-all' ) !== - 1 ;
99+ item . addEventListener ( 'click' , event => {
100+ event . target . parentElement . parentElement . querySelectorAll ( '.custom-checkbox input' ) . forEach ( checkbox => {
101+ checkbox . checked = target_value ;
102+ } ) ;
103+ event . preventDefault ( ) ;
104+ } )
105+ } )
106+ </ script >
107+ {{ datetime_helper() }}
108+ {% endmacro %}
109+
110+ {% macro multi_checkbox(field) %}
111+ < div class ="multi-checkbox-controls ">
112+ < button multi-checkbox-data ="select-all "> Select All</ button >
113+ < button multi-checkbox-data ="select-none "> Select None</ button >
114+ </ div >
115+ < div id ="{{ field.id }} " class ="d-flex flex-row flex-wrap multi-checkbox ">
116+ {% for choice_id, choice in field.choices %}
117+ < div class ="custom-control custom-checkbox ">
118+ < input type ="checkbox " name ="{{ field.name }} "
119+ {{ 'checked="checked "' if field.data and choice_id in field.data else "" }}
120+ class="custom-control-input" value="{{ choice_id }}" id="{{ field.id }}{{choice_id}}">
121+ < label class ="col-form-label custom-control-label " for ="{{ field.id }}{{ choice_id }} "> {{choice}}</ label >
122+ </ div >
123+ {% endfor %}
124+ </ div >
125+ {% endmacro %}
126+
127+ {% macro field_widget(field) %}
128+ {% if field.type == "MultiCheckboxField" %}
129+ {{ multi_checkbox(field) }}
130+ {% else %}
131+ {% if field.flags.disabled %}{% set _dummy = kwargs.update({'disabled': field.flags.disabled}) %}{% endif %}
132+ {% if field.flags.readonly %}{% set _dummy = kwargs.update({'readonly': field.flags.readonly}) %}{% endif %}
133+ {{ field(class_='form-control is-invalid' if field.errors else 'form-control', **kwargs) }}
134+ {% endif %}
135+ {{ field_errors(field) }}
136+ {% endmacro %}
137+
138+ {# Renders checkbox fields since they are represented differently in bootstrap
139+ Params:
140+ field - WTForm field (there are no check, but you should put here only BooleanField.
141+ kwargs - pass any arguments you want in order to put them into the html attributes.
142+ There are few exceptions: for - for_, class - class_, class__ - class_
143+
144+ Example usage:
145+ {{ horiz_form.checkbox_field(form.remember_me) }}
146+ #}
147+ {% macro checkbox_field(field) -%}
148+ {% call div_form_group(field, **kwargs) %}
149+ < div class ="col-sm-9 offset-sm-3 ">
150+ < div class ="pt-2 checkbox form-check custom-control custom-checkbox ">
151+ {% if field.flags.disabled %}{% set _dummy = kwargs.update({'disabled': field.flags.disabled}) %}{% endif %}
152+ {{ field(type='checkbox', class_='form-check-input custom-control-input' + (' is-invalid' if field.errors else ''), **kwargs) }}
153+ {# pt-0 is to align the label with the checkbox by removing the padding #}
154+ < label class ="pt-0 col-form-label form-check-label custom-control-label " for ="{{ field.id }} ">
155+ {{ field.label }}
156+ </ label >
157+ {{ field_errors(field) }}
158+ </ div >
159+ {% if field.description %}
160+ {{ description(field) }}
161+ {% endif %}
162+ </ div >
163+ {% endcall %}
164+ {%- endmacro %}
165+
166+ {# Renders radio field
167+ Params:
168+ field - WTForm field (must have an `iter_choices` method)
169+ kwargs - pass any arguments you want in order to put them into the html attributes.
170+ There are few exceptions: for - for_, class - class_, class__ - class_
171+
172+ Example usage:
173+ {{ horiz_form.radio_field(form.answers) }}
174+ #}
175+ {% macro radio_field(field, label_visible=true) -%}
176+ < fieldset class ="row mb-3 {{ 'required' if field.flags.required }} {{ kwargs.get('class_', '') }} ">
177+ {% if label_visible %}
178+ {{ field.label(class_='col-form-label col-3') }}
179+ {% endif %}
180+ < div class ="col-9 ">
181+ {% for value, label, checked in field.iter_choices() %}
182+ < div class ="radio form-check ">
183+ < input type ="radio "
184+ class ="form-check-input{{' is-invalid' if field.errors }} "
185+ name ="{{ field.id }} "
186+ id ="{{ field.id }}-{{ value | lower }} "
187+ value ="{{ value }} "
188+ {{ 'checked' if checked }}
189+ {{ 'disabled' if field.flags.disabled or (field.flags.readonly and not checked) }}
190+ >
191+ < label class ="form-check-label ">
192+ {{ label }}
193+ </ label >
194+ {% if loop.last %}{{ field_errors(field) }}{% endif %}
195+ </ div >
196+ {% endfor %}
197+ </ div >
198+ {% if field.description %}
199+ {{ description(field) }}
200+ {% endif %}
201+ </ fieldset >
202+ {%- endmacro %}
203+
204+ {% macro submit_group(action_text='Submit', btn_class='btn btn-primary', cancel_url='') -%}
205+ < div class ="row mb-3 col-9 offset-sm-3 ">
206+ < div class ="p-0 ">
207+ < button type ="submit " class ="{{ btn_class }} "> {{ action_text }} </ button >
208+ {% if cancel_url %}
209+ < a href ="{{cancel_url}} " class ="cancel "> Cancel</ a >
210+ {% endif %}
211+ </ div >
212+ </ div >
213+ {%- endmacro %}
214+
215+ {% macro render_field(f) -%}
216+ {% if f is none %}
217+ {# Do nothing b/c the field is None #}
218+ {% elif f.type == 'BooleanField' or f.widget.input_type == 'checkbox' %}
219+ {{ checkbox_field(f, **kwargs) }}
220+ {% elif f.type == 'RadioField' %}
221+ {{ radio_field(f, **kwargs) }}
222+ {% elif f.type == 'FormField' %}
223+ {{ render_form_fields(f, render_hidden=true) }}
224+ {% else %}
225+ {{ field(f, **kwargs) }}
226+ {% endif %}
227+ {%- endmacro %}
228+
229+
230+ {% macro form_errors(form) -%}
231+ {% if form.form_errors %}
232+ < div class ="text-danger ">
233+ {% for e in form.form_errors %}
234+ < p > {{ e }}</ p >
235+ {% endfor %}
236+ </ div >
237+ {% endif %}
238+ {% endmacro %}
239+
240+
241+ {# Renders WTForm in bootstrap way. There are two ways to call function:
242+ - as macros: it will render all field forms using cycle to iterate over them
243+ - as call: it will insert form fields as you specify:
244+ e.g. {% call macros.render_form(form, action_url=url_for('login_view'), action_text='Login',
245+ class_='login-form') %}
246+ {{ macros.render_field(form.email, placeholder='Input email', type='email') }}
247+ {{ macros.render_field(form.password, placeholder='Input password', type='password') }}
248+ {{ macros.render_checkbox_field(form.remember_me, type='checkbox') }}
249+ {% endcall %}
250+
251+ Params:
252+ form - WTForm class
253+ action_url - url where to submit this form
254+ action_text - text of submit button
255+ class_ - sets a class for form
256+ #}
257+ {% macro form(
258+ form,
259+ field_names=None,
260+ action_url='',
261+ action_text='Submit',
262+ class_='',
263+ btn_class='btn btn-primary',
264+ cancel_url='',
265+ form_upload=false,
266+ dirty_check=false,
267+ form_id=None,
268+ form_name=None,
269+ form_attrs=None
270+ ) -%}
271+
272+ < form method ="POST "
273+ action ="{{ action_url }} "
274+ role ="form "
275+ class ="{{ class_ }} {{ 'was-validated needs-validation' if form.errors else 'needs-validation' }} "
276+ {% if form_id %}id ="{{ form_id }} "{% endif %}
277+ {% if form_name %}name ="{{ form_name }} "{% endif %}
278+ {% if form_upload %}enctype ="multipart/form-data "{% endif %}
279+ {% if dirty_check %}data-dirty-check ="on "{% endif %}
280+ novalidate
281+ {% for attr_key, attr_value in (form_attrs or {}).items() %}
282+ {{attr_key}}{% if attr_value %} ="{{attr_value}} "{% endif %}
283+ {% endfor %}
284+ >
285+ {{ form.hidden_tag() if form.hidden_tag }}
286+ {{ form_errors(form) }}
287+
288+ {% if caller %}
289+ {{ caller() }}
290+ {% elif field_names %}
291+ {{ fields(form, field_names) }}
292+ {% else %}
293+ {{ render_form_fields(form) }}
294+ {% endif %}
295+ {{ submit_group(action_text=action_text, btn_class=btn_class, cancel_url=cancel_url) }}
296+ </ form >
297+ {%- endmacro %}
298+
299+ {% macro render_form_fields(form, render_hidden=false) -%}
300+ {# Render hidden tags if flag is passed (for subforms only) #}
301+ {{ form.hidden_tag() if render_hidden and form.hidden_tag }}
302+ {% for f in form %}
303+ {% if not f.widget.input_type == 'hidden' %}
304+ {{ render_field(f) }}
305+ {% endif %}
306+ {% endfor %}
307+ {%- endmacro %}
308+
309+ {% macro fields(form, field_names) -%}
310+ {% for field_name in field_names %}
311+ {{ render_field(form[field_name]) }}
312+ {% endfor %}
313+ {%- endmacro %}
314+
315+ {% macro section(heading, form, field_names) -%}
316+ < h2 > {{heading}}</ h2 >
317+ {% if caller %}
318+ {{ caller() }}
319+ {% else %}
320+ {{ fields(form, field_names) }}
321+ {% endif %}
322+ {%- endmacro %}
323+
324+ {% macro datetime_helper() -%}
325+ < script type ="text/javascript ">
326+ { % include "keg-elements/forms/datetime-helper.js" % }
327+ </ script >
328+ {%- endmacro %}
0 commit comments