Extending a JavaScript template

A task consists of a set of classes (JavaScript functions) that implement all aspects of its lifecycle. You can use the task classes for creating custom templates.

List of classes:

  • Task.

    Responsible for the task, its rendering, validation, and interface. If the task needs to have non-standard behavior, this usually requires extending and redefining the Task methods.

  • TaskSuite.

    A “wrapper” class for a task page. It creates instances of task classes. You can redefine this class if, for example, you need to display a shared element on the page with tasks or get more control over tasks (custom keyboard shortcuts and so on).

You can use services for specific needs (such as subscribing to keyboard presses, or getting the performer's GPS coordinates).

Lifecycle of a task

When a performer begins a task, the workspace is initialized in the iframe. A request is made to get the task page. To display the task page, an instance of the TaskSuite class is created. This class creates an instance of the Task class for each task.

To render the task page (TaskSuite), the #render() method is called. It calls the method for each task object and adds their elements to its own DOM element.

After the tasks are completed or skipped, the task page is destroyed. The #destroy() method is called for the task page, and then for each task. After this, a new task is requested and the process is repeated.

For convenience using base classes, TaskSuite and Task have the #onRender() and #onDestroy() methods defined. The #onRender() method can be redefined in order to change the DOM element created by the #render() method. The #onDestroy() method is useful for releasing resources after the task is completed (handlers, caches, and other resources reserved in the global namespace). This cleanup may be necessary in order to not clog memory and avoid data leaks.

Class inheritance

To keep the code from looking heavy, you can use the following function for class inheritance and redefinition:

function extend(ParentClass, constructorFunction, prototypeHash) {    
    constructorFunction = constructorFunction || function() {};    
    prototypeHash = prototypeHash || {};        
    if (ParentClass) {        
        constructorFunction.prototype = Object.create(ParentClass.prototype);    
    }    
    for (var i in prototypeHash) {        
        constructorFunction.prototype[i] = prototypeHash[i];    
    }    
    return constructorFunction;
}

Function call:

var ChildClass = extend(ParentClass, function() {    
    ParentClass.call(this);
}, {    
    method: function() {}
})

Task class

The Task base class is responsible for the task interface. This class is available in the global variable window.TolokaTask.

Task methods
class TolokaTask {

    /**
     * Task interface constructor.
     * @param {Task} options.task Task model.
     * @param {Specs} options.specs Specifications (Input and output data, template).
     * @param {{isReadOnly: boolean}} [options.workspaceOptions] Sandbox initialization parameters.
     */
    constructor(options) {...}

    /**
     * @return {Object} Parameters passed to the constructor (see the "options" format for the constructor).
     */
    getOptions() {}

    /**
     * @return {Object} Sandbox initialization parameters (see the "options.workspaceOptions" format for the constructor).
     */
    getWorkspaceOptions() {}

    /**
     * @return {Task} Task model.
     */
    getTask() {}

    /**
     * @return {Solution} Task responses.
     */
    getSolution() {}

    /**
     * The full URL for accessing data on the proxy server.
     * @param {string} path relative file URL>
     * @return {string} full URL 
     */
    getProxyUrl() {}
    
    /**
     * @param {Solution} solution Responses to set.
     */
    setSolution(solution) {}

    /**
     * @return {HTMLElement} DOM element of the task.
     */
    getDOMElement() {}

    /**
     * @return {HTMLElement} DOM element for task styles that is added to "document.head" when rendering.
     */
    getStyleDOMElement() {}

    /**
     * Validates responses according to input data parameters.
     * @param {Solution} [solution] Task solution. If it isn't passed, the current one is used (#getSolution()).
     * @return {SolutionValidationError|null} If responses are valid, returns null.
     */
    validate(solution) {}

    /**
     * Implements the logic for setting the focus on the task; calls the #onFocus() method.
     */
    focus() {}

    /**
     * Implements the logic for taking the focus off of the task; calls the #onBlur() method.
     */
    blur() {}

    /**
     * Task template engine. Takes the "markup" field from the template and replaces occurrences of ${fieldX} with the appropriate value with the "fieldX" key from the "data" parameter.
    * @param {Object} data Object with data to substitute in the template.
    * @return {string} HTML string.
    */
    template(data) {}

    /**
    * Forms the DOM representation of the task interface. Calls #onRender().
     * @return this
     */
    render() {}

    /**
     * Releases services, event handlers, and resources reserved in the global namespace. Calls #onDestroy().
     */
    destroy() {}

    /**
     * Called after rendering the task (#render()). All manipulations of the task's DOM element should be performed here.
     */
    onRender() {}
    /**
     * Called after destroying the task (#destroy()). The most suitable method for clearing memory is deleting global event handlers, DOM elements, and so on.
     */
    onDestroy() {}

    /**
     * Called after setting the focus.
     */
    onFocus() {}

    /**
     * Called after removing the focus.
     */
    onBlur() {}

    /**
     * @param {string} key An alpha-numeric character pressed on the keyboard. Can be used as a hotkey.
     */
    onKey(key) {}

    /**
     * Called after failed validation, with the error description in the parameter. 
     * @param {SolutionValidationError} errors
     */
    onValidationFail(errors) {}
}

TaskSuite class

The main purpose of the TaskSuite class is to render tasks on the page (the #render() method). It is also used for collecting responses (#getSolutions()), validating them (#validate(solutions)), and managing keyboard shortcuts (#focusNextTask(), #onKey(key) and so on).

The base class for TaskSuite is available in the window.TolokaTaskSuite global variable.

TaskSuite methods
class TolokaTaskSuite {

    /**
     * Constructor for the base class of the task page.
     * 
     * @param {Task[]} options.tasks Array of task models.
     * @param {Specs} options.specs of the Specification (input and output data, template).
     * @param {String} options.assignmentId Identifiers issued to the employee jobs.
     * @param {{ isReadOnly: boolean }} [options.workspaceOptions] the initialization Parameters of the sandbox.
     * @param {Function} [options.TaskClass=BaseTask] Class of created tasks.
     * @param {Solution[]} [options.solutions] Array of responses (if any).
     */
    constructor(options) {}

    /**
     * @return {Object} Parameters passed to the constructor (see the constructor options format)
     */
    getOptions() {}

    /**
     * @return {Object} sandbox initialization Parameters (see the options format.constructor workspaceOptions).
     */
    getWorkspaceOptions() {}

    /**
     * @return {Task[]} The array is initialized (according to models).
     */
    getTasks() {}

    /**
     * Method for more convenient access to the task by its ID.
     * 
     * @return {{"<taskId>": Task, ... }} Indexed by task IDs.
     */
    getTasksIndexed() {}

    /**
     * The full URL to access the data on the proxy server.
     * @param {{<relative file URL>}}
     * @return {{<full URL>}} 
     */
    getProxyUrl() {}

    /**
     * Collects the responses from the jobs.
     *
     * @return {Solution[]} Array of responses.
     */
    getSolutions() {}

    /**
     * @return {HTMLElement} the DOM element of the page (before it is rendered the element is empty, and after redering it is initialized and contains the interface).
     */
    getDOMElement() {}

    /**
     * @return {HTMLElement} DOM element of page styles.
     */
    getStyleDOMElement() {}

    /**
     * Validates responses for output parameters.
     * 
     * @param {Solution[]} [solutions] Array of solutions. If omitted, the current ones are obtained via #geSolutions().
     * @return {Promise | SolutionValidationError[] / null} either a promise (with an error array or null), or an error array, or null if all the responses are valid. 
     */
    validate (solutions) {}

    /**
     * Hotkey handler initializer:
     * - Sets the focus on the previous task on the left/up arrow.
     * - Sets the focus to the next task with the right/down arrow.
     * - Passes keys to the active task.
     * - Sets the focus on the first task.
     */
    initHotkeys() {}

    /**
     * Sets the focus on the task by index.
     * 
     * @param {string} index The task index in the array.
     */
    focusTask (index) {}

    /**
     * Sets the focus to the next task.
     */
    focusNextTask() {}

    /**
     * Sets the focus on the previous task.
     */
    focusPreviousTask() {}

    /**
     * Passes the pressed key to the active task.
     */
    onKey(key) {}

    /**
     * Generates a DOM representation of the task page: renders all tasks on the page (calls #render()). Calls #onRender ().
    *
    * @return this
    */
    render() {}

    /**
    * Destroys all tasks on the page. Releases resources, services, and event handlers used in the global space. Calls #onDestroy().
     */
    destroy() {}

    /**
     * Called after rendering the page (#render()). All manipulations of the task's DOM element should be performed here.
     */
    onRender() {}

    /**
     * Called after destroying the page (#destroy()). The most suitable place for clearing memory and deleting global event handlers, DOM elements, and so on.
     */
    onDestroy() {}

    /**
     * Called after failed validation, with an error description in the parameter. 
     *
     * @param {SolutionValidationError[]} errors Array of errors.
     */
    onValidationFail(errors) {}
}

Data types

Task object

{
    "id": <string>,
    "input_values": {
        "<field ID with the input data>": <value>,
        …
     }}

Key

Value

id

Task ID.

input_values

Task input data in the format "<field ID=>":"<field value>". Example:
"input_values": {
            "image": "http://images.com/1.png"
          }

Solution object

{
    "task_ID": <string>,
    "output_values": { 
        "<field ID>: <field value>,
        …
    }}

Key

Value

task_id

Task ID.

output_values

Responses in the format "<input field ID>":"<value>". Example:
"outputValues": {
            "colour": "white",
            "comment": "So white"
          }

SolutionValidationError object

{
    "task_ID": string,
    "errors": { 
        "<field ID>": {
            "code": "<error code>",
            ["message": <string>]
        },
        …
    }}

Key

Value

task_id

Task ID.

errors

Errors in the format: "<field ID>": {code: "<error code>", [message: "<error message>"]}. Example:
"errors": { 
        "colour": {
            "code": "REQUIRED",
            ["message": "Required field"]
        },

Services

The following services are available for specific purposes in the base classes:

  • Geolocation.

    Gets the GPS coordinates of the performer, if they are available. In TaskSuite and Task, it is available via this.geolocation.

    class Geolocation {
    
       /**
        * Duplicates the functionality of navigator.geolocation.getCurrentPosition() - https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/getCurrentPosition
        */
       getCurrentPosition(success, error, options) {}
    
       /**
        * Duplicates the functionality of navigator.geolocation.watchPosition() - https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/watchPosition
        */
       watchPosition(success, error, options) {}
    }
    
  • Storage

    Storing data on the client. In TaskSuite and Task, it is available via this.storage.

    class Storage {
    
       /**
        * Store the value by a specific key.
        *
        * @param {string} key — key.
        * @param {*} value — value. Can be any type. Serialized by casting to a string.    
    * @param {Date|number} [expiration] — Date when storage ends. By default, 24 hours.
        */
       setItem(key, value, expiration) {}
    
       /**
        * Get the stored value by its key.
        *
        * @param key — Key.
        * @returns {*}
        */
       getItem(key) {}
    
       /**
        * Delete a value by its key.
        *
        * @param {string} key
        */
       removeItem(key) {}
    }
    
  • TaskInterface

    Task interface. In TaskSuite and Task, it is available via this.taskInterface.

    class TaskInterface {
    
        /*
         * Opens the instructions.
         */
         showInstructionPopup() {}
    
       /**
         * Expands the task to full screen if it is minimized. Otherwise minimizes the task.
        */
        toggleFullscreen() {}
    }
  • Hotkey

    Lets you subscribe to pressed keys. In TaskSuite and Task, it is available via this.hotkey.

    class Hotkey {
       /**
        * Subscribes the passed handler to a specific event. The following events are tracked (the event parameter):
        *  "enter" — The “Enter” key.
        *  "esc" — The “Escape” key.
        *  "arrow-left", "arrow-right", "arrow-up", "arrow-down" — Arrow keys.
        *  "key" — Alpha-numeric keys. The handler ("handler" parameter) gets the pressed key as the first argument.
        * 
        * @param {string} event — event name
        * @param {Function} handler — event handler
        * @param {Object} context — "this" for the handler
        */
       on(event, handler, context) {}
    }