Source code for wildewidgets.widgets.buttons
from __future__ import annotations
from copy import copy
from typing import Any
from .base import Block
[docs]class Button(Block):
"""
Render a ``<button>`` with Bootstrap styling.
Example:
.. code-block:: python
from wildewidgets import Button
button = Button(text="My Button")
When rendered in the template with the ``wildewdigets`` template tag, this
will produce:
.. code-block:: html
<button type="button" class="button btn btn-secondary">My Button</button>
Keyword Args:
text: The text to use for the button
color: The Boostrap color class to use for the button
close: The Boostrap close icon will be used for the button
size: The Bootstrap button size - None, 'sm', 'lg'
disabled: If ``True``, the button will be disabled
"""
block: str = "button"
tag: str = "button"
#: The Boostrap color class to use for the button
color: str = "secondary"
#: The text to use for the button
text: str = "Button"
#: If ``True``, ignore :py:attr:`text` and make this into a Bootstrap
#: "close" button with a close icon.
close: bool = False
#: If ``True``, ignore :py:attr:`text` and make this into a Bootstrap
#: "close" button with a close icon.
size: str | None = None
disabled: bool = False
def __init__(
self,
text: str | None = None,
color: str | None = None,
close: bool | None = None,
size: str | None = None,
disabled: bool | None = None,
**kwargs: Any,
) -> None:
self.color = color if color is not None else self.color
self.text = text if text is not None else self.text
self.close = close if close is not None else self.close
self.size = size if size is not None else self.size
self.disabled = disabled if disabled is not None else self.disabled
if self.disabled:
if "attributes" in kwargs:
kwargs["attributes"]["disabled"] = True
else:
kwargs["attributes"] = {"disabled": True}
if self.close:
self.text = ""
super().__init__(self.text, **kwargs)
self._attributes["type"] = "button"
if self.close:
self.add_class("btn-close")
self._aria_attributes["label"] = "Close"
else:
self.add_class("btn")
self.add_class(f"btn-{self.color}")
if self.size:
self.add_class(f"btn-{self.size}")
[docs]class ModalButton(Button):
"""
Render a ``<button>`` with Bootstrap styling which toggles a Bootstrap modal.
Example:
.. code-block:: python
from wildewidgets import ModalButton
button = ModalButton(text="My Button", target='#mymodal')
When rendered in the template with the ``wildewdigets`` template tag, this
will produce:
.. code-block:: html
<button type="button" class="button button--modal btn btn-secondary"
data-toggle="modal" data-target="#mymodal">My Button</button>
Keyword Args:
target: The CSS target for the Bootstrap modal
"""
block: str = "button button--modal"
#: The CSS target for the Bootstrap modal
target: str | None = None
def __init__(self, target: str | None = None, **kwargs: Any) -> None:
self.target = target or self.target
assert self.target is not None, "ModalButton requires a target" # noqa: S101
super().__init__(**kwargs)
self._data_attributes["toggle"] = "modal"
self._data_attributes["target"] = self.target
[docs]class CollapseButton(Button):
"""
Render a ``<button>`` with Bootstrap styling which toggles a ``collapse``.
Example:
... code-block:: python
from wildewidgets import CollapseButton
button = CollapseButton(text="My Button", target='#mymodal')
When rendered in the template with the ``wildewdigets`` template tag, this
will produce:
... code-block:: html
<button type="button" class="button button--collapse btn btn-secondary"
data-toggle="collapse" data-target="#mymodal"
aria-expanded="false" aria-controls="mymodal">My Button</button>
Keyword Args:
target: The CSS target for the Bootstrap collapse
"""
block: str = "button button--collapse"
#: The CSS target for the Bootstrap collapse
target: str | None = None
def __init__(self, target: str | None = None, **kwargs: Any) -> None:
self.target = target or self.target
assert self.target is not None, "CollapseButton requires a target" # noqa: S101
super().__init__(**kwargs)
self._data_attributes["toggle"] = "collapse"
self._data_attributes["target"] = self.target
self._aria_attributes["expanded"] = "false"
self._aria_attributes["controls"] = self.target.lstrip("#")
[docs]class LinkButton(Button):
"""
Render an ``<a>`` with Bootstrap button styling which toggles a Bootstrap
modal.
Example:
... code-block:: python
from wildewidgets import LinkButton
button = LinkButton(text="My Button", url='https://myexample.com')
When rendered in the template with the ``wildewdigets`` template tag, this
will produce:
... code-block:: html
<a href="https://myexample.com"
class="button button--link btn btn-secondary">
My Button
</a>
Keyword Args:
url: The URL for the ``href`` attribute of the <a> record, defaults to no URL.
"""
block: str = "button button--link"
tag: str = "a"
url: str | None = None
def __init__(self, url: str | None = None, **kwargs: Any) -> None:
self.url = url or self.url
assert self.url is not None, "LinkButton requires a url" # noqa: S101
super().__init__(**kwargs)
del self._attributes["type"]
if url:
self._attributes["href"] = self.url
[docs]class InputButton(Button):
"""
Render an ``<input type="submit">`` with Bootstrap button styling.
Example::
.. code-block:: python
from wildewidgets import InputButton
button = InputButton(text="Save")
When rendered in the template with the ``wildewdigets`` template tag, this
will produce:
.. code-block:: html
<input type="submit"
class="button button--submit btn btn-secondary" value="Save">
"""
template_name: str = "wildewidgets/block--simple.html"
block: str = "button--submit"
tag: str = "input"
confirm_text: str | None = None
def __init__(self, confirm_text: str | None = None, **kwargs: Any) -> None:
self.confirm_text = confirm_text or self.confirm_text
super().__init__(**kwargs)
self._attributes["type"] = "submit"
self._attributes["value"] = self.text
if self.confirm_text:
self._attributes["onclick"] = f"return confirm('{self.confirm_text}');"
self.text = ""
[docs]class FormButton(Block):
"""
Render a ``<form>`` with optional hidden inputs and a submit button.
Example::
.. code-block:: python
from wildewidgets import FormButton
button = FormButton(
text="Save",
action='/my/form/action',
data={'field1': 'value1'}
)
When rendered in the template with the ``wildewdigets`` template tag, this
will produce:
.. code-block:: html
<form action="/my/form/action" method="post" class="button-form">
<input type="hidden" name="csrfmiddlewaretoken"
value="__THE_CSRF_TOKEN__">
<input type="hidden" name="field1" value="value1">
<input type="submit" class="button button--submit btn btn-secondary"
value="Save">
</form>
All the constructor parameters can be set in a subclass of this class as
class attributes. Parameters to the constructor override any defined class
attributes.
Keyword Args:
text: The text to use for the button, defaults to 'Button'
color: The Boostrap color class to use for the button, defaults to 'secondary'
button_css_class: a string of classes to apply to the button, defaults
to no classes.
button_attributes: Set any additional attributes for the button as key,
value pairs, defaults to no additional attributes.
button_data_attributes: Set ``data-`` attributes for the button, defaults to no
data attributes
"""
template_name: str = "wildewidgets/button--form.html"
tag: str = "form"
block: str = "button-form"
action: str | None = None
method: str = "post"
text: str | None = None
color: str = "secondary"
button_css_class: str | None = None
button_attributes: dict[str, str] = {} # noqa: RUF012
button_data_attributes: dict[str, str] = {} # noqa: RUF012
confirm_text: str | None = None
data: dict[str, Any] = {} # noqa: RUF012
def __init__(self, **kwargs: Any) -> None:
action = kwargs.pop("action", self.action)
method = kwargs.pop("method", self.method)
self.close = kwargs.pop("close", False)
self.data = kwargs.pop("data", copy(self.data))
self.button = self.__build_button(kwargs)
super().__init__(**kwargs)
self._attributes["method"] = method
self._attributes["action"] = action
def __build_button(self, kwargs: dict[str, Any]) -> InputButton:
"""
Build the submit button for the form.
Creates an InputButton with appropriate styling and attributes based on
the configuration provided to the FormButton.
Args:
kwargs: Dictionary of keyword arguments to process
Returns:
InputButton: The configured submit button for the form
"""
button_kwargs = {
"text": kwargs.pop("text", self.text),
"color": kwargs.pop("color", self.color),
"css_class": kwargs.pop("button_css_class", self.button_css_class),
"attributes": kwargs.pop("button_attributes", copy(self.button_attributes)),
"data_attributes": kwargs.pop(
"button_data_attributes", copy(self.button_data_attributes)
),
}
if self.close:
button_kwargs["close"] = True
button_kwargs["aria_attributes"] = {"label": "Close"}
button_kwargs["confirm_text"] = kwargs.pop("confirm_text", self.confirm_text)
return InputButton(**button_kwargs)
[docs] def get_context_data(self, *args, **kwargs) -> dict[str, Any]:
context = super().get_context_data(*args, **kwargs)
context["button"] = self.button
context["data"] = self.data
return context
[docs]class ButtonRow(Block):
"""
Renders a row of buttons with right alignment.
This class creates a horizontal row of buttons with right-aligned (end)
justification using Bootstrap's flexbox utilities. It's useful for placing
action buttons at the bottom of forms or dialogs.
Note:
This class is planned for deprecation in favor of
:py:class:`HorizontalLayoutBlock`.
Example:
.. code-block:: python
from wildewidgets import Button, ButtonRow
save_button = Button(text="Save", color="primary")
cancel_button = Button(text="Cancel")
button_row = ButtonRow(save_button, cancel_button)
When rendered, this produces:
.. code-block:: html
<div class="d-flex justify-content-end">
<button type="button" class="button btn btn-primary">Save</button>
<button type="button" class="button btn btn-secondary">Cancel</button>
</div>
Args:
*blocks: Button instances or other blocks to include in the row
Keyword Args:
**kwargs: Additional keyword arguments passed to the parent Block class
"""
def __init__(self, *blocks: Any, **kwargs: Any) -> None:
"""
Initialize a ButtonRow with buttons and styling.
Adds flexbox utility classes to create a right-aligned row of buttons.
Args:
*blocks: Button instances or other blocks to include in the row
**kwargs: Additional keyword arguments including css_class
"""
css_class = kwargs.get("css_class", "")
css_class += " d-flex justify-content-end"
kwargs["css_class"] = css_class
super().__init__(*blocks, **kwargs)