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 and fa-solid fa-eye-slash

  • The text_field should switch from type password to text

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)

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

# [...]