Support Home Scripting

Scripting

  • Introduction
  • Functionality Overview
  • Scripting Tab
  • Creating a New Script
  • Hello World
  • Anatomy of a Script
  • Using Binding Within Scripts
  • Tips
  • Examples
  • Custom Spreadsheet Randomisation
  • Custom Scoring
  • Custom Data Saving
  • Custom Task Component - Click Handling
  • Reference

Introduction


What the new scripting tools are and who they are for

Scripting tools are available to users who wish to take Gorilla to the next level and further customise their tasks by creating and adding new components to the Builder UI. Gorilla scripting tools use the TypeScript programming language.

The tools are available to all users in the Scripting tab of Task Builder 2 and Game Builder. Previous exposure to writing code is recommended to navigate around the tools. However, if you have never scripted before, you can watch our Intro to Javascript video below as a first step:

You can also walk through scripting examples in Gorilla legacy tools.

Navigate the menu to the left to learn more about the tools!


Functionality Overview


Gorilla Tooling operates based on the Object-Component system. The basic idea is that each element on the screen is an object made up of several components, each of which do different things.

The new scripting tools allow users to create their own components which can provide new, unique functionalities in the tasks. The key point is that every new script created in the Script tab becomes a new component that will show up in the list of components and can be added to the task.

For example, if you create a Task Component called ‘MyNewComponent’ and then go to Displays tab > Object > Add component, you will find ‘MyNewComponent’ appearing on the list of components available to be added to an object. You can combine this component with other components, creating unique objects that meet your specific research needs.

Image showing Gorilla Task Builder 2 object/component menu with a new component called MyNewComponent in the menu available to be added to the object.

Depending on the functionality you're trying to implement, you will either create a component that is used on the Screen tab (to control things like scoring, saving data or branching), or a component that you add to the Objects tab on the screen itself (to control the appearance or behaviour of individual objects).

Warning

Have you scripted in Gorilla legacy tools before? Scripting in the legacy tools allowed to add functionalities to pre-existing zones, spreadsheet content etc. while in the new tools, users can create entire components with novel functionalities, making new scripting tools most powerful yet. Since the two generations of tooling differ significantly in their architecture, scripts from the legacy tools are not transferable into the new scripting tools. Familiarity with JavaScript programming from Task Builder 1 scripting, however, is helpful for coding in Task Builder 2.

Scripting Tab


Scripting tools are accessible from the Scripting tab, located in the Task Builder menu, on the left-hand side:

Image showing Scripting tab in Gorilla Task Builder 2 tool

The Scripting tab is divided into four main panels:

  1. The left-hand panel shows the list of your scripts that you can click on and access.
  2. The middle panel is the script editor where you write your script.
  3. The right-hand panel is a zoomed out view of your whole script so you can scroll through longer scripts easily.
  4. The bottom panel is the error console that provides helpful messages on any errors in your scripts.
Image showing Scripting tab in Gorilla Task Builder 2 with four sections numbered 1 to 4
Warning

Note: Error messages displaying in the error console will show errors from all the scripts in the task. It is important that all the errors are corrected before you try the functionality of your newly scripted components because errors in one script can impact the functionality of another script, preventing the components from working properly.

Creating a New Script


When you go to the Scripting tab for the first time, the script editor is blank. In order to start scripting, you must first enter the Edit mode by clicking the Edit button in the top right corner of the Task Builder.

When you are in the Edit mode, in the script tab, you will see a +New Script button in the top right corner. Clicking on the button brings up a menu of script templates for the most common use cases. For information on templates, head to the Examples section of this guide.

Gorilla Scripting tools allow you to control almost anything you want within your task, but there are some most commonly used cases for creating custom components in Gorilla. These include creating new screen components, setting up custom spreadsheet randomisation etc.

Templates for the most common scripting cases are available in the Template Menu when creating a new script - these will give you a pre-populated script that you can simply edit and fill in. We generally recommend that you use one of these as a starting point and adapt it to add the functionality you want. You can also choose a completely blank slate and then use one of the other templates from the guides below (or, for tech-savvy programmers, implement your own code).

Image showing a template menu that shows after pressing the New Script button in Gorilla Task Builder 2 Scripting tab. Template menu shows a list of five template options as follows 1 Task Component, 2 Task Drawable Component, 3 Screen component, 4 Spreadsheet Randomisation Component and 5 Blank Slate as well as big red create button at the bottom of the menu

Gorilla Hello World


To see the scripting tools in action, let's create a simple 'Hello World' component. It will allow you to add it to an object on your screen, and contain a single configurable field where you can enter some text. When you preview your task, it will display the text you entered in an alert box.


Firstly, make sure you are editing your task (by clicking the Edit button in the top right corner), open the Scripting tab, and click New Script. You should see the New Script menu:

Image showing a template menu that shows after pressing the New Script button in Gorilla Task Builder 2 Scripting tab, with the name of the script set to Hello World

Choose a Blank Slate for now (we'll talk about the templates later), and name your script HelloWorld.

Next, paste the following code into the editor:

//------------------------------------------------------------------------------ // HelloWorld // // [Add a short description describing what your component does] // // //------------------------------------------------------------------------------ import { TaskComponent, TaskComponentFactory, component, registerEditor, registerSimple } from "@gorilla/compiled/task-builder.js" //------------------------------------------------------------------------------ export interface HelloWorldFactory extends TaskComponentFactory { // // YOUR SETTINGS GO IN HERE // mySetting: string } //------------------------------------------------------------------------------ @component('task.component.HelloWorld') export class HelloWorld extends TaskComponent<HelloWorldFactory> { public apply(f: HelloWorldFactory) { super.apply(f); } public screenStart() { if(this.runtimeModeOrEditorPlaying && this.factory.mySetting?.length) { alert(this.factory.mySetting) } } } //------------------------------------------------------------------------------ registerEditor("HelloWorld", { label: "HelloWorld", icon: "far fa-circle", form: { elements: [ { class: "FormElementText", field: "mySetting", label: "My Setting", } ] } }) //------------------------------------------------------------------------------ registerSimple("component", "HelloWorld", { description: "My component does something awesome" }) //------------------------------------------------------------------------------

In your task, create a new display and screen (consider using the Instructions Screen template so you've got a working screen), and then click Add Object. Search for HelloWorld, and you should see your new component appear in the list:

Image showing the entity builder menu while searching for HelloWorld

Select the object, then click on it on the list of Objects. Add some text into the 'My Setting' box (e.g. put the text "Hello World!"):

Image showing the entity builder menu while searching for HelloWorld

Finally, let's preview the functionality of your new component. Click the Play button at the top of the screen, and you should see the text you entered appear in an alert box:

Image showing a browser alert box with the configured text

Congratulations, you have added a new scripting component to your task! Now, continue to the next section to learn more about the anatomy of a script.


Anatomy of a Script


In this section, we go through elements of a script step by step to help you understand how it's built and how to edit it.


All components in Gorilla follow the same basic structure - whatever kind of component you're making, it will always contain the following parts:

  • Introductory comment block
  • Imports
  • Factory
  • Component
  • Editor Registrator
  • Component Registrator

Read about each part below:


Introductory comment block

//------------------------------------------------------------------------------ // HelloWorld // // [Add a short description describing what your component does] // // //------------------------------------------------------------------------------

This section is optional, but we think it's good practice to have a standard comment block at the top of the script that details what the component does.


Imports

import { TaskComponent, TaskComponentFactory, component, registerEditor, registerSimple } from "@gorilla/compiled/task-builder.js"

You can import any of the Gorilla types from @gorilla/compiled/task-builder.js. This is the compiled bundle that exports all the types and classes that Gorilla uses. Note that the @gorilla part is a placeholder - this gets expanded to the correct path when your script is compiled.

If you have multiple scripts of your own, you can import between them using simple relative paths. For example, if you have two scripts called scriptA.js and scriptB.js, and you want to use a class or function from scriptA.js in scriptB.js, you can simply do:

// in scriptB.js import { myFunction } from "./scriptA.js"

Factory

//------------------------------------------------------------------------------ export interface HelloWorldFactory extends TaskComponentFactory { // // YOUR SETTINGS GO IN HERE // mySetting: string } //------------------------------------------------------------------------------

When you create a task, each screen is made up of objects, which in turn have one or more components (see our Object-Component System guide for reference). Each of those components has some settings that you can configure, and we call the structure that contains those settings the component's factory. A factory is simply where you define what the settings are that your component exposes, and in your script, you can access those settings via this.factory. This allows you to build your component's core functionality without worrying about the specific content that it's going to use - you can think in terms of 'some text' or 'an image', and allow the specific text or image to be specified later (or, indeed, to be different on different screens).


Component

//------------------------------------------------------------------------------ @component('task.component.HelloWorld') export class HelloWorld extends TaskComponent<HelloWorldFactory> { public apply(f: HelloWorldFactory) { super.apply(f); } public screenStart() { if(this.runtimeModeOrEditorPlaying && this.factory.mySetting?.length) { alert(this.factory.mySetting) } } } //------------------------------------------------------------------------------

This is the code for the actual component itself. There are a few key parts to this:

  • Note the @component() directive at the top - this is a special piece of code called a decorator that tells the Gorilla system that your component exists.
  • Our new class needs to extend an existing class - this will vary depending on the kind of component you're creating. For screen level components (e.g. a custom Scorer) you will need to derive from ScreenComponent rather than TaskComponent.
  • We also pass in the type of our factory (... extends TaskComponent<HelloWorldFactory>) to the baseclass - this makes sure that when you access your factory using this.factory you see all of your custom fields.

Note that, most of the time, this will all be done for you when you use a script template - you pick the baseclass you want to start from, and all of these settings will already be configured.

The actual body of the component is where you put your own custom code - you can implement the functionality that you need by implementing one or more core functions. In this HelloWorld example, you can see that we use the screenStart() function to run some logic when the screen starts. There are several other functions which you can override which get called at key points in the task logic. We call these lifecycle functions (as they are called at specific points in the component's lifecycle). You can see a full list of them here:

Component Lifecycle Functions


Editor Registration

//------------------------------------------------------------------------------ registerEditor("HelloWorld", { label: "HelloWorld", icon: "far fa-circle", form: { elements: [ { class: "FormElementText", field: "mySetting", label: "My Setting", } ] } }) //------------------------------------------------------------------------------

This section allows you to specify how your component appears in the Gorilla editor. You can configure a label and an icon, and most importantly, the form fields that should appear in the configuration panel. Crucially, these form elements should match the fields in your component's Factory - this is how you (and others) will configure those factory settings when using your component.

There are several different classes you can use for the editor fields (e.g. text, dropdown, toggle etc) - you can see the full listing of them here:

Form Field Reference Guide


Component Registration

//------------------------------------------------------------------------------ registerSimple("component", "HelloWorld", { description: "My component does something awesome" }) //------------------------------------------------------------------------------

This final section registers your component with the system, and allows you to add a description. This description will appear in the Add Object and Add Component menus, and should be a short, single sentence describing what your component does.


Using Binding Within Scripts


You can use the Binding system in your scripts to give you access to dynamic variables from the spreadsheet, store, manipulations and so on.


Making fields bindable

When configuring a field in your component's factory that you want to be bindable, the factory part doesn't change:

//------------------------------------------------------------------------------ export interface HelloWorldFactory extends TaskComponentFactory { // // YOUR SETTINGS GO IN HERE // myBindbableSetting: string } //------------------------------------------------------------------------------

You just need to use one of the bindable form types when registering your component:

//------------------------------------------------------------------------------ registerEditor("HelloWorld", { label: "HelloWorld", icon: "far fa-circle", form: { elements: [ { class: "FormElementBindableText", // <- CHANGE THIS field: "myBindableSetting", label: "My Bindable Setting", } ] } }) //------------------------------------------------------------------------------

This will tell the Gorilla editor that, as well as allowing the actual text, you're also happy to receive a binding string instead. This is a special string that contains information about where to look for a particular value, rather than containing the value itself.

Binding strings have the following format:

${sourceName:settingName}

So, for example, when bound to a spreadsheet column called instruction, it would look like this:

${spreadsheet:instruction}

If bound to a manipulation called 'prompt', it would look like this:

${manipulation:prompt}


Using bound fields

The simplest way to use a bound field is to simply call this.injectBindings(). This will invoke the binding system and return the final, resolved value. This is most useful when using a bound value once (e.g. when configuring some text at the start of the screen):

// assume there is some other internal function setText() which sets the current text public screenStart() { const text = this.injectBindings(this.factory.myBindableSetting); this.setText(text); }

Now, whatever myBindableSetting is bound to (or even if it's not bound at all), the call to injectBindings will resolve the binding chain and return a value that you can use.

This is fine for many use-cases, but has some drawbacks:

  1. If the value that myBindableField is bound to changes during the screen, our text will not update, because we only set it once at the start of the screen.
  2. If we're using this to populate some content (i.e. an image, video or audio), because we only set it at the start of the screen, it will have no time to load and introduce a loading delay before the screen starts.

To mitigate the two issues above, you can use a 'live' binding. This takes a little more setup:

Live bindings

To use a live binding, we first need to declare a member variable to hold our Binding object, and create it in our construct() function:

// assume there is some other internal function setText() which sets the current text public construct() { this.myBinding = this.createBinding((value) => { this.setText(value); }); } private myBinding: Binding;

This creates our binding object, and also allows us to pass in a callback to call whenever the value of the binding changes. In this callback, value will be the new value of the binding, so we can pass this directly to our setText() function without needing to call this.injectBindings() (as this step has already happened).

We also need to pass our factory setting through to our binding in the apply() function:

public apply(f: HelloWorldFactory) { super.apply(f); this.myBinding.parseIfExists(f.myBindableSetting); }

Note the call to super.apply(f) - we need to make sure we still call into our baseclass.

Now that we have an actual binding object, there are some extra things we can do:

Access the resolved value at any time

We can use this.myBinding.value to access a resolved value for our binding at any time. If there are other lifecycle functions where you want to access the bound value, you can always do so here.

React whenever the bound value changes

We can use the callback we specified in this.createBinding() to run some logic whenever the bound value changes.


Tips


If you are a pro programmer who feels comfortable in scripting environment, you can skip this part. But if you are fairly new to programming, find it challenging or just want to hear some tips that we find helpful, see our top tips below.

1. Know what you want to achieve

This one might seem obvious but do write down your end goal. Make sure it’s specific as this will make writing the script easier.

2. Break the idea into small steps

If you are using scripting tools you probably want to introduce something innovative into your Gorilla task and, like with every big idea, this might initially seem daunting. A helpful strategy is to think of what actions need to happen in your code to achieve your goal and then to break these actions into small steps. Write those steps down - this is a helpful practice to make sure you understand the code and can work through it step by step, in manageable chunks, rather than struggle with a big complicated picture.

3. Comment, comment, comment

Use comments within the script to document every step involved in your code, describe the variables you are using and the purpose and outcome of different functions in your code. This will really help you staying on top of the script, making sure you understand what each part is responsible for. If you have a lot of projects happening simultaneously, or go for a long holiday, these notes will help you stay on track with your work - you will thank yourself later! This practice is also excellent for collaborations and sharing your work with others in the spirit of Open Science 🙂

4. Fill in the gaps

Once you have your steps written down and therefore are clear on what you need to do in order to write your component, start filling in the steps with code. Gorilla tools uses TypeScript as a programming language.

5. Check the error console

Before previewing your newly scripted component, fix any errors that might be showing in the error console in your Scripting tab. The error messages displaying in this panel will show errors from all the scripts you have created for this task. It is important that all the errors are corrected because the errors in one script can impact the functionality of another script and therefore your components might not work as intended.

6. Collaborate and Share your work

At Gorilla, we are dedicated to providing powerful tools that allow researchers to collaborate and share their work within their team, as well as with broader science community. You can share your work with others on Gorilla Open Materials page. There, you can also see other’s work and use it for your studies - it’s a win-win!


Custom Spreadsheet Randomisation


In Gorilla, the order in which displays in the tasks and scenes in the games are shown is specified in the spreadsheet, where each row represents a time step in the task/game.

Spreadsheet Randomisation is used when you want to randomise the order in which participants see the parts of your tasks/games.

There are many built-in randomisation options which allow you to dynamically change the order of the trials - you can review them in our Spreadsheet Randomisation Guide. However, you might wish to create a new randomisation method for your tasks/games and you can do so by creating your own spreadsheet randomisation component.


Start by creating a new component with the Spreadsheet Randomisation Component template:

Image showing a new script panel with the Spreadsheet Randomisation Component template selected and the name MySpreadsheetRandomisation in the name box

You can now implement your logic in the randomiseSpreadsheet() lifecycle method:

// in your component body public randomiseSpreadsheet(name: string, columns: string[], rows: any[]) { // add your randomisation logic here return rows; }

Scripting Sample

For a worked example of a new spreadsheet randomisation component see our Custom Spreadsheet Randomisation Example.



Custom Scoring


Scoring in Task Builder 2 and Game Builder allows you to control whether a response should be considered correct or incorrect. The built-in scoring components Scorer and Scorer Multi work on the basis of simply matching the given response with a particular string (or one of a list of strings).

But what if you wanted to write your own scoring logic? Perhaps you're collecting numeric responses, and want to mark something as correct if it's above or below a certain value, or within a certain range. Or maybe you want to tie the correct answer to the time of day - some responses are correct in the morning but incorrect in the afternoon. For any of these types of scenarios, you can create your own scorer.


Start by creating a new component with the Screen Component template:

New script panel showing the Screen Component template selected and the name MyScorer in the name box

You can now implement your logic in the scoreResponse() lifecycle method:

// in your component body public scoreResponse(response: ProcessedResponse) { let isCorrect = false; // add your logic here to decide if the response should be correct // // access the response itself: // const response = response.response; // // access the reactio time: // const rt = response.reactionTime; response.correct = isCorrect; }

The response object that is passed in contains lots of information about the response that is being processed. You can find a full reference of all the properties here:

Scripting Types Reference


Scripting Sample

For a worked example of a new scoring component see our Custom Scoring Example.


Custom Data Saving


In Gorilla, you can save participants' data, such as their responses or accuracy scores, in the Store. Each piece of data that you save in the Store will be saved in a named field, so that you can retrieve it and use it later in the task, game, or experiment.

There are several readily available save-to-store components in the Screen Components menu, which simply store specified data to a designated field, e.g. a Save Accuracy component saves response accuracy to a chosen field. However, you might wish to customise the data saving to e.g. manipulate stored data by adding some values to it. You can achieve this by creating your own data saver.


Start by creating a new component with the Screen Component template:

New script panel showing the Screen Component template selected and the name MyScorer in the name box

You can now implement your logic in the construct() and screenFinish() lifecycle methods:

// in your component body public construct() { this.sourceBinding = this.createBinding(); this.destBinding = this.createBinding(); super.construct(); } public apply(f: CustomDataSaverFactory) { this.sourceBinding.parseIfExists(f.source); this.destBinding.parseIfExists(f.destination); } public screenFinish() { super.screenFinish(); // at the end of the screen, we read in the source, do something with it // and then write it to the destination let val = parseInt(this.sourceBinding.value); if(!isNaN(val)) { // write your custom data saving logic here this.destBinding.write(val); } } private sourceBinding: Binding; private destBinding: Binding;

Note that, for the destination binding, you'll want to use a specific form element to tell Gorilla that you want to bind to a destination, not a source:

// when registering your component //------------------------------------------------------------------------------ registerEditor('MyDataSaver', { label: 'My Data Saver', icon: 'fas fa-backpack', form: { elements: [ { class: "FormElementBindableText", label: "Source", field: "source" }, { class: "FormElementBindableField", label: "Destination", field: "destination" } ] } }) //------------------------------------------------------------------------------

Scripting Sample

For a worked example of a new data saving component see our Custom Data Saving Example.


Custom Task Component - Click Handling


Gorilla Tools' UI provides numerous components to select from when building your tasks or games. Components can be combined to build unique objects performing various functions - visit our Task Builder 2 Components Guide and Game Builder Components Guide to see what's currently available.

However, you might have specific research needs that require unique components. In this case, you can create your own task components and introduce new functionalities for your study. An example of a custom Task Component is a Click Handling component such as a hold/release button. See how to build one below:


Start by creating a new component with the Task Component template:

New script panel showing the Task Component template selected and the name MyNewTaskComponent in the name box

You can now implement your logic in the construct(), screenStart() and screenResponsesComplete lifecycle methods:

public construct() { super.construct(); this.holdTimeBinding = this.createBinding((value) => {}); this.responseBinding = this.createBinding((value) => {}); } public apply(f: HoldReleaseFactory) { super.apply(f); this.holdTimeBinding.parseIfExists(f.holdTime); this.responseBinding.parseIfExists(f.response); } public screenStart() { super.screenStart(); this.isComplete = false; this.onsetTime = 0; } public onTouch() { if(this.enabled && !this.isComplete) { this.onsetTime = this.screenManager.elapsedTime; } } public onTouchRelease() { if(this.enabled && !this.isComplete) { let responseTime = this.screenManager.elapsedTime - this.onsetTime; let holdTime = parseInt(this.holdTimeBinding.value); if(!isNaN(holdTime) && responseTime >= holdTime) { this.triggerResponse({ responseType: ResponseType.Response, onsetTime: this.onsetTime, response: this.responseBinding.value, tag: this.factory.tag }) } } } public screenResponsesComplete(promises: Promise[]) { this.isComplete = true; }

Scripting Sample

For a worked example of a new task component see our Custom Task Component Example.


Reference


Gorilla Scripting Examples
Library of tasks with example scripting functionality

Form Fields Reference Guide
List of form fields you can use to connect your components to the Gorilla editor UI

Component Lifecycle Methods
Listing of all lifecycle methods you can use in your components

Types Reference
Listing of all types and interfaces used in the Gorilla system