Commit 6d34ef25 authored by Maik Messerschmidt's avatar Maik Messerschmidt
Browse files

Added docs from the 0.7.0 branch of the Lou framework.

parent c4842788
# Lou Templates: Rationale
> Note: This document describes design ideas which are not fully implemented
yet, but are very likely to be implemented in the next iteration of the
Lou framework.
Within the Lou framework templates are used
in order to implement trials for online studies.
Each template is itself already a fully functional trial,
but can also be used to describe a whole class of trials.
(If you are familiar with some code reuse paradigms in programming
this approach roughly resembles a prototype-based inheritance instead of a
class-based inheritance.)
Since the visual structure of trials for online experiment must be
represented in HTML and CSS
(or some other languages which are then transformed into HTML and CSS)
and in my experience the changes to the look and feel of a trial are
much more frequent then changes to the actual trial logic it made sense to
represent a trial (or at least large parts of it) as HTML and CSS.
That's why a Lou template actual is nothing more than an HTML document to which
a description of the trial logic is added in order to achieve the intended
behavior.
This way it's possible to separate *what* is displayed (HTML) and *how* it
is displayed (CSS) from the behavior it shows (trial logic).
This makes sense in terms of the interactions between people working
on the project, which can be distinguished into at least three roles:
The scientist, the web-designer and the programmer
(which are not necessarily different people).
This is useful in order to give control over certain aspects of the experiment
to the role, which is responsible or most knowledgeable of the given aspect:
| Aspect | Technology | Role | Example |
|-----------------------------------|-------------------------|----------------------------|-----------------------------------------------------------|
| Content | HTML | Scientist | Display an image and a "Continue" button |
| Visual representation | CSS | Scientist / Web-designer | Center the image and make it use 50% of the screen width |
| Description of the trial logic | Actions and Components | Scientist / programmer | Stop the trial, if the "Continue" button is clicked |
| Implementation of the trial logic | Javascript / Typescript | Programmer | (The implementing Javascript code) |
But even if all these roles fall into one person it still makes sense to
implement different aspects with different languages:
HTML is designed to represent logical structured information
whereas CSS is designed to represent the visual representation of this structure.
But why separate the description of the trial logic from it's implementation?
There are multiple reasons for this:
1. Using a descriptive representation of the trial logic
allows us to talk about the trial logic "in code" - that is: we can use the
same level of abstraction in the code that we use when talking about the
trial: Example A is a much better representation of the trial than example B.
**Example A:**
```html
<img id="image" src="images/cat1.png" hidden>
<button data-lou-on-click="show('#image')">Show the image.</button>
```
**Example B:**
```html
<img id="image" src="images/cat1.png" hidden>
<button id="show-button">Show the image.</button>
<script>
const image = document.querySelector("#image")
const button = document.querySelector("#show-button")
button.addEventListener("click", () => image.hidden = false)
</script>
```
2. But there are more advantages: ``show('#image')`` can represent much more
then the actual instructions necessary in order to show the ``image``-Element.
There's at least one more information, which can be used in this term,
which is, that within the document, there's an element with the id ``image``.
By a suitable implementation of the ``show()``-Function this information
can be used by the Lou framework in order to check the internal consistency
of a Lou template - if we want to show the ``image``-Element there'd better
be an element with that id.
Because this connection can be checked before ever running the trial,
we can move errors related to this from a point in time in our experiment
when the trial is run to the point in time when we load the experiment.
(Maybe these checks can even be moved to command-line tools which are run
automatically before we even deploy the code to our (test) servers
or open the experiment in a web browser.)
From a programming point of view a trial is nothing more,
then a asynchronous function, that takes some input (the trial variables)
and after some interaction with the participant produces some output
(the response of the participant, measured response time, etc.).
# Events, signals and conditions
Let's say our first trial in an experiment is a consent form with
a checkbox "I do consent." which must be checked in order to be able to
click the "continue" button.
Okay. So we listen for a ``check`` event on that checkbox and enable the button,
if it is fired and listen for a ``uncheck`` event and disable the button,
if it is fired.
On problem though: Javascript doesn't define a ``check`` or ``uncheck`` event
for checkboxes (or any HTML element for that matter).
But it does define a ``change`` event, which is fired,
whenever the checked state changes.
That's where signals come in: They are an abstraction over Javascript events
and can be used to express more complex state changes then Javascript events
provide out of the box.
Don't get me wrong: Implementing such behavior with pure Javascript events
is not that hard, but the ``on-check`` directly describes our mental model
of a checkbox:
We check the checkbox and the signal is emitted.
That way, we can define a ``check`` and ``uncheck`` signal for checkboxes.
The ``check`` signal would be emitted, if a ``change`` event is fired
by the checkbox **and** the checkbox is now in a checked state:
```html
<!-- consent.html -->
<!doctype html>
<html lang="en">
<head>
<title>Consent form</title>
<meta charset="utf-8">
<script src="js/lou.min.js"></script>
</head>
<body class="lou-template">
<h1>Consent form</h1>
<p>
Some text here...
</p>
<p>
<input type="checkbox"
data-lou-on-check="enable('#continue')"
data-lou-on-uncheck="disable('#continue')"
>
I do consent.
</p>
<button id="continue" data-lou-on-click="stop" disabled>Continue</button>
</body>
</html>
```
But what, if we want to allow the trial to receive an input variable ``checked``
which defines, whether the checkbox should be initially checked or not?
Then we not only need to set the checkbox's ``checked`` attribute but
also the ``disabled`` attribute of the button.
While this is certainly possible, something else comes to mind:
The relation we want to express here is, that the button is enabled,
whenever the checkbox is checked and disabled, whenever the checkbox is
unchecked - independent of the question, whether we actually interacted
with the checkbox or not.
That means, we describe a condition rather than an event.
A ``checked`` condition, would test, whether the checkbox is checked at
the start of the trial and run the ``enable`` action accordingly.
This could look like this:
```html
<!-- consent.html -->
<!doctype html>
<html lang="en">
<head>
<title>Consent form</title>
<meta charset="utf-8">
<script src="js/lou.min.js"></script>
</head>
<body
class="lou-template"
data-lou-default-for-checked="false">
<h1>Consent form</h1>
<p>
Some text here...
</p>
<p>
<input type="checkbox" {{ #checked }}checked{{ /checked }}
data-lou-if-checked="enable('#continue')"
data-lou-if-not-checked="disable('#continue')"
>
I do consent.
</p>
<button id="continue" data-lou-on-click="stop">Continue</button>
</body>
</html>
```
We can go even further.
If there exists a duality for some conditions (e.g. checked, unchecked) and
actions (e.g. disable, enable), we could define an "inverse" for these actions
and then use them in terms of applying "if and only if" (iff), that is
we run the ``enable`` action, if the box is ``checked`` and run the
``disable`` action, if the box is ``unchecked``:
```html
<!-- consent.html -->
<!doctype html>
<html lang="en">
<head>
<title>Consent form</title>
<meta charset="utf-8">
<script src="js/lou.min.js"></script>
</head>
<body
class="lou-template"
data-lou-default-for-checked="false">
<h1>Consent form</h1>
<p>
Some text here...
</p>
<p>
<input type="checkbox" {{ #checked }}checked{{ /checked }}
data-lou-iff-checked="enable('#continue')"
>
I do consent.
</p>
<button id="continue" data-lou-on-click="stop">Continue</button>
</body>
</html>
```
These features could also be used completely outside of the experiment context
when we want to define some simple logic on HTML pages and don't want to
or can't use frameworks like ``vue``.
Implementation note:
Using events seems reasonable for the case described above,
but [MutationObservers](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver)
are really interesting as well.
# Lou Templates: Step by step
> Note: This document describes design ideas which are not fully implemented
yet, but are very likely to be implemented in the next iteration of the
Lou framework.
This step by step guide will take you through the process of creating
increasingly complex trials.
## 1. Welcome
Let's start with a Lou template
which shows a simple welcome page with a ``Continue`` button.
We have a headline, a paragraph and the button:
`templates/welcome.html`:
```xml
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Welcome</title>
</head>
<body>
<h1>Welcome to my experiment.</h1>
<p>Click the button below in order to start.</p>
<button>Continue</button>
</body>
</html>
```
![First iteration of the Welcome trial](images/welcome1.png)
Well, that's a good start, but it could be prettier, so let's add some
[CSS](https://www.w3schools.com/css/css_intro.asp) to improve the look of
things.
I'll use the [bootstrap](https://getbootstrap.com/) CSS library here, but
we can use whatever we want or even have a web-designer write our HTML and
CSS from scratch:
`templates/welcome.html`
```xml
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Welcome</title>
<!-- Bootstrap CSS library -->
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css"
integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l"
crossorigin="anonymous">
</head>
<body class="d-flex flex-column align-items-center justify-content-center min-vh-100">
<h1>Welcome to my experiment.</h1>
<p>Click the button below in order to start.</p>
<button type="button" class="btn btn-light">Continue</button>
</body>
</html>
```
![Second iteration of the Welcome trial](images/welcome2.png)
That looks better, even though Bootstrap clutters our HTML a bit.
At this point we're done with the web-design part of the trial and
want to add the trial logic.
This is where **Lou** comes in.
If we simply want to express, that we want to go to stop the trial, when
the participant clicks on something, we *describe* this behavior with an
"action":
`templates/welcome.html`
```xml
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Welcome</title>
<!-- Bootstrap CSS library -->
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css"
integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l"
crossorigin="anonymous">
<!-- The Lou framework code -->
<script src="js/lou.min.js"></script>
</head>
<body class="lou d-flex flex-column align-items-center justify-content-center min-vh-100">
<h1>Welcome to my experiment.</h1>
<p>Click the button below in order to start.</p>
<button type="button" class="btn btn-light" data-lou-on-click="stop">Continue</button>
</body>
</html>
```
You'll notice we had to add the **Lou** framework code in the HTML ``head`` and
the ``lou`` class to the document ``body``.
This tells **Lou**, that everything inside the document ``body`` describes a trial -
but that's it.
If we click on the button now, we'll get the output of the trial rendered to
the screen - it's empty:
```json
{}
```
We have now successfully created our first **Lou** trial. :-)
# 2. How old are you?
Our next trial will ask for the age of the participant.
We will allow ages from 0 to 130.
I'll skip right to the styled HTML:
```xml
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Demographics</title>
<!-- Bootstrap CSS library -->
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css"
integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l"
crossorigin="anonymous">
</head>
<body class="d-flex flex-column align-items-center justify-content-center min-vh-100">
<form>
<div class="form-group">
<label for="age">How old are you?</label>
<input type="number" min="0" max="130" class="form-control" value="" id="age" placeholder="Enter age" required>
</div>
<button type="button" class="btn btn-light">Continue</button>
</form>
</body>
</html>
```
![Demographics trial](images/demographics.png)
But as before the button does nothing right now. We'll have to:
1. Include the Lou framework code
2. Add the ``lou`` class to the document ``body``.
3. Add an action to the button, which describes that we want to validate the
``age``-field, output it's value on success and then stop the trial.
When we're done, the HTML looks like this:
```xml
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Demographics</title>
<!-- Bootstrap CSS library -->
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css"
integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l"
crossorigin="anonymous">
<!-- The Lou framework code -->
<script src="js/lou.min.js"></script>
</head>
<body class="d-flex flex-column align-items-center justify-content-center min-vh-100">
<form>
<div class="form-group">
<label for="age">How old are you?</label>
<input type="number" min="0" max="130" value="0" class="form-control" id="age" placeholder="Enter age" required>
</div>
<button
type="button"
class="btn btn-light"
data-lou-on-click="chain(validate('#age'), output('#age'), stop)">
Continue
</button>
</form>
</body>
</html>
```
When entering a value and pressing ``Continue`` we now get proper output.
By default the ``output``-action uses the ``id`` attribute of HTML elements
in order to choose the name of the output variable (which is ``age`` in this case)
- this is true for all HTML elements that save some value.
Even though all HTML forms provide their data as strings, Lou will
automatically convert the data for us (thus not showing ``"46"`` but ``46``),
because the input type is declared as "number":
```json
{
"age": 46
}
```
# 3. Is this an animal?
In our next trial, we'll ask the participant, whether a given image shows
an animal or not and measure the response time.
This is functionally equivalent to the
[image-button-response plugin](https://www.jspsych.org/plugins/jspsych-image-button-response/)
in [jsPsych](https://www.jspsych.org/), but instead of writing a plugin,
which tries to cover every possible use case, we only write a template which
covers our use case.
> It is a common pattern in Lou to take a working template prototype and make
it more general later on instead of the other way around.
Here's the HTML with Lou already included and working buttons:
```xml
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Is this an animal?</title>
<!-- Bootstrap CSS library -->
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css"
integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l"
crossorigin="anonymous">
<!-- The Lou framework code -->
<script src="js/lou.min.js"></script>
</head>
<body class="lou d-flex flex-column align-items-center justify-content-center min-vh-100">
<h1>Is this an animal?</h1>
<img alt="Image of something" src="https://picsum.photos/id/237/400">
<div class="row">
<button type="button" value="yes" class="btn btn-light">Yes</button>
<button type="button" value="no" class="btn btn-light">No</button>
</div>
</body>
</html>
```
![Animal trial](images/animal.png)
There are still a few problems with this trial:
1. The response time isn't measured.
2. We don't want to show the same image every time.
3. We don't know, which button was pressed.
For the first problem we add actions, which create start and stop a ``Timer``
component, when the trial starts / stops:
```xml
<div
data-lou-on-start="start(Timer('rt'))"
data-lou-on-stop="stop(Timer('rt'))"
></div>
```
> If a component's functionality is not specific to a certain HTML element
we can simply create an empty HTML element and add the Lou component
there.
In order to solve the second problem, we introduce a trial variable -
let's call it ``image`` - and replace the image url with the variable
surrounded by double curly braces:
```xml
<img alt="Image of something" src="{{ image }}">
```
> Lou uses the [mustache](https://mustache.github.io/) template library
in order to translate expressions like ``{{ image }}``, which means
you can use all functionality of mustache within a ``lou`` element.
Whenever a trial requires dynamic content, which only changes between
multiple runs of the trial, using trial variables and mustache is
the right approach.
We now have a general solution and an experiment, which instructs the trial
which image should be displayed can use this solution, but if we open the
trial itself it doesn't show anything, because we didn't provide a
default image url.
We need to tell Lou, what default value should be used for the ``image``
variable.
This is done by adding an HTML
[data attribute](https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes)
to the ``lou`` element:
```xml
<body class="lou ..." data-default-for-image="https://picsum.photos/id/237/400">...</div>
```
As for the last problem: We add an action to the buttons,
which output the value of the ``element`` the action is defined on and
then stops the trial:
```xml
<button type="button" value="yes" class="btn btn-light" data-lou-on-click="chain(output(element, 'response'), stop)">Yes</button>
...
```
The final HTML looks like this:
```xml
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Is this an animal?</title>
<!-- Bootstrap CSS library -->
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css"
integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l"
crossorigin="anonymous">
<!-- The Lou framework code -->
<script src="js/lou.min.js"></script>
</head>
<body
class="lou d-flex flex-column align-items-center justify-content-center min-vh-100">
data-default-for-image="https://picsum.photos/id/237/400"
data-lou-on-start="start(Timer('rt'))"
data-lou-on-stop="stop(Timer('rt'))"
>
<h1>Is this an animal?</h1>
<img src="{{ image }}">
<div class="row">
<button type="button" value="yes" class="btn btn-light" data-lou-on-click="chain(output(element, 'response'), stop)">Yes</button>
<button type="button" value="no" class="btn btn-light" data-lou-on-click="chain(output(element, 'response'), stop)">No</button>
</div>
</body>
</html>
```
It will produce output like this:
```json
{
"rt": 1338,
"response": "no"
}
```
When we open the trial in a browser it will show us the default image,
but we can test the trial for other values of the ``image`` variable
by changing the [query string](https://en.wikipedia.org/wiki/Query_string)
like this.
This way we can easily test our trial for different input:
```
animals.html?image=https://picsum.photos/id/1037/200
```
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment