Create a custom widget#
In this tutorial we will show how to create a custom reusable widget. It will be then possible to use this widget in any tile.
Requirements#
Vuetify has a huge number of component that can serve many purposes. Several others have been build in the sepal_ui library and can be found here. Sometimes a specific situation require complex assembly of multiple components. In this case it is better to create a custom widget rather than write numerous lines of code in your tile.
Let’s assume that you want to create a workflow that require your end-user to fill credentials to access an API. for the username you’ll surely decide to use a Textfield as :
import ipyvuetfy as v
username_field = v.TextField(
label = "Username",
placeholder = "Your API username",
v_model = None
)
But for the password you will have no built-in Vuetify object to use. let’s build a custom password widget that will be then use in our tile. We want :
a TextField like input
a eye button to hide or show the password
a direct link to the io object to not display the password with the bind
Note
When using the sepalwidgets.Alert.bind
method, the value is displayed on the screen in plain text. If we want to keep the password secret, we need to bypass the use of this method
Design the object#
Create the file#
Widget package is currently empty so we will create an extra password_field.py
. and add this file to the __init__.py
file :
# component/widget/__init__.py
from .paswword_field import *
Note
Advanced user
We here decided to use the web convention 1 file 1 object, which may sound weird for the most pythonic freaks.
If you prefer to use the PEP 8 module convention. delete the widget folder and write everything in a widget.py
module.
Initialize the object#
Here we will create the object with its expected attributes
# component/widget/password_field.py
import ipyvuetify as v
import sepal_ui.sepalwidgets as sw
class PasswordField(sw.SepalWidget, v.Layout):
def __init__(self, label="Password", **kwargs):
# create the eye icon
self.eye = v.Icon(class_ = 'ml-1', children=['fa-solid fa-eye'])
# create the texfied
self.text_field = v.TextField(
type = 'password',
label = label,
v_model = None
)
# create the layout
super().__init__(
row = True,
children = [self.eye, self.text_field],
**kwargs
)
Tip
Respect the writing convention of Python: CamelCase
for class and snake_case
for variables.
Here we embed our widget in a line layout. In this layout I used 2 widgets, a v.TextField
and a v.Icon
. The eye is an eye icon from the material design icon list.
I used the class “ml-1” (margin left 1) to let some room between the TextField
and the Password
.
The text_field is using the keyword type
to display a password
in the HTML convention. it means that the input will no be displayed.
Base colors#
To be aligned with the sepal UI, is highly recommended to use the sepal theme colors in your components. By default, widgets will use a color palette depending on the current theme, however, if you want to customize their style, you can use any of the sepal base colors. To display them, use the following lines.
from sepal_ui import sepalwidgets as sw
# correct colors for the documentation
# set to dark in SEPAL by default
import ipyvuetify as v
v.theme.dark = False
from sepal_ui import color
color._dark_theme = True
display(color)
color._dark_theme = False
display(color)
Current theme: dark
primary#b3842e | accent#a1458e | secondary#324a88 | success#3f802a | info#79b1c9 | warning#b8721d | error#a63228 | main#24221f | darker#1a1a1a | bg#121212 | menu#424242 |
Current theme: light
primary#1976D2 | accent#82B1FF | secondary#424242 | success#4CAF50 | info#2196F3 | warning#FB8C00 | error#FF5252 | main#2e7d32 | darker#005005 | bg#FFFFFF | menu#FFFFFF |
Toggle visibility#
Now we want to add a behavior to our object. When we click on the eye, the PasswordField
should toggle its visibility:
The eye should switch from
fa-solid fa-eye
andfa-solid fa-eye-slash
The text_field should switch from type
password
totext
To do so we will first add 2 class static variable (caps lock) to list the 2 types and icon and set them on the two attributes of my class. a new attribute needs to be created to remind the current state of the password.
I’ll call it password_viz
as the viz
parameter is already an attribute of SepalWidget
.
# component/widget/password_field.py
import ipyvuetify as v
import sepal_ui.sepalwidgets as sw
class PasswordField(sw.SepalWidget, v.Layout):
EYE_ICONS = ['fa-solid fa-eye', 'fa-solid fa-eye-slash'] # new icon list
TYPES = ['password', 'text'] # new type list
def __init__(self, label="Password", **kwargs):
# the viz attribute
self.password_viz = False
# create the eye icon
self.eye = v.Icon(class_ = 'ml-1', children=[EYE_ICON[False]])
# create the texfied
self.text_field = v.TextField(
type = TYPES[False],
label = label,
v_model = None
)
# create the layout
super().__init__(
row = True,
children = [self.eye, self.text_field],
**kwargs
)
now I will create a function to dynamically switch the state of my password visibility. this class method should never be called outside the object so I’ll add a ‘_’ to start its name.
It will be used as a callback function in a click event, so it will have the following 3 attributes : widget
, data
, event
.
def _toggle_viz(self, widget, event, data):
viz = not self.password_viz
# change the password viz
self.password_viz = viz
self.eye.children = [EYE_ICONS[viz]]
self.text_field.type = self.TYPES[viz]
return
called in the end of my __init__
method by
self.eye.on_event('click', self._toggle_viz)
link to the Model
#
The newly created widget embed a v_model
trait so it can be bonded to any Model
object using the bind
method.
# component/tile/my_tile.py
from sepal_ui import sepalwidgets as sw
from component.widget import *
class MyTile(sw.Tile):
def __init__(self, model, **kwargs):
# create a password
self.password_field = PasswordField(label = 'PasswordField')
# link it to the model
model.bind(self.password_field, 'password')
# [...]
final password widget#
finally we obtain the following reusable widget :
# component/widget/password_field.py
import ipyvuetify as v
import sepal_ui.sepalwidgets as sw
class PasswordField(sw.SepalWidget, v.Layout):
EYE_ICONS = ['fa-solid fa-eye', 'fa-solid fa-eye-slash'] # new icon list
TYPES = ['password', 'text'] # new type list
def __init__(self, label="Password", **kwargs):
# the viz attribute
self.password_viz = False
# create the eye icon
self.eye = v.Icon(class_ = 'ml-1', children=[EYE_ICON[False]])
# create the texfied
self.text_field = v.TextField(
type = TYPES[False],
label = label,
v_model = None
)
# create the layout
super().__init__(
row = True,
children = [self.eye, self.text_field],
**kwargs
)
# link the different functions
self.eye.on_event('click', self._toggle_viz)
def _toggle_viz(self, widget, event, data):
viz = not self.password_viz
# change the password viz
self.password_viz = viz
self.eye.children = [EYE_ICONS[viz]]
self.text_field.type = self.TYPES[viz]
return
Usage#
To reuse my object in a tile I should first import the widget component and then initialize it with all the other widgets
# component/tile/my_tile.py
from sepal_ui import sepalwidgets as sw
from component.widget import *
class MyTile(sw.Tile):
def __init__(self, model, **kwargs):
# create a password
self.password_field = PasswordField(label = 'PasswordField')
# create a username
username_field = v.TextField(
label = "Username",
placeholder = "Your API username",
v_model = None
)
# link it to io
self.model = model \
.bind(self.username_field, 'username') \
.bind(self.password_field, 'password')
# [...]