diff --git a/docs/source/getting-started.rst b/docs/source/getting-started.rst
index 8dfc348..cc9f8e8 100644
--- a/docs/source/getting-started.rst
+++ b/docs/source/getting-started.rst
@@ -42,7 +42,7 @@ from settings. The first of these defined is used:
Form selects are rendered with select2 in templates extending ``keg-elements/form-view.html``.
``keg-elements/select2-scripts.html`` and ``keg-elements/select2-styles.html`` can be included
in templates to render select2s without extending form-view. Apps can opt out of select2
-rendering with ``KEG_USE_SELECT2`` config.
+rendering with ``KEG_USE_ENHANCED_SELECTS`` config.
.. _gs-model:
diff --git a/keg_elements/templates/keg-elements/form-view.html b/keg_elements/templates/keg-elements/form-view.html
index 794b8ce..bd7a16e 100644
--- a/keg_elements/templates/keg-elements/form-view.html
+++ b/keg_elements/templates/keg-elements/form-view.html
@@ -1,7 +1,7 @@
{% if is_read_only %}
{% import 'keg-elements/forms/horizontal-static.html' as horizontal %}
{% else %}
- {% import 'keg-elements/forms/horizontal-b4.html' as horizontal %}
+ {% import 'keg-elements/forms/horizontal.html' as horizontal %}
{% endif %}
{% set base_template = config.get('BASE_TEMPLATE') or config.get('KEG_BASE_TEMPLATE') %}
@@ -9,18 +9,18 @@
{% extends base_template %}
{% endif %}
-{% block scripts %}
+{% block keg_scripts %}
{{ super() }}
-{% if config.get('KEG_USE_SELECT2', True) %}
- {% include 'keg-elements/forms/select2-scripts.html' %}
+{% if config.get('KEG_USE_ENHANCED_SELECTS', True) %}
+ {% include 'keg-elements/forms/enhanced-select-scripts.html' %}
{% endif %}
{{ horizontal.custom_js() }}
{% endblock %}
-{% block styles %}
+{% block keg_styles %}
{{ super() }}
-{% if config.get('KEG_USE_SELECT2', True) %}
- {% include 'keg-elements/forms/select2-styles.html' %}
+{% if config.get('KEG_USE_ENHANCED_SELECTS', True) %}
+ {% include 'keg-elements/forms/enhanced-select-styles.html' %}
{% endif %}
{{ horizontal.custom_css() }}
{% endblock %}
diff --git a/keg_elements/templates/keg-elements/forms/enhanced-select-scripts.html b/keg_elements/templates/keg-elements/forms/enhanced-select-scripts.html
new file mode 100644
index 0000000..7e55b37
--- /dev/null
+++ b/keg_elements/templates/keg-elements/forms/enhanced-select-scripts.html
@@ -0,0 +1,7 @@
+{% if config.get('KEG_TEMPLATE_FLAVOR', 'bootstrap') == 'bootstrap' %}
+ {% if config.get('KEG_BOOTSTRAP_MAJOR_VERSION', 5) >= 5 %}
+ {% include 'keg-elements/forms/tomselect-scripts.html' %}
+ {% else %}
+ {% include 'keg-elements/forms/select2-scripts.html' %}
+ {% endif %}
+{% endif %}
\ No newline at end of file
diff --git a/keg_elements/templates/keg-elements/forms/enhanced-select-styles.html b/keg_elements/templates/keg-elements/forms/enhanced-select-styles.html
new file mode 100644
index 0000000..2755478
--- /dev/null
+++ b/keg_elements/templates/keg-elements/forms/enhanced-select-styles.html
@@ -0,0 +1,7 @@
+{% if config.get('KEG_TEMPLATE_FLAVOR', 'bootstrap') == 'bootstrap' %}
+ {% if config.get('KEG_BOOTSTRAP_MAJOR_VERSION', 5) >= 5 %}
+ {% include 'keg-elements/forms/tomselect-styles.html' %}
+ {% else %}
+ {% include 'keg-elements/forms/select2-styles.html' %}
+ {% endif %}
+{% endif %}
\ No newline at end of file
diff --git a/keg_elements/templates/keg-elements/forms/horizontal-b5.html b/keg_elements/templates/keg-elements/forms/horizontal-b5.html
new file mode 100644
index 0000000..76a31ca
--- /dev/null
+++ b/keg_elements/templates/keg-elements/forms/horizontal-b5.html
@@ -0,0 +1,328 @@
+{%- if _ is not defined -%}
+ {% macro _(message) -%}
+ {{ message }}
+ {%- endmacro %}
+{%- endif -%}
+
+{# Renders field for bootstrap 4 standards.
+
+ Params:
+ field - WTForm field
+ kwargs - pass any arguments you want in order to put them into the html attributes.
+ There are few exceptions: for - for_, class - class_, class__ - class_
+
+ Example usage:
+ {{ horz_form.field(form.email, placeholder='Input email', type='email') }}
+#}
+
+{% macro div_form_group(field) -%}
+
+ {{ caller(**kwargs) }}
+
+{%- endmacro %}
+
+{% macro description(field) -%}
+
+ {% if field.description is callable %}
+ {{ field.description()|safe }}
+ {% else %}
+ {{ field.description|safe }}
+ {% endif %}
+
+{%- endmacro %}
+
+{% macro field_errors(field) -%}
+{# You will need javascript similar to below for validations to work properly.
+ Alternatively, remove .was-validated from the form element to disable native client side validations by default.
+
+ var forms = document.getElementsByClassName('needs-validation');
+ var validation = Array.prototype.filter.call(forms, function(form) {
+ form.addEventListener('submit', function(event) {
+ if (form.checkValidity() === false) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ form.classList.add('was-validated');
+ }, false);
+ });
+
+ var fields = document.getElementsByClassName('is-invalid');
+ Array.prototype.filter.call(fields, function(field) {
+ field.setCustomValidity(false);
+ });
+#}
+
+ {% if field.errors %}
+ {% for e in field.errors %}
+
{{ e }}
+ {% endfor %}
+ {% elif field.flags.required %}
+
This field is required.
+ {% endif %}
+
+{% endmacro %}
+
+{% macro field(field, label_visible=true) -%}
+ {% call div_form_group(field, **kwargs) %}
+ {% if (field.type != 'HiddenField' and field.type != 'CSRFTokenField') and label_visible %}
+ {{ field.label(class_='col-form-label col-3') }}
+ {% endif %}
+
+ {{ field_widget(field, **kwargs) }}
+ {% if field.description %}
+ {{ description(field) }}
+ {% endif %}
+
+ {% endcall %}
+{%- endmacro %}
+
+
+{% macro custom_css() %}
+
+
+{% endmacro %}
+
+{% macro custom_js() %}
+
+
+{{ datetime_helper() }}
+{% endmacro %}
+
+{% macro multi_checkbox(field) %}
+
+ Select All
+ Select None
+
+
+ {% for choice_id, choice in field.choices %}
+
+
+ {{choice}}
+
+ {% endfor %}
+
+{% endmacro %}
+
+{% macro field_widget(field) %}
+ {% if field.type == "MultiCheckboxField" %}
+ {{ multi_checkbox(field) }}
+ {% else %}
+ {% if field.flags.disabled %}{% set _dummy = kwargs.update({'disabled': field.flags.disabled}) %}{% endif %}
+ {% if field.flags.readonly %}{% set _dummy = kwargs.update({'readonly': field.flags.readonly}) %}{% endif %}
+ {{ field(class_='form-control is-invalid' if field.errors else 'form-control', **kwargs) }}
+ {% endif %}
+ {{ field_errors(field) }}
+{% endmacro %}
+
+{# Renders checkbox fields since they are represented differently in bootstrap
+ Params:
+ field - WTForm field (there are no check, but you should put here only BooleanField.
+ kwargs - pass any arguments you want in order to put them into the html attributes.
+ There are few exceptions: for - for_, class - class_, class__ - class_
+
+ Example usage:
+ {{ horiz_form.checkbox_field(form.remember_me) }}
+ #}
+{% macro checkbox_field(field) -%}
+ {% call div_form_group(field, **kwargs) %}
+
+
+ {% if field.flags.disabled %}{% set _dummy = kwargs.update({'disabled': field.flags.disabled}) %}{% endif %}
+ {{ field(type='checkbox', class_='form-check-input custom-control-input' + (' is-invalid' if field.errors else ''), **kwargs) }}
+ {# pt-0 is to align the label with the checkbox by removing the padding #}
+
+ {{ field.label }}
+
+ {{ field_errors(field) }}
+
+ {% if field.description %}
+ {{ description(field) }}
+ {% endif %}
+
+ {% endcall %}
+{%- endmacro %}
+
+{# Renders radio field
+ Params:
+ field - WTForm field (must have an `iter_choices` method)
+ kwargs - pass any arguments you want in order to put them into the html attributes.
+ There are few exceptions: for - for_, class - class_, class__ - class_
+
+ Example usage:
+ {{ horiz_form.radio_field(form.answers) }}
+ #}
+{% macro radio_field(field, label_visible=true) -%}
+
+ {% if label_visible %}
+ {{ field.label(class_='col-form-label col-3') }}
+ {% endif %}
+
+ {% for value, label, checked in field.iter_choices() %}
+
+
+
+ {{ label }}
+
+ {% if loop.last %}{{ field_errors(field) }}{% endif %}
+
+ {% endfor %}
+
+ {% if field.description %}
+ {{ description(field) }}
+ {% endif %}
+
+{%- endmacro %}
+
+{% macro submit_group(action_text='Submit', btn_class='btn btn-primary', cancel_url='') -%}
+
+
+
{{ action_text }}
+ {% if cancel_url %}
+
Cancel
+ {% endif %}
+
+
+{%- endmacro %}
+
+{% macro render_field(f) -%}
+ {% if f is none %}
+ {# Do nothing b/c the field is None #}
+ {% elif f.type == 'BooleanField' or f.widget.input_type == 'checkbox' %}
+ {{ checkbox_field(f, **kwargs) }}
+ {% elif f.type == 'RadioField' %}
+ {{ radio_field(f, **kwargs) }}
+ {% elif f.type == 'FormField' %}
+ {{ render_form_fields(f, render_hidden=true) }}
+ {% else %}
+ {{ field(f, **kwargs) }}
+ {% endif %}
+{%- endmacro %}
+
+
+{% macro form_errors(form) -%}
+ {% if form.form_errors %}
+
+ {% for e in form.form_errors %}
+
{{ e }}
+ {% endfor %}
+
+ {% endif %}
+{% endmacro %}
+
+
+{# Renders WTForm in bootstrap way. There are two ways to call function:
+ - as macros: it will render all field forms using cycle to iterate over them
+ - as call: it will insert form fields as you specify:
+ e.g. {% call macros.render_form(form, action_url=url_for('login_view'), action_text='Login',
+ class_='login-form') %}
+ {{ macros.render_field(form.email, placeholder='Input email', type='email') }}
+ {{ macros.render_field(form.password, placeholder='Input password', type='password') }}
+ {{ macros.render_checkbox_field(form.remember_me, type='checkbox') }}
+ {% endcall %}
+
+ Params:
+ form - WTForm class
+ action_url - url where to submit this form
+ action_text - text of submit button
+ class_ - sets a class for form
+#}
+{% macro form(
+ form,
+ field_names=None,
+ action_url='',
+ action_text='Submit',
+ class_='',
+ btn_class='btn btn-primary',
+ cancel_url='',
+ form_upload=false,
+ dirty_check=false,
+ form_id=None,
+ form_name=None,
+ form_attrs=None
+) -%}
+
+
+{%- endmacro %}
+
+{% macro render_form_fields(form, render_hidden=false) -%}
+ {# Render hidden tags if flag is passed (for subforms only) #}
+ {{ form.hidden_tag() if render_hidden and form.hidden_tag }}
+ {% for f in form %}
+ {% if not f.widget.input_type == 'hidden' %}
+ {{ render_field(f) }}
+ {% endif %}
+ {% endfor %}
+{%- endmacro %}
+
+{% macro fields(form, field_names) -%}
+ {% for field_name in field_names %}
+ {{ render_field(form[field_name]) }}
+ {% endfor %}
+{%- endmacro %}
+
+{% macro section(heading, form, field_names) -%}
+ {{heading}}
+ {% if caller %}
+ {{ caller() }}
+ {% else %}
+ {{ fields(form, field_names) }}
+ {% endif %}
+{%- endmacro %}
+
+{% macro datetime_helper() -%}
+
+{%- endmacro %}
\ No newline at end of file
diff --git a/keg_elements/templates/keg-elements/forms/horizontal.html b/keg_elements/templates/keg-elements/forms/horizontal.html
new file mode 100644
index 0000000..7145419
--- /dev/null
+++ b/keg_elements/templates/keg-elements/forms/horizontal.html
@@ -0,0 +1,3 @@
+{% if config.get('KEG_TEMPLATE_FLAVOR', 'bootstrap') == 'bootstrap' %}
+ {% extends 'keg-elements/forms/horizontal-b' + config.get('KEG_BOOTSTRAP_MAJOR_VERSION', 5)|string + '.html' %}
+{% endif %}
\ No newline at end of file
diff --git a/keg_elements/templates/keg-elements/forms/tomselect-scripts.html b/keg_elements/templates/keg-elements/forms/tomselect-scripts.html
new file mode 100644
index 0000000..afa29e8
--- /dev/null
+++ b/keg_elements/templates/keg-elements/forms/tomselect-scripts.html
@@ -0,0 +1,11 @@
+
+
\ No newline at end of file
diff --git a/keg_elements/templates/keg-elements/forms/tomselect-styles.html b/keg_elements/templates/keg-elements/forms/tomselect-styles.html
new file mode 100644
index 0000000..2f0ae44
--- /dev/null
+++ b/keg_elements/templates/keg-elements/forms/tomselect-styles.html
@@ -0,0 +1,11 @@
+
+
\ No newline at end of file
diff --git a/keg_elements/templates/keg-elements/grid-view.html b/keg_elements/templates/keg-elements/grid-view.html
index f400504..7bfb87c 100644
--- a/keg_elements/templates/keg-elements/grid-view.html
+++ b/keg_elements/templates/keg-elements/grid-view.html
@@ -30,7 +30,7 @@ {{ title }}
{% endif %}
{% endblock %}
-{% block scripts %}
+{% block keg_scripts %}
{% if base_template %}
{{ super() }}
{{ grid_scripts() }}
diff --git a/kegel_app/config.py b/kegel_app/config.py
index 50b04cb..8ab246d 100644
--- a/kegel_app/config.py
+++ b/kegel_app/config.py
@@ -7,6 +7,8 @@ class DefaultProfile(object):
SQLALCHEMY_TRACK_MODIFICATIONS = True
SQLALCHEMY_DATABASE_URI = os.environ.get('SQLALCHEMY_DATABASE_URI', 'sqlite://')
+ KEG_BOOTSTRAP_MAJOR_VERSION = 4
+
class TestProfile(object):
KEG_KEYRING_ENABLE = False
diff --git a/kegel_app/templates/keg_element/demo-form.html b/kegel_app/templates/keg_element/demo-form.html
index 45c3a4b..a72b4f9 100644
--- a/kegel_app/templates/keg_element/demo-form.html
+++ b/kegel_app/templates/keg_element/demo-form.html
@@ -1,4 +1,4 @@
-{% import 'keg-elements/forms/horizontal-b4.html' as horizontal %}
+{% import 'keg-elements/forms/horizontal.html' as horizontal %}
{% block main %}
{{ title }}