Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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 @@ -25,6 +25,10 @@
"QuerySelectMultipleField",
"SelectField",
"Select2TagsField",
"BooleanField",
"FileField",
"UuidField",
"TextAreaField",
]


Expand Down Expand Up @@ -439,3 +443,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 @@ -33,3 +33,12 @@
gap: 8px;
}

.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 @@ -206,3 +206,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
});


// Shows the number of characters in a textarea..

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo

$('.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
});
38 changes: 37 additions & 1 deletion sqladmin/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import json
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 @@ -124,3 +124,39 @@ def __call__(self, field: Field, **kwargs: Any) -> Markup:
+ str(Markup.escape(super().__call__(field, **kwargs)))
+ "</div>"
)


class TextAreaWidget(widgets.Input):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not widgets.TextArea?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in this case you can downsize call override

"""
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")

kwargs["class"] = class_

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This happens even when class_ is None. Skip instead.


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 = ""

return Markup(
"<textarea %s>%s</textarea>"
% (html_params(name=field.name, **kwargs), escape(kwargs.get("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 @@ -16,6 +16,7 @@
QuerySelectMultipleField,
Select2TagsField,
SelectField,
TextAreaField,
UuidField,
)
from tests.common import DummyData
Expand Down Expand Up @@ -202,3 +203,12 @@ class F(Form):

form = F(DummyData(uuid=["00000000-0000-000000000001"]))
assert form.validate() is False


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