Toloka

Расширение шаблона на Javascript

Задание представляет собой набор классов (Javascript-функций), которые реализуют все аспекты его жизненного цикла. Вы можете использовать классы задания для создания нестандартных шаблонов.

Список классов:

  • Task.

    Отвечает за задание, его отрисовку, валидацию и интерфейс взаимодействия. Если от задания требуется нестандартное поведение, как правило нужно расширить и переопределить методы Task.

  • TaskSuite.

    Класс-«обертка» для набора заданий, создает экземпляры классов заданий. Вы можете переопределить этот класс, например, чтобы отобразить общий элемент на странице с заданиями или получить больший контроль над заданиями (нестандартные горячие клавиши или т.п.).

Для решения специфических задач (например, подписка на нажатие клавиш, получение GPS-координат пользователя) можно использовать сервисы.

Жизненный цикл задания

Когда пользователь начинает выполнять задания, во встроенном фрейме инициализируется его рабочее место. Выполняется запрос на получение набора заданий. Для отображения набора создается экземпляр класса TaskSuite, который создает экземпляр класса Task для каждого задания.

Для отрисовки набора заданий (TaskSuite) вызывается метод #render(), который вызывает метод для каждого объекта задания, добавляет их элементы в свой элемент DOM.

После выполнения или пропуска заданий набор уничтожается: вызывается метод #destroy() для набора заданий, а затем каждого задания. После этого запрашивается новое задание и процесс повторяется.

Для удобства использования базовых классов в TaskSuite и Task определены методы #onRender() и #onDestroy(). Метод #onRender() можно переопределить, чтобы изменить DOM-элемент, созданный методом #render(). Метод #onDestroy() пригодится, чтобы очистить ресурсы (зарезервированные в глобальном пространстве обработчики, кэши и т. п.) после выполнения задания. Такая очистка может понадобиться, чтобы не засорять память и не провоцировать утечки данных.

Наследование классов

Чтобы код не выглядел громоздким, для наследования и переопределения классов можно использовать функцию:

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;
}

Вызов функции:

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

Класс Task

Базовый класс Task отвечает за интерфейс задания. Класс доступен в глобальной переменной window.TolokaTask.

Методы Task
class TolokaTask {
    
    /**
     * Конструктор интерфейса задания.
     * @param {Task} options.task Модель задания.
     * @param {Specs} options.specs Спецификации (входные и выходные данные, шаблон).
     * @param {{isReadOnly: boolean}} [options.workspaceOptions] Параметры инициализации песочницы.
     */
    constructor(options) {...}
    
    /**
     * @return {Object} Параметры, переданные конструктору (см. формат options конструктора).
     */
    getOptions() {}
    
    /**
     * @return {Object} Параметры инициализации песочницы (см. формат options.workspaceOptions конструктора).
     */
    getWorkspaceOptions() {}
    
    /**
     * @return {Task} Модель задания.
     */
    getTask() {}
    
    /**
     * @return {Solution} Ответы задания.
     */
    getSolution() {}
    
    /**
     * Полный URL для доступа к данным на прокси-сервере.
     * @param {string} path относительный URL файла>
     * @return {string} полный URL 
     */
    getProxyUrl() {}
    
    /**
     * @param {Solution} solution Устанавливаемые ответы.
     */
    setSolution(solution) {}

    /**
     * @return {HTMLElement} DOM-элемент задания.
     */
    getDOMElement() {}

    /**
     * @return {HTMLElement} DOM-элемент для стилей задания, который добавляется в document.head при отрисовке.
     */
    getStyleDOMElement() {}

    /**
     * Валидирует ответы согласно параметрам входных данных.
     * @param {Solution} [solution] Решение задания. Если не передается, берется текущее (#getSolution()).
     * @return {SolutionValidationError|null} Если ответы корректные, возвращает null.
     */
    validate(solution) {}

    /**
     * Реализует логику установки фокуса на задание, вызывает метод #onFocus().
     */
    focus() {}

    /**
     * Реализует логику снятия фокуса с задания, вызывает метод #onBlur().
     */
    blur() {}

    /**
     * Шаблонизатор задания. Берет поле markup из шаблона и заменяет вхождения типа ${fieldX} на соответсвующее значение с ключем fieldX из параметра data.
     * @param {Object} data Объект с данными для подстановки в шаблон.
     * @return {string} HTML строка.
     */
    template(data) {}

    /**
     * Формирует DOM-представление интерфейса задания. Вызывает #onRender().
     * @return this
     */
    render() {}

    /**
     * Освобождает занятые в глобальном пространстве ресурсы, сервисы, обработчики событий. Вызывает #onDestroy().
     */
    destroy() {}

    /**
     * Вызывается после отрисовки задания (#render()). Все манипуляции с DOM-элементом задания следует производить здесь.
     */
    onRender() {}
    /**
     * Вызывается после уничтожения задания (#destroy()). Наиболее подходящий метод для очистки занятой памяти, удаления глобальных обработчиков событий, DOM-элементов и т. п.
     */
    onDestroy() {}

    /**
     * Вызывается после установки фокуса.
     */
    onFocus() {}

    /**
     * Вызывается после удаления фокуса.
     */
    onBlur() {}

    /**
     * @param {string} key Буквенно-числовой символ, нажатый на клавиатуре. Может быть использован как горячая клавиша.
     */
    onKey(key) {}

    /**
     * Вызывается после неудачной валидации с описанием ошибки в параметре. 
     * @param {SolutionValidationError} errors
     */
    onValidationFail(errors) {}
}

Класс TaskSuite

Основная задача класса TaskSuite — отрисовать задания в наборе (метод #render()). Через него также происходит сбор ответов (#getSolutions()), валидация (#validate(solutions)) и управление горячими клавишами (#focusNextTask(), #onKey(key) и т.д.).

Базовый класс для TaskSuite доступен в глобальной переменной window.TolokaTaskSuite.

Методы TaskSuite
class TolokaTaskSuite {

    /**
     * Констуктор базового класса набора заданий.
     * 
     * @param {Task[]} options.tasks Массив моделей заданий.
     * @param {Specs} options.specs Спецификации (входные и выходные данные, шаблон).
     * @param {String} options.assignmentId Идентификаторы выданных работнику заданий.
     * @param {{ isReadOnly: boolean }} [options.workspaceOptions] Параметры инициализации песочницы.
     * @param {Function} [options.TaskClass=BaseTask] Класс создаваемых заданий.
     * @param {Solution[]} [options.solutions] Массив ответов (если есть).
     */
    constructor(options) {}

    /**
     * @return {Object} Параметры, переданные конструктору (см. формат options конструктора)
     */
    getOptions() {}

    /**
     * @return {Object} Параметры инициализации песочницы (см формат options.workspaceOptions конструктора).
     */
    getWorkspaceOptions() {}

    /**
     * @return {Task[]} Массив иницализированных (согласно переданным моделям) заданий.
     */
    getTasks() {}

    /**
     * Метод для более удобного обращения к заданию по его идентификатору.
     * 
     * @return  {{"<taskId>": Task, ... }} Индексированные по идентификаторам задания.
     */
    getTasksIndexed() {}

    /**
     * Полный URL для доступа к данным на прокси-сервере.
     * @param {{<относительный URL файла>}}
     * @return  {{<полный URL>}} 
     */
    getProxyUrl() {}

    /**
     * Собирает ответы с заданий.
     *
     * @return {Solution[]} Массив ответов.
     */
    getSolutions() {}

    /**
     * @return {HTMLElement} DOM-элемент набора (до отрисовки пустой, после - инициализированный, содержащий интерфейс).
     */
    getDOMElement() {}

    /**
     * @return {HTMLElement} DOM-элемент стилей набора.
     */
    getStyleDOMElement() {}

    /**
     * Валидирует ответы по параметрам выходных данных.
     * 
     * @param {Solution[]} [solutions] Массив решений. Если не передается,  берутся текищие через #geSolutions().
     * @return {SolutionValidationError[] | null} Массив ошибок. Если все ответы валидны — null.
     */
    validate(solutions) {}

    /**
     * Инициализатор обработчиков горячих клавиш:
     *   - Устанавливает фокус на предыдущее задание по стрелке влево/вверх.
     *   - Устанавливает фокус на следующее задание по стрелке вправо/вниз.
     *   - Передает нажимаемые клавиши активному заданию.
     *   - Устанавливает фокус на первое задание.
     */
    initHotkeys() {}

    /**
     * Устанавливает фокус на задание по индексу.
     * 
     * @param {string} index Индекс задания в массиве.
     */
    focusTask(index) {}

    /**
     * Устанавливает фокус на следующее задание.
     */
    focusNextTask() {}

    /**
     * Устанавливает фокус на предыдущее задание.
     */
    focusPreviousTask() {}

    /**
     * Передает нажатую клавишу активному заданию.
     */
    onKey(key) {}

    /**
     * Формирует DOM-представление набора заданий: отрисовывает все задания в наборе (вызывает #render()). Вызывает #onRender().
     *
     * @return this
     */
    render() {}

    /**
     * Уничтожает все входящие в набор задания.Освобождает занятые в глобальном пространстве ресурсы, сервисы, обработчики событий. Вызывает #onDestroy().
     */
    destroy() {}

    /**
     * Вызывается после отрисовки набора (#render()). Все манипуляции с DOM-элементом следует производить здесь.
     */
    onRender() {}

    /**
     * Вызывается после уничтожения набора (#destroy()). Наиболее подходящее место для очистки занятой памяти, удаления глобальных обработчиков событий, DOM-элементов и т. п.
     */
    onDestroy() {}

    /**
     * Вызывается после неудачной валидации с описанием ошибок в параметре. 
     *
     * @param {SolutionValidationError[]} errors Массив ошибок.
     */
    onValidationFail(errors) {}
}

Типы данных

Объект Task

{
    "id
[no-highlight[

Значение

Идентификатор задания.

]no-highlight]
": <строка>, "input_values
[no-highlight[

Значение

Входные данные задания в формате “<id поля>“:“<значение поля>“. Пример:

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

]no-highlight]
": { "<id поля с входными данными>": <значение>, … } }

Объект Solution

{
    "task_id
[no-highlight[

Значение

Идентификатор задания.

]no-highlight]
": <строка>, "output_values
[no-highlight[

Значение

Ответы в формате “<id поля ввода>“:“<значение>“. Пример:

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

]no-highlight]
": { "<id поля ввода>": <значение>, … } }

Объект SolutionValidationError

{
    "task_id
[no-highlight[

Значение

Идентификатор задания.

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

Значение

Ошибки в формате: “<id поля ввода>“: {code: “<код ошибки>“, [message: “<сообщение об ошибке>“]}. Пример:

“errors“: { 
        “colour“: {
            “code“: “REQUIRED“,
            [“message“: “Обязательное поле“]
        },

]no-highlight]
": { "<id поля ввода>": { "code": "<код ошибки>", ["message": <строка>] }, … } }

Сервисы

Для решения специфических задач в базовых классах доступны сервисы:

  • Geolocation.

    Позволяет получить GPS-координаты пользователя, если они доступны. В TaskSuite и Task доступен через this.geolocation.

    class Geolocation {
    
       /**
        * Повторяет функциональность navigator.geolocation.getCurrentPosition() - https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/getCurrentPosition
        */
       getCurrentPosition(success, error, options) {}
    
       /**
        * Повторяет функциональность navigator.geolocation.watchPosition() - https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/watchPosition
        */
       watchPosition(success, error, options) {}
    }
    
  • Hotkey

    Позволяет подписаться на нажатие клавиш. В TaskSuite и Task доступен через this.hotkey.

    class Hotkey {
       /**
        * Подписывает переданный обработчик на определенное событие. Отслеживаются следующие события (параметр event):
        *  "enter" — клавиша «ввод».
        *  "esc" — клавиша «отмена».
        *  "arrow-left", "arrow-right", "arrow-up", "arrow-down" — стрелки.
        *  "key" — буквенно-числовые клавиши. Обработчик (параметр handler) получит нажатую клавишу в качестве первого аргумента.
        * 
        * @param {string} event — имя события
        * @param {Function} handler — обработчик события
        * @param {Object} context — "this" для обраточика
        */
       on(event, handler, context) {}
    }
  • Storage

    Сохранение данных на клиенте. В TaskSuite и Task доступен через this.storage.

    class Storage {
    
       /**
        * Сохранить значение под определенным ключем.
        *
        * @param {string} key — ключ.
        * @param {*} value — значение. Может быть любого типа, сериализуется приведением к строке.
        * @param {Date|number} [expiration] — дата истечения срока хранения. По умолчанию 24 ч.
        */
       setItem(key, value, expiration) {}
    
       /**
        * Получить сохраненное значение по ключу.
        *
        * @param key — ключ.
        * @returns {*}
        */
       getItem(key) {}
    
       /**
        * Удалить значение по ключу.
        *
        * @param {string} key
        */
       removeItem(key) {}
    }