<gh_stars>1-10
"""
Provides elements that make stuff happen.

.. moduleauthor: <NAME> <<EMAIL>>
"""

from typing import Union
from typing import List
from uuid import uuid4

import cmarkgfm
from cmarkgfm.cmark import Options as cmarkgfmOptions
from emoji import emojize

from ..exceptions import AlfredError
from .._helper import inherit_kwargs

from .core import jinja_env
from .core import _Choice
from .core import Row
from .core import Element
from .misc import JavaScript
from .input import SingleChoiceButtons
from .input import SingleChoiceBar
from .input import SingleChoiceList
from .input import SelectPageList

@inherit_kwargs
class SubmittingButtons(SingleChoiceButtons):
    """
    SingleChoiceButtons that trigger submission of the current page
    on click.

    Args:
        *choice_labels: Variable numbers of choice labels. See
            :class:`.ChoiceElement` for details.
        {kwargs}

    Examples:
        Using submitting buttons together with the
        :class:`.HideNavigation` element::

            import alfred3 as al
            exp = al.Experiment()

            @exp.member
            class Demo(al.Page):
                name = "demo"

                def on_exp_access(self):

                    self += al.HideNavigation()
                    self += al.SubmittingButtons("choice1", "choice2", name="b1")

    """

    js_template = jinja_env.get_template("js/submittingbuttons.js.j2")

    def __init__(self, *choice_labels, **kwargs):
        super().__init__(*choice_labels, **kwargs)
    
    def prepare_web_widget(self):
        super().prepare_web_widget()

        self._js_code = []
        js = self.js_template.render(choices=self.choices)
        self.add_js(js)


@inherit_kwargs
class SubmittingBar(SingleChoiceBar):
    """
    SingleChoiceButtons that trigger submission of the current page
    on click.

    Args:
        *choice_labels: Variable numbers of choice labels. See
            :class:`.ChoiceElement` for details.
        {kwargs}

    Examples:
        Using submitting buttons together with the
        :class:`.HideNavigation` element::

            import alfred3 as al
            exp = al.Experiment()

            @exp.member
            class Demo(al.Page):
                name = "demo"

                def on_exp_access(self):

                    self += al.HideNavigation()
                    self += al.SubmittingButtons("choice1", "choice2", name="b1")

    """

    js_template = jinja_env.get_template("js/submittingbuttons.js.j2")

    def __init__(self, *choice_labels, **kwargs):
        super().__init__(*choice_labels, **kwargs)
    
    def prepare_web_widget(self):
        super().prepare_web_widget()

        self._js_code = []
        js = self.js_template.render(choices=self.choices)
        self.add_js(js)


@inherit_kwargs
class JumpButtons(SingleChoiceButtons):
    """
    SingleChoiceButtons that trigger jumps to specific pages on click.

    Each button can target a different page.

    Args:
        *choice_labels: Tuples of the form ``("label", "target_name")``,
            where "label" is the text that will be displayed and
            "target_name" is the name of the page that the experiment
            will jump to on click of the button.

        {kwargs}

    Attributes:
        targets (str): List of target page names.

    Examples:
        Simple jump buttons::

            import alfred3 as al
            exp = al.Experiment()

            @exp.member
            class Demo1(al.Page):
                name = "demo1"

                def on_exp_access(self):
                    self += al.JumpButtons(
                        ("jump to demo2", "demo2"),
                        ("jump to demo3", "demo3"),
                        name="jump1"
                        )

            @exp.member
            class Demo2(al.Page):
                name = "demo2"

            @exp.member
            class Demo3(al.Page):
                name = "demo3"

        Jump buttons with target page taken from the input on a previous
        page, using the :meth:`.Page.on_first_show` hook::

            import alfred3 as al
            exp = al.Experiment()

            @exp.member
            class Demo1(al.Page):
                name = "demo1"

                def on_exp_access(self):
                    self += al.TextEntry(toplab="Enter target page name", name="text1")

            @exp.member
            class Demo2(al.Page):
                name = "demo2"

                def on_first_show(self):
                    target = self.exp.values.get("text1")
                    self += al.JumpButtons(("jump to target", target), name="jump1")

            @exp.member
            class Demo3(al.Page):
                name = "demo3"

    """

    #: JavaScript template for the code that submits the form on click
    js_template = jinja_env.get_template("js/jumpbuttons.js.j2")

    def __init__(self, *choice_labels, button_style: Union[str, list] = "btn-primary", **kwargs):
        super().__init__(*choice_labels, button_style=button_style, **kwargs)
        self.choice_labels, self.targets = map(list, zip(*choice_labels))

    def prepare_web_widget(self):
        
        super().prepare_web_widget()

        if self.page.prefix_element_names and not self.page.has_been_shown:
            self.targets = [self.page.name + "_" + target for target in self.targets]

        self._js_code = []

        for choice, target in zip(self.choices, self.targets):
            js = self.js_template.render(id=choice.id, target=target)
            self.add_js(js)

    def validate_data(self):
        if not self.should_be_shown:
            return True
        
        cond1 = bool(self.input) if self.force_input else True
        return cond1

@inherit_kwargs
class DynamicJumpButtons(JumpButtons):
    """
    JumpButtons, where the target pages depend on the input to
    other elements on the same page.

    Args:
        *choice_labels: Tuples of the form ``("label", "element_name")``,
            where "label" is the text that will be displayed and
            "element_name" is the name of the element on the *same page*,
            whose input value will be inserted as the name of the target
            page.

        {kwargs}

    Attributes:
        targets (str): List of target page names.

    Examples:
        ::

            import alfred3 as al
            exp = al.Experiment()

            @exp.member
            class Demo1(al.Page):
                name = "demo1"

                def on_exp_access(self):
                    self += al.TextEntry(toplab="Enter a target page", name="text1")
                    self += al.DynamicJumpButtons(("Jump", "text1"), name="jump1")

            @exp.member
            class Demo2(al.Page):
                name = "demo2"

            @exp.member
            class Demo3(al.Page):
                name = "demo3"

    """

    # Documented at :class:`.JumpButtons`
    js_template = jinja_env.get_template("js/dynamic_jumpbuttons.js.j2")

    def validate_data(self):
        
        return True
        # cond1 = bool(self.data) if self.force_input else True

        # cond2 = True
        # for target in self.targets:
        #     value = self.experiment.data.get(target, None)
        #     cond2 = value in self.experiment.root_section.all_pages
        #     if not cond2:
        #         break

        # cond2 = all([target in self.experiment.root_section.all_pages for target in self.targets])
        # return cond1 and cond2


@inherit_kwargs
class JumpList(Row):
    """
    Allows participants to select a page from a dropdown menu and jump
    to it.

    Args:
        scope: Can be 'exp', or a section name. If *scope* is 'exp', all
            pages in the experiment are listed as choices. If *scope* is
            a section name, all pages in that section are listed.
        include_self: Whether to include the current page in the list,
            if it is in scope.
        check_jumpto: If *True* (default), pages that cannot be jumped
            to will be marked as disabled options. The evaluation is
            based on the :attr:`.Section.allow_jumpto` attribute of
            each page's direct parent section (and only that section).
        check_jumpfrom: If *True*, the element will check, if the
            current page can be left via jump. If not, all pages in the
            list will be marked as disabled options.
        show_all_in_scope: If *True* (default), all pages in the scope
            will be shown, including those that cannot be jumped to.
        display_page_name: If *True*, the page name will be displayed
            in the select list. Defaults to *True*.
        label: Label to display on the jump button.
        button_style: Style of the jump button. See
            :class:`.SingleChoiceButtons` for more details on this
            argument.
        button_round_corners: Boolean, determining whether the button
            should have rounded corners.
        debugmode: Boolean switch, telling the JumpList whether it
            should operate in debug mode. In debugmode, *save_data* 
            defaults to *False*, i.e. the JumpList will not save any
            data.
        
        {kwargs}

    Notes:
        Different from other input-type elements, the JumpList does not
        have to be named.

    Examples:
        Minimal example::

            import alfred3 as al
            exp = al.Experiment()

            @exp.member
            class Demo1(al.Page):
                name = "demo2"

                def on_exp_access(self):
                    self += al.JumpList()

            @exp.member
            class Demo2(al.Page):
                name = "demo2"

    """

    def __init__(
        self,
        scope: str = "exp",
        include_self: bool = False,
        check_jumpto: bool = True,
        check_jumpfrom: bool = True,
        show_all_in_scope: bool = True,
        label: str = "Jump",
        button_style: Union[str, list] = "btn-dark",
        button_round_corners: bool = False,
        debugmode: bool = False,
        display_page_name: bool = True,
        **kwargs,
    ):

        random_name = "jumplist_" + uuid4().hex
        name = kwargs.get("name", random_name)
        select_name = name + "_select"
        btn_name = name + "_btn"
        save_data = kwargs.pop("save_data", not debugmode)
        select = SelectPageList(
            scope=scope,
            include_self=include_self,
            name=select_name,
            check_jumpto=check_jumpto,
            check_jumpfrom=check_jumpfrom,
            show_all_in_scope=show_all_in_scope,
            display_page_name=display_page_name,
            save_data=save_data
        )
        btn = DynamicJumpButtons(
            (label, select.name),
            name=btn_name,
            button_style=button_style,
            button_round_corners=button_round_corners,
            save_data=save_data
        )
        super().__init__(select, btn, **kwargs)

        self.layout.width_sm = [10, 2]
        self.debugmode = debugmode

    def prepare_web_widget(self):
        
        super().prepare_web_widget()

        if not self.page.section.allow_jumpfrom:
            # disable button (disabling the list is controlled via init arguments of the list)
            self.elements[1].disabled = True

        if self.debugmode:
            for el in self.elements:
                el.disabled = False
        

@inherit_kwargs
class Button(Element):
    """
    A button that triggers execution of a python function on click.
    
    Args:
        text (str): Button text

        func (callable): Python function to be called on button click.
            The function must take zero arguments, but it can be a 
            method of a class or instance.
        
        followup (str): What to do after the python function was called.
            Can take the following values: 
            
            - ``refresh`` submits and reloads the current page (default),
            - ``none`` does nothing,
            - ``forward`` submits the current page and moves forward,
            - ``backward`` submits the current page and moves backward,
            - ``jump>page_name`` submits the current page and triggers
              a jump to a page with the name 'page_name'
            - ``custom`` executes custom JavaScript. If you choose this 
              option, you must supply your custom JavaScript through the
              argument *custom_js*.
        
        submit_first (bool): If True, the current values of all input
            elements on the current page will be saved on button click,
            before *func* is called. This way, these values will be
            available in *func* through :attr:`.ExperimentSession.values`, 
            if func has access to the ExperimentSession object.
            See Example 3. Defaults to True.
        
        button_style (str): Can be used for quick color-styling, using
            Bootstraps default color keywords: btn-primary, btn-secondary,
            btn-success, btn-info, btn-warning, btn-danger, btn-light,
            btn-dark. You can also use the "outline" variant to get
            outlined buttons (eg. "btn-outline-secondary"). Advanced users can
            supply their own CSS classes for button-styling. Defaults
            to "btn-primary".
        
        button_round_corners (bool): A boolean switch to toggle whether 
            the button should be displayed with rounded corners. Defaults 
            to False. 
        
        button_block (bool): A boolean switch to toggle whether the button
            should take up all horizontal space that is available. Can be
            quite useful when arranging buttons in :class:`.Row`. 
            Defaults to False.
        
        custom_js (str): Custom JavaScript to execute after the python
            function specified in *func* was called. Only takes effect,
            if *followup* is set to 'custom'.

        {kwargs}
    
    Notes:

        Note that by default, the Button will not save data itself. If
        you wish to save information about button clicks, you can utilize
        the :attr:`.ExperimentSession.adata` dictionary in the button's
        callable. See Example 4.

        .. warning:: This element is very powerful. Remember that with great
            power comes great responsibility. Be aware that the callable
            *func* will be executed *every time* the button is clicked.
    
    Examples:

        Example 1: A minimal example that will print some text to your terminal
        window on button click::

            import alfred3 as al
            exp = al.Experiment()

            @exp.member
            class Demo(al.Page):

                def on_exp_access(self):
                    self += al.Button("Demo Button", func=self.demo_function)
                
                def demo_function(self):
                    print("\\nThis is a demonstration")
                    print(self.exp.exp_id)
                    print("\\n")
        
        Example 2: An example with a jump after the function call::

            import alfred3 as al
            exp = al.Experiment()

            @exp.member
            class Demo(al.Page):

                def on_exp_access(self):
                    self += al.Button("Demo Button", func=self.demo_function, followup="jump>page3")
            
                def demo_function(self):
                    print("\\nThis is a demonstration")
                    print(self.exp.exp_id)
                    print("\\n")

            exp += al.Page(title="Page 2", name="page2")
            exp += al.Page(title="Page 3", name="page3")

        
        Example 3: An example that uses values entered on the current page::
            
            import alfred3 as al
            exp = al.Experiment()


            @exp.member
            class Demo(al.Page):
                
                def on_exp_access(self):
                    self += al.TextEntry(leftlab="Enter", name="entry1")
                    self += al.Button("Demo Button", func=self.demo_function)

                def demo_function(self):
                    print("\\nThis is a demonstration")
                    print(self.exp.exp_id)
                    print(self.exp.values["entry1"])
                    print("\\n")
        
        Example 4: An example that saves a count of how often the button
        was clicked::
            
            import alfred3 as al
            exp = al.Experiment()

            @exp.member
            class Demo(al.Page):
                
                def on_exp_access(self):
                    self += al.Button("Demo Button", func=self.demo_function)

                def demo_function(self):
                    print("\\nThis is a demonstration")
                    
                    if "demo_button" in self.exp.adata:
                        self.exp.adata["demo_button"] += 1
                    else:
                        self.exp.adata["demo_button"] = 1
                        
                    print("\\n")


    """
    element_template = jinja_env.get_template("html/ActionButtonElement.html.j2")
    js_template = jinja_env.get_template("js/actionbutton.js.j2")

    def __init__(
        self, 
        text: str, 
        func: callable, 
        followup: str = "refresh",
        submit_first: bool = True,
        button_style: str = "btn-primary",
        button_round_corners: bool = False, 
        button_block: bool = False,
        custom_js: str = "",
        **kwargs):
        super().__init__(**kwargs)
        
        self.func = func
        self.submit_first = submit_first
        self.followup = followup
        self.url = None
        self.text = text
        self.custom_js = custom_js

        if not followup in {"refresh", "forward", "backward", "none", "custom"} and not followup.startswith("jump>"):
            raise ValueError(f"{followup} is an inappropriate value for 'followup'.")

        if self.followup == "custom" and not self.custom_js:
            raise ValueError("If you set 'followup' to 'custom', you must specify custom Javascript to run.")

        self.button_style = button_style
        self.button_round_corners = button_round_corners
        self.button_block = button_block
    
    @property
    def template_data(self):
        d = super().template_data
        text = emojize(self.text, use_aliases=True)
        text = cmarkgfm.github_flavored_markdown_to_html(text, options=cmarkgfmOptions.CMARK_OPT_UNSAFE)
        d["text"] = text
        d["button_block"] = "btn-block" if self.button_block else ""
        d["button_style"] = self.button_style
        return d
    
    def added_to_experiment(self, experiment):
        super().added_to_experiment(experiment)
        self.url = self.exp.ui.add_callable(self.func)

    def prepare_web_widget(self):
        
        self._js_code = []
        d = {}
        d["url"] = self.url
        d["expurl"] = f"{self.exp.ui.basepath}/experiment"
        d["followup"] = self.followup
        d["custom_js"] = self.custom_js
        d["name"] = self.name
        d["submit_first"] = self.submit_first
        d["set_data_url"] = self.exp.ui.set_page_data_url
        js = self.js_template.render(d)
        self.add_js(js)

        # Round corners part
        if self.button_round_corners:
            self._css_code = []
            css = f"#{ self.name } {{border-radius: 1rem;}}"
            self.add_css(css)


@inherit_kwargs(exclude=["func", "submit_first", "custom_js"])
class BackButton(Button):
    """
    A backward button.

    Args:
        text (str, optional): Button text. If *None* (default), alfred3
            uses the default text for backward buttons from config.conf.
        {kwargs}
    
    Examples:
        Simple usage::

            import alfred3 as al

            exp = al.Experiment()

            @exp.member
            class Demo(al.Page):

                def on_exp_access(self):
                    self += al.BackButton()
    
    """
    

    def __init__(self, 
        text: str = None, 
        button_style: str = "btn-dark",
        button_round_corners: bool = False, 
        button_block: bool = False,
        **kwargs):
        
        for arg in ["func", "submit_first", "custom_js"]:
            val = kwargs.pop(arg, None)
            if val is not None:
                raise TypeError(f"{type(self).__name__} got an unexpected keyword argument '{arg}'")

        super().__init__(text=text, button_style=button_style, button_round_corners=button_round_corners, button_block=button_block, followup= "backward", func=lambda: None, **kwargs)
    
    def added_to_experiment(self, experiment):
        super().added_to_experiment(experiment)

        if self.text is None:
            self.text = self.exp.config.get("navigation", "backward")


@inherit_kwargs(exclude=["func", "submit_first", "custom_js"])
class ForwardButton(Button):
    """
    A forward button.

    Args:
        text (str, optional): Button text. If *None* (default), alfred3
            uses the default text for forward buttons from config.conf.
        {kwargs}
    
    Examples:
        Simple usage::

            import alfred3 as al

            exp = al.Experiment()

            @exp.member
            class Demo(al.Page):

                def on_exp_access(self):
                    self += al.ForwardButton()
    
    """

    def __init__(self, 
        text: str = None, 
        button_style: str = "btn-dark",
        button_round_corners: bool = False, 
        button_block: bool = False,
        **kwargs):
        
        for arg in ["func", "submit_first", "custom_js"]:
            val = kwargs.pop(arg, None)
            if val is not None:
                raise TypeError(f"{type(self).__name__} got an unexpected keyword argument '{arg}'")

        super().__init__(text=text, button_style=button_style, button_round_corners=button_round_corners, button_block=button_block, followup= "forward", func=lambda: None, **kwargs)
    
    def added_to_experiment(self, experiment):
        super().added_to_experiment(experiment)

        if self.text is None:
            self.text = self.exp.config.get("navigation", "forward")

"""
Provides elements that make stuff happen.

.. moduleauthor: <NAME> <<EMAIL>>
"""

from typing import Union
from typing import List
from uuid import uuid4

import cmarkgfm
from cmarkgfm.cmark import Options as cmarkgfmOptions
from emoji import emojize

from ..exceptions import AlfredError
from .._helper import inherit_kwargs

from .core import jinja_env
from .core import _Choice
from .core import Row
from .core import Element
from .misc import JavaScript
from .input import SingleChoiceButtons
from .input import SingleChoiceBar
from .input import SingleChoiceList
from .input import SelectPageList

@inherit_kwargs
class SubmittingButtons(SingleChoiceButtons):
    """
    SingleChoiceButtons that trigger submission of the current page
    on click.

    Args:
        *choice_labels: Variable numbers of choice labels. See
            :class:`.ChoiceElement` for details.
        {kwargs}

    Examples:
        Using submitting buttons together with the
        :class:`.HideNavigation` element::

            import alfred3 as al
            exp = al.Experiment()

            @exp.member
            class Demo(al.Page):
                name = "demo"

                def on_exp_access(self):

                    self += al.HideNavigation()
                    self += al.SubmittingButtons("choice1", "choice2", name="b1")

    """

    js_template = jinja_env.get_template("js/submittingbuttons.js.j2")

    def __init__(self, *choice_labels, **kwargs):
        super().__init__(*choice_labels, **kwargs)
    
    def prepare_web_widget(self):
        super().prepare_web_widget()

        self._js_code = []
        js = self.js_template.render(choices=self.choices)
        self.add_js(js)


@inherit_kwargs
class SubmittingBar(SingleChoiceBar):
    """
    SingleChoiceButtons that trigger submission of the current page
    on click.

    Args:
        *choice_labels: Variable numbers of choice labels. See
            :class:`.ChoiceElement` for details.
        {kwargs}

    Examples:
        Using submitting buttons together with the
        :class:`.HideNavigation` element::

            import alfred3 as al
            exp = al.Experiment()

            @exp.member
            class Demo(al.Page):
                name = "demo"

                def on_exp_access(self):

                    self += al.HideNavigation()
                    self += al.SubmittingButtons("choice1", "choice2", name="b1")

    """

    js_template = jinja_env.get_template("js/submittingbuttons.js.j2")

    def __init__(self, *choice_labels, **kwargs):
        super().__init__(*choice_labels, **kwargs)
    
    def prepare_web_widget(self):
        super().prepare_web_widget()

        self._js_code = []
        js = self.js_template.render(choices=self.choices)
        self.add_js(js)


@inherit_kwargs
class JumpButtons(SingleChoiceButtons):
    """
    SingleChoiceButtons that trigger jumps to specific pages on click.

    Each button can target a different page.

    Args:
        *choice_labels: Tuples of the form ``("label", "target_name")``,
            where "label" is the text that will be displayed and
            "target_name" is the name of the page that the experiment
            will jump to on click of the button.

        {kwargs}

    Attributes:
        targets (str): List of target page names.

    Examples:
        Simple jump buttons::

            import alfred3 as al
            exp = al.Experiment()

            @exp.member
            class Demo1(al.Page):
                name = "demo1"

                def on_exp_access(self):
                    self += al.JumpButtons(
                        ("jump to demo2", "demo2"),
                        ("jump to demo3", "demo3"),
                        name="jump1"
                        )

            @exp.member
            class Demo2(al.Page):
                name = "demo2"

            @exp.member
            class Demo3(al.Page):
                name = "demo3"

        Jump buttons with target page taken from the input on a previous
        page, using the :meth:`.Page.on_first_show` hook::

            import alfred3 as al
            exp = al.Experiment()

            @exp.member
            class Demo1(al.Page):
                name = "demo1"

                def on_exp_access(self):
                    self += al.TextEntry(toplab="Enter target page name", name="text1")

            @exp.member
            class Demo2(al.Page):
                name = "demo2"

                def on_first_show(self):
                    target = self.exp.values.get("text1")
                    self += al.JumpButtons(("jump to target", target), name="jump1")

            @exp.member
            class Demo3(al.Page):
                name = "demo3"

    """

    #: JavaScript template for the code that submits the form on click
    js_template = jinja_env.get_template("js/jumpbuttons.js.j2")

    def __init__(self, *choice_labels, button_style: Union[str, list] = "btn-primary", **kwargs):
        super().__init__(*choice_labels, button_style=button_style, **kwargs)
        self.choice_labels, self.targets = map(list, zip(*choice_labels))

    def prepare_web_widget(self):
        
        super().prepare_web_widget()

        if self.page.prefix_element_names and not self.page.has_been_shown:
            self.targets = [self.page.name + "_" + target for target in self.targets]

        self._js_code = []

        for choice, target in zip(self.choices, self.targets):
            js = self.js_template.render(id=choice.id, target=target)
            self.add_js(js)

    def validate_data(self):
        if not self.should_be_shown:
            return True
        
        cond1 = bool(self.input) if self.force_input else True
        return cond1

@inherit_kwargs
class DynamicJumpButtons(JumpButtons):
    """
    JumpButtons, where the target pages depend on the input to
    other elements on the same page.

    Args:
        *choice_labels: Tuples of the form ``("label", "element_name")``,
            where "label" is the text that will be displayed and
            "element_name" is the name of the element on the *same page*,
            whose input value will be inserted as the name of the target
            page.

        {kwargs}

    Attributes:
        targets (str): List of target page names.

    Examples:
        ::

            import alfred3 as al
            exp = al.Experiment()

            @exp.member
            class Demo1(al.Page):
                name = "demo1"

                def on_exp_access(self):
                    self += al.TextEntry(toplab="Enter a target page", name="text1")
                    self += al.DynamicJumpButtons(("Jump", "text1"), name="jump1")

            @exp.member
            class Demo2(al.Page):
                name = "demo2"

            @exp.member
            class Demo3(al.Page):
                name = "demo3"

    """

    # Documented at :class:`.JumpButtons`
    js_template = jinja_env.get_template("js/dynamic_jumpbuttons.js.j2")

    def validate_data(self):
        
        return True
        # cond1 = bool(self.data) if self.force_input else True

        # cond2 = True
        # for target in self.targets:
        #     value = self.experiment.data.get(target, None)
        #     cond2 = value in self.experiment.root_section.all_pages
        #     if not cond2:
        #         break

        # cond2 = all([target in self.experiment.root_section.all_pages for target in self.targets])
        # return cond1 and cond2


@inherit_kwargs
class JumpList(Row):
    """
    Allows participants to select a page from a dropdown menu and jump
    to it.

    Args:
        scope: Can be 'exp', or a section name. If *scope* is 'exp', all
            pages in the experiment are listed as choices. If *scope* is
            a section name, all pages in that section are listed.
        include_self: Whether to include the current page in the list,
            if it is in scope.
        check_jumpto: If *True* (default), pages that cannot be jumped
            to will be marked as disabled options. The evaluation is
            based on the :attr:`.Section.allow_jumpto` attribute of
            each page's direct parent section (and only that section).
        check_jumpfrom: If *True*, the element will check, if the
            current page can be left via jump. If not, all pages in the
            list will be marked as disabled options.
        show_all_in_scope: If *True* (default), all pages in the scope
            will be shown, including those that cannot be jumped to.
        display_page_name: If *True*, the page name will be displayed
            in the select list. Defaults to *True*.
        label: Label to display on the jump button.
        button_style: Style of the jump button. See
            :class:`.SingleChoiceButtons` for more details on this
            argument.
        button_round_corners: Boolean, determining whether the button
            should have rounded corners.
        debugmode: Boolean switch, telling the JumpList whether it
            should operate in debug mode. In debugmode, *save_data* 
            defaults to *False*, i.e. the JumpList will not save any
            data.
        
        {kwargs}

    Notes:
        Different from other input-type elements, the JumpList does not
        have to be named.

    Examples:
        Minimal example::

            import alfred3 as al
            exp = al.Experiment()

            @exp.member
            class Demo1(al.Page):
                name = "demo2"

                def on_exp_access(self):
                    self += al.JumpList()

            @exp.member
            class Demo2(al.Page):
                name = "demo2"

    """

    def __init__(
        self,
        scope: str = "exp",
        include_self: bool = False,
        check_jumpto: bool = True,
        check_jumpfrom: bool = True,
        show_all_in_scope: bool = True,
        label: str = "Jump",
        button_style: Union[str, list] = "btn-dark",
        button_round_corners: bool = False,
        debugmode: bool = False,
        display_page_name: bool = True,
        **kwargs,
    ):

        random_name = "jumplist_" + uuid4().hex
        name = kwargs.get("name", random_name)
        select_name = name + "_select"
        btn_name = name + "_btn"
        save_data = kwargs.pop("save_data", not debugmode)
        select = SelectPageList(
            scope=scope,
            include_self=include_self,
            name=select_name,
            check_jumpto=check_jumpto,
            check_jumpfrom=check_jumpfrom,
            show_all_in_scope=show_all_in_scope,
            display_page_name=display_page_name,
            save_data=save_data
        )
        btn = DynamicJumpButtons(
            (label, select.name),
            name=btn_name,
            button_style=button_style,
            button_round_corners=button_round_corners,
            save_data=save_data
        )
        super().__init__(select, btn, **kwargs)

        self.layout.width_sm = [10, 2]
        self.debugmode = debugmode

    def prepare_web_widget(self):
        
        super().prepare_web_widget()

        if not self.page.section.allow_jumpfrom:
            # disable button (disabling the list is controlled via init arguments of the list)
            self.elements[1].disabled = True

        if self.debugmode:
            for el in self.elements:
                el.disabled = False
        

@inherit_kwargs
class Button(Element):
    """
    A button that triggers execution of a python function on click.
    
    Args:
        text (str): Button text

        func (callable): Python function to be called on button click.
            The function must take zero arguments, but it can be a 
            method of a class or instance.
        
        followup (str): What to do after the python function was called.
            Can take the following values: 
            
            - ``refresh`` submits and reloads the current page (default),
            - ``none`` does nothing,
            - ``forward`` submits the current page and moves forward,
            - ``backward`` submits the current page and moves backward,
            - ``jump>page_name`` submits the current page and triggers
              a jump to a page with the name 'page_name'
            - ``custom`` executes custom JavaScript. If you choose this 
              option, you must supply your custom JavaScript through the
              argument *custom_js*.
        
        submit_first (bool): If True, the current values of all input
            elements on the current page will be saved on button click,
            before *func* is called. This way, these values will be
            available in *func* through :attr:`.ExperimentSession.values`, 
            if func has access to the ExperimentSession object.
            See Example 3. Defaults to True.
        
        button_style (str): Can be used for quick color-styling, using
            Bootstraps default color keywords: btn-primary, btn-secondary,
            btn-success, btn-info, btn-warning, btn-danger, btn-light,
            btn-dark. You can also use the "outline" variant to get
            outlined buttons (eg. "btn-outline-secondary"). Advanced users can
            supply their own CSS classes for button-styling. Defaults
            to "btn-primary".
        
        button_round_corners (bool): A boolean switch to toggle whether 
            the button should be displayed with rounded corners. Defaults 
            to False. 
        
        button_block (bool): A boolean switch to toggle whether the button
            should take up all horizontal space that is available. Can be
            quite useful when arranging buttons in :class:`.Row`. 
            Defaults to False.
        
        custom_js (str): Custom JavaScript to execute after the python
            function specified in *func* was called. Only takes effect,
            if *followup* is set to 'custom'.

        {kwargs}
    
    Notes:

        Note that by default, the Button will not save data itself. If
        you wish to save information about button clicks, you can utilize
        the :attr:`.ExperimentSession.adata` dictionary in the button's
        callable. See Example 4.

        .. warning:: This element is very powerful. Remember that with great
            power comes great responsibility. Be aware that the callable
            *func* will be executed *every time* the button is clicked.
    
    Examples:

        Example 1: A minimal example that will print some text to your terminal
        window on button click::

            import alfred3 as al
            exp = al.Experiment()

            @exp.member
            class Demo(al.Page):

                def on_exp_access(self):
                    self += al.Button("Demo Button", func=self.demo_function)
                
                def demo_function(self):
                    print("\\nThis is a demonstration")
                    print(self.exp.exp_id)
                    print("\\n")
        
        Example 2: An example with a jump after the function call::

            import alfred3 as al
            exp = al.Experiment()

            @exp.member
            class Demo(al.Page):

                def on_exp_access(self):
                    self += al.Button("Demo Button", func=self.demo_function, followup="jump>page3")
            
                def demo_function(self):
                    print("\\nThis is a demonstration")
                    print(self.exp.exp_id)
                    print("\\n")

            exp += al.Page(title="Page 2", name="page2")
            exp += al.Page(title="Page 3", name="page3")

        
        Example 3: An example that uses values entered on the current page::
            
            import alfred3 as al
            exp = al.Experiment()


            @exp.member
            class Demo(al.Page):
                
                def on_exp_access(self):
                    self += al.TextEntry(leftlab="Enter", name="entry1")
                    self += al.Button("Demo Button", func=self.demo_function)

                def demo_function(self):
                    print("\\nThis is a demonstration")
                    print(self.exp.exp_id)
                    print(self.exp.values["entry1"])
                    print("\\n")
        
        Example 4: An example that saves a count of how often the button
        was clicked::
            
            import alfred3 as al
            exp = al.Experiment()

            @exp.member
            class Demo(al.Page):
                
                def on_exp_access(self):
                    self += al.Button("Demo Button", func=self.demo_function)

                def demo_function(self):
                    print("\\nThis is a demonstration")
                    
                    if "demo_button" in self.exp.adata:
                        self.exp.adata["demo_button"] += 1
                    else:
                        self.exp.adata["demo_button"] = 1
                        
                    print("\\n")


    """
    element_template = jinja_env.get_template("html/ActionButtonElement.html.j2")
    js_template = jinja_env.get_template("js/actionbutton.js.j2")

    def __init__(
        self, 
        text: str, 
        func: callable, 
        followup: str = "refresh",
        submit_first: bool = True,
        button_style: str = "btn-primary",
        button_round_corners: bool = False, 
        button_block: bool = False,
        custom_js: str = "",
        **kwargs):
        super().__init__(**kwargs)
        
        self.func = func
        self.submit_first = submit_first
        self.followup = followup
        self.url = None
        self.text = text
        self.custom_js = custom_js

        if not followup in {"refresh", "forward", "backward", "none", "custom"} and not followup.startswith("jump>"):
            raise ValueError(f"{followup} is an inappropriate value for 'followup'.")

        if self.followup == "custom" and not self.custom_js:
            raise ValueError("If you set 'followup' to 'custom', you must specify custom Javascript to run.")

        self.button_style = button_style
        self.button_round_corners = button_round_corners
        self.button_block = button_block
    
    @property
    def template_data(self):
        d = super().template_data
        text = emojize(self.text, use_aliases=True)
        text = cmarkgfm.github_flavored_markdown_to_html(text, options=cmarkgfmOptions.CMARK_OPT_UNSAFE)
        d["text"] = text
        d["button_block"] = "btn-block" if self.button_block else ""
        d["button_style"] = self.button_style
        return d
    
    def added_to_experiment(self, experiment):
        super().added_to_experiment(experiment)
        self.url = self.exp.ui.add_callable(self.func)

    def prepare_web_widget(self):
        
        self._js_code = []
        d = {}
        d["url"] = self.url
        d["expurl"] = f"{self.exp.ui.basepath}/experiment"
        d["followup"] = self.followup
        d["custom_js"] = self.custom_js
        d["name"] = self.name
        d["submit_first"] = self.submit_first
        d["set_data_url"] = self.exp.ui.set_page_data_url
        js = self.js_template.render(d)
        self.add_js(js)

        # Round corners part
        if self.button_round_corners:
            self._css_code = []
            css = f"#{ self.name } {{border-radius: 1rem;}}"
            self.add_css(css)


@inherit_kwargs(exclude=["func", "submit_first", "custom_js"])
class BackButton(Button):
    """
    A backward button.

    Args:
        text (str, optional): Button text. If *None* (default), alfred3
            uses the default text for backward buttons from config.conf.
        {kwargs}
    
    Examples:
        Simple usage::

            import alfred3 as al

            exp = al.Experiment()

            @exp.member
            class Demo(al.Page):

                def on_exp_access(self):
                    self += al.BackButton()
    
    """
    

    def __init__(self, 
        text: str = None, 
        button_style: str = "btn-dark",
        button_round_corners: bool = False, 
        button_block: bool = False,
        **kwargs):
        
        for arg in ["func", "submit_first", "custom_js"]:
            val = kwargs.pop(arg, None)
            if val is not None:
                raise TypeError(f"{type(self).__name__} got an unexpected keyword argument '{arg}'")

        super().__init__(text=text, button_style=button_style, button_round_corners=button_round_corners, button_block=button_block, followup= "backward", func=lambda: None, **kwargs)
    
    def added_to_experiment(self, experiment):
        super().added_to_experiment(experiment)

        if self.text is None:
            self.text = self.exp.config.get("navigation", "backward")


@inherit_kwargs(exclude=["func", "submit_first", "custom_js"])
class ForwardButton(Button):
    """
    A forward button.

    Args:
        text (str, optional): Button text. If *None* (default), alfred3
            uses the default text for forward buttons from config.conf.
        {kwargs}
    
    Examples:
        Simple usage::

            import alfred3 as al

            exp = al.Experiment()

            @exp.member
            class Demo(al.Page):

                def on_exp_access(self):
                    self += al.ForwardButton()
    
    """

    def __init__(self, 
        text: str = None, 
        button_style: str = "btn-dark",
        button_round_corners: bool = False, 
        button_block: bool = False,
        **kwargs):
        
        for arg in ["func", "submit_first", "custom_js"]:
            val = kwargs.pop(arg, None)
            if val is not None:
                raise TypeError(f"{type(self).__name__} got an unexpected keyword argument '{arg}'")

        super().__init__(text=text, button_style=button_style, button_round_corners=button_round_corners, button_block=button_block, followup= "forward", func=lambda: None, **kwargs)
    
    def added_to_experiment(self, experiment):
        super().added_to_experiment(experiment)

        if self.text is None:
            self.text = self.exp.config.get("navigation", "forward")
Provides elements that make stuff happen.  .. moduleauthor: <NAME> <<EMAIL>> SingleChoiceButtons that trigger submission of the current page     on click.      Args:         *choice_labels: Variable numbers of choice labels. See             :class:`.ChoiceElement` for details.         {kwargs}      Examples:         Using submitting buttons together with the         :class:`.HideNavigation` element::              import alfred3 as al             exp = al.Experiment()              @exp.member             class Demo(al.Page):                 name = "demo"                  def on_exp_access(self):                      self += al.HideNavigation()                     self += al.SubmittingButtons("choice1", "choice2", name="b1") SingleChoiceButtons that trigger submission of the current page     on click.      Args:         *choice_labels: Variable numbers of choice labels. See             :class:`.ChoiceElement` for details.         {kwargs}      Examples:         Using submitting buttons together with the         :class:`.HideNavigation` element::              import alfred3 as al             exp = al.Experiment()              @exp.member             class Demo(al.Page):                 name = "demo"                  def on_exp_access(self):                      self += al.HideNavigation()                     self += al.SubmittingButtons("choice1", "choice2", name="b1") SingleChoiceButtons that trigger jumps to specific pages on click.      Each button can target a different page.      Args:         *choice_labels: Tuples of the form ``("label", "target_name")``,             where "label" is the text that will be displayed and             "target_name" is the name of the page that the experiment             will jump to on click of the button.          {kwargs}      Attributes:         targets (str): List of target page names.      Examples:         Simple jump buttons::              import alfred3 as al             exp = al.Experiment()              @exp.member             class Demo1(al.Page):                 name = "demo1"                  def on_exp_access(self):                     self += al.JumpButtons(                         ("jump to demo2", "demo2"),                         ("jump to demo3", "demo3"),                         name="jump1"                         )              @exp.member             class Demo2(al.Page):                 name = "demo2"              @exp.member             class Demo3(al.Page):                 name = "demo3"          Jump buttons with target page taken from the input on a previous         page, using the :meth:`.Page.on_first_show` hook::              import alfred3 as al             exp = al.Experiment()              @exp.member             class Demo1(al.Page):                 name = "demo1"                  def on_exp_access(self):                     self += al.TextEntry(toplab="Enter target page name", name="text1")              @exp.member             class Demo2(al.Page):                 name = "demo2"                  def on_first_show(self):                     target = self.exp.values.get("text1")                     self += al.JumpButtons(("jump to target", target), name="jump1")              @exp.member             class Demo3(al.Page):                 name = "demo3" #: JavaScript template for the code that submits the form on click JumpButtons, where the target pages depend on the input to     other elements on the same page.      Args:         *choice_labels: Tuples of the form ``("label", "element_name")``,             where "label" is the text that will be displayed and             "element_name" is the name of the element on the *same page*,             whose input value will be inserted as the name of the target             page.          {kwargs}      Attributes:         targets (str): List of target page names.      Examples:         ::              import alfred3 as al             exp = al.Experiment()              @exp.member             class Demo1(al.Page):                 name = "demo1"                  def on_exp_access(self):                     self += al.TextEntry(toplab="Enter a target page", name="text1")                     self += al.DynamicJumpButtons(("Jump", "text1"), name="jump1")              @exp.member             class Demo2(al.Page):                 name = "demo2"              @exp.member             class Demo3(al.Page):                 name = "demo3" # Documented at :class:`.JumpButtons` # cond1 = bool(self.data) if self.force_input else True # cond2 = True # for target in self.targets: #     value = self.experiment.data.get(target, None) #     cond2 = value in self.experiment.root_section.all_pages #     if not cond2: #         break # cond2 = all([target in self.experiment.root_section.all_pages for target in self.targets]) # return cond1 and cond2 Allows participants to select a page from a dropdown menu and jump     to it.      Args:         scope: Can be 'exp', or a section name. If *scope* is 'exp', all             pages in the experiment are listed as choices. If *scope* is             a section name, all pages in that section are listed.         include_self: Whether to include the current page in the list,             if it is in scope.         check_jumpto: If *True* (default), pages that cannot be jumped             to will be marked as disabled options. The evaluation is             based on the :attr:`.Section.allow_jumpto` attribute of             each page's direct parent section (and only that section).         check_jumpfrom: If *True*, the element will check, if the             current page can be left via jump. If not, all pages in the             list will be marked as disabled options.         show_all_in_scope: If *True* (default), all pages in the scope             will be shown, including those that cannot be jumped to.         display_page_name: If *True*, the page name will be displayed             in the select list. Defaults to *True*.         label: Label to display on the jump button.         button_style: Style of the jump button. See             :class:`.SingleChoiceButtons` for more details on this             argument.         button_round_corners: Boolean, determining whether the button             should have rounded corners.         debugmode: Boolean switch, telling the JumpList whether it             should operate in debug mode. In debugmode, *save_data*              defaults to *False*, i.e. the JumpList will not save any             data.                  {kwargs}      Notes:         Different from other input-type elements, the JumpList does not         have to be named.      Examples:         Minimal example::              import alfred3 as al             exp = al.Experiment()              @exp.member             class Demo1(al.Page):                 name = "demo2"                  def on_exp_access(self):                     self += al.JumpList()              @exp.member             class Demo2(al.Page):                 name = "demo2" # disable button (disabling the list is controlled via init arguments of the list) A button that triggers execution of a python function on click.          Args:         text (str): Button text          func (callable): Python function to be called on button click.             The function must take zero arguments, but it can be a              method of a class or instance.                  followup (str): What to do after the python function was called.             Can take the following values:                           - ``refresh`` submits and reloads the current page (default),             - ``none`` does nothing,             - ``forward`` submits the current page and moves forward,             - ``backward`` submits the current page and moves backward,             - ``jump>page_name`` submits the current page and triggers               a jump to a page with the name 'page_name'             - ``custom`` executes custom JavaScript. If you choose this                option, you must supply your custom JavaScript through the               argument *custom_js*.                  submit_first (bool): If True, the current values of all input             elements on the current page will be saved on button click,             before *func* is called. This way, these values will be             available in *func* through :attr:`.ExperimentSession.values`,              if func has access to the ExperimentSession object.             See Example 3. Defaults to True.                  button_style (str): Can be used for quick color-styling, using             Bootstraps default color keywords: btn-primary, btn-secondary,             btn-success, btn-info, btn-warning, btn-danger, btn-light,             btn-dark. You can also use the "outline" variant to get             outlined buttons (eg. "btn-outline-secondary"). Advanced users can             supply their own CSS classes for button-styling. Defaults             to "btn-primary".                  button_round_corners (bool): A boolean switch to toggle whether              the button should be displayed with rounded corners. Defaults              to False.                   button_block (bool): A boolean switch to toggle whether the button             should take up all horizontal space that is available. Can be             quite useful when arranging buttons in :class:`.Row`.              Defaults to False.                  custom_js (str): Custom JavaScript to execute after the python             function specified in *func* was called. Only takes effect,             if *followup* is set to 'custom'.          {kwargs}          Notes:          Note that by default, the Button will not save data itself. If         you wish to save information about button clicks, you can utilize         the :attr:`.ExperimentSession.adata` dictionary in the button's         callable. See Example 4.          .. warning:: This element is very powerful. Remember that with great             power comes great responsibility. Be aware that the callable             *func* will be executed *every time* the button is clicked.          Examples:          Example 1: A minimal example that will print some text to your terminal         window on button click::              import alfred3 as al             exp = al.Experiment()              @exp.member             class Demo(al.Page):                  def on_exp_access(self):                     self += al.Button("Demo Button", func=self.demo_function)                                  def demo_function(self):                     print("\\nThis is a demonstration")                     print(self.exp.exp_id)                     print("\\n")                  Example 2: An example with a jump after the function call::              import alfred3 as al             exp = al.Experiment()              @exp.member             class Demo(al.Page):                  def on_exp_access(self):                     self += al.Button("Demo Button", func=self.demo_function, followup="jump>page3")                              def demo_function(self):                     print("\\nThis is a demonstration")                     print(self.exp.exp_id)                     print("\\n")              exp += al.Page(title="Page 2", name="page2")             exp += al.Page(title="Page 3", name="page3")                   Example 3: An example that uses values entered on the current page::                          import alfred3 as al             exp = al.Experiment()               @exp.member             class Demo(al.Page):                                  def on_exp_access(self):                     self += al.TextEntry(leftlab="Enter", name="entry1")                     self += al.Button("Demo Button", func=self.demo_function)                  def demo_function(self):                     print("\\nThis is a demonstration")                     print(self.exp.exp_id)                     print(self.exp.values["entry1"])                     print("\\n")                  Example 4: An example that saves a count of how often the button         was clicked::                          import alfred3 as al             exp = al.Experiment()              @exp.member             class Demo(al.Page):                                  def on_exp_access(self):                     self += al.Button("Demo Button", func=self.demo_function)                  def demo_function(self):                     print("\\nThis is a demonstration")                                          if "demo_button" in self.exp.adata:                         self.exp.adata["demo_button"] += 1                     else:                         self.exp.adata["demo_button"] = 1                                              print("\\n") # Round corners part A backward button.      Args:         text (str, optional): Button text. If *None* (default), alfred3             uses the default text for backward buttons from config.conf.         {kwargs}          Examples:         Simple usage::              import alfred3 as al              exp = al.Experiment()              @exp.member             class Demo(al.Page):                  def on_exp_access(self):                     self += al.BackButton() A forward button.      Args:         text (str, optional): Button text. If *None* (default), alfred3             uses the default text for forward buttons from config.conf.         {kwargs}          Examples:         Simple usage::              import alfred3 as al              exp = al.Experiment()              @exp.member             class Demo(al.Page):                  def on_exp_access(self):                     self += al.ForwardButton()