Toloka

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 suite. 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 user's GPS coordinates).

Lifecycle of a task

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

To render the task suite (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 suite is destroyed. The #destroy() method is called for the task suite, 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 construtor (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 in a set (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 a task suite.
     * 
     * @param {Task[]} options.tasks Array of task models.
     * @param {Specs} options.specs Specifications (input and output data, template).
     * @param {String} options.assignmentId IDs of tasks assigned to the user.
     * @param {{ isReadOnly: boolean }} [options.workspaceOptions] Sandbox initialization parameters.
     * @param {Function} [options.TaskClass=BaseTask] Class for created tasks.
     * @param {Solution[]} [options.solutions] Array of responses (if there are any).
     */
    constructor(options) {}
    /**
     * @return {Object} Parameters passed to the constructor (see the constructor's "options" format)
     */
    getOptions() {}
    /**
     * @return {Object} Parameters of sandbox initialization (see the constructor's "options.workspaceOptions" format).
     */
    getWorkspaceOptions() {}
    /**
     * @return {Task[]} Array of initialized tasks (according to the models passed).
     */
    getTasks() {}
    /**
     * Method for more convenient access to a task using its ID.
     * 
     * @return  {{"<taskId>": Task, ... }} Indexing by task IDs.
     */
    getTasksIndexed() {}
    /**
     * Full URL for accessing data on the proxy server.
     * @param {{<relative file URL>}}
     * @return  {{<full URL>}} 
     */
    getProxyUrl() {}
    /**
     * Collects responses from tasks.
     *
     * @return {Solution[]} Array of responses.
     */
    getSolutions() {}
    /**
     * @return {HTMLElement} DOM element for a task suite (empty before rendering; after rendering, it is initialized and contains the interface).
     */
    getDOMElement() {}
    /**
     * @return {HTMLElement} DOM element of styles for the task suite.
     */
    getStyleDOMElement() {}
    /**
     * Validates responses using output data parameters.
     * 
     * @param {Solution[]} [solutions] Array of responses. If it isn't passed, the current responses are taken from #geSolutions().
     * @return {SolutionValidationError[] | null} Array of errors. If all responses are valid — null.
     */
    validate(solutions) {}
    /**
     * Initiates hotkey handlers:
     *   - Sets the focus on the previous task for the up or left arrow.
     *   - Sets the focus on the next task for the down or right arrow.
     *   - Passes pressed keys to the active task.
     *   - Sets the focus on the first task.
     */
    initHotkeys() {}
    /**
     * Sets the focus on a task by the index.
     * 
     * @param {string} index Task index in the array.
     */
    focusTask(index) {}
    /**
     * Sets the focus on the next task.
     */
    focusNextTask() {}
    /**
     * Sets the focus on the previous task.
     */
    focusPreviousTask() {}
    /**
     * Passes the pressed key to the active task.
     */
    onKey(key) {}
    /**
     * Forms the DOM representation of a task suite: renders all the tasks in the suite (calls #render()). Calls #onRender().
     *
     * @return this
     */
    render() {}
    /**
     * Destroys all tasks in the suite. Releases resources reserved in the global namespace, services, and event handlers. Calls #onDestroy().
     */
    destroy() {}
    /**
     * Called after rendering the suite (#render()). All manipulations of the task's DOM element should be performed here.
     */
    onRender() {}
    /**
     * Called after destroying the suite (#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
[no-highlight[

Value

Task ID.

]no-highlight]
": <string>, "input_values
[no-highlight[

Value

Input data for the task in the format “<field id>“:“<field value>“. Example:

“input_values“: {
            “image“: “http://images.com/1.png“
          }

]no-highlight]
": { "<id of the field with input data>": <value>, … } }

Solution object

{
    "task_id
[no-highlight[

Value

Task ID.

]no-highlight]
": <string>, "output_values
[no-highlight[

Value

Responses in the format “<input field id>“:“<value>“. Example:

“outputValues“: {
            “colour“: “white“,
            “comment“: “So white“
          }

]no-highlight]
": { "<input field id>": <value>, … } }

SolutionValidationError object

{
    "task_id
[no-highlight[

Value

Task ID.

]no-highlight]
": string, "errors
[no-highlight[

Value

Errors in the format: “<input field id>“: {code: “<error code>“, [message: “<error message>“]}. Example:

“errors“: { 
        “colour“: {
            “code“: “REQUIRED“,
            [“message“: “Required field“]
        },

]no-highlight]
": { "<input field id>": { "code": "<error code>", ["message": <string>] }, … } }

Services

For solving specific problems in the base classes, the following services are available:

  • Geolocation.

    Allows you to get the user's GPS coordinates, if 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) {}
    }
  • 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) {}
    }
  • 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) {}
    }