Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add radio button #186

Closed
wants to merge 2 commits into from
Closed

Conversation

keliomer
Copy link

@keliomer keliomer commented Jun 1, 2017

Added RadioButton and RadioButtonLabel widgets

@keliomer keliomer mentioned this pull request Jun 1, 2017
@dddomodossola
Copy link
Collaborator

dddomodossola commented Jun 1, 2017

@keliomer Well done, and thank you for contributing. But, are you sure that RadioButtonLabel is useful? I see that 'radio' input accepts text appended, like:

input type="radio" name="gender" value="female"> Female

so maybe RadioButton itself is enough.
EDIT: I'm wrong.

@dddomodossola
Copy link
Collaborator

dddomodossola commented Jun 1, 2017

@keliomer Testing the RadioButton I was unable to generate the on_change event correctly. Have you got it working?
It happens that "on_change" is generated only for the clicked RadioButton. Others related RadioButtons, where the "checked" state switches to False, are not triggered, and so this causes inconsistency.

@keliomer
Copy link
Author

keliomer commented Jun 1, 2017

I see what you mean now! I'll need to look at how we can track those.

Do you have example code with a description? I definitely don't mind getting it to do what we need it

@dddomodossola
Copy link
Collaborator

Do you mean example code to reproduce the problem?
However I think that there is a solution: we have to create an appropriate container for RadioButtons (Qt calls it QRadioGroup), that listens the onclick events coming from children and manages the status of them (if one RadioButton gets clicked, its state is True, the other are False)

@keliomer
Copy link
Author

keliomer commented Jun 2, 2017 via email

@dddomodossola
Copy link
Collaborator

Consider that, this 'container' have to be NOT a layout. It should be just an event listener. This is because, differently, the user would loose the possibility to choice for a custom layout.

@m-kareem
Copy link

m-kareem commented Apr 16, 2020

Hi @dddomodossola
Any progress/ update about radio button in remi? I really need to use it in my project.

thanks

@dddomodossola
Copy link
Collaborator

Just a week ago I created a radio button for another user. I will integrate this soon in remi. I will try to provide you an example tomorrow, I can't promise because I'm a bit busy this period

@m-kareem
Copy link

Thanks @dddomodossola , this is very kind of you. I will be waiting for your example.

Cheers,

@dddomodossola
Copy link
Collaborator

@keliomer @m-kareem Here is a radio button example:

# -*- coding: utf-8 -*-

from remi import gui
from remi import start, App


class LabelForInputs(gui.Widget, gui._MixinTextualWidget):
    """ Non editable text label widget. Specifically designed to be used in conjunction with checkboxes and radiobuttons
    """

    def __init__(self, text, widget_for_instance, *args, **kwargs):
        """
        Args:
            text (str): The string content that have to be displayed in the Label.
            widget_for_instance (gui.Widget): checkbox or radiobutton instance
            kwargs: See Container.__init__()
        """
        super(LabelForInputs, self).__init__(*args, **kwargs)
        self.type = 'label'
        self.attributes['for'] = widget_for_instance.identifier
        self.set_text(text)

class InputCheckable(gui.Input):
    """It is the base class for checkable widgets (Switch, RadioButton, Checkbox).
        Checkable are the widgets that contains attribute 'checked' to determine the status
        The developer has to pass the the argument _type to the constructor
         to determine the kind of widget.
    """

    def __init__(self, status_on=False, input_type='checkbox', *args, **kwargs):
        """
        Args:
            status_on (bool):
            kwargs: See Widget.__init__()
        """
        super(InputCheckable, self).__init__(input_type, *args, **kwargs)
        self.set_value(status_on)
        self.attributes[gui.Widget.EVENT_ONCHANGE] = \
            "var params={};params['value']=document.getElementById('%(emitter_identifier)s').checked;" \
            "sendCallbackParam('%(emitter_identifier)s','%(event_name)s',params);" % \
            {'emitter_identifier': str(self.identifier), 'event_name': gui.Widget.EVENT_ONCHANGE}

    @gui.decorate_event
    def onchange(self, value):
        value = value in ('True', 'true')
        self.set_value(value)
        self._set_updated()
        return (value,)

    def set_value(self, status_on):
        if status_on:
            self.attributes['checked'] = 'checked'
        else:
            if 'checked' in self.attributes:
                del self.attributes['checked']

    def get_value(self):
        """
        Returns:
            bool:
        """
        return 'checked' in self.attributes



class RadioButton(InputCheckable):
    """RadioButton widget, useful for exclusive selection.
        different radiobuttons have to be assigned to the
        same group name in order to switch exclusively.
    """

    @property
    def attr_name(self):
        return self.attributes.get('name', '')

    @attr_name.setter
    def attr_name(self, value):
        self.attributes['name'] = str(value)

    def __init__(self, status_on=False, group='group', *args, **kwargs):
        """
        Args:
            status_on (bool): the initial value
            group (str): the group name. RadioButtons with same group will be exclusively activated
            kwargs: See Widget.__init__()
        """
        super(RadioButton, self).__init__(status_on, input_type='radio', *args, **kwargs)
        self.attr_name = group

    def set_value(self, status_on):
        InputCheckable.set_value(self, status_on)

        # if on and has a parent, all other radios with same group name are switched off
        if status_on and self.get_parent():
            for w in self.get_parent().children.values():
                if isinstance(w, type(self)):
                    if w.identifier != self.identifier:
                        if w.get_group() == self.get_group():
                            w.set_value(False)

    def set_group(self, group_name):
        self.attr_name = group_name

    def get_group(self):
        return self.attr_name

class RadioButtonWithLabel(gui.Container):
    _radio = None
    _label = None

    @property
    def text(self):
        return self._label.get_text()

    @text.setter
    def text(self, value):
        self._label.set_text(value)

    @property
    def attr_name(self):
        return self._radio.attr_name

    @attr_name.setter
    def attr_name(self, value):
        self._radio.attr_name = str(value)

    def __init__(self, text='radiobutton', status_on=False, group='group', *args, **kwargs):
        """
        Args:
            text (str): the text label
            status_on (bool): the initial value
            group (str): the group name. RadioButtons with same group will be exclusively activated
            kwargs: See Widget.__init__()
        """
        super(RadioButtonWithLabel, self).__init__(*args, **kwargs)

        self._radio = RadioButton(status_on, group=group)
        self.append(self._radio, key='radio')

        self._label = LabelForInputs(text, self._radio)
        self.append(self._label, key='label')

        self._radio.onchange.connect(self.onchange)

    @gui.decorate_event
    def onchange(self, widget, value):
        self.__update_other_radios()
        return (value,)

    def get_text(self):
        return self._label.text

    def set_text(self, text):
        self._label.text = text

    def set_group(self, group_name):
        self.attr_name = group_name

    def get_group(self):
        return self.attr_name

    def get_value(self):
        return self._radio.get_value()

    def set_value(self, value):
        """ Args:
                value (bool): defines the checked status for the radio button
        """
        self._radio.set_value(value)
        self.__update_other_radios()

    def __update_other_radios(self):
        # if on and has a parent,
        # all other radios, in the same container,
        # with same group name are switched off
        if self.get_value() and self.get_parent():
            for w in self.get_parent().children.values():
                if isinstance(w, type(self)):
                    if w.identifier != self.identifier:
                        if w.get_group() == self.get_group():
                            w.set_value(False)



class MyApp(App):
    def main(self):
        container = gui.VBox(style={'margin':'0px auto'})
        self.lbl_output = gui.Label()

        radio1 = RadioButtonWithLabel('Banana',False, 'groupFruits')
        radio2 = RadioButtonWithLabel('Apple',False, 'groupFruits')
        radio3 = RadioButtonWithLabel('Orange',False, 'groupFruits')

        radio1.onchange.do(self.radio_changed)
        radio2.onchange.do(self.radio_changed)
        radio3.onchange.do(self.radio_changed)

        container.append([self.lbl_output, radio1, radio2, radio3])
        return container

    def radio_changed(self, emitter, value):
        self.lbl_output.set_text("The radio button labeled: %s is %s"%(emitter.get_text(), str(value)))
        

if __name__ == "__main__":
    start(MyApp)

@jimbob88
Copy link

The above example no longer works, due to a change in REMI, it can be easily fixed by replacing

self.attributes[gui.Widget.EVENT_ONCHANGE] = \
            "var params={};params['value']=document.getElementById('%(emitter_identifier)s').checked;" \
            "sendCallbackParam('%(emitter_identifier)s','%(event_name)s',params);" % \
            {'emitter_identifier': str(self.identifier), 'event_name': gui.Widget.EVENT_ONCHANGE}

with:

self.attributes[gui.Widget.EVENT_ONCHANGE] = \
            "var params={};params['value']=document.getElementById('%(emitter_identifier)s').checked;" \
            "remi.sendCallbackParam('%(emitter_identifier)s','%(event_name)s',params);" % \
            {'emitter_identifier': str(self.identifier), 'event_name': gui.Widget.EVENT_ONCHANGE}

(Remi made sendCallbackParam a function of remi, hence the change)

@dddomodossola
Copy link
Collaborator

Thank you a lot @jimbob88 ;-)

@jimbob88
Copy link

There also seems to be a strange glitch with this where it doesn't work with the TabBox widget. When one changes tabs after having changed the currently selected radio button, the last checked radio is not saved (moreover the html tag for that radio button never reads <"checked"="checked">)

So, for the first radio button that you use set_value(True) on, the value of the checkbutton is remembered and the html tag includes <"checked"="checked">; however, if you then click and change the radio button the <"checked"="checked"> is removed from the prior radio button but it is missing from the last selected radio button so it dissapears on a change of tab.
This can be seen in the following code & video:

HTML.mp4
# -*- coding: utf-8 -*-

from remi import gui
from remi import start, App


class LabelForInputs(gui.Widget, gui._MixinTextualWidget):
    """ Non editable text label widget. Specifically designed to be used in conjunction with checkboxes and radiobuttons
    """

    def __init__(self, text, widget_for_instance, *args, **kwargs):
        """
        Args:
            text (str): The string content that have to be displayed in the Label.
            widget_for_instance (gui.Widget): checkbox or radiobutton instance
            kwargs: See Container.__init__()
        """
        super(LabelForInputs, self).__init__(*args, **kwargs)
        self.type = 'label'
        self.attributes['for'] = widget_for_instance.identifier
        self.set_text(text)

class InputCheckable(gui.Input):
    """It is the base class for checkable widgets (Switch, RadioButton, Checkbox).
        Checkable are the widgets that contains attribute 'checked' to determine the status
        The developer has to pass the the argument _type to the constructor
         to determine the kind of widget.
    """

    def __init__(self, status_on=False, input_type='checkbox', *args, **kwargs):
        """
        Args:
            status_on (bool):
            kwargs: See Widget.__init__()
        """
        super(InputCheckable, self).__init__(input_type, *args, **kwargs)
        self.set_value(status_on)
        self.attributes[gui.Widget.EVENT_ONCHANGE] = \
            "var params={};params['value']=document.getElementById('%(emitter_identifier)s').checked;" \
            "remi.sendCallbackParam('%(emitter_identifier)s','%(event_name)s',params);" % \
            {'emitter_identifier': str(self.identifier), 'event_name': gui.Widget.EVENT_ONCHANGE}

    @gui.decorate_event
    def onchange(self, value):
        value = value in ('True', 'true')
        self.set_value(value)
        self._set_updated()
        return (value,)

    def set_value(self, status_on):
        if status_on:
            self.attributes['checked'] = 'checked'
        else:
            if 'checked' in self.attributes:
                del self.attributes['checked']

    def get_value(self):
        """
        Returns:
            bool:
        """
        return 'checked' in self.attributes



class RadioButton(InputCheckable):
    """RadioButton widget, useful for exclusive selection.
        different radiobuttons have to be assigned to the
        same group name in order to switch exclusively.
    """

    @property
    def attr_name(self):
        return self.attributes.get('name', '')

    @attr_name.setter
    def attr_name(self, value):
        self.attributes['name'] = str(value)

    def __init__(self, status_on=False, group='group', *args, **kwargs):
        """
        Args:
            status_on (bool): the initial value
            group (str): the group name. RadioButtons with same group will be exclusively activated
            kwargs: See Widget.__init__()
        """
        super(RadioButton, self).__init__(status_on, input_type='radio', *args, **kwargs)
        self.attr_name = group

    def set_value(self, status_on):
        InputCheckable.set_value(self, status_on)

        # if on and has a parent, all other radios with same group name are switched off
        if status_on and self.get_parent():
            for w in self.get_parent().children.values():
                if isinstance(w, type(self)):
                    if w.identifier != self.identifier:
                        if w.get_group() == self.get_group():
                            w.set_value(False)

    def set_group(self, group_name):
        self.attr_name = group_name

    def get_group(self):
        return self.attr_name

class RadioButtonWithLabel(gui.Container):
    _radio = None
    _label = None

    @property
    def text(self):
        return self._label.get_text()

    @text.setter
    def text(self, value):
        self._label.set_text(value)

    @property
    def attr_name(self):
        return self._radio.attr_name

    @attr_name.setter
    def attr_name(self, value):
        self._radio.attr_name = str(value)

    def __init__(self, text='radiobutton', status_on=False, group='group', *args, **kwargs):
        """
        Args:
            text (str): the text label
            status_on (bool): the initial value
            group (str): the group name. RadioButtons with same group will be exclusively activated
            kwargs: See Widget.__init__()
        """
        super(RadioButtonWithLabel, self).__init__(*args, **kwargs)

        self._radio = RadioButton(status_on, group=group)
        self.append(self._radio, key='radio')

        self._label = LabelForInputs(text, self._radio)
        self.append(self._label, key='label')

        self._radio.onchange.connect(self.onchange)

    @gui.decorate_event
    def onchange(self, widget, value):
        self.__update_other_radios()
        return (value,)

    def get_text(self):
        return self._label.text

    def set_text(self, text):
        self._label.text = text

    def set_group(self, group_name):
        self.attr_name = group_name

    def get_group(self):
        return self.attr_name

    def get_value(self):
        return self._radio.get_value()

    def set_value(self, value):
        """ Args:
                value (bool): defines the checked status for the radio button
        """
        self._radio.set_value(value)
        self.__update_other_radios()

    def __update_other_radios(self):
        # if on and has a parent,
        # all other radios, in the same container,
        # with same group name are switched off
        if self.get_value() and self.get_parent():
            for w in self.get_parent().children.values():
                if isinstance(w, type(self)):
                    if w.identifier != self.identifier:
                        if w.get_group() == self.get_group():
                            w.set_value(False)



class MyApp(App):
    def main(self):
        tb = gui.TabBox()
        tb.style.update({"margin": "0px", "width": "800px", "height": "680px",
                         "top": "5%", "left": "15px", "position": "absolute", "overflow": "auto"})

        container = gui.VBox(style={'margin':'0px auto'})
        self.lbl_output = gui.Label()

        radio1 = RadioButtonWithLabel('Banana',False, 'groupFruits')
        radio2 = RadioButtonWithLabel('Apple',False, 'groupFruits')
        radio3 = RadioButtonWithLabel('Orange',False, 'groupFruits')

        radio1.onchange.do(self.radio_changed)
        radio2.onchange.do(self.radio_changed)
        radio3.onchange.do(self.radio_changed)
        radio1.set_value(True)
        container.append([radio1, radio2, radio3])
        container.style['position'] = 'relative'

        container2 = gui.VBox(style={'margin':'0px auto'})
        container2.style['position'] = 'relative'
        container2.append([self.lbl_output])
        tb.add_tab(container, "Container", None)
        tb.add_tab(container2, "Container2", None)
        return tb

    def radio_changed(self, emitter, value):
        print("The radio button labeled: %s is %s"%(emitter.get_text(), str(value)))
        self.lbl_output.set_text("The radio button labeled: %s is %s"%(emitter.get_text(), str(value)))
        

if __name__ == "__main__":
    start(MyApp, port=8081, start_browser=False)

@zhuxinchen1206
Copy link

Comment this line self._set_updated() in class InputCheckable(gui.Input): , you can solve this problem
def onchange(self, value):
value = value in ('True', 'true')
self.set_value(value)
# self._set_updated()
return (value,)

@dddomodossola
Copy link
Collaborator

Thank you very much ;-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants