diff --git a/docs/conf.py b/docs/conf.py
index b45a45b..b5031a0 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -16,7 +16,7 @@
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
-#sys.path.insert(0, os.path.abspath('.'))
+sys.path.insert(0, os.path.abspath('..'))
# -- General configuration -----------------------------------------------------
diff --git a/docs/index.rst b/docs/index.rst
index 28a4154..1387ba5 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -102,6 +102,12 @@ code and display format as parameters.
)
+GenderField
+-----------
+
+.. automodule:: wtforms_components.fields.gender
+
+
ColorField
----------
diff --git a/tests/fields/test_gender_field.py b/tests/fields/test_gender_field.py
new file mode 100644
index 0000000..b5b9827
--- /dev/null
+++ b/tests/fields/test_gender_field.py
@@ -0,0 +1,163 @@
+from wtforms import Form
+
+from tests import MultiDict
+from wtforms_components import GenderField
+
+
+def test_no_value():
+ class TestForm(Form):
+ gender = GenderField()
+
+ form = TestForm()
+ gender_html = form.gender()
+ expected_html = (
+ ''
+ )
+ assert gender_html == expected_html
+
+
+def test_male_value():
+ class TestForm(Form):
+ gender = GenderField()
+
+ values = MultiDict(gender='male')
+
+ form = TestForm(values)
+ gender_html = form.gender()
+ expected_html = (
+ ''
+ )
+ assert gender_html == expected_html
+
+
+def test_female_value():
+ class TestForm(Form):
+ gender = GenderField()
+
+ values = MultiDict(gender='female')
+
+ form = TestForm(values)
+ gender_html = form.gender()
+ expected_html = (
+ ''
+ )
+ assert gender_html == expected_html
+
+
+def test_non_binary_value():
+ class TestForm(Form):
+ gender = GenderField()
+
+ values = MultiDict(gender='non-binary')
+
+ form = TestForm(values)
+ gender_html = form.gender()
+ expected_html = (
+ ''
+ )
+ assert gender_html == expected_html
+
+
+def test_non_simple_value():
+ class TestForm(Form):
+ gender = GenderField()
+
+ values = MultiDict(gender='transgender')
+
+ form = TestForm(values)
+ gender_html = form.gender()
+ expected_html = (
+ ''
+ )
+ assert gender_html == expected_html
+
+
+def test_customizable_simple_values():
+ simple_genders = (
+ ('', "Not Specified"),
+ ('female', "Female"),
+ ('male', "Male"),
+ ('trans', "Transgender"),
+ ('non-binary', "Custom"),
+ )
+
+ class TestForm(Form):
+ gender = GenderField(simple_genders=simple_genders)
+
+ form = TestForm()
+ gender_html = form.gender()
+ expected_html = (
+ ''
+ )
+ assert gender_html == expected_html
+
+
+def test_customizable_simple_value_selected():
+ simple_genders = (
+ ('', "Not Specified"),
+ ('female', "Female"),
+ ('male', "Male"),
+ ('trans', "Transgender"),
+ ('non-binary', "Custom"),
+ )
+
+ class TestForm(Form):
+ gender = GenderField(simple_genders=simple_genders)
+
+ values = MultiDict(gender='trans')
+
+ form = TestForm(values)
+ gender_html = form.gender()
+ expected_html = (
+ ''
+ )
+ assert gender_html == expected_html
+
+
+def test_customized_non_simple_value():
+ simple_genders = (
+ ('', "Not Specified"),
+ ('female', "Female"),
+ ('male', "Male"),
+ ('trans', "Transgender"),
+ ('non-binary', "Custom"),
+ )
+
+ class TestForm(Form):
+ gender = GenderField(simple_genders=simple_genders)
+
+ values = MultiDict(gender='genderfluid')
+
+ form = TestForm(values)
+ gender_html = form.gender()
+ expected_html = (
+ ''
+ )
+ assert gender_html == expected_html
diff --git a/wtforms_components/__init__.py b/wtforms_components/__init__.py
index e8fdcc5..b2b4370 100644
--- a/wtforms_components/__init__.py
+++ b/wtforms_components/__init__.py
@@ -12,6 +12,7 @@
DecimalSliderField,
EmailField,
FloatIntervalField,
+ GenderField,
IntegerField,
IntegerSliderField,
IntIntervalField,
@@ -44,6 +45,7 @@
Email,
EmailField,
FloatIntervalField,
+ GenderField,
If,
IntegerField,
IntegerSliderField,
diff --git a/wtforms_components/fields/__init__.py b/wtforms_components/fields/__init__.py
index 5dd504b..8852c94 100644
--- a/wtforms_components/fields/__init__.py
+++ b/wtforms_components/fields/__init__.py
@@ -1,5 +1,6 @@
from .ajax import AjaxField
from .color import ColorField
+from .gender import GenderField
from .html5 import (
DateField,
DateTimeField,
@@ -28,6 +29,7 @@
__all__ = (
AjaxField,
ColorField,
+ GenderField,
DateField,
DateIntervalField,
DateTimeField,
diff --git a/wtforms_components/fields/gender.py b/wtforms_components/fields/gender.py
new file mode 100644
index 0000000..1cb76f3
--- /dev/null
+++ b/wtforms_components/fields/gender.py
@@ -0,0 +1,136 @@
+"""
+GenderField is a field that represents the gender identity of a person. It is
+as inclusive as possible, suggesting "simple" genders like "male" and "female"
+but also supporting arbitrary text input for non-binary gender identities.
+Note that properly using this field requires a bit of Javascript.
+
+Simple example::
+
+ from wtforms import Form
+ from wtforms_components import GenderField
+
+ class UserProfileForm(Form):
+ gender = GenderField()
+
+To make this field fully functional, you should also add some Javascript to
+the page so that when the user selects a non-binary gender, the user is able
+to type in their own gender identity.
+Here is an example script that you can use:
+
+.. code-block:: javascript
+
+ function selectRenderAsText(event) {
+ var select = event.target;
+ var option = select.selectedOptions[0];
+ if("renderAsText" in option.dataset) {
+ var input = document.createElement("input");
+ input.type = "text";
+ input.name = select.name;
+ input.id = select.id;
+ input.className = select.className;
+ input.value = option.value;
+ select.parentNode.replaceChild(input, select);
+ input.focus()
+ };
+ }
+ function attachRenderAsTextEvents(event) {
+ var selects = document.getElementsByTagName("select");
+ for (var i = 0; i < selects.length; ++i) {
+ selects[i].addEventListener("change", selectRenderAsText);
+ }
+ }
+ document.addEventListener("DOMContentLoaded", attachRenderAsTextEvents);
+
+Or using jQuery:
+
+.. code-block:: javascript
+
+ $(function() {
+ $("select").change(function() {
+ if("renderAsText" in $("option:selected", this).data()) {
+ var input = $("", {
+ "type": "text",
+ "name": $(this).attr("name"),
+ "id": $(this).attr("id"),
+ "class": $(this).attr("class"),
+ "value": $(this).val(),
+ });
+ $(this).replaceWith(input);
+ input.focus();
+ }
+ });
+ });
+
+The field includes the following gender options by default:
+"Not Specified", "Male", "Female", and "Non-Binary".
+To change these options, pass a list of value-label pairs to the
+``simple_genders`` parameter when creating this field. For example, to include
+"Transgender" in the dropdown, you could do the following::
+
+ simple_genders = (
+ ('', "Not Specified"),
+ ('female', "Female"),
+ ('male', "Male"),
+ ('trans', "Transgender"),
+ ('non-binary', "Custom"),
+ )
+
+ class UserProfileForm(Form):
+ gender = GenderField(simple_genders=simple_genders)
+
+By default, the "Non-Binary" option includes a ``data-render-as-text``
+attribute, which indicates to the Javascript on the page that when the
+user selects this option, the ``