Reset field after field has been set to invisible

Description of the limitation and why it is relevant to address

As a VIKTOR app developer, I would like the fields that are not visible to be empty or reset, so that other fields are not influenced by invisible fields.

I think this is relevant for the VIKTOR platform because it will decrease the number of statements needed in visible, increase the readability of the code and avoid unwanted visibility of fields.

Example which shows the problem:

I have field_1 (What do you want on your bread?) with options Cheese and Peanut butter.
I have field_2 (What kind of Cheese do you want?) with options Gouda or Beemster, and I only want to show this if field_1 == Cheese.
Then I have field_3 (Do you want 30+ or 48+ Gouda Cheese), and I only want to show this if field_2 == Gouda.

The problem is, when in a previous attempt in the same session, field_2 is set to Gouda, but field_1 is now equal to Peanut butter (which makes field_2 invisible), it will still show field_3 even though it is not logical to show this. This can be solved by using both statements, but this can become quite messy.

Submitter proposed design (optional)

I would suggest making the field empty/reset the field when it becomes invisible.

Current workarounds

Use multiple statements, which can become quite a mess.
For the example (if field_3 is a NumberField):

field_3 = NumberField('field_3', visible = And(IsEqual(Lookup('field_1'), 'Cheese'), IsEqual(Lookup('field_2'), 'Gouda')))

In this case it only includes two statements, but I already have some cases where I use 4 or 5.

Hi Marieke,

First of all, thanks for the wonderful description! To understand your case, I retyped your example to some code that can be applied in a VIKTOR app:

from viktor.parametrization import ViktorParametrization, NumberField, OptionField, IsEqual, Lookup, And


class Parametrization(ViktorParametrization):
    field_1 = OptionField('Cheese or Peanut butter', options=['Cheese', 'Peanut butter'])
    field_2 = OptionField("What kind of Cheese do you want?", options=['Gouda', 'Beemster'], visible=IsEqual(Lookup('field_1'), 'Cheese'))
    field_3 = NumberField("Do you want 30 + or 48 + Gouda Cheese", visible=And(IsEqual(Lookup('field_1'), 'Cheese'), IsEqual(Lookup('field_2'), 'Gouda')))

Just to check, do you know you can use a callback function to define the visibility as well? Here is an example:

from viktor.parametrization import ViktorParametrization, NumberField, OptionField


def is_cheese(params, **kwargs):
    return params.field_1 == 'Cheese'


def is_gouda(params, **kwargs):
    if is_cheese(params):
        return params.field_2 == 'Gouda'
    return False


class Parametrization(ViktorParametrization):
    field_1 = OptionField('Cheese or Peanut butter', options=['Cheese', 'Peanut butter'])
    field_2 = OptionField("What kind of Cheese do you want?", options=['Gouda', 'Beemster'], visible=is_cheese)
    field_3 = NumberField("Do you want 30 + or 48 + Gouda Cheese", visible=is_gouda)

It is a bit more lengthy, but much clearer to read.

Another approach would be to add your statements to clearly defined variables, which makes the code look even more readable for this example:

from viktor.parametrization import ViktorParametrization, NumberField, OptionField, IsEqual, Lookup, And


is_cheese = IsEqual(Lookup('field_1'), 'Cheese')
is_gouda = And(is_cheese, IsEqual(Lookup('field_2'), 'Gouda'))


class Parametrization(ViktorParametrization):
    field_1 = OptionField('Cheese or Peanut butter', options=['Cheese', 'Peanut butter'])
    field_2 = OptionField("What kind of Cheese do you want?", options=['Gouda', 'Beemster'], visible=is_cheese)
    field_3 = NumberField("Do you want 30 + or 48 + Gouda Cheese", visible=is_gouda)

Let me know if any of the above mentioned examples suit your needs. If not, could you indicate why the examples do not work?

(By the way, life would have been much easier if you just had chosen Peanut butter, and not gave the option :wink: )

Hi,

Thanks for the elaborate explanation! This will make the code more readable indeed :slight_smile:
I do still struggle with having ‘too’ many callback functions now, especially when I want to combine different ones.

So, to make life more complex: Is it possible to combine different callback functions within visible? Or combine callback functions with the normal way to use visible?

For example, there is an independent question where I ask if they want a large or small baguette. And I only want field_3 (about the 30 + and 48 + Gouda) to show if they choose a large baguette (and if they choose Cheese and Gouda). I prefer not to combine these in a callback function because then I will have to make >20 different callback functions (not in this case, but in my current app).

def is_large(params, **kwargs): # new
    return params.field_0 == 'Large'

def is_cheese(params, **kwargs):
    return params.field_1 == 'Cheese'

def is_gouda(params, **kwargs):
    if is_cheese(params):
        return params.field_2 == 'Gouda'
    return False

class Parametrization(ViktorParametrization):
    field_0 = OptionField('Large or small baguette?', options=['Large', 'Small']) # new
    field_1 = OptionField('Cheese or Peanut butter', options=['Cheese', 'Peanut butter'])
    field_2 = OptionField("What kind of Cheese do you want?", options=['Gouda', 'Beemster'], visible=is_cheese)
    
    field_3 = NumberField("Do you want 30 + or 48 + Gouda Cheese", visible=[is_gouda, is_large]) # new preferred
    or
    field_3 = NumberField("Do you want 30 + or 48 + Gouda Cheese", visible=[is_gouda, IsEqual(Lookup('field_0'), 'Large')]) # new alternative

So I would like to achieve something like the last line or second-last line, where I combine both callback functions without having to make a new callback function.

I know it’s getting quite messy, so I am trying to make it as not messy as possible :grimacing:

I like your suggestion of stacking callback functions. I will take this feedback and put it on our internal issue board.

Thanks for contributing!

I’ve taken some thought in this limitation, and came to the idea that you can also build something yourself that can help with this. In this example I introduce you a function that can act as an alternative the And object: myAnd

Here it is used in one of the previous code snippets. It unfortunately only works with callback functions, but may help you in concatenating all your callbacks without writing a new callback function.

from viktor import ViktorController
from viktor.parametrization import ViktorParametrization, NumberField, OptionField


def is_cheese(params, **kwargs):
    return params.field_1 == 'Cheese'


def is_gouda(params, **kwargs):
    if is_cheese(params):
        return params.field_2 == 'Gouda'
    return False


def myAnd(*args):
    def my_function(params, **kwargs):
        return all([arg(params, **kwargs) for arg in args])
    return my_function


class Parametrization(ViktorParametrization):
    field_1 = OptionField('Cheese or Peanut butter', options=['Cheese', 'Peanut butter'])
    field_2 = OptionField("What kind of Cheese do you want?", options=['Gouda', 'Beemster'], visible=is_cheese)
    field_3 = NumberField("Do you want 30 + or 48 + Gouda Cheese", visible=myAnd(is_cheese, is_gouda))

I hope this helps.

Ah, that’s a great solution, thanks! :smiley:
This will reduce the number of callback functions needed by a lot, so this solves my problem.

1 Like