How to set UI controls at runtime? (Like LogLinear Analysis)

Everything related to the development of modules in jamovi
Post Reply
Fhas
Posts: 27
Joined: Wed Sep 12, 2018 2:55 pm

How to set UI controls at runtime? (Like LogLinear Analysis)

Post by Fhas »

Hi,

I am trying to set UI controls at runtime in the following contexts:

1. A default value can only be calculated after a variable is selected (e.g. because it is based on variable length)
2. A default value must be recalculated based on user input changes to other UI fields

This must be possible, because something like it is achieved in the log-linear analysis [Frequencies] which allows to choose reference levels, etc. after a variable is entered. It is unclear to me from the documentation / other modules how I can achieve this.

Hope you can help!
dropmann
Posts: 79
Joined: Thu Feb 02, 2017 10:26 am

Re: How to set UI controls at runtime? (Like LogLinear Analy

Post by dropmann »

To do this you will need to create an event.js file. The tutorial support for this is lacking currently but will be improved soon. I'll layout a quick summary below. I'll use an example analysis called ‘fred’.

In this example I will create a system where textbox will declare the state of a checkbox by saying ‘Checkbox ON’ or ‘Checkbox OFF’. The textbox will be call textbox1 and the checkbox will be called checkbox1. I am making the assumption that the fred.a.yaml and the fred.u.yaml have already been setup appropriately to create and display these controls in the options panel when the fred analysis is run.

To create this system, we will need to attach some javascript code to some events. In this case the events will be the ‘change’ event for the checkbox and the ‘update’ event for the analysis.

In the jamovi directory of your module there should be a directory called ‘js’ (if not create it). It’s in this directory that the event.js files live for the module. It is good to have one per analysis. For this example, I will create a file called fred.events.js. This file will contain the javascript that is to be executed in response to specific events that will occur in this analysis.

The content of the file would look like this:

Code: Select all

const events = {

    // in here is where the event functions go

    update: function(ui) {
        updateTextbox1(ui);
    },

    onChange_checkbox1: function(ui) {
        updateTextbox1(ui);
    }
}

// out here we can have support functions if necessary

let updateTextbox1 = function(ui, context) {
    let value = ui.checkbox1.value();
    if (value) 
        ui.textbox1.setValue('Checkbox ON');
    else
        ui.textbox1.setValue('Checkbox OFF');
};

module.exports = events;
The ‘update’ function is to be called when the ‘fred’ analysis is selected. It initialises the state of the controls.

The ‘onChange_checkbox1’ function is to be called when the value of the control is changed. In the case of ‘checkbox1’, the value will change between ‘true’ and ‘false’ when checked or unchecked.

Next, we will need to attach these event functions to the controls and analysis so that they will listen for the events to occur. To do this we will need to modify our ‘fred.u.yaml’ file.

At the top of the u.yaml file, at the analysis level, we would add an ‘events’ group (if it doesn’t already exist). Inside the events group we create a property called ‘update’ and make its value the path to our newly created ‘update’ function. The path follows this pattern: ‘./filename::function’.

Code: Select all

name: fred
title: FRED
jus: '2.0'
compilerMode: tame
events:
    update: './fred.events::update'
Then, we can do the same to the checkbox description and link to the ‘onChange_checkbox1’ function that we created earlier.

Code: Select all

- type: CheckBox
  name: checkbox1
  events:
      change: './fred.events::onChange_checkbox1'
Below is a list of the different control events that can be attached to:

Analysis:
  • update: Is fired whenever an analysis is selected.
All controls:
  • change: Is fired when the value of the control is changed.
  • changing: Is fired before the value of the control is changed.
ListBox:
  • listItemAdded: Is fired when a control is added to a list box.
  • listItemRemoved: Is fired when a control is removed from a list box.
If we then create and install the module we should be in business. I hope this is helpful. I appreciate that it does not address your exact situation but should provide a knowledge base to move forward.

Please let me know if something doesn't work, just in case i have forgotten something.
Fhas
Posts: 27
Joined: Wed Sep 12, 2018 2:55 pm

Re: How to set UI controls at runtime? (Like LogLinear Analy

Post by Fhas »

Great, thanks! I understand the mechanism following your instructions, I can change the value using setValue(), but not yet to the value I need:

I need to know the length of a variable that is selected into a TargetLayoutBox with a VariablesListBox-child named y1 (maxItemCount: 1, isTarget: true).

If I get the length of that variable I want to do some calculations and report the result in a TextBox named maxScale (so the user can change the default to another value).

Currently the .events.js looks like this:

Code: Select all

const events = {

    update: function(ui) {
        updateMaxScale(ui);
    },

    onChange_y1: function(ui) {
        updateMaxScale(ui);
    },

    onChange_maxScale: function(ui) {
        updateMaxScale(ui);
    }
}

let updateMaxScale = function(ui, context) {
    var scaleMax = ui.maxScale.value();
    var scaleMin = ui.minScale.value();
    var nrow     = ui.y1.length;
    // Change to floor(log2(NROW(y)/2)) if the default value is found (-1)
    if (scaleMax < 0) {
        scaleMax = Math.floor(Math.log2(nrow/2));
        ui.maxScale.setValue(scaleMax);
    } else {
        ui.maxScale.setValue(scaleMax);
   }
};

module.exports = events;
Some questions:
- I am checking against scaleMax < -1 (default in a.yaml), eventually scaleMax has to change if invalid values are given, e.g. scaleMin > scaleMax && scaleMax < nrow. Is this the best place to do the check, or should I evaluate this before the updateMaxscale function is called?
- I guess ui.y1.value() gives me the variable name? It doesn't appear to be js array or string, because I can't get ui.y1.length to work. How do I get the length of the variable?
- Where should events::onChange_y1 go? Under TargetLayoutBox, or VariablesListBox Can it be an onChange or should it be of the listItemAdded kind to work properly?

Thanks!
User avatar
jonathon
Posts: 2613
Joined: Fri Jan 27, 2017 10:04 am

Re: How to set UI controls at runtime? (Like LogLinear Analy

Post by jonathon »

hi fred,

at the moment we don't have the number of rows in the data set plumbed through to the UI. (it's not out of the question that we'd provide this, but we'd need to discuss it, and it probably won't happen soon).

i'm not sure i completely understand what you're trying to do, so bear with my if i'm missing the point.
1. A default value can only be calculated after a variable is selected (e.g. because it is based on variable length)
2. A default value must be recalculated based on user input changes to other UI fields
so the idea of a "changing default value" is a bit problematic, no? you can't be sure if the user wants to continue using the value in there, of if they want their 'default' updated?

what if instead, you have a radio button which let's the user choose between 'the default', and a user specified value (and then have a textbox where the user can specify their value). this way it's clear what the user's intentions are.

wrt letting the user know that the specified value is "out of range", is this the sort of thing you can handle in the analysis itself? i.e. if a person specifies a value which exceeds the number of rows, you just throw an error?

you'd need to do this anyway, because the number of rows in the data set could change (i.e. the user could delete some, or they could turn on a filter), and they may not have the analysis UI open.

let me know if i've misunderstood.

kind regards

jonathon
Fhas
Posts: 27
Joined: Wed Sep 12, 2018 2:55 pm

Re: How to set UI controls at runtime? (Like LogLinear Analy

Post by Fhas »

Hi Jonathon
so the idea of a "changing default value" is a bit problematic, no? you can't be sure if the user wants to continue using the value in there, of if they want their 'default' updated?
I see, my use of the word 'default' might have been a bit confusing:
- The analyses I am implementing (fluctuation analyses) need to divide a time series into bins of increasing size
- There's a 'rule of thumb' formula for deciding what the maximum bin-size should be: floor(log2(NROW(y)/2)), it depends on the length of the series.
- It will vary from case to case whether this is a sensible choice, so a user should be able to change it. To do so sensibly, the user should know the value.

I can see how your radiobutton solution could work, I would need to add a kind of informative results element listing the parameter values if 'default' is selected.

Thanks!
Fhas
Posts: 27
Joined: Wed Sep 12, 2018 2:55 pm

Re: How to set UI controls at runtime? (Like LogLinear Analy

Post by Fhas »

Hi,

sorry to have to get back to this...

I was trying to implement your radio button solution, based on the info on this page: https://dev.jamovi.org/ui-radiobutton.html (I created an option "auto" and "user", which has a child textbox, like ciWidth in the example)

That didn't work... So I copied the code provided with the example, same thing happens: the textbox entry is deleted in tame mode and replaced by 'children: [ ]'

So.. eh.. what am I doing wrong here?

(the aggressive mode changes the radio buttons to comboboxes, but that's what it should be doing I guess)
dropmann
Posts: 79
Joined: Thu Feb 02, 2017 10:26 am

Re: How to set UI controls at runtime? (Like LogLinear Analy

Post by dropmann »

So if you are after radio buttons then you will need to use tame. As you know aggressive forces options into certain controls. In regards to the control disappearing, i would think its because you haven't added a corresponding option for that textbox into the a.yaml file. The compiler automatically removes any controls that have been orphaned. So in regards to the example in the tutorials, you would have to add something like the code block below to the a.yaml file.

Code: Select all

- name: ciWidth
      title: Confidence level
      type: Number
      min: 50
      max: 99.9
      default: 95
I can only assume this is what is happening. Please let me know if you have added that option.
Fhas
Posts: 27
Joined: Wed Sep 12, 2018 2:55 pm

Re: How to set UI controls at runtime? (Like LogLinear Analy

Post by Fhas »

Thanks! I figured it out eventually.

It would be helpful for others if you could add the 'missing' code to the radio button example at: https://dev.jamovi.org/ui-radiobutton.html - Maybe I'm just slow, but it wasn't obvious at first that something was missing.

Code: Select all

- name: ciWidth
   title: Confidence level
   type: Number
   min: 50
   max: 99.9
   default: 95
dropmann
Posts: 79
Joined: Thu Feb 02, 2017 10:26 am

Re: How to set UI controls at runtime? (Like LogLinear Analy

Post by dropmann »

All good. Thanks for the feedback.
Post Reply