Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions sqladmin/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@
"SelectField",
"Select2TagsField",
"file_display_formatter",
"BooleanField",
"FileField",
"UuidField",
"TextAreaField",
]


Expand Down Expand Up @@ -533,3 +537,46 @@ def process_data(self, value: str | UUID | None) -> None:
self.data = None
else:
self.data = None


class TextAreaField(fields.StringField):
"""
This field represents an HTML `textarea` and can be used to take
multi-line input.

To disable automatic updating of the input size,
you need to pass enable_autoresize = False to the field arguments.
```python
form_args = {
'field_name': {
'enable_autoresize': False
}
}
```

To disable the display of the number of characters,
you need to pass the value show_chars_count = False to the field arguments.

```python
form_args = {
'field_name': {
'show_chars_count': False
}
}
```
"""

def __init__(
self,
label: str | None = None,
validators: list | None = None,
*,
enable_autoresize: bool = True,
show_chars_count: bool = True,
**kwargs: Any,
):
super().__init__(label, validators, **kwargs)
self.enable_autoresize = enable_autoresize
self.show_chars_count = show_chars_count

widget = sqladmin_widgets.TextAreaWidget()
2 changes: 1 addition & 1 deletion sqladmin/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
Form,
IntegerField,
StringField,
TextAreaField,
TimeField,
validators,
)
Expand Down Expand Up @@ -57,6 +56,7 @@
QuerySelectMultipleField,
Select2TagsField,
SelectField,
TextAreaField,
UuidField,
)
from sqladmin.helpers import (
Expand Down
9 changes: 9 additions & 0 deletions sqladmin/statics/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,12 @@
border: 0;
}

.autoresize-textarea {
min-height: 25px;
overflow-y: hidden;
height: 25px;
}

.chars-count-label.warning {
color: red;
}
43 changes: 43 additions & 0 deletions sqladmin/statics/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -538,3 +538,46 @@ $(':input[data-role="select2-tags"]').each(function () {
$(this).append(option).trigger('change');
}
});

// Automatically resize textarea on input events.
$('.autoresize-textarea').each(function() {
const $textarea = $(this);
if (!$textarea) return;

const updateTextareaHeight = () => {
$textarea.css('height', 'auto');
$textarea.css('height', $textarea[0].scrollHeight + 'px');
};

$textarea.on('input propertychange', function() {
updateTextareaHeight();
});

updateTextareaHeight(); // Resize on start
});


// Displays the number of characters in the text field.
$('.chars-count-label').each(function() {
const $charsCountLabel = $(this);
if (!$charsCountLabel) return;

const $textarea = $charsCountLabel.prev('textarea');
if (!$textarea.length) return;

const $maxLength = parseInt($textarea.attr('maxlength'), 10) || 0;

const updateCharsCountLabel = () => {
if (!$charsCountLabel.length) return;

const $currentLength = $textarea.val().length;
$charsCountLabel.text(`Number of characters: ${$currentLength}${$maxLength ? ` / ${$maxLength}` : ''}`);
$charsCountLabel.toggleClass('warning', $maxLength > 0 && $currentLength >= $maxLength);
};

$textarea.on('input propertychange', function() {
updateCharsCountLabel();
});

updateCharsCountLabel(); // Show on start
});
41 changes: 40 additions & 1 deletion sqladmin/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import logging
from typing import TYPE_CHECKING, Any

from markupsafe import Markup
from markupsafe import Markup, escape
from wtforms import Field, SelectFieldBase, widgets
from wtforms.widgets import html_params

Expand Down Expand Up @@ -170,3 +170,42 @@ def __call__(self, field: Field, **kwargs: Any) -> Markup:
)

return template.format(text=super().__call__(field, **kwargs))


class TextAreaWidget(widgets.TextArea):
"""
Render a textarea that automatically resizes on input.
"""

validation_attrs = ["required", "disabled", "readonly", "maxlength", "minlength"]

def __call__(self, field: Field, **kwargs: Any) -> Markup:
kwargs.setdefault("id", field.id)
flags = getattr(field, "flags", {})
for k in dir(flags):
if k in self.validation_attrs and k not in kwargs:
kwargs[k] = getattr(flags, k)

class_ = kwargs.get("class")
if getattr(field, "enable_autoresize", True) is True:
class_ = " ".join(filter(None, (class_, "autoresize-textarea")))
kwargs.setdefault("rows", "1")

if class_:
kwargs["class"] = class_

if hasattr(field, "_value") and callable(field._value):
kwargs["value"] = field._value()

if getattr(field, "show_chars_count", True) is True:
chars_count = '<div class="chars-count-label pt-1"></div>'
else:
chars_count = ""

value = kwargs.pop("value", "")

return Markup(
"<textarea %s>%s</textarea>"
% (html_params(name=field.name, **kwargs), escape(value))
+ chars_count
) # nosec: markupsafe_markup_xss
Comment thread
mmzeynalli marked this conversation as resolved.
10 changes: 10 additions & 0 deletions tests/test_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
QuerySelectMultipleField,
Select2TagsField,
SelectField,
TextAreaField,
UuidField,
)
from tests.common import DummyData
Expand Down Expand Up @@ -226,3 +227,12 @@ class FRequired(Form):
form = FRequired()
html = form.boolean()
assert "required" in html


def test_textarea_field() -> None:
class F(Form):
text = TextAreaField()

form = F()
assert "autoresize-textarea" in form.text()
assert "chars-count-label" in form.text()
Loading