Repository: 12rambau/sepal_ui

Issue:
create a function to set the text of the btn dynamically
icon and text should be editable dynamically 
https://github.com/12rambau/sepal_ui/blob/8af255ec0d1cb3ad4dd74d021ad140fafef756f6/sepal_ui/sepalwidgets/btn.py#L38

Discussion:

Patch:
diff --git a/docs/source/widgets/btn.rst b/docs/source/widgets/btn.rst
index 949d5468..91d92967 100644
--- a/docs/source/widgets/btn.rst
+++ b/docs/source/widgets/btn.rst
@@ -20,8 +20,8 @@ The default color is set to "primary".
     v.theme.dark = False
     
     btn = sw.Btn(
-        text = "The One btn",
-        icon = "fas fa-cogs"
+        msg = "The One btn",
+        gliph = "fas fa-cogs"
     )
     btn
     
@@ -42,8 +42,8 @@ Btn can be used to launch function on any Javascript event such as "click".
     v.theme.dark = False
     
     btn = sw.Btn(
-        text = "The One btn",
-        icon = "fas fa-cogs"
+        msg = "The One btn",
+        gliph = "fas fa-cogs"
     )
     btn.on_event('click', lambda *args: print('Hello world!'))
     
diff --git a/sepal_ui/reclassify/reclassify_view.py b/sepal_ui/reclassify/reclassify_view.py
index f4d6ca40..18a90455 100644
--- a/sepal_ui/reclassify/reclassify_view.py
+++ b/sepal_ui/reclassify/reclassify_view.py
@@ -33,8 +33,8 @@ class ImportMatrixDialog(v.Dialog):
         # create the 3 widgets
         title = v.CardTitle(children=["Load reclassification matrix"])
         self.w_file = sw.FileInput(label="filename", folder=folder)
-        self.load_btn = sw.Btn("Load")
-        cancel = sw.Btn("Cancel", outlined=True)
+        self.load_btn = sw.Btn(msg="Load")
+        cancel = sw.Btn(msg="Cancel", outlined=True)
         actions = v.CardActions(children=[cancel, self.load_btn])
 
         # default params
@@ -81,8 +81,8 @@ class SaveMatrixDialog(v.Dialog):
         # create the widgets
         title = v.CardTitle(children=["Save matrix"])
         self.w_file = v.TextField(label="filename", v_model=None)
-        btn = sw.Btn("Save matrix")
-        cancel = sw.Btn("Cancel", outlined=True)
+        btn = sw.Btn(msg="Save matrix")
+        cancel = sw.Btn(msg="Cancel", outlined=True)
         actions = v.CardActions(children=[cancel, btn])
         self.alert = sw.Alert(children=["Choose a name for the output"]).show()
 
@@ -464,7 +464,7 @@ class ReclassifyView(sw.Card):
 
         self.btn_list = [
             sw.Btn(
-                "Custom",
+                msg="Custom",
                 _metadata={"path": "custom"},
                 small=True,
                 class_="mr-2",
@@ -472,7 +472,7 @@ class ReclassifyView(sw.Card):
             )
         ] + [
             sw.Btn(
-                f"use {name}",
+                msg=f"use {name}",
                 _metadata={"path": path},
                 small=True,
                 class_="mr-2",
@@ -490,18 +490,20 @@ class ReclassifyView(sw.Card):
         self.save_dialog = SaveMatrixDialog(folder=out_path)
         self.import_dialog = ImportMatrixDialog(folder=out_path)
         self.get_table = sw.Btn(
-            ms.rec.rec.input.btn, "far fa-table", color="success", small=True
+            msg=ms.rec.rec.input.btn, gliph="far fa-table", color="success", small=True
         )
         self.import_table = sw.Btn(
-            "import",
-            "fas fa-download",
+            msg="import",
+            gliph="fas fa-download",
             color="secondary",
             small=True,
             class_="ml-2 mr-2",
         )
-        self.save_table = sw.Btn("save", "fas fa-save", color="secondary", small=True)
+        self.save_table = sw.Btn(
+            msg="save", gliph="fas fa-save", color="secondary", small=True
+        )
         self.reclassify_btn = sw.Btn(
-            ms.rec.rec.btn, "fas fa-chess-board", small=True, disabled=True
+            msg=ms.rec.rec.btn, gliph="fas fa-chess-board", small=True, disabled=True
         )
 
         self.toolbar = v.Toolbar(
diff --git a/sepal_ui/reclassify/table_view.py b/sepal_ui/reclassify/table_view.py
index c3f8a35a..24ac31b5 100644
--- a/sepal_ui/reclassify/table_view.py
+++ b/sepal_ui/reclassify/table_view.py
@@ -49,19 +49,24 @@ class ClassTable(sw.DataTable):
         # create the 4 CRUD btn
         # and set them in the top slot of the table
         self.edit_btn = sw.Btn(
-            ms.rec.table.btn.edit,
-            icon="fas fa-pencil-alt",
+            msg=ms.rec.table.btn.edit,
+            gliph="fas fa-pencil-alt",
             class_="ml-2 mr-2",
             color="secondary",
             small=True,
         )
         self.delete_btn = sw.Btn(
-            ms.rec.table.btn.delete, icon="fas fa-trash-alt", color="error", small=True
+            msg=ms.rec.table.btn.delete,
+            gliph="fas fa-trash-alt",
+            color="error",
+            small=True,
         )
         self.add_btn = sw.Btn(
-            ms.rec.table.btn.add, icon="fas fa-plus", color="success", small=True
+            msg=ms.rec.table.btn.add, gliph="fas fa-plus", color="success", small=True
+        )
+        self.save_btn = sw.Btn(
+            msg=ms.rec.table.btn.save, gliph="far fa-save", small=True
         )
-        self.save_btn = sw.Btn(ms.rec.table.btn.save, icon="far fa-save", small=True)
 
         slot = v.Toolbar(
             class_="d-flex mb-6",
@@ -212,20 +217,19 @@ class EditDialog(v.Dialog):
         self.title = v.CardTitle(children=[self.TITLES[0]])
 
         # Action buttons
-        self.save = sw.Btn(ms.rec.table.edit_dialog.btn.save.name)
+        self.save = sw.Btn(msg=ms.rec.table.edit_dialog.btn.save.name)
         save_tool = sw.Tooltip(
             self.save, ms.rec.table.edit_dialog.btn.save.tooltip, bottom=True
         )
 
-        self.modify = sw.Btn(
-            ms.rec.table.edit_dialog.btn.modify.name
-        ).hide()  # by default modify is hidden
+        self.modify = sw.Btn(msg=ms.rec.table.edit_dialog.btn.modify.name)
+        self.modify.hide()  # by default modify is hidden
         modify_tool = sw.Tooltip(
             self.modify, ms.rec.table.edit_dialog.btn.modify.tooltip, bottom=True
         )
 
         self.cancel = sw.Btn(
-            ms.rec.table.edit_dialog.btn.cancel.name, outlined=True, class_="ml-2"
+            msg=ms.rec.table.edit_dialog.btn.cancel.name, outlined=True, class_="ml-2"
         )
         cancel_tool = sw.Tooltip(
             self.cancel, ms.rec.table.edit_dialog.btn.cancel.tooltip, bottom=True
@@ -437,7 +441,7 @@ class SaveDialog(v.Dialog):
             v_model=ms.rec.table.save_dialog.placeholder,
         )
 
-        self.save = sw.Btn(ms.rec.table.save_dialog.btn.save.name)
+        self.save = sw.Btn(msg=ms.rec.table.save_dialog.btn.save.name)
         save = sw.Tooltip(
             self.save,
             ms.rec.table.save_dialog.btn.save.tooltip,
@@ -446,7 +450,7 @@ class SaveDialog(v.Dialog):
         )
 
         self.cancel = sw.Btn(
-            ms.rec.table.save_dialog.btn.cancel.name, outlined=True, class_="ml-2"
+            msg=ms.rec.table.save_dialog.btn.cancel.name, outlined=True, class_="ml-2"
         )
         cancel = sw.Tooltip(
             self.cancel, ms.rec.table.save_dialog.btn.cancel.tooltip, bottom=True
@@ -600,8 +604,8 @@ class TableView(sw.Card):
             folder=self.class_path,
         )
         self.btn = sw.Btn(
-            ms.rec.table.classif.btn,
-            icon="far fa-table",
+            msg=ms.rec.table.classif.btn,
+            gliph="far fa-table",
             color="success",
             outlined=True,
         )
diff --git a/sepal_ui/sepalwidgets/btn.py b/sepal_ui/sepalwidgets/btn.py
index c6437d86..137622fa 100644
--- a/sepal_ui/sepalwidgets/btn.py
+++ b/sepal_ui/sepalwidgets/btn.py
@@ -1,6 +1,9 @@
+import warnings
 from pathlib import Path
 
 import ipyvuetify as v
+from deprecated.sphinx import deprecated
+from traitlets import Unicode, observe
 
 from sepal_ui.scripts import utils as su
 from sepal_ui.sepalwidgets.sepalwidget import SepalWidget
@@ -14,27 +17,83 @@ class Btn(v.Btn, SepalWidget):
     the color will be defaulted to 'primary' and can be changed afterward according to your need
 
     Args:
+        msg (str, optional): the text to display in the btn
+        gliph (str, optional): the full name of any mdi/fa icon
         text (str, optional): the text to display in the btn
         icon (str, optional): the full name of any mdi/fa icon
         kwargs (dict, optional): any parameters from v.Btn. if set, 'children' will be overwritten.
+
+    .. deprecated:: 2.13
+        ``text`` and ``icon`` will be replaced by ``msg`` and ``gliph`` to avoid duplicating ipyvuetify trait.
     """
 
     v_icon = None
     "v.Icon: the icon in the btn"
 
-    def __init__(self, text="Click", icon="", **kwargs):
+    gliph = Unicode("").tag(sync=True)
+    "traitlet.Unicode: the name of the icon"
+
+    msg = Unicode("").tag(sync=True)
+    "traitlet.Unicode: the text of the btn"
+
+    def __init__(self, msg="Click", gliph="", **kwargs):
+
+        # deprecation in 2.13 of text and icon
+        # as they already exist in the ipyvuetify Btn traits (as booleans)
+        if "text" in kwargs:
+            if isinstance(kwargs["text"], str):
+                msg = kwargs.pop("text")
+                warnings.warn(
+                    '"text" is deprecated, please use "msg" instead', DeprecationWarning
+                )
+        if "icon" in kwargs:
+            if isinstance(kwargs["icon"], str):
+                gliph = kwargs.pop("icon")
+                warnings.warn(
+                    '"icon" is deprecated, please use "gliph" instead',
+                    DeprecationWarning,
+                )
 
         # create the default v_icon
         self.v_icon = v.Icon(left=True, children=[""])
-        self.set_icon(icon)
 
         # set the default parameters
         kwargs["color"] = kwargs.pop("color", "primary")
-        kwargs["children"] = [self.v_icon, text]
+        kwargs["children"] = [self.v_icon, self.msg]
 
         # call the constructor
         super().__init__(**kwargs)
 
+        self.gliph = gliph
+        self.msg = msg
+
+    @observe("gliph")
+    def _set_gliph(self, change):
+        """
+        Set a new icon. If the icon is set to "", then it's hidden
+        """
+        new_gliph = change["new"]
+        self.v_icon.children = [new_gliph]
+
+        # hide the component to avoid the right padding
+        if not new_gliph:
+            su.hide_component(self.v_icon)
+        else:
+            su.show_component(self.v_icon)
+
+        return self
+
+    @observe("msg")
+    def _set_text(self, change):
+        """
+        Set the text of the btn
+        """
+
+        self.children = [self.v_icon, change["new"]]
+
+        return self
+
+    @deprecated(version="2.14", reason="Replace by the private _set_gliph")
     def set_icon(self, icon=""):
         """
         set a new icon. If the icon is set to "", then it's hidden.
@@ -45,13 +104,7 @@ class Btn(v.Btn, SepalWidget):
         Return:
             self
         """
-        self.v_icon.children = [icon]
-
-        if not icon:
-            su.hide_component(self.v_icon)
-        else:
-            su.show_component(self.v_icon)
-
+        self.gliph = icon
         return self
 
     def toggle_loading(self):
diff --git a/sepal_ui/sepalwidgets/inputs.py b/sepal_ui/sepalwidgets/inputs.py
index 5a9507ad..1bb0e850 100644
--- a/sepal_ui/sepalwidgets/inputs.py
+++ b/sepal_ui/sepalwidgets/inputs.py
@@ -256,7 +256,7 @@ class FileInput(v.Flex, SepalWidget):
                     "name": "activator",
                     "variable": "x",
                     "children": Btn(
-                        icon="fas fa-search", v_model=False, v_on="x.on", text=label
+                        gliph="fas fa-search", v_model=False, v_on="x.on", msg=label
                     ),
                 }
             ],


Test patch:
diff --git a/tests/test_Btn.py b/tests/test_Btn.py
index 407c0e3b..058cb775 100644
--- a/tests/test_Btn.py
+++ b/tests/test_Btn.py
@@ -50,6 +50,47 @@ class TestBtn:
 
         return
 
+    def test_set_gliph(self, btn):
+
+        # new gliph
+        gliph = "fas fa-folder"
+        btn.gliph = gliph
+
+        assert isinstance(btn.v_icon, v.Icon)
+        assert btn.v_icon.children[0] == gliph
+
+        # change existing icon
+        gliph = "fas fa-file"
+        btn.gliph = gliph
+        assert btn.v_icon.children[0] == gliph
+
+        # remove all gliph
+        gliph = ""
+        btn.gliph = gliph
+        assert "d-none" in btn.v_icon.class_
+
+        # assert deprecation
+        with pytest.deprecated_call():
+            sw.Btn(icon="fas fa-folder")
+
+        return
+
+    def test_test_msg(self, btn):
+
+        # test the initial text
+        assert btn.children[1] == "Click"
+
+        # update msg
+        msg = "New message"
+        btn.msg = msg
+        assert btn.children[1] == msg
+
+        # test deprecation notice
+        with pytest.deprecated_call():
+            sw.Btn(text="Deprecation")
+
+        return
+
     @pytest.fixture
     def btn(self):
         """Create a simple btn"""