Skip to content

Scripting in ESMira

Selina edited this page Jul 8, 2024 · 4 revisions

ESMira has the capability of executing custom scripts at various points to achieve various dynamic effects. This page gives an overview of the scripting system, and how it is integrated in ESMira. For more information on Merlin, ESMira's scripting language, see the Language Reference page. You can also take a look at the Examples page for some concrete examples of applications of this system.

Relevance Scripts

One type of script available in ESMira are Relevance scripts. You may specify these for individual items or pages in a questionnaire. Relevance scripts are used to determine whether an item or a page should be shown or not, and thus allow to dynamically adapt a questionnaire. E.g., if the first page of a questionnaiere asks whether or not a certain event has occurred, and the second page has follow up questions to such an event, then a relevance script for the second page can dynamically show or skip the second page, depending on the participant's answer on the first page.

Note

If the relevance script of an item or page is empty, no script will be executed. Per default, all items and pages will be shown (despite an empty script technically evaluating to none, which would be falsy.)

Caution

To prevent situations where participants open a questionnaire where all pages are skipped, which would seem like an error, the first page is always shown, and it's relevance script is not evaulated.

Relevance scripts try to evaluate the truthiness of value returned from the script. If the return value is truthy, the item/page will be shown, if it is falsy, the item/page will be hidden. Due to implicit return values, simple scripts may consist of only one expression. E.g., a relevance script that always hides an item can look as follows:

return false;

which can even be shortened to:

false;

Here is a more relevant example. Suppose there is a binary item called "demo_item" on the first page. If the participant selects "no" that item will save a value of 0, if they select "yes" that item will save 1 (and if the participant omits it, it will save none). If an item on the second page should only be shown if the participant selected "yes", then you could attach the fololwing script to it:

getQuestionnaireVar("demo_item");

That's it. Due to implicit return values and the values of the queried item matching the expected truthiness this will work.

Caution

It is currently not possible to safely use relevance scripts with items marked as required (or pages containing such items). Doing so would result in a deadlock situation, as ESMira would not allow the participant to continue to the next page unless they filled out a hidden item, which is impossible. Please be aware of this limitation and avoid such a configuration until this bug is resolved.

Text Script

All items may specify a text script. If present, the code in that script will be evaluated for a returned string value, which will then be displayed instead of the shown text property. This allows to create dynamic text in items, and could be used to, e.g., display the result of a test as part of the same questionnaire. If the text script is empty, the shown text will be displayed as usual.

It is possible to include html-tags in the output of a text script.

Note that the result of the text script will completely replace the shown text property. Thus you need to return the complete item text from the script. Here is an example: suppose a previous script has calculated the result of a test and saved the result under globals.test_score. The average result is between 10 and 15. You could then use the following text_script in a text display item:

out = "You reached <b>"..globals.test_score.." points</b> in this test. ";
out = out.."Your result is ";

if(globals.test_score < 10){
   out = out.."below ";
}elif(globals.test_score > 15){
   out = out.."above ";
}
out = out.."average.";
return out;

This script builds the string in the variable out step by step, using the string concatenation operator .. to assemble strings and save them in the out variable. Note the use of out = out... This ensures that the saved variable is a combination of the previous text and the text appended in that line. Using <b> and </b> will render the number of points in bold.

Questionnaire End Block

A questionnaire may have an end block that gets executed right before the questionnaiere is saved. This makes it a convinient place to set the value of virtual items, or to trigger notifications or immediate follow-up questionnaires.

Execution Order

In some cases, especially when certain side effects are concerned, the order of execution might be important to consider. When ESMira tries to display a questionnaire page it first checks whether the page has a relevance script. If so, that script gets executed and the return value evaluated. If the value is truthy, the page will be displayed, otherwise ESMira will skip ahead to the next page. If the page is skipped, ESMira will also not execute any of the relevance or text scripts of items on that page. Otherwise, if the page is displayed, it will execute all available relevance scripts of items in order, before displaying them. Once ESMira tries to display an item, the text script will be evaluated (meaning the text script items hidden by a relevance script will not be evaluated). All scripts will only be evaluated once per questionnaire.

Note

Because relevance scripts are only evaluated once when loading a questionnaire page, only items from preceding pages may contain values. When trying to access values of the current or subsequent pages, the function will always return a null value, even for existing items.

When the participant clicks "Save" on the last page, ESMira will execute the end block script. This happens just before saving, so that the value of virtual items can be set in this script.

Global Variables

ESMira manages a globals object that is persistent between script executions within the same study. This allows you to keep variables between executions of the same script, of different scripts within a questionnaire, and even of different scripts within the same study. Any variable stored as a field of globals will still be there the next time a script in that study will be executed. See some of the examples on how this can be used.

Important

All scripts within the same study share the same globals object.

This allows different scripts to access the same variables. However, this means you also need to make sure that you don't accidentally use the same global variable in different scripts if you mean different things. E.g., a globals.counter variable is a good way to share the state of a counter between different scripts, even different questiorraires. However, if two scripts both use globals.counter, but try to count different things, then this can lead to some weird, unintended behavior. One option to avoid name-collisions is to use sub-objects as namespaces. E.g.,

init {
    globals.demo_questionnaire = object;
    globals.demo_questionnaire.counter = 0;
}

globals.demo_questionnaire.counter = globals.demo_questionnaire.counter + 1;

This could be used to create a counter that is specific to "demo_questionnaire". While this is a bit verbose, it also makes it harder to confuse this courter with globals.other_questionnaire.counter.