Kapitel 14. Action-Befehle und -Blöcke

Inhaltsverzeichnis

Allgemeines
Actionbefehle
Kontrollstrukturen

Allgemeines

Mit den bisher vorgestellten Mitteln fällt es schwer, Berechnungen durchzuführen, die sich z.B. zur Steuerung des Interview-Ablaufs nutzen lassen. Wenn beispielsweise per compute eine Variable definiert wird, die eine Berechnung darstellt:

compute a = b+c;
  

dann wird bei jedem Zugriff auf die Variable a (z.B. innerhalb von Berechnungen oder Bedingungen) der aktuelle Wert der Addition b+c ermittelt und als Wert von a benutzt. Damit können aber z.B. keine Zähler realisiert werden. Eine Definition wie

compute count = count+1;
  

würde zu einer endlosen Rekursion führen, da bei dem Zugriff auf den Wert von count die Berechnung count+1 durchgeführt wird, die aber wiederum einen Zugriff auf count darstellt, und so weiter.

Action-Blöcke schaffen hier Abhilfe und erlauben es, bei der Steuerung Quotenstände oder Zufallsauswahlen zu berücksichtigen oder Berechnungen bei Start oder Wiederaufnahme eines Interviews durchzuführen. Dabei stellen Action-Blöcke Kontrollstrukturen zur Verfügung, wie sie aus herkömmlichen Programmiersprachen bekannt sind: wenn-dann, von-bis usw.

Ein ActionBlock enthält eine beliebige Anzahl von Befehlen und wird an ein bestimmtes Ereignis gekoppelt, bei dessen Eintreten er ausgeführt wird. Hierbei gibt es sowohl aufs Interview bezogene Ereignisse (Start, Abbruch, beendet) als auch fragespezifische (vor dem Anzeigen, nach Klick auf »weiter« etc.). Konkret stehen zur Verfügung:

Interview-spezifisch:

  • startActionBlock (wird beim Start ausgeführt, auch bei Wiederaufnahme. Das gilt auch bei abgeschlossenen Fällen oder einer deaktivierten Studie, damit Q. eventuelle Meldungen in der richtigen Sprache ausgeben kann.)

  • resumeActionBlock (wird beim Wiederaufnahme nach einem Abbruch ausgeführt)

  • timeoutActionBlock (wird ausgeführt, bevor ein Interview wg. zu langer Inaktivität abgebrochen wird)

  • cmplActionBlock (wird ausgeführt wenn ein Interview beendet wird, egal mit welchem Status)

Frage- bzw. Block-spezifisch:

  • initActionBlock (wird beim Initialisieren vor dem Anzeigen einer Frage oder eines Blocks ausgeführt)

  • preAssertionActionBlock (wird nach Klick auf den submit-Button, aber vor dem Validieren der asserts ausgeführt)

  • continueActionblock (wird nach erfolgreicher Validierung einer Frage oder eines Blocks vor dem Weiterleiten zum nächsten Screen ausgeführt)

Innerhalb dieser Befehlsblöcke können keine Variablen definiert werden; dies muss vorher mit compute geschehen. Eine Ausnahme hiervon bilden Zählvariablen von Schleifen.

compute count=0;

SingleQ q1;
...
flt=count lt 3;
continueActionBlock={ inc(count); };

SingleQ q2;
...
flt=count lt 3;
continueActionBlock={ inc(count); };

SingleQ q3;
...
flt=count lt 3;
continueActionBlock={ inc(count); };

SingleQ q4;
...
flt=count lt 3;
continueActionBlock={ inc(count); };

SingleQ q5;
...
flt=count lt 3;
continueActionBlock={ inc(count); };

block zufall=(q1 q2 q3 q4 q5); random;
  

Beim Befehl inc (für »increment«) handelt es sich um einen Action-Befehl. Er erhöht den Zähler count nach jeder Frage um eins. Außerdem wird jede Frage danach gefiltert, ob der Zählerstand 3 erreicht ist.

Die Befehle eines ActionBlocks müssen innerhalb geschweifter Klammern stehen. Sie dienen auch zum Definieren weiterer Blockstrukturen innerhalb eines ActionBlocks etwa bei Bedingungen und Schleifen.

Q. erlaubt es zwar ActionBlöcke auch an Screens zu schreiben, eine Funktion/Bedeutung haben ActionBlöcke allerdings nur an Fragen oder teilweise an Blöcken ([Init|Continue]ActionBlock).

Man kann die Action-Befehle in zwei Kategorien aufteilen: »normale Befehle« und Befehle zur Ablaufsteuerung (Bedingungen, Schleifen).

Actionbefehle

Mit Action-Befehlen lassen sich Dinge wie Zähler, Bedingungen und Schleifen realisieren. Die folgenden alphabetisch sortierten Action-Befehle können nur innerhalb von Action-Blöcken verwendet werden.

add

ersetzt die erste Missing-Stelle eines Arrays ARRAY durch VALUE. Wenn der Array vollständig belegt ist, macht die Anweisung nichts.

	    add(ARRAY, VALUE);
	  
Array container[3];
actionblock set_container = {
    set( container[1] = 1 );
    set( container[3] = 3 );
};

actionblock add_container = {
  add(container, 7);
};

continueActionblock = { print(container); };
// ergibt in actionlog.txt:
// PrintAction: variable: container; value: 1 7 3 
          
appendText

hängt TEXT an TextElement TEXTVAR an.

	    appendText(TEXTVAR, "TEXT");
	  
appendText(textelem, "... wird angehängt");
          
changePBar

setzt den Fortschrittsbalken um #QUESTIONS Stellen weiter, sofern BEDINGUNG zutrifft. Alternativ (und gebräuchlicher) kann der Fortschrittsbalken auch innerhalb von Filtern für Fragen, Screens und Blöcken definiert werden, siehe „Filter an Fragen, Blöcken, Screens“.

	    changePBar((BEDINGUNG) #QUESTIONS);
	  
changePBar((var eq 1) 2);
          
clear

abhängig vom arrayInitMode wird jedem Element von ARRAY der Missing-Wert _missing oder der jeweilige Stellenindex zugewiesen.

	    clear(ARRAY);
	  
[bei arrayInitMode=missing]
ARRAY-Inhalt vor  clear(): { 1, 2, 3, 4, 5 }
ARRAY-Inhalt nach clear(): {  ,  ,  ,  ,   }
          
dec

steht für decrement und verringert den Wert von VAR um 1.

	    dec(VAR);
	  
exit

Mit exit wird das Interview am Ende des aktuellen Actionblocks mit FINISHCODE beendet und die Endseite angezeigt.

	    exit(FINISHCODE);
	  
findMinValIndex, findMaxValIndex

Die Funktionen suchen in einem Array (ARR) den niedrigsten bzw. den höchsten Wert und speichern den Index der Fundstelle in einer Variablen (VAR).

	    findMinValIndex(ARR, VAR)
	  
compute min = 0;
compute max = 0;
set( arr = [1 3 5 -1 6:9 0 0] ); // {1, 3, 5, -1, 6, 7, 8, 9, 0, 0}
findMinValIndex(arr, min); // min erhält Wert 4
findMaxValIndex(arr, max); // max erhält Wert 8	    
	  
getCountValue

Der ActionBefehl

	    getCountValue(VAR, "KEY")
	  

ruft Zählerstände ab, die in der Datei countvars.lst im Studienverzeichnis gepflegt werden. Dabei wird zuerst der Zähler KEY inkrementiert und anschließend an die Variable VAR übergeben.

compute z1 = 0;

textq welcome;
text="
  Herzlich Willkommen!<br />
  Sie sind der @insert(z1). Teilnehmer an dieser Befragung!
";
initactionblock={
  getCountValue(z1, "zaehler1");
};
          

Mit dem Anzeigen der Frage wird der Zählerstand erhöht. Die Nachrichten werden der Reihe nach wie folgt angezeigt:

Sie sind der 1. Teilnehmer an dieser Befragung!
Sie sind der 2. Teilnehmer an dieser Befragung!
Sie sind der 3. Teilnehmer an dieser Befragung!
	  

Die Datei countvars.lst besteht aus einfachen Key-Value Paaren:

zaehler1 0
zaehler2 0
zaehler3 0
	  

inc

steht für increment und erhöht den Wert von VAR um 1.

	    inc(VAR);
	  
incQuota

erhöht den Wert einer QUOTAVAR explizit um 1.

	    incQuota(QUOTAVAR);
	  
insertLabels

hängt in der Labelliste von Frage FRAGENAME nach dem Label mit Code CODE ein neues Label LABEL ein. Je nach Fragetyp muss hierbei darauf geachtet werden, dass die ursprünglich angelegte Frage auch genug Platz hat, alle möglichen Antworten zu speichern. Wäre die Frage im gegebenen Beispiel eine MultiQ, müsste mit oversizeArray(1); dafür gesorgt werden, dass die ursprünglich mit 2 Labels angelegte Frage ausreichend Platz dafür hat auch alle 3 Antwortcodes zu speichern.

	    insertLabels(FRAGENAME, CODE, [ LABEL ]);
	  
multiq bekannt;
labels=
1 "Lafite Rothschild"
3 "Haut-Brion"
;
initActionBlock={
  insertLabels(bekannt, 1, [ 2 "Mouton Rothschild" ]);
};
oversizeArray(1);
          
lalign

verschiebt jedes Element von ARRAY, das ungleich dem Missing-Wert _missing ist, nach links (zum kleinsten Index), indem es so viele Plätze wie möglich mit Missing-Werten tauscht, siehe auch ralign.

	    lalign(ARRAY);
	  
ARRAY-Inhalt vor  lalign(): {  , 2, 3,  , 5, 6,  ,  ,   }
ARRAY-Inhalt nach lalign(): { 2, 3, 5, 6,  ,  ,  ,  ,   }
          
load

kopiert den Inhalt von Variable VAR2 in VAR1. Der Unterschied zu set ist, dass nicht nur ein Wert/Zahl/Ergebnis zugewiesen wird, sondern der gesamte Inhalt einer Variable in eine andere. So lassen sich z.B. auch ganze Array-Inhalte kopieren.

	    load(VAR1 = VAR2);
	  
performHttpRequest

dient zur Belegung von Variablen und Fragen eines Interviews. Der Befehl stellt eine HTTP Anfrage an die Webadresse URL und erwartet eine Antwort mit Daten im JSON Format. Der zweite Parameter data="INFO" ist optional und kann zusätzliche Informationen bei der Anfrage übermitteln. Die angegebenen Werte werden in die benannten Variablen übernommen, sofern sie existieren. Der Befehl funktioniert analog zu readJsonDataFile.

	    performHttpRequest( url="URL"; data="INFO"; ); 
	  
performHttpRequest(
   url="http://www.gessgroup.de/AntwortServlet";
   data="optionale Zusatzinformation";
);
///////////////////////////////
// Server-Antwort:
///////////////////////////////
// {
//    "variables": {
//       "singleq": 4,
//       "multiq": [2, 3, 5, 7, 9],
//       "openq": "dies ist ein text",
//       "numq": [14, 19, 5, 27],
//       "sgq.1": [1,2,3,2],
//       "mgq.1.1": [1,2,3],
//       "mgq.1.2": [2,1]
//    }
// }
          
pow

aus den angegebenen Zahlen wird mit pow(BASE, EXPONENT) eine Potenz gebildet und schreibt sie in die Variable VAR.

             set(var = pow(10, 3)); // 10^3
          
print

schreibt den Inhalt von Variable VAR in eine Zeile der Datei actionlog.lst in das text-Verzeichnis der Studie. Ist die Datei nicht vorhanden, wird sie neu angelegt.

	    print(VAR);
	  
printText

schreibt den angegebenen TEXT in eine Zeile der Datei actionlog.lst in das text-Verzeichnis der Studie. Ist die Datei nicht vorhanden, wird sie neu angelegt.

	    printText("TEXT");
	  
ralign

verschiebt jedes Element von ARRAY, das ungleich dem Missing-Wert _missing ist, nach rechts (zum größten Index), indem es so viele Plätze wie möglich mit Missing-Werten tauscht, siehe auch lalign.

	    ralign(ARRAY);
	  
ARRAY-Inhalt vor  ralign(): {  , 2, 3,  , 5, 6,  ,  ,   }
ARRAY-Inhalt nach ralign(): {  ,  ,  ,  ,  , 2, 3, 5, 6 }
          
readDataFile

dient zur Belegung von Variablen und Fragen eines Interviews. Der Befehl sucht in einer strukturierten Datendatei FILE nach einer Zeile mit Schlüsselwert VAR, und übernimmt die darin angegebenen Werte in die entsprechenden Variablen, sofern sie existieren. FILE muss sich im text-Ordner der jeweiligen Studie befinden.

	    readDataFile(FILE, VAR);
	  
// z.B. für ID 67
readDataFile("data1.txt", _respid);
///////////////////////////////
// data1.txt
///////////////////////////////
// ...
// 67 ( (p_intnr "einunddreißig") (s1 "6") )
// ...
          

Bitte beachten: Wenn VAR eine numerische Information ist (angelegt z.B. Compute oder NumQ), sollte DecimalFormat (s. „ Textersatz mit @insert “ verwendet werden. Sonst kann es passieren, dass große Zahlen in Exponentialschreibweise gewandelt oder eine Nachkommastelle zugefügt werden und dadurch die Suche keine Treffer liefert.

readJsonDataFile, JSON

dient zur Belegung von Variablen und Fragen eines Interviews. Der Befehl liest einen JSON formatierten Datensatz FILE ein und übernimmt angegebene Werte in die benannten Variablen, sofern sie existieren. FILE muss sich im Preset-Ordner der Q. Installation befinden (siehe auch „Konfigurationsdatei qonline.cfg“).

	    readJsonDataFile(FILE);
          
readJsonDataFile("data1.json");
///////////////////////////////
// data1.json
///////////////////////////////
// {
//    "variables": {
//       "singleq": 4,
//       "multiq": [2, 3, 5, 7, 9],
//       "openq": "dies ist ein text",
//       "numq": [14, 19, 5, 27],
//       "sgq.1": [1,2,3,2],
//       "mgq.1.1": [1,2,3],
//       "mgq.1.2": [2,1]
//    }
// }
          
readTextRessource

liest eine Sprach-Ressourcen-Datei FILE aus dem text-Verzeichnis der Studie ein und ändert zu diesem Zeitpunkt alle in der Quelle enthaltenen Textinformationen.

	    readTextRessource(FILE);
	  
if(lang eq 1) {
  readTextRessource("englisch.lrs");
};
          
remove

ersetzt einen oder mehrere Werte VAL in ARRAY durch _missing.

	    remove(ARRAY, VAL, VAL, ...);
	  
ARRAY-Inhalt vor  remove():            { 3, 2, 3, 5, 5, 6, 1, 6, 6 }
ARRAY-Inhalt nach remove(ARRAY, 2, 6): { 3,  , 3, 5, 5,  , 1,  ,   }
          
removeDuplicates

entfernt Werte, die mehrfach in ARRAY enthalten sind. Nur ihr jeweils erstes Vorkommnis bleibt erhalten. Alle weiteren werden durch den Missingwert _missing ersetzt.

	    removeDuplicates(ARRAY);
	  
ARRAY-Inhalt vor  removeDuplicates(): { 3, 2, 3, 5, 5, 6, 1, 6, 6 }
ARRAY-Inhalt nach removeDuplicates(): { 3, 2,  , 5,  , 6, 1,  ,   }
          
rotateLeft

versetzt die Werte eines ARRAY um die angegebene Stellenanzahl nach links.

            rotateLeft(ARRAY, ANZ-STELLEN);
          
ARRAY-Inhalt vor rotateLeft(): {1 2 3 4 5}
ARRAY-Inhalt nach rotateLeft(ARR, 2) : {3 4 5 1 2}
          
round

rundet den Wert der angegebenen Variable VAR auf eine ganze Zahl.

            set(var = round(var));
          
script

bettet Javascript Syntax in Actionblöcke ein. Mit @insert() können Werte von Q. Variablen ins Javascript eingebunden werden. @save(VAR = VAL); definiert welche Werte am Ende der Ausführungslogik in welche Q. Variablen zurückgespielt werden. Dieser Befehl benötigt Java 8 und ist unter Android noch nicht verfügbar.

            script("JAVASCRIPT");
          
compute nhTestResult = 5;
compute x = 0;
compute y = 0;
compute z = 0;
Array nhTestArray[3];
OpenQ te;

vararray xyz = (x y z te nhTestArray);

// ...
Actionblock ab = {
  script("
    var x = 1;
    var y = 2;
    var z = @insert(nhTestResult);
    var arr = new Array();
    arr.push(x);
    arr.push(y);
    arr.push(z);
  
    @save(xyz[1]) = x;
    @save(xyz[2]) = y;
    @save(xyz[3]) = z;
    @save(nhTestResult) = x+y+z;
    @save(xyz[5]) = arr;
    @save(xyz[4]) = 'nachher';
  ");
};
          
set

weist Variable VAR den Wert VALUE zu. VALUE kann auch das Ergebnis einer Berechnung sein.

	    set(VAR = VALUE);
	  
set(var = 0);
set(var = a * b + 1);
          

Im Gebrauch mit Arrays kann set einen Array in einem Schritt belegen. Ab Arrayindex 1 werden dabei fortlaufend alle Stellen belegt, für die Werte angegeben wurden. Überschüssige Werte werden nicht berücksichtigt.

array arr[10];
set( arr = [1 3 5 -1 6:9 0 0] ); // {1, 3, 5, -1, 6, 7, 8, 9, 0, 0}	    
          

(Die Schreibweise 6:9 kürzt die Angabe 6 7 8 9 ab.)

setCurrentScreen

Setzt den aktuellen Screen neu und greift sofort bei nächster Teilnehmerinteraktion. Verwirft Eingaben in Screens, die ein Timeout überdauern oder wenn setCurrentScreen in einem InitActionBlock verwendet wird.

          setCurrentScreen(SCREEN);
          
 // Kann syntaktisch mit/ohne Anführungszeichen geschrieben werden.

 // Ohne Anführungszeichen wird die Screenexistenz geprüft 
TimeoutActionBlock = { setCurrentScreen(m4); };
 // Mit Anführungszeichen kann z.B. @insert() verwendet werden
 //     setCurrentScreen("@insert(te_m4)");
 //     setCurrentScreen("m4");
          
setDecimalSeparator

legt das Dezimaltrennzeichen fest (Voreinstellung Komma: ",")

	          setDecimalSeparator(".");
	        
setGroupingSeparator

legt das Tausendertrennzeichen fest (Voreinstellung Punkt: ".")

	   setGroupingSeparator(",");
	  
setNextScreen

Mit dem Befehl setNextScreen wird in der Umfrage ein einmalig definierter Sprung initiiert, ohne dass der vorher definierte Pfad dabei verändert wird. setNextScreen kann an beliebiger Stelle für einen beliebigen Screen verwendet werden. Bei der Benutzung ist zu beachten, dass der Fortschrittsbalken nicht kompatibel mit dem Überspringen der Fragen oder Screens ist und daher weder parallel zum Interviewverlauf fortschreitet, noch parallel abschließt.

	    setNextScreen(SOURCE,TARGETSCREEN);
	  
setNextScreen(a,c);
          
shuffle

ordnet die Inhalte von ARRAY zufällig neu an.

	    shuffle(ARRAY);
	  
ARRAY-Inhalt vor  shuffle(): { 1, 2, 3, 4, 5 }
ARRAY-Inhalt nach shuffle(): { 4, 3, 5, 1, 2 }
          
strCutLeft

schneidet die ersten ANZAHL Zeichen des Inhalts von VAR ab und speichert das Resultat in TEXTELEMENT.

	    strCutLeft(TEXTELEMENT, VAR, ANZAHL);
	  
TextElement cutlery;
OpenQ q1;
text="Bitte etwas eingeben";
continueActionBlock={ strCutLeft(cutlery, q1, 2); };
          
strCutRight

schneidet die letzten ANZAHL Zeichen des Inhalts von VAR ab und speichert das Resultat in TEXTELEMENT.

	    strCutRight(TEXTELEMENT, VAR, ANZAHL);
	  
syncsort

sortiert die Inhalte von ARR1 aufsteigend (ascending) oder absteigend (descending) an. Synchron zu dem Schema, wie die Elemente in ARR1 ihre Positionen verändern, wird auch ARR2 umsortiert.

	    syncsort(ARR1, ARR2, ascending|descending);
	  
syncsort(array1, array2, ascending);

// ARRAY-Inhalte vor  syncsort(): array1{ 3, 7, 1, 9, 4 } array2{ 1, 2, 3, 4, 5 }
// ARRAY-Inhalte nach syncsort(): array1{ 1, 3, 4, 7, 9 } array2{ 3, 1, 5, 2, 4 }
          
writeJsonToSurvey

schreibt einen Rohdatensatz FILE in den Studienordner der Studie SURVEYNAME, der die angegebenen Variablen VAR im JSON Format enthält.

	    writeJsonToSurvey(SURVEYNAME, FILE, (VAR, VAR, ...));
	  

Das Beispiel:

writeJsonToSurvey(beispielstudie, "case123.json", (sq1 sgq.1));
	  

ergibt

///////////////////////////////
// case123.json
///////////////////////////////
{
  "rvars": [
    {
      "name": "sq1",
      "value": "3.0"
    },
    {
      "name": "sgq.1",
      "value": "4.0 4.0 5.0 5.0"
    }
  ]
}
          

Kontrollstrukturen

Für die Ablaufsteuerung bietet Q. gängige Kontrollstrukturen an.

  • if-else: Wenn-Dann

  • for: Schleife (von-bis)

  • forEach: Schleife (für-jeden)

  • while: Schleife (solange)

if(<Bedingung>)
{
  ...
};
    

Das if kann auch mit einem else kombiniert werden:

if(<Bedingung>)
{
  ...
}
else
{
  ...
};
    

Es stehen zwei Typen von Schleifen zur Verfügung: Die for-Schleife und die while-Schleife. Bei der for-Schleife wird eine Zählvariable angelegt, die in Einer-Schritten vom Start- bis zum Zielwert bei jedem Schleifendurchlauf inkrementiert wird. Als Start- und Zielwert können sowohl Zahlenwerte als auch Variablen angegeben werden.

for(x=1 to 10)
{
  ...
};
    

Die Schleife wird 10 Mal ausgeführt, und x durchläuft dabei die Werte von 1 bis 10.

for(y=var1 to var2)
{
  ...
};
    

Hier sind als Start- und Zielwerte Variablen angegeben. Dabei ist natürlich zu beachten, dass man hiermit Endlosschleifen bauen kann, die nie abbrechen, und vor allem wenn die Start- und Zielvariablen innerhalb der Schleife manipuliert werden, sind sehr seltsame Verhaltensweisen möglich.

Außerdem ist zu beachten, dass die Zählvariable neu angelegt wird, und daher vorher keine Variable mit dem gleichen Namen vorhanden gewesen sein darf. Das führt auch dazu, dass die Zählvariablen verschiedener for-Schleifen auch verschiedene Namen haben müssen.

Eine andere Art der for-Schleife, ist die forEach. Mit ihr kann eine Serie bestimmter Werte abgearbeitet werden.

forEach(y=1 3 5 7 9 50:55)
{
  ... durchläuft y mit 1, 3, 5, 7, 9, 50, 51, 52, 53, 54, 55
};
    

Bei der while-Schleife gibt es keine Zählvariable, sondern die Schleife wird solange wiederholt, wie die angegebene Bedingung erfüllt ist:

while(<Bedingung>)
{
  ...
};