<?xml version="1.0" encoding="UTF-8" standalone="no"?><addon xmlns="http://icplayer.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="WritingCalculations" xsi:schemaLocation="http://icplayer.com ../../tools/addon.xsd">
    <model>
        <property isDefault="true" name="Value" nameLabel="WritingCalculations_property_value" type="text"/>
        <property name="Signs" nameLabel="WritingCalculations_property_signs" type="list">
            <property name="Addition" nameLabel="WritingCalculations_property_addition" type="string"/>
            <property name="Subtraction" nameLabel="WritingCalculations_property_subtraction" type="string"/>
            <property name="Division" nameLabel="WritingCalculations_property_division" type="string"/>
            <property name="Multiplication" nameLabel="WritingCalculations_property_multiplication" type="string"/>
            <property name="Equals" nameLabel="WritingCalculations_property_equals" type="string"/>
        </property>
        <property displayName="Styles" name="Styles" nameLabel="WritingCalculations_property_styles" type="list">
            <property name="Column" nameLabel="WritingCalculations_property_column" type="string"/>
            <property name="Row" nameLabel="WritingCalculations_property_row" type="string"/>
            <property name="Class" nameLabel="WritingCalculations_property_class" type="string"/>
            <property name="Style" nameLabel="WritingCalculations_property_style" type="string"/>
        </property>
        <property displayName="Commutativity" name="Commutativity" nameLabel="WritingCalculations_property_commutativity" type="boolean"/>
        <property displayName="Is not activity" name="Is not activity" nameLabel="WritingCalculations_property_is_not_activity" type="boolean"/>
        <property displayName="Enable more digits in gap" name="Multisigns" nameLabel="WritingCalculations_property_is_not_activity" type="boolean"/>
        <property displayName="Use numeric keyboard" name="useNumericKeyboard" nameLabel="WritingCalculations_use_numeric_keyboard" type="boolean"/>
        <property name="showAllAnswersInGSA" nameLabel="WritingCalculations_show_all_answers_in_GSA" type="boolean"/>
        <property displayName="Description of operation" name="DescriptionOfOperation" nameLabel="WritingCalculations_description_of_operation" type="string"/>
        <property displayName="Lang attribute" name="LangTag" nameLabel="WritingCalculations_lang_attribute" type="string"/>
        <property displayName="Speech texts" name="speechTexts" nameLabel="WritingCalculations_speech_texts" type="staticlist">

            <property name="Gap" nameLabel="WritingCalculations_gap" type="staticrow">
                <property name="Gap" nameLabel="WritingCalculations_gap" type="string"/>
            </property>

            <property name="AdditionalGap" nameLabel="WritingCalculations_additional_gap" type="staticrow">
                <property name="AdditionalGap" nameLabel="WritingCalculations_additional_gap" type="string"/>
            </property>

            <property name="Empty" nameLabel="WritingCalculations_empty" type="staticrow">
                <property name="Empty" nameLabel="WritingCalculations_empty" type="string"/>
            </property>

            <property name="AdditionSign" nameLabel="WritingCalculations_addition_sign" type="staticrow">
                <property name="AdditionSign" nameLabel="WritingCalculations_addition_sign" type="string"/>
            </property>

            <property name="SubtractionSign" nameLabel="WritingCalculations_subtraction_sign" type="staticrow">
                <property name="SubtractionSign" nameLabel="WritingCalculations_subtraction_sign" type="string"/>
            </property>

            <property name="DivisionSign" nameLabel="WritingCalculations_division_sign" type="staticrow">
                <property name="DivisionSign" nameLabel="WritingCalculations_division_sign" type="string"/>
            </property>

            <property name="MultiplicationSign" nameLabel="WritingCalculations_multiplication_sign" type="staticrow">
                <property name="MultiplicationSign" nameLabel="WritingCalculations_multiplication_sign" type="string"/>
            </property>

            <property name="EqualsSign" nameLabel="WritingCalculations_equals_sign" type="staticrow">
                <property name="EqualsSign" nameLabel="WritingCalculations_equals_sign" type="string"/>
            </property>

            <property name="DecimalSeparator" nameLabel="WritingCalculations_decimal_separator" type="staticrow">
                <property name="DecimalSeparator" nameLabel="WritingCalculations_decimal_separator" type="string"/>
            </property>

            <property name="Line" nameLabel="WritingCalculations_line" type="staticrow">
                <property name="Line" nameLabel="WritingCalculations_line" type="string"/>
            </property>

            <property name="Correct" nameLabel="WritingCalculations_correct" type="staticrow">
                <property name="Correct" nameLabel="WritingCalculations_correct" type="string"/>
            </property>

            <property name="Wrong" nameLabel="WritingCalculations_wrong" type="staticrow">
                <property name="Wrong" nameLabel="WritingCalculations_wrong" type="string"/>
            </property>

        </property>
        <property displayName="Use alternative TTS navigation" name="UseAlternativeTTSNavigation" nameLabel="WritingCalculations_property_use_alternative_tts_navigation" type="boolean"/>
        <property displayName="Rows' alt texts" name="RowsAltTexts" nameLabel="WritingCalculations_property_rows_alt_texts" type="list">
            <property name="AltText" nameLabel="WritingCalculations_property_rows_alt_texts_alt_text" type="string"/>
        </property>
    </model>
<css>.wrapper-cell, .writing-calculations-input {
    float: left;
    width: 50px;
    height: 25px;
    line-height: 25px;
    padding: 0;
    text-align: center;
    font-size: 12px;
    margin: 1px 0;
}

.writing-calculations-input {
    width: 25px;
    float: none;
    height: 23px;
    text-align: center !important;
}

.wrapper-row {
    float: left;
}

.writing-calculations-input.incorrect {
    border: 1px solid red !important;
    background-color: #faa !important;
}

.writing-calculations-input.correct {
    border: 1px solid green !important;
    background-color: #afa !important;
}

.wrapper-line {
    float: left;
    width: 50px;
}

.container-line {
    border-bottom: 1px solid #111;
    height: 5px;
}

.wrapper-empty-space {
    float: left;
    height: 1px;
    width: 50px;
}

.wrapper-dot {
    float: left;
    width: 0px;
    position: relative;
}

.container-dot {
    position: absolute;
    left: -4px;
    top: 4px;
}

.wrapper-cell {
    min-height: 3px;
}

.wrong {
    border: 1px solid #ff1111;
}

.correct {
    border: 1px solid #00bb00;
}

.container-helpBox .writing-calculations-input {
    background-color: lightslategray;
    width: 20px;
    height: 20px;
}
</css><view>&lt;div id="writing-calculations-wrapper"&gt;&lt;/div&gt;
</view><preview>&lt;div id="writing-calculations-wrapper"&gt;&lt;/div&gt;
</preview><presenter>function AddonWritingCalculations_create() {
    var presenter = function () {};

    presenter.$view = null;
    presenter.model = null;
    presenter.correctAnswersList = [];
    presenter.playerController = null;
    presenter.answers = [];
    presenter.isCommutativity;
    presenter.useNumericKeyboard = false;
    presenter.isGradualShowAnswersActive = false;
    presenter.isScoreSaved = false;
    presenter.userAnswers = [];
    presenter.helpBoxesDefaultValues = [];
    presenter.helpBoxesUserAnswers = [];
    presenter.isDisabled = false;
    presenter.isWCAGOn = false;
    presenter.tableElements = [];
    presenter.rowsAltTexts = [];
    presenter.gapElements = [];
    presenter.rowsStatusesOfNavigabilityInAltTTSNavigation = [];
    presenter.isGapFocused = false;
    var eventBus;

    const DESCRIPTION_OF_OPERATION_ROW_INDEX = -1;
    const DESCRIPTION_OF_OPERATION_COLUMN_INDEX = 0;
    const ALT_TEXT_FOR_ROW_COLUMN_INDEX = -1;
    const NO_GAP_INDEX = -1;

    presenter.keyboardState = {
        x: DESCRIPTION_OF_OPERATION_COLUMN_INDEX,
        y: DESCRIPTION_OF_OPERATION_ROW_INDEX,
        gapIndex: NO_GAP_INDEX
    };

    presenter.KEYBOARD_SELECTED_CLASS = 'keyboard_navigation_active_element';

    presenter.ELEMENT_TYPE = {
        "NUMBER" : 1,        // Non box numbers
        "EMPTY_BOX" : 2,
        "SYMBOL" : 3,
        "EMPTY_SPACE" : 4,
        "LINE" : 5,
        "DOT" : 6,
        "HELP_BOX" : 7,
    };

    presenter.upgradeModel = function (model) {
        let upgradedModel = presenter.upgradeSigns(model);
        upgradedModel = presenter.upgradeNumericKeyboard(upgradedModel);
        upgradedModel = presenter.upgradeModelAddShowAllAnswersInGSA(upgradedModel);
        upgradedModel = presenter.upgradeTTS(upgradedModel);
        return presenter.upgradeModelWithAlternativeTTSNavigation(upgradedModel);
    };

    presenter.upgradeModelAddShowAllAnswersInGSA = function (model) {
        const upgradedModel = {};
        $.extend(true, upgradedModel, model);

        if (!upgradedModel.hasOwnProperty('showAllAnswersInGSA')) {
            upgradedModel['showAllAnswersInGSA'] = 'False';
        }

        return upgradedModel;
    };

    presenter.upgradeModelWithAlternativeTTSNavigation = function (model) {
        const upgradedModel = {};
        $.extend(true, upgradedModel, model);

        if (!upgradedModel.hasOwnProperty("UseAlternativeTTSNavigation")) {
            upgradedModel["UseAlternativeTTSNavigation"] = "False";
        }

        if (!upgradedModel.hasOwnProperty("RowsAltTexts")) {
            upgradedModel["RowsAltTexts"] = [{"AltText": ""}];
        }

        return upgradedModel;
    };

    presenter.upgradeSigns = function (model) {
        if ('Signs' in model) return model;

        var upgradedModel = {};
        $.extend(true, upgradedModel, model); // Deep copy of model object
        upgradedModel['Signs'] = [{
            'Addition' : '',
            'Subtraction' : '',
            'Division' : '',
            'Multiplication' : '',
            'Equals' : ''
        }];
        return upgradedModel;
    };

    presenter.upgradeTTS = function (model) {
        if ('speechTexts' in model) return model;

        var upgradedModel = {};
        $.extend(true, upgradedModel, model); // Deep copy of model object

        if (!upgradedModel["speechTexts"]) {
            upgradedModel["speechTexts"] = {};
        }
        if (!upgradedModel["speechTexts"]["Gap"]) {
            upgradedModel["speechTexts"]["Gap"] = {Gap: ""};
        }
        if (!upgradedModel["speechTexts"]["AdditionalGap"]) {
            upgradedModel["speechTexts"]["AdditionalGap"] = {AdditionalGap: ""};
        }
        if (!upgradedModel["speechTexts"]["Empty"]) {
            upgradedModel["speechTexts"]["Empty"] = {Empty: ""};
        }
        if (!upgradedModel["speechTexts"]["AdditionSign"]) {
            upgradedModel["speechTexts"]["AdditionSign"] = {AdditionSign: ""};
        }
        if (!upgradedModel["speechTexts"]["SubtractionSign"]) {
            upgradedModel["speechTexts"]["SubtractionSign"] = {SubtractionSign: ""};
        }
        if (!upgradedModel["speechTexts"]["DivisionSign"]) {
            upgradedModel["speechTexts"]["DivisionSign"] = {DivisionSign: ""};
        }
        if (!upgradedModel["speechTexts"]["MultiplicationSign"]) {
            upgradedModel["speechTexts"]["MultiplicationSign"] = {MultiplicationSign: ""};
        }
        if (!upgradedModel["speechTexts"]["EqualsSign"]) {
            upgradedModel["speechTexts"]["EqualsSign"] = {EqualsSign: ""};
        }
        if (!upgradedModel["speechTexts"]["DecimalSeparator"]) {
            upgradedModel["speechTexts"]["DecimalSeparator"] = {DecimalSeparator: ""};
        }
        if (!upgradedModel["speechTexts"]["Line"]) {
            upgradedModel["speechTexts"]["Line"] = {Line: ""};
        }
        if (!upgradedModel["speechTexts"]["Correct"]) {
            upgradedModel["speechTexts"]["Correct"] = {Correct: ""};
        }
        if (!upgradedModel["speechTexts"]["Wrong"]) {
            upgradedModel["speechTexts"]["Wrong"] = {Wrong: ""};
        }

        if (!upgradedModel["DescriptionOfOperation"]) {
            upgradedModel["DescriptionOfOperation"] = "";
        }

        if (!upgradedModel["LangTag"]) {
            upgradedModel["LangTag"] = "";
        }

        return upgradedModel;
    };

    presenter.ERROR_MESSAGES = {
        V01 : 'Error in row number %rowIndex%. Missing closing square bracket.',
        V02 : 'Error in row number %rowIndex%. Missing opening square bracket.',
        V03 : 'Error in row number %rowIndex%. Missing number between square brackets.',
        V04 : 'Error in row number %rowIndex%. Number between brackets must be from 0 to 9.',
        V05 : 'Error in row number %rowIndex%. Given value "%value%" in square brackets is not a valid number.',
        V06 : 'Error in row number %rowIndex%. Missing closing curly bracket.',
        V07 : 'Error in row number %rowIndex%. Missing opening curly bracket.',
        V08 : 'Error in row number %rowIndex%. Given value "%value%" in curly brackets is not a valid. Curly brackets must be empty or have a valid number.',
        RAT01 : "Number of rows' alt texts (%rowsAltTextsNumber%) is not equal to number of navigable rows in alternative TTS navigation (%navigableRowsNumber%)."
    };

    presenter.run = function(view, model) {
        presenterLogic(view, model);
        presenter.setVisibility(presenter.isVisible);
        eventBus = presenter.playerController.getEventBus();
        eventBus.addEventListener('ShowAnswers', this);
        eventBus.addEventListener('HideAnswers', this);
        eventBus.addEventListener('GradualShowAnswers', this);
        eventBus.addEventListener('GradualHideAnswers', this);
    };

    presenter.createPreview = function(view, model) {
        presenterLogic(view, model);
        presenter.setVisibility(true);
    };

    presenter.upgradeNumericKeyboard = function (model) {
        var upgradedModel = {};
        jQuery.extend(true, upgradedModel, model); // Deep copy of model object

        if(model.useNumericKeyboard === undefined) {
            upgradedModel["useNumericKeyboard"] = "False";
        }

        return upgradedModel;
    };

    function presenterLogic(view, model) {
        model = presenter.upgradeModel(model);
        presenter.isCommutativity = ModelValidationUtils.validateBoolean(model['Commutativity']) || false;
        presenter.$view = $(view);
        presenter.model = presenter.upgradeModel(model);
        presenter.signs = presenter.readSigns( presenter.model['Signs'][0] );
        presenter.isNotActivity = ModelValidationUtils.validateBoolean(model['Is not activity']);
        presenter.useNumericKeyboard = ModelValidationUtils.validateBoolean(model['useNumericKeyboard']);
        presenter.multisigns = ModelValidationUtils.validateBoolean(model['Multisigns']);
        presenter.isVisible = ModelValidationUtils.validateBoolean(model['Is Visible']);
        presenter.isVisibleByDefault = ModelValidationUtils.validateBoolean(model['Is Visible']);
        presenter.ID = model["ID"];
        presenter.showAllAnswersInGSA = ModelValidationUtils.validateBoolean(model['showAllAnswersInGSA']);
        presenter.langTag = model["LangTag"];
        presenter.descriptionOfOperation = model["DescriptionOfOperation"];
        presenter.setSpeechTexts(model["speechTexts"]);

        const validatedModelValue = presenter.validateModelValue(model['Value']);
        if (!validatedModelValue.isValid) {
            presenter.showErrorMessage(
                presenter.ERROR_MESSAGES[validatedModelValue.errorCode],
                validatedModelValue.errorMessageSubstitutions
            );
            return;
        }

        presenter.useAlternativeTTSNavigation = ModelValidationUtils.validateBoolean(model["UseAlternativeTTSNavigation"]);
        if (presenter.useAlternativeTTSNavigation) {
            createRowsStatuesOfNavigability(model["RowsAltTexts"], validatedModelValue.value);
            const validatedModelRowsAltTexts = presenter.validateRowsAltTexts(model["RowsAltTexts"]);
            if (!validatedModelRowsAltTexts.isValid) {
                presenter.showErrorMessage(
                    presenter.ERROR_MESSAGES[validatedModelRowsAltTexts.errorCode],
                    validatedModelRowsAltTexts.errorMessageSubstitutions
                );
                return;
            }
        }
        presenter.setAltTexts(model["RowsAltTexts"]);
        presenter.createView(validatedModelValue.value);
        presenter.bindValueChangeEvent();
        presenter.setContainerWidth();
        presenter.addAdditionalStyles();
    }

    presenter.setSpeechTexts = function(speechTexts) {
        presenter.speechTexts = {
            gap: 'gap',
            additionalGap: 'additional gap',
            empty: 'empty',
            additionSign: 'addition sign',
            subtractionSign: 'subtraction sign',
            divisionSign: 'division sign',
            multiplicationSign: 'multiplication sign',
            equalsSign: 'equals sign',
            decimalSeparator: 'decimal separator',
            line: 'line',
            correct: 'correct',
            wrong: 'wrong',

        };

        if (!speechTexts) {
            return;
        }

        presenter.speechTexts = {
            gap: TTSUtils.getSpeechTextProperty(speechTexts['Gap']['Gap'], presenter.speechTexts.gap),
            additionalGap: TTSUtils.getSpeechTextProperty(speechTexts['AdditionalGap']['AdditionalGap'], presenter.speechTexts.additionalGap),
            empty: TTSUtils.getSpeechTextProperty(speechTexts['Empty']['Empty'], presenter.speechTexts.empty),
            additionSign: TTSUtils.getSpeechTextProperty(speechTexts['AdditionSign']['AdditionSign'], presenter.speechTexts.additionSign),
            subtractionSign: TTSUtils.getSpeechTextProperty(speechTexts['SubtractionSign']['SubtractionSign'], presenter.speechTexts.subtractionSign),
            divisionSign: TTSUtils.getSpeechTextProperty(speechTexts['DivisionSign']['DivisionSign'], presenter.speechTexts.divisionSign),
            multiplicationSign: TTSUtils.getSpeechTextProperty(speechTexts['MultiplicationSign']['MultiplicationSign'], presenter.speechTexts.multiplicationSign),
            equalsSign: TTSUtils.getSpeechTextProperty(speechTexts['EqualsSign']['EqualsSign'], presenter.speechTexts.equalsSign),
            decimalSeparator: TTSUtils.getSpeechTextProperty(speechTexts['DecimalSeparator']['DecimalSeparator'], presenter.speechTexts.decimalSeparator),
            line: TTSUtils.getSpeechTextProperty(speechTexts['Line']['Line'], presenter.speechTexts.line),
            correct: TTSUtils.getSpeechTextProperty(speechTexts['Correct']['Correct'], presenter.speechTexts.correct),
            wrong: TTSUtils.getSpeechTextProperty(speechTexts['Wrong']['Wrong'], presenter.speechTexts.wrong)
        };
    }

    presenter.setAltTexts = function(rowsAltTexts) {
        presenter.rowsAltTexts = []

        if (!rowsAltTexts) {
            return;
        }

        for (let rowIndex = 0; rowIndex &lt; rowsAltTexts.length; rowIndex++) {
            presenter.rowsAltTexts.push(rowsAltTexts[rowIndex].AltText);
        }
    }

    presenter.readSigns = function( signs ) {
        var properSigns = {};
        for ( var key in signs ) {
            if ( signs.hasOwnProperty(key) ) {
                if ( signs[key] == '' || signs[key] == '&lt;br&gt;' ) {
                    properSigns[key] = presenter.useDefaultSign( key );
                } else {
                    properSigns[key] = signs[key];
                }
            }
        }
        return properSigns;
    };

    presenter.useDefaultSign = function( key ) {
        if (key == 'Addition') {
            return "\\(+\\)";
        }
        if (key == 'Subtraction') {
            return "\\(-\\)";
        }
        if (key == 'Division') {
            return "\\(\\big)\\)";
        }
        if (key == 'Multiplication') {
            return "\\(\\times\\)";
        }
        if (key == 'Equals') {
            return "\\(=\\)";
        }
    };

    presenter.addAdditionalStyleToElement = function (row, column, style, clazz) {
        var rowElement = presenter.$view.find('.row-' + row),
            cellElement = rowElement.find('.cell-' + column);

        cellElement.addClass(clazz);
        cellElement.attr('style', style);
    };

    presenter.addAdditionalStyles = function() {
        if (!presenter.model['Styles']) {
            return;
        }
        $.each(presenter.model['Styles'], function() {
            var columns = this['Column'],
                rows = this['Row'];

            if (rows) {
                rows = rows.split(',');
            }

            if (columns) {
                columns = columns.split(',');
            }

            for (var row = 0; row &lt; rows.length; row++) {
                for (var column = 0; column &lt; columns.length; column++) {
                    presenter.addAdditionalStyleToElement(rows[row], columns[column], this['Style'], this['Class']);
                }
            }
        });
    };

    presenter.setContainerWidth = function() {
        var viewWrapper = this.$view.find("#writing-calculations-wrapper");
        var width = $(viewWrapper).children().outerWidth();
        $(viewWrapper).css('width', width);
    };

    presenter.bindValueChangeEvent = function() {
        const $inputs = presenter.getInputs();
        const $emptyBoxesInputs = presenter.getEmptyBoxesInputs();

        $inputs.on('click', function(event) {
            event.stopPropagation();
        });

        $inputs.on('keyup', function(event) {
            presenter.onKeyUp(event);
        });

        $inputs.on('keypress', function(event) {
            presenter.onKeyPress(event);
        });

        $emptyBoxesInputs.on("change", function(event) {
            event.stopPropagation();

            var value = event.target.value;
            var rowIndex = $(event.target).attr("row");
            var cellIndex = $(event.target).attr("cell");
            var item = rowIndex + "-" + cellIndex;
            var isCorrect = 0;
            var answer = presenter.createAnswer(rowIndex, cellIndex, value);
            if(presenter.isCorrect(answer)) {
                isCorrect = 1;
            }

            if (presenter.isCommutativity &amp;&amp; presenter.isAllEmptyBoxInputsFilled()) {
                presenter.triggerValueChangeEvent("", "all", presenter.isAllEmptyBoxInputsCorrectlyFilled() ? 1 : 0);
            }

            if (!presenter.isCommutativity) {
                presenter.triggerValueChangeEvent(value, item, isCorrect);
            }

            if(!presenter.isCommutativity &amp;&amp; presenter.allAnswersCorrect()) {
                presenter.triggerValueChangeEvent("", "all", "");
            }
        });
    };

    presenter.onKeyUp = function(event) {
        event.stopPropagation();
        if (presenter.useNumericKeyboard) {
            var newText = String(event.target.value);
            var pattern = StringUtils.getNumericPattern();
            if (newText.length &gt; 0 &amp;&amp; !newText.match(pattern)) {
                var patternWithoutLastCharacter = pattern.slice(0, -1);
                var regExp = RegExp(patternWithoutLastCharacter);
                var match = regExp.exec(newText);
                var rowIndex = $(event.target).attr("row");
                var cellIndex = $(event.target).attr("cell");

                if (match) {
                    presenter.setCellElementValue(rowIndex, cellIndex, match[0]);
                } else {
                    presenter.setCellElementValue(rowIndex, cellIndex, "");
                }
            }
        }
    };

    presenter.onKeyPress = function(event) {
        event.stopPropagation();
        if (presenter.useNumericKeyboard) {
            var key = String.fromCharCode(!event.charCode ? event.which : event.charCode);
            var selectionStartIdx = event.target.selectionStart;
            var selectionEndIdx = event.target.selectionEnd;
            var oldText = String(event.target.value);
            var newText = oldText.substring(0, selectionStartIdx)
                            + key
                            + oldText.substring(selectionEndIdx);
            var pattern = StringUtils.getNumericPattern();
            if (!newText.match(pattern)) {
                event.preventDefault();
            }
        }
    };

    presenter.createView = function (elementsData) {
        presenter.elementsTable = [];
        presenter.gapElements = [];
        const $viewWrapper = this.$view.find("#writing-calculations-wrapper");
        elementsData.forEach((elementsRow, rowIndex) =&gt; {
            const createdElementsRow = [];
            const $rowWrapper = presenter.createRowWrapper(rowIndex);
            let cellIndex = 0;
            let gapInRowIndex = 0;
            elementsRow.forEach((elementData) =&gt; {
                const createdElement = presenter.createElement(elementData.type);

                if (elementData.type !== presenter.ELEMENT_TYPE.LINE) {
                    addCellClass(createdElement, cellIndex);
                }

                presenter.transformElement(createdElement, elementData.parsedValue, elementData.type);

                const ELEMENT_TYPES_WITH_POSITION = [
                    presenter.ELEMENT_TYPE.EMPTY_BOX,
                    presenter.ELEMENT_TYPE.NUMBER,
                    presenter.ELEMENT_TYPE.HELP_BOX
                ];
                if (elementData.isVisiblePosition()
                    &amp;&amp; (ELEMENT_TYPES_WITH_POSITION.includes(elementData.type))) {
                    presenter.addPosition(createdElement, elementData.getPosition());
                }

                $rowWrapper.append(createdElement);

                if (elementData.type !== presenter.ELEMENT_TYPE.DOT) {
                    cellIndex++;
                    createdElementsRow.push(createdElement);
                }

                if (elementData.type === presenter.ELEMENT_TYPE.EMPTY_BOX || elementData.type === presenter.ELEMENT_TYPE.HELP_BOX) {
                    presenter.gapElements.push({
                        x: createdElementsRow.length - 1,
                        y: presenter.tableElements.length,
                        el: createdElement,
                        gapInRowIndex: gapInRowIndex++
                    });
                }
            });
            $viewWrapper.append($rowWrapper);
            presenter.tableElements.push(createdElementsRow);
        });
    };

    function addCellClass(createdElement, cellIndex) {
        $(createdElement).addClass('cell-' + (cellIndex + 1));
    }

    presenter.addPosition = function(element, position) {
        const input = $(element).find(".writing-calculations-input, .container-number")[0];

        $(input).attr({
            "row" : position.rowIndex,
            "cell" : position.cellIndex
        });
    };

    presenter.setCellElementValue = function (row, cell, value) {
        const $inputs = presenter.getInputs();

        for (let i = 0; i &lt; $inputs.length; i++) {
            if ($($inputs[i]).attr("row") === row &amp;&amp; $($inputs[i]).attr("cell") === cell) {
                $($inputs[i]).val(value);
            }
        }
    };

    presenter.parseValue = function (value) {
        if (!this.isEmptyBox(value) &amp;&amp; !this.isHelpBox(value)) {
            return value;
        }

        const openingBracketIndex = 1;
        const endingBracketIndex = value.length - 1;
        return value.slice(openingBracketIndex, endingBracketIndex);
    };

    presenter.createRowWrapper = function(index) {
        const rowWrapper = document.createElement("div");
        rowWrapper.classList.add("wrapper-row");
        rowWrapper.classList.add("row-" + (index + 1));
        return $(rowWrapper);
    };

    presenter.createElement = function(type) {
        let createdElement;
        switch (type) {
            case this.ELEMENT_TYPE.NUMBER:
                createdElement = this.createWrapperAndContainer("number");
                break;
            case this.ELEMENT_TYPE.SYMBOL:
                createdElement = this.createWrapperAndContainer("symbol");
                break;
            case this.ELEMENT_TYPE.EMPTY_SPACE:
                createdElement = this.createWrapperAndContainer("emptySpace", 'wrapper-empty-space');
                break;
            case this.ELEMENT_TYPE.EMPTY_BOX:
                createdElement = this.createWrapperAndContainer("emptyBox");
                break;
            case this.ELEMENT_TYPE.LINE:
                createdElement = this.createWrapperAndContainer("line", 'wrapper-line');
                break;
            case this.ELEMENT_TYPE.DOT:
                createdElement = this.createWrapperAndContainer("dot", 'wrapper-dot');
                break;
            case this.ELEMENT_TYPE.HELP_BOX:
                createdElement = this.createWrapperAndContainer("helpBox");
                break;
        }

        return createdElement;
    };

    presenter.getContainerType = function ($el) {
        if ($el.hasClass("container-number")) return presenter.ELEMENT_TYPE.NUMBER;
        if ($el.hasClass("container-symbol")) return presenter.ELEMENT_TYPE.SYMBOL;
        if ($el.hasClass("container-emptySpace")) return presenter.ELEMENT_TYPE.EMPTY_SPACE;
        if ($el.hasClass("container-emptyBox")) return presenter.ELEMENT_TYPE.EMPTY_BOX;
        if ($el.hasClass("container-line")) return presenter.ELEMENT_TYPE.LINE;
        if ($el.hasClass("container-dot")) return presenter.ELEMENT_TYPE.DOT;
        if ($el.hasClass("container-helpBox")) return presenter.ELEMENT_TYPE.HELP_BOX;
        return -1;
    }

    presenter.createWrapperAndContainer = function(cssClass, wrapperClass) {
        if (!wrapperClass || wrapperClass === undefined) {
            wrapperClass = "wrapper-cell";
        }
        var wrapper = $("&lt;div&gt;&lt;/div&gt;");
        wrapper.addClass(wrapperClass);
        var container = $("&lt;div&gt;&lt;/div&gt;");
        container.addClass("container-" + cssClass);
        wrapper.append(container);
        return wrapper;
    };

    presenter.transformElement = function(element, value, type) {
        const $container = $(element).find("[class*=container]");
        let inputType, input;
        switch(type) {
            case this.ELEMENT_TYPE.EMPTY_SPACE:
                break;
            case this.ELEMENT_TYPE.EMPTY_BOX:
                inputType = presenter.useNumericKeyboard ? "tel" : "text";
                input = $("&lt;input type='" + inputType + "'&gt;");
                input.addClass("writing-calculations-input");
                if(!presenter.multisigns){
                    input.attr("maxlength", 1);
                }
                if (presenter.useNumericKeyboard) {
                    input.attr("step", "any");
                }
                $container.append(input);
                break;
            case this.ELEMENT_TYPE.LINE:
                break;
            case this.ELEMENT_TYPE.SYMBOL:
                $container.html(this.convertLaTeX(value));
                $container.data('value',value);
                break;
            case this.ELEMENT_TYPE.DOT:
                $container.html(value);
                break;
            case this.ELEMENT_TYPE.HELP_BOX:
                inputType = presenter.useNumericKeyboard ? "tel" : "text";
                input = $("&lt;input type='" + inputType + "'&gt;");
                input.addClass("writing-calculations-input");
                if (presenter.useNumericKeyboard) {
                    input.attr("step", "any");
                }
                input.val(value);
                $container.append(input);
                break;
            default:
                $container.html(value);
        }
    };

    presenter.convertLaTeX = function (value) {
        if (value === "*") {
            return presenter.signs['Multiplication'];
        } else if (value === ":" || value === ")") {
            return presenter.signs['Division'];
        } else if (value === "+") {
            return presenter.signs['Addition'];
        } else if (value === "-") {
            return presenter.signs['Subtraction'];
        } else if (value === "#") {
            return presenter.signs['Equals'];
        }
    };

    presenter.getElementType = function(element) {
        if( this.isInteger(element) ) return this.ELEMENT_TYPE.NUMBER;
        if( this.isSymbol(element) ) return this.ELEMENT_TYPE.SYMBOL;
        if( this.isEmptySpace(element) ) return this.ELEMENT_TYPE.EMPTY_SPACE;
        if( this.isEmptyBox(element) ) return this.ELEMENT_TYPE.EMPTY_BOX;
        if( this.isLine(element) ) return this.ELEMENT_TYPE.LINE;
        if( this.isDot(element)) return this.ELEMENT_TYPE.DOT;
        if( this.isHelpBox(element)) return this.ELEMENT_TYPE.HELP_BOX;
    };

    presenter.isDot = function(element) {
        return element == "." || element == ",";
    };

    /**
     Check if valid empty box == will be empty box in run
     @method isEmptyBox

     @param {String} element element HTML
    */
    presenter.isEmptyBox = function(element) {
        if ((element.length &lt; 2)
            || (element[0] !== '[' || element[element.length - 1] !== ']')) {
            return false;
        }

        if (!presenter.multisigns) {
            const pattern = /\[[\d.,]?\]/g; // matches: '[number]' or '[.]' or '[,]'
            return pattern.test(element);
        }

        const content = element.slice(1, element.length - 1);
        return (presenter.isDot(content) || presenter.isIntegerOrFloat(content));
    };

    /**
     Check if valid help box
     @method isHelpBox

     @param {String} element element HTML
    */
    presenter.isHelpBox = function(element) {
        if ((element.length &lt; 2)
            || (element[0] !== '{' || element[element.length - 1] !== '}')) {
            return false;
        }

        const content = element.slice(1, element.length - 1);
        return (presenter.isDot(content) || presenter.isIntegerOrFloat(content)) || content === "";
    };

    presenter.isEmptySpace = function(element) {
        return element == "_";
    };

    presenter.isSymbol = function(element) {
        if (element.length != 1) {
            return false;
        }
        const pattern = /[#+\-*:\)]/g; // matches: '#', '+', '-', ':', ')' and '*'
        return pattern.test(element);
    };

    presenter.isInteger = function (value) {
        if (value === undefined || value === null) {
            return false;
        }
        value = value.toString().trim();
        const commonTestsResult = isNaNCommonTests(value);
        if (commonTestsResult === false) {
            return false;
        }

        if (value % 1 !== 0) {
            return false;
        }

        return /\d+/.test(value);
    };

    presenter.isIntegerOrFloat = function (value) {
        if (value === undefined || value === null) {
            return false;
        }
        value = value.toString().trim();
        const commonTestsResult = isNaNCommonTests(value);
        if (commonTestsResult === false) {
            return false;
        }

        if (value.charAt(0) == "-") {
            value = value.slice(1, value.length);
        }

        let i, commasNumber = 0;
        const digitPattern = /[0-9]/;
        const separatorPattern = /[,.]/;

        if (separatorPattern.test(value.charAt(0))) {
            return false;
        }

        for (i = 0; i &lt; value.length; i++) {
            if (separatorPattern.test(value.charAt(i))) {
                if (commasNumber &gt;= 1) {
                    return false;
                }
                commasNumber ++;
            } else {
                if (digitPattern.test(value.charAt(i))) {
                    continue;
                } else {
                    return false;
                }
            }
        }

        return true;
    };

    function isNaNCommonTests (value) {
        value = value.trim();
        return !(ModelValidationUtils.isStringEmpty(value)
            || isStartsWithPlus(value)
            || isTooManyZeroes(value)
            || isMinusZero(value)
            || isOnlyMinus(value)
        );
    }

    function isStartsWithPlus (value) {
        return value.charAt(0) == "+";
    }

    function isMinusZero (value) {
        return value == "-0";
    }

    function isOnlyMinus (value) {
        return value == "-";
    }

    function isTooManyZeroes (value) {
        const tooManyZeroesPattern = /^-?0{2,}/;
        return tooManyZeroesPattern.test(value);
    }

    presenter.isLine = function(element) {
        return element == "=";
    };

    presenter.convertStringToArray = function(stringToConvert) {
        return stringToConvert.split(/[\n\r]+/);
    };

    presenter.isCorrect = function(answer) {
        var result = false;
        var correctAnswers = presenter.correctAnswersList;
        for (var i = 0; i &lt; correctAnswers.length; i++) {
            if (presenter.isEqual(answer, correctAnswers[i])) {
                result = true;
            }
        }
        return result;
    };

    presenter.allAnswersCorrect = function() {
        var maxScore = presenter.getPoints("all");
        var score = presenter.getPoints("correct");
        var errorCount = presenter.getPoints("incorrect");

        return maxScore === score &amp;&amp; errorCount === 0;
    };

    presenter.executeCommand = function (name, params) {
        var commands = {
            'isAllOK': presenter.isAllOK,
            'show': presenter.show,
            'hide': presenter.hide
        };

        return Commands.dispatch(commands, name, params, presenter);
    };

    presenter.setVisibility = function (isVisible) {
        presenter.$view.css("visibility", isVisible ? "visible" : "hidden");
    };

    presenter.show = function () {
        presenter.setVisibility(true);
        presenter.isVisible = true;
    };

    presenter.hide = function () {
        presenter.setVisibility(false);
        presenter.isVisible = false;
    };

    presenter.isAllOK = function () {
        var maxScore = presenter.getPoints("all"),
            score = presenter.getPoints("correct"),
            errorCount = presenter.getPoints("incorrect");

        return maxScore === score &amp;&amp; errorCount === 0;
    };

    presenter.isEqual = function(answer, correctAnswer) {
        return answer.value === correctAnswer.value &amp;&amp; answer.rowIndex === correctAnswer.rowIndex &amp;&amp; answer.cellIndex === correctAnswer.cellIndex;
    };

    presenter.getInputs = function() {
        return this.$view.find(".writing-calculations-input");
    }

    presenter.getEmptyBoxesInputs = function() {
        return this.$view.find(".container-emptyBox .writing-calculations-input");
    };

    presenter.getHelpBoxesInputs = function() {
        return this.$view.find(".container-helpBox .writing-calculations-input");
    };

    presenter.isAllEmptyBoxInputsFilled = function() {
        const $emptyBoxesInputs = presenter.getEmptyBoxesInputs();

        for (let i = 0; i &lt; $emptyBoxesInputs.length; i++) {
            if ($($emptyBoxesInputs[i]).val().length == 0) return false;
        }

        return true;
    };

    presenter.handleShownAnswers = function () {
        presenter.isShowAnswersActive &amp;&amp; presenter.hideAnswers();
        presenter.isGradualShowAnswersActive &amp;&amp; presenter.gradualHideAnswers();
    }

    presenter.setShowErrorsMode = function() {
        if(presenter.isNotActivity){
            return;
        }

        const $emptyBoxesInputs = presenter.getEmptyBoxesInputs();
        const $helpBoxesInputs = presenter.getHelpBoxesInputs();
        disableInputs($helpBoxesInputs);

        presenter.handleShownAnswers();

        if (!presenter.isCommutativity) {
            $.each($emptyBoxesInputs, function(){
                const answer = presenter.createAnswer($(this).attr("row"), $(this).attr("cell"), $(this).val());

                if (ModelValidationUtils.isStringEmpty($(this).val())) {
                    presenter.markEmpty($(this));
                } else if (presenter.isCorrect(answer)) {
                    presenter.markCorrect($(this));
                } else {
                    presenter.markIncorrect($(this));
                }
            });
        } else if (presenter.isAllEmptyBoxInputsFilled()) {
            const isCorrect = presenter.isAllEmptyBoxInputsCorrectlyFilled();

            if (isCorrect) {
                presenter.$view.addClass('correct');
            } else {
                presenter.$view.addClass('wrong');
            }

            disableInputs($emptyBoxesInputs);
        } else {
            disableInputs($emptyBoxesInputs);
        }
    };

    function disableInputs(inputs) {
        inputs.attr("disabled", "disabled");
    }

    presenter.compareAnswers = function(correctAnswers, userAnswers) {
        var answers = $.extend(true, [], userAnswers);

        correctAnswers = $.extend(true, [], correctAnswers);

        var userResult = answers.pop(),
            declaredResult = correctAnswers.pop();

        var cleanedAnswers = [],
            cleanedCorrectAnswers = [],
            logicalFoundArray = [],
            found = false,
            result = true,
            i;

        for (i = 0; i &lt;= answers.length; i++) {
            if (answers[i] !== undefined) {
                cleanedAnswers.push(answers[i]);
            }

            if (correctAnswers[i] !== undefined) {
                cleanedCorrectAnswers.push(correctAnswers[i]);
            }
        }

        for (i = 0; i &lt; cleanedCorrectAnswers.length; i++) {
            found = presenter.wasRowFound(cleanedCorrectAnswers[i], cleanedAnswers);
            logicalFoundArray.push(found);
        }

        jQuery.each(logicalFoundArray, function (_, element) {
            if (!element) {
                result = false;
                return false; // jQuery.each break statement
            }
        });

        result = result &amp;&amp; presenter.compareResults(userResult, declaredResult);

        return result;
    };


    presenter.compareResults = function(userResult, declaredResult) {
        return userResult.toString() === declaredResult.toString();
    };

    /*
     This method get one of correctAnswers row
     and comparing it with each givenAnswers rows.

     Takes: correctAnswers as all correct values in one row
     and givenAnswers as answers in all rows given by user.

     Returns: boolean value if declared row in correctAnswers
     was found in givenAnswers.
     */
    presenter.wasRowFound = function(correctAnswers, givenAnswers) {
        var wasRowFound = false;

        for (var j = 0; j &lt; givenAnswers.length; j++) { //answers given by user
            if (givenAnswers[j] !== undefined &amp;&amp; correctAnswers.toString() === givenAnswers[j].toString()) {
                delete givenAnswers[j];
                wasRowFound = true;
                break;
            } else {
                wasRowFound = false;
            }
        }

        return wasRowFound;
    };

    presenter.getAllAnswers = function($elements) {
        const answers = [];

        $.each($elements, function(){
            if ($(this).hasClass('writing-calculations-input')) {
                if (answers[$(this).attr("row") - 1] === undefined) {
                    answers[$(this).attr("row") - 1] = [$(this).val()];
                } else {
                    answers[$(this).attr("row") - 1].push($(this).val());
                }
            } else if ($(this).hasClass('container-number')) {
                if (answers[$(this).attr("row") - 1] === undefined) {
                    answers[$(this).attr("row") - 1] = [$(this).html()];
                } else {
                    answers[$(this).attr("row") - 1].push($(this).html());
                }
            }
        });

        return answers;
    };

    presenter.isAllEmptyBoxInputsCorrectlyFilled = function() {
        const $elements = this.$view.find('.container-number, .container-emptyBox .writing-calculations-input');
        const answers = presenter.getAllAnswers($elements);

        return presenter.compareAnswers(presenter.answers, answers);
    };

    presenter.createAnswer = function(rowIndex, cellIndex, rawValue) {
        return {
            rowIndex: parseInt(rowIndex, 10),
            cellIndex: parseInt(cellIndex, 10),
            value: presenter.parseValue(rawValue)
        }
    };

    presenter.createElementData = function (rowIndex, cellIndex, elementValue, isVisiblePosition = false) {
        return new ElementData(rowIndex, cellIndex, elementValue, isVisiblePosition)
    };

    function ElementData (rowIndex, cellIndex, elementValue, isVisiblePosition = false) {
        this.rawValue = elementValue;
        this.parsedValue = presenter.parseValue(elementValue);
        this.type = presenter.getElementType(elementValue);

        // Element's position data
        this.visiblePosition = isVisiblePosition;
        this.rowIndex = rowIndex;
        this.cellIndex = cellIndex;
    }

    ElementData.prototype.getPosition = function () {
        return {
            rowIndex: this.rowIndex,
            cellIndex: this.cellIndex,
        };
    }

    ElementData.prototype.createAnswer = function () {
        return {
            rowIndex: this.rowIndex,
            cellIndex: this.cellIndex,
            value: this.parsedValue,
        };
    }

    ElementData.prototype.isVisiblePosition = function () {
        return this.visiblePosition;
    }

    presenter.markIncorrect = function(element) {
        presenter.markAs(element, "incorrect");
    };

    presenter.markCorrect = function(element) {
        presenter.markAs(element, "correct");
    };

    presenter.markEmpty = function(element) {
        presenter.markAs(element, "empty");
    };

    presenter.markAs = function(element, className) {
        $(element).addClass(className);
        $(element).attr("disabled", "disabled");
    };

    presenter.setWorkMode = function() {
        if(presenter.isNotActivity){
            return;
        }

        this.clean(true, false);
    };

    presenter.reset = function() {
        presenter.isErrorCheckingMode &amp;&amp; presenter.setWorkMode();
        presenter.isShowAnswersActive &amp;&amp; presenter.hideAnswers();
        presenter.isGradualShowAnswersActive &amp;&amp; presenter.gradualHideAnswers();
        presenter.clean(true, true);

        const $helpBoxesInputs = presenter.getHelpBoxesInputs();
        const defaultValues = presenter.helpBoxesDefaultValues.flat();
        $.each($helpBoxesInputs, function(index){
            $(this).val(defaultValues[index]);
        });
        presenter.userAnswers = [];
        presenter.helpBoxesUserAnswers = [];

        presenter.setVisibility(presenter.isVisibleByDefault);
        presenter.isVisible = presenter.isVisibleByDefault;
        presenter.isScoreSaved = false;
        presenter.isDisabled = false;
    };

    presenter.clean = function(removeMarks, removeValues) {
        const $inputs = presenter.getInputs();
        $.each($inputs, function(){
            if(removeMarks) {
                presenter.removeMark($(this));
            }
            if(removeValues) {
                presenter.removeValue($(this));
            }
        });
        if (presenter.$view.hasClass('wrong')) {
            presenter.$view.removeClass('wrong');
        } else if (presenter.$view.hasClass('correct')) {
            presenter.$view.removeClass('correct');
        }
    };

    presenter.removeMark = function(element) {
        $(element).removeClass("incorrect correct empty");
        $(element).removeAttr("disabled");
    };

    presenter.removeValue = function(element) {
        $(element).val("");
    };

    presenter.getEmptyBoxesInputsData = function() {
        const $inputs = presenter.getEmptyBoxesInputs();
        const inputsData = {
            values : [],
            correctAnswersCount : 0,
            incorrectAnswersCount : 0
        };

        presenter.handleShownAnswers();

        $.each($inputs, function () {
            let value = $(this).val();
            value = presenter.parseValue(value);
            inputsData.values.push(value);

            if (value === undefined || value === "") return true; // jQuery.each continue

            const answer = presenter.createAnswer($(this).attr("row"), $(this).attr("cell"), $(this).val());
            if( presenter.isCorrect(answer) ) {
                inputsData.correctAnswersCount++;
            } else {
                inputsData.incorrectAnswersCount++;
            }
        });
        return inputsData;
    };

    presenter.getHelpBoxesInputsData = function() {
        const $inputs = presenter.getHelpBoxesInputs();
        const inputsData = {
            values : [],
        };

        presenter.handleShownAnswers();

        $.each($inputs, function () {
            let value = $(this).val();
            value = presenter.parseValue(value);
            inputsData.values.push(value);
        });
        return inputsData;
    };

    presenter.getState = function() {
        presenter.handleShownAnswers();

        return JSON.stringify({
            "inputsData" : this.getEmptyBoxesInputsData(),
            "helpBoxesInputsData" : presenter.getHelpBoxesInputsData(),
            "isVisible" : presenter.isVisible,
        });
    };

    presenter.setState = function(stateString) {
        if (ModelValidationUtils.isStringEmpty(stateString)) return;

        const state = JSON.parse(stateString);
        if (state.inputsData) {
            const $emptyBoxesInputs = presenter.getEmptyBoxesInputs();
            const inputsData = state.inputsData;
            $.each($emptyBoxesInputs, function(index){
                $(this).val(inputsData.values[index].toString());
            });
        }

        if (state.helpBoxesInputsData) {
            const $helpBoxesInputs = presenter.getHelpBoxesInputs();
            const helpBoxesInputsData = state.helpBoxesInputsData;
            $.each($helpBoxesInputs, function(index){
                $(this).val(helpBoxesInputsData.values[index].toString());
            });
        }

        if (state.isVisible != undefined) {
            presenter.isVisible = state.isVisible;
            presenter.setVisibility(presenter.isVisible);
        }
    };

    presenter.getScore = function() {
        if(presenter.isNotActivity){
            return 0;
        }

        if (presenter.isShowAnswersActive || presenter.isGradualShowAnswersActive) {
            return presenter.currentScore;
        }
        return this.getPoints("correct");
    };

    presenter.getMaxScore = function() {
        if(presenter.isNotActivity){
            return 0;
        }

        if (presenter.isShowAnswersActive || presenter.isGradualShowAnswersActive) {
            return presenter.currentMaxScore;
        }
        return this.getPoints("all");
    };

    presenter.getErrorCount = function() {
        if(presenter.isNotActivity){
            return 0;
        }

        if (presenter.isShowAnswersActive || presenter.isGradualShowAnswersActive) {
            return presenter.currentErrorCount;
        }
        return this.getPoints("incorrect");
    };

    presenter.getPoints = function(type) {
        const inputsData = this.getEmptyBoxesInputsData();

        if (presenter.isCommutativity) {
            switch (type) {
                case 'correct':
                    return presenter.isAllEmptyBoxInputsCorrectlyFilled() ? 1 : 0;
                case 'incorrect':
                    if (presenter.isAllEmptyBoxInputsFilled()) {
                        return presenter.isAllEmptyBoxInputsCorrectlyFilled() ? 0 : 1;
                    } else {
                        return 0;
                    }
                case 'all':
                    return 1;
                default:
                    return 0;
            }
        } else {
            switch (type) {
                case 'correct':
                    return inputsData.correctAnswersCount;
                case 'incorrect':
                    return inputsData.incorrectAnswersCount;
                case 'all':
                    return inputsData.values.length;
                default:
                    return 0;
            }
        }
    };

    presenter.createEventData = function(value, item, isCorrect) {
        return {
            source : this.model.ID,
            item : "" + item,
            value : "" + value,
            score : "" + isCorrect
        };
    };

    presenter.triggerValueChangeEvent = function(value, item, isCorrect) {
        var eventData = this.createEventData(value, item, isCorrect);
        if (this.playerController !== null) {
            this.playerController.getEventBus().sendEvent('ValueChanged', eventData);
        }
    };

    presenter.setPlayerController = function(controller) {
        this.playerController = controller;
    };

    presenter.onEventReceived = function (eventName, eventData) {
        switch (eventName) {
            case "ShowAnswers":
                presenter.showAnswers();
                break;

            case "GradualShowAnswers":
                presenter.gradualShowAnswers(eventData);
                break;

            case "HideAnswers":
                presenter.hideAnswers();
                break;

            case "GradualHideAnswers":
                presenter.gradualHideAnswers();
                break;
        }
    };

    presenter.disableInputs = function () {
        if (presenter.isDisabled) {
            return;
        }

        presenter.isDisabled = true;
        const $inputs = presenter.getInputs();
        $.each($inputs, function() {
            $(this).attr("disabled", true);
        });
    };

    presenter.enableInputs = function () {
        presenter.isDisabled = false;

        const $inputs = presenter.getInputs();
        $.each($inputs, function() {
            $(this).attr("disabled", false);
        });
    };

    presenter.showAnswers = function () {
        if (presenter.isNotActivity){
            return;
        }

        if (presenter.isShowAnswersActive) {
            presenter.hideAnswers();
        }

        presenter.saveCurrentScore();
        presenter.isShowAnswersActive = true;
        presenter.clean(true,false);
        presenter.isDisabled = true;
        const correctAnswers = presenter.correctAnswersList;

        const $emptyBoxesInputs = presenter.getEmptyBoxesInputs();
        $.each($emptyBoxesInputs, function(index){
            $(this).addClass('writing-calculations_show-answers');
            $(this).attr("disabled", true);
            presenter.userAnswers.push($(this).val());
            $(this).val(correctAnswers[index].value);
        });

        const $helpBoxesInputs = presenter.getHelpBoxesInputs();
        $.each($helpBoxesInputs, function(){
            $(this).addClass('writing-calculations_show-answers');
            $(this).attr("disabled", true);
            presenter.helpBoxesUserAnswers.push($(this).val());
            $(this).val("");
        });
    };

    presenter.gradualShowAnswers = function (eventData) {
        presenter.disableInputs();

        if (eventData.moduleID !== presenter.ID) {
            return;
        }

        if (presenter.showAllAnswersInGSA) {
            presenter.showAnswers();
            return;
        }

        if (!presenter.isGradualShowAnswersActive) {
            const $helpBoxesInputs = presenter.getHelpBoxesInputs();
            $.each($helpBoxesInputs, function(){
                $(this).addClass('writing-calculations_show-answers');
                $(this).attr("disabled", true);
                presenter.helpBoxesUserAnswers.push($(this).val());
                $(this).val("");
            });
        }
        presenter.isGradualShowAnswersActive = true;
        presenter.saveCurrentScore();
        const input = presenter.getEmptyBoxesInputs()[eventData.item];

        $(input).addClass('writing-calculations_show-answers');
        presenter.userAnswers.push($(input).val());
        $(input).val(presenter.correctAnswersList[eventData.item].value);
    };

    presenter.hideAnswers = function () {
        if (presenter.isNotActivity || !presenter.isShowAnswersActive) {
            return;
        }

        presenter.isShowAnswersActive = false;
        const $emptyBoxesInputs = presenter.getEmptyBoxesInputs();
        $.each($emptyBoxesInputs, function(index){
            $(this).val(presenter.userAnswers[index]);
            $(this).removeClass('writing-calculations_show-answers');
            $(this).attr("disabled", false);
        });

        const $helpBoxesInputs = presenter.getHelpBoxesInputs();
        $.each($helpBoxesInputs, function(index){
            $(this).val(presenter.helpBoxesUserAnswers[index]);
            $(this).removeClass('writing-calculations_show-answers');
            $(this).attr("disabled", false);
        });

        presenter.userAnswers = [];
        presenter.helpBoxesUserAnswers = [];
        presenter.isDisabled = false;
    };

    presenter.gradualHideAnswers = function () {
        if (presenter.showAllAnswersInGSA &amp;&amp; presenter.isShowAnswersActive) {
            presenter.hideAnswers();
            return;
        }

        presenter.enableInputs();

        if (presenter.isNotActivity || !presenter.isGradualShowAnswersActive) {
            return;
        }

        presenter.isGradualShowAnswersActive = false;
        presenter.isScoreSaved = false;

        const $emptyBoxesInputs = presenter.getEmptyBoxesInputs();
        presenter.userAnswers.forEach((userAnswer, index) =&gt; {
            const input = $emptyBoxesInputs[index];
            $(input).val(presenter.userAnswers[index]);
            $(input).removeClass('writing-calculations_show-answers');
        });

        const $helpBoxesInputs = presenter.getHelpBoxesInputs();
        $.each($helpBoxesInputs, function(index){
            $(this).val(presenter.helpBoxesUserAnswers[index]);
            $(this).removeClass('writing-calculations_show-answers');
        });

        presenter.userAnswers = [];
        presenter.helpBoxesUserAnswers = [];
    };

    presenter.saveCurrentScore = function () {
        if (presenter.isScoreSaved) {
            return;
        }
        presenter.currentScore = presenter.getScore();
        presenter.currentErrorCount = presenter.getErrorCount();
        presenter.currentMaxScore = presenter.getMaxScore();

        presenter.isScoreSaved = true;
    }

    presenter.getActivitiesCount = function () {
        return presenter.showAllAnswersInGSA ? 1 : presenter.getEmptyBoxesInputs().length;
    }

    function createRowsStatuesOfNavigability(modelRowsAltTexts, elementsData) {
        presenter.rowsStatusesOfNavigabilityInAltTTSNavigation = [{rowIndex: -1, isNavigable: true}]; // First navigable row is DESCRIPTION_OF_OPERATION
        elementsData.forEach((rowElements, rowIndex) =&gt; {
            let isNavigable = false;
            for (let columnIndex = 0; columnIndex &lt; rowElements.length; columnIndex++) {
                const containerType = rowElements[columnIndex].type;
                if (containerType !== presenter.ELEMENT_TYPE.LINE &amp;&amp; containerType !== presenter.ELEMENT_TYPE.EMPTY_SPACE) {
                    isNavigable = true;
                    break;
                }
            }
            presenter.rowsStatusesOfNavigabilityInAltTTSNavigation.push({rowIndex: rowIndex, isNavigable: isNavigable});
        });
    }

    presenter.validateRowsAltTexts = function(modelRowsAltTexts) {
        const numberOfNavigableRows = presenter.rowsStatusesOfNavigabilityInAltTTSNavigation.filter((status) =&gt; status.isNavigable).length;
        const numberOfAdditionalRows = 1;
        if (numberOfNavigableRows - numberOfAdditionalRows === modelRowsAltTexts.length) {
            return getCorrectObject(modelRowsAltTexts);
        }
        return getErrorObject("RAT01", {
            rowsAltTextsNumber: modelRowsAltTexts.length,
            navigableRowsNumber: numberOfNavigableRows - numberOfAdditionalRows
        });
    }

    presenter.validateModelValue = function (modelValue) {
        const rows = presenter.convertStringToArray(modelValue);
        const elementsData = [];

        for (let rowIndex = 0; rowIndex &lt; rows.length; rowIndex++) {
            let row = rows[rowIndex];

            const validatedAnswerBracketsNumber = validateAnswerBracketsNumber(row, rowIndex);
            if (!validatedAnswerBracketsNumber.isValid) {
                return validatedAnswerBracketsNumber;
            }

            const validatedHelpBracketsNumber = validateHelpBracketsNumber(row, rowIndex);
            if (!validatedHelpBracketsNumber.isValid) {
                return validatedHelpBracketsNumber;
            }

            let cellBoxIndex = 0;
            elementsData.push([]);
            for (let startingIndex = 0; startingIndex &lt; row.length; ) {
                let elementValue = row[startingIndex];
                let elementLength = 1;
                let elementData = {};

                const isEndOfAnswerGap = elementValue === ']';
                if (isEndOfAnswerGap) {
                    return getErrorObject("V02", {rowIndex: rowIndex + 1});
                }

                const isEndOfHelpGap = elementValue === '}';
                if (isEndOfHelpGap) {
                    return getErrorObject("V07", {rowIndex: rowIndex + 1});
                }

                const isStartOfAnswerGap = elementValue === '[';
                const isStartOfHelpGap = elementValue === '{';
                if (isStartOfHelpGap) {
                    const validatedGapLength = validateHelpGapLength(row, rowIndex, startingIndex);
                    if (!validatedGapLength.isValid) {
                        return validatedGapLength;
                    }

                    elementLength = validatedGapLength.value;
                    elementValue = row.slice(startingIndex, startingIndex + elementLength);
                    elementData = presenter.createElementData(rowIndex + 1, ++cellBoxIndex, elementValue, true);
                    if (elementData.type !== presenter.ELEMENT_TYPE.HELP_BOX) {
                        return getErrorObject("V08", {rowIndex: rowIndex + 1, value: elementData.rawValue});
                    }

                    if (presenter.helpBoxesDefaultValues[rowIndex] === undefined) {
                        presenter.helpBoxesDefaultValues[rowIndex] = [];
                    }

                    const answer = elementData.createAnswer();
                    presenter.helpBoxesDefaultValues[rowIndex].push(answer.value);
                } else if (isStartOfAnswerGap) {
                    const validatedGapLength = validateAnswerGapLength(row, rowIndex, startingIndex);
                    if (!validatedGapLength.isValid) {
                        return validatedGapLength;
                    }

                    elementLength = validatedGapLength.value;
                    elementValue = row.slice(startingIndex, startingIndex + elementLength);
                    elementData = presenter.createElementData(rowIndex + 1, ++cellBoxIndex, elementValue, true);
                    if (elementData.type !== presenter.ELEMENT_TYPE.EMPTY_BOX) {
                        return getErrorObject("V05", {rowIndex: rowIndex + 1, value: elementData.rawValue});
                    }

                    if (presenter.answers[rowIndex] === undefined) {
                        presenter.answers[rowIndex] = [];
                    }

                    const answer = elementData.createAnswer();
                    presenter.answers[rowIndex].push(answer.value);
                    presenter.correctAnswersList.push(answer);
                } else {
                    if (presenter.isCommutativity) {
                        ++cellBoxIndex;
                    }
                    elementData = presenter.createElementData(rowIndex + 1, cellBoxIndex, elementValue, presenter.isCommutativity);
                    if (!isNaN(parseInt(elementValue, 10))) {
                        if (presenter.answers[rowIndex] === undefined) {
                            presenter.answers[rowIndex] = [];
                        }

                        presenter.answers[rowIndex].push(elementValue);
                    }
                }
                startingIndex += elementLength;
                elementsData[rowIndex].push(elementData);
            }
        }
        return getCorrectObject(elementsData);
    }

    function validateAnswerBracketsNumber(row, rowIndex) {
        const errorMessageSubstitutions = {rowIndex: rowIndex + 1};
        const openingBracketsCount = row.split('').filter(x =&gt; x === '[').length;
        const closingBracketsCount = row.split('').filter(x =&gt; x === ']').length;
        if (openingBracketsCount &gt; closingBracketsCount) {
            return getErrorObject("V01", errorMessageSubstitutions);
        } else if (openingBracketsCount &lt; closingBracketsCount) {
            return getErrorObject("V02", errorMessageSubstitutions);
        }
        return getCorrectObject(openingBracketsCount + closingBracketsCount);
    }

    function validateHelpBracketsNumber(row, rowIndex) {
        const errorMessageSubstitutions = {rowIndex: rowIndex + 1};
        const openingBracketsCount = row.split('').filter(x =&gt; x === '{').length;
        const closingBracketsCount = row.split('').filter(x =&gt; x === '}').length;
        if (openingBracketsCount &gt; closingBracketsCount) {
            return getErrorObject("V06", errorMessageSubstitutions);
        } else if (openingBracketsCount &lt; closingBracketsCount) {
            return getErrorObject("V07", errorMessageSubstitutions);
        }
        return getCorrectObject(openingBracketsCount + closingBracketsCount);
    }

    function validateAnswerGapLength(rowString, rowIndex, startIndex) {
        const errorMessageSubstitutions = {rowIndex: rowIndex + 1};

        const nextClosingAnswerBracketIndex = rowString.indexOf(']', startIndex);
        if (nextClosingAnswerBracketIndex === -1) {
            return getErrorObject("V01", errorMessageSubstitutions);
        }

        const nextOpeningAnswerBracketIndex = rowString.indexOf('[', startIndex + 1);
        if (nextOpeningAnswerBracketIndex !== - 1 &amp;&amp; nextOpeningAnswerBracketIndex &lt;= nextClosingAnswerBracketIndex) {
            return getErrorObject("V01", errorMessageSubstitutions);
        }

        const length = nextClosingAnswerBracketIndex - startIndex + 1;
        if (length === 2) {
            return getErrorObject("V03", errorMessageSubstitutions);
        }

        if (!presenter.multisigns &amp;&amp; length !== 3) {
            return getErrorObject("V04", errorMessageSubstitutions);
        }
        return getCorrectObject(length);
    }

    function validateHelpGapLength(rowString, rowIndex, startIndex) {
        const errorMessageSubstitutions = {rowIndex: rowIndex + 1};

        const nextClosingHelpBracketIndex = rowString.indexOf('}', startIndex);
        if (nextClosingHelpBracketIndex === -1) {
            return getErrorObject("V06", errorMessageSubstitutions);
        }

        const nextOpeningHelpBracketIndex = rowString.indexOf('{', startIndex + 1);
        if (nextOpeningHelpBracketIndex !== - 1 &amp;&amp; nextOpeningHelpBracketIndex &lt;= nextClosingHelpBracketIndex) {
            return getErrorObject("V07", errorMessageSubstitutions);
        }

        const length = nextClosingHelpBracketIndex - startIndex + 1;
        return getCorrectObject(length);
    }

    presenter.showErrorMessage = function(message, substitutions) {
        let errorContainer;
        if (typeof(substitutions) == 'undefined') {
            errorContainer = '&lt;p&gt;' + message + '&lt;/p&gt;';
        } else {
            let messageSubst = message;
            for (const key in substitutions) {
                if (!substitutions.hasOwnProperty(key)) continue;
                messageSubst = messageSubst.replace('%' + key + '%', substitutions[key]);
            }
            errorContainer = '&lt;p&gt;' + messageSubst + '&lt;/p&gt;';
        }

        presenter.$view.html(errorContainer);
    };

    function getErrorObject (errorCode, errorMessageSubstitutions) {
        return {
            isValid: false,
            errorCode: errorCode,
            errorMessageSubstitutions: errorMessageSubstitutions,
        };
    }

    function getCorrectObject (value) {
        return {
            isValid: true,
            value: value
        };
    }

    presenter.setWCAGStatus = function(isOn) {
        presenter.isWCAGOn = isOn;
    }

     presenter.keyboardController = function(keycode, isShift, event) {
        if (!presenter.isGapFocused ||
            (isAlternativeKeyboardNavigationWithTTS()
                &amp;&amp; [window.KeyboardControllerKeys.ARROW_LEFT, window.KeyboardControllerKeys.ARROW_RIGHT].includes(keycode))
        ) {
            event.preventDefault();
        }
        const shouldReadAfterMove = !presenter.isGapFocused || isAlternativeKeyboardNavigationWithTTS();

        switch (keycode) {
            case window.KeyboardControllerKeys.TAB:
                if (isShift) {
                    prevTabElement();
                    if (shouldReadAfterMove) readSelectedElement();
                } else {
                    nextTabElement()
                    if (shouldReadAfterMove) readSelectedElement();
                }
                break;
            case window.KeyboardControllerKeys.ENTER:
                if (isShift) {
                    exitModuleInTTS();
                } else {
                    enter();
                    readSelectedElement();
                }
                break;
            case window.KeyboardControllerKeys.SPACE:
                break;
            case window.KeyboardControllerKeys.ARROW_UP:
                prevRow();
                if (shouldReadAfterMove) readSelectedElement();
                break;
            case window.KeyboardControllerKeys.ARROW_DOWN:
                nextRow();
                if (shouldReadAfterMove) readSelectedElement();
                break;
            case window.KeyboardControllerKeys.ARROW_LEFT:
                prevColumn();
                if (shouldReadAfterMove) readSelectedElement();
                break;
            case window.KeyboardControllerKeys.ARROW_RIGHT:
                nextColumn();
                if (shouldReadAfterMove) readSelectedElement();
                break;
            case window.KeyboardControllerKeys.ESCAPE:
                escape(event);
                break;
        }
    }

    function nextRow() {
        if (!presenter.isWCAGOn || (presenter.isGapFocused &amp;&amp; !presenter.useAlternativeTTSNavigation)) return;
        if (presenter.keyboardState.y + 1 &lt; presenter.tableElements.length) {
            if (presenter.useAlternativeTTSNavigation) {
                presenter.keyboardState.y = findNextNavigableRowIndexForAlternativeTTSNavigation(presenter.keyboardState.y);
            } else {
                presenter.keyboardState.y += 1;
            }
            if (presenter.keyboardState.y !== DESCRIPTION_OF_OPERATION_ROW_INDEX) {
                if (presenter.useAlternativeTTSNavigation) {
                    presenter.keyboardState.x = ALT_TEXT_FOR_ROW_COLUMN_INDEX;
                } else if (presenter.keyboardState.x &gt;= presenter.tableElements[presenter.keyboardState.y].length) {
                    presenter.keyboardState.x = presenter.tableElements[presenter.keyboardState.y].length - 1;
                }
            }
        }
        updateGapIndex();
        selectKeyboardNavigationElement();
    }

    function prevRow() {
        if (!presenter.isWCAGOn || (presenter.isGapFocused &amp;&amp; !presenter.useAlternativeTTSNavigation)) return;
        if (presenter.keyboardState.y - 1 &gt;= DESCRIPTION_OF_OPERATION_ROW_INDEX) {
            if (presenter.useAlternativeTTSNavigation) {
                presenter.keyboardState.y = findPrevNavigableRowIndexForAlternativeTTSNavigation(presenter.keyboardState.y);
            } else {
                presenter.keyboardState.y -= 1;
            }
            if (presenter.keyboardState.y === DESCRIPTION_OF_OPERATION_ROW_INDEX) {
                presenter.keyboardState.x = DESCRIPTION_OF_OPERATION_COLUMN_INDEX;
            } else if (presenter.useAlternativeTTSNavigation) {
                presenter.keyboardState.x = ALT_TEXT_FOR_ROW_COLUMN_INDEX;
            } else if (presenter.keyboardState.x &gt;= presenter.tableElements[presenter.keyboardState.y].length) {
                presenter.keyboardState.x = presenter.tableElements[presenter.keyboardState.y].length - 1;
            }
        }
        updateGapIndex();
        selectKeyboardNavigationElement();
    }

    function nextColumn() {
        if (!presenter.isWCAGOn || (presenter.isGapFocused &amp;&amp; !presenter.useAlternativeTTSNavigation)) return;
        if (presenter.keyboardState.y !== DESCRIPTION_OF_OPERATION_ROW_INDEX) {
            if (presenter.useAlternativeTTSNavigation) {
                presenter.keyboardState.x = findNextGapColumnInRow(presenter.keyboardState.y, presenter.keyboardState.x);
            } else if (presenter.keyboardState.x + 1 &lt; presenter.tableElements[presenter.keyboardState.y].length) {
                presenter.keyboardState.x += 1;
            }
        }
        updateGapIndex();
        selectKeyboardNavigationElement();
    }

    function findNextGapColumnInRow(y, x) {
        for (let gapIndex = 0; gapIndex &lt; presenter.gapElements.length; gapIndex++) {
            const gapElement = presenter.gapElements[gapIndex];
            if (gapElement.y !== y) {
                continue;
            }
            if (gapElement.x &gt; x) {
                return gapElement.x
            }
        }
        return x;
    }

    function prevColumn() {
        if (!presenter.isWCAGOn || (presenter.isGapFocused &amp;&amp; !presenter.useAlternativeTTSNavigation)) return;
        if (presenter.keyboardState.y !== DESCRIPTION_OF_OPERATION_ROW_INDEX) {
            if (presenter.useAlternativeTTSNavigation) {
                presenter.keyboardState.x = findPrevColumnInRow(presenter.keyboardState.y, presenter.keyboardState.x);
            } else if (presenter.keyboardState.x &gt; 0) {
                presenter.keyboardState.x -= 1;
            }
        }
        updateGapIndex();
        selectKeyboardNavigationElement();
    }

    function findPrevColumnInRow(y, x) {
        for (let gapIndex = presenter.gapElements.length - 1; gapIndex &gt;= 0; gapIndex--) {
            const gapElement = presenter.gapElements[gapIndex];
            if (gapElement.y !== y) {
                continue;
            }
            if (x === undefined) {
                return gapElement.x
            } else if (gapElement.x &lt; x) {
                return gapElement.x
            }
        }
        return ALT_TEXT_FOR_ROW_COLUMN_INDEX;
    }

    function nextTabElement() {
        if (isAlternativeKeyboardNavigationWithTTS()) {
            nextGapOrRow();
        } else {
            nextGap();
        }
    }

    function nextGapOrRow() {
        const nextX = findNextGapColumnInRow(presenter.keyboardState.y, presenter.keyboardState.x);
        if (nextX === presenter.keyboardState.x) {
            nextRow();
        } else {
            presenter.keyboardState.x = nextX;
            updateGapIndex();
            selectKeyboardNavigationElement();
        }
    }

    function nextGap() {
        if (presenter.isGapFocused) return;
        if (presenter.keyboardState.gapIndex + 1 &lt; presenter.gapElements.length) {
            presenter.keyboardState.gapIndex += 1;
            const gap = presenter.gapElements[presenter.keyboardState.gapIndex];
            presenter.keyboardState.x = gap.x;
            presenter.keyboardState.y = gap.y;
            selectKeyboardNavigationElement();
        }
    }

    function prevTabElement() {
        if (isAlternativeKeyboardNavigationWithTTS()) {
            prevGapOrRow();
        } else {
            prevGap();
        }
    }

    function prevGapOrRow() {
        let foundGapOrRow = false;
        let prevX = findPrevColumnInRow(presenter.keyboardState.y, presenter.keyboardState.x);
        if (prevX === presenter.keyboardState.x) {
            const prevNavigableRowIndex = findPrevNavigableRowIndexForAlternativeTTSNavigation(presenter.keyboardState.y)
            if (prevNavigableRowIndex !== presenter.keyboardState.y) {
                foundGapOrRow = true;
                presenter.keyboardState.y = prevNavigableRowIndex;
                if (prevNavigableRowIndex === DESCRIPTION_OF_OPERATION_ROW_INDEX) {
                    prevX = DESCRIPTION_OF_OPERATION_COLUMN_INDEX;
                } else {
                    prevX = findPrevColumnInRow(prevNavigableRowIndex);
                }
            }
        } else {
            foundGapOrRow = true;
        }

        if (foundGapOrRow) {
            presenter.keyboardState.x = prevX;
            updateGapIndex();
            selectKeyboardNavigationElement();
        }
    }

    function findNextNavigableRowIndexForAlternativeTTSNavigation(currentRowIndex) {
        for (let nextRowIndex = currentRowIndex + 1; nextRowIndex &lt; presenter.tableElements.length; nextRowIndex++) {
            if (isRowNavigableInAltTTSNavigation(nextRowIndex)) {
                return nextRowIndex;
            }
        }
        return currentRowIndex;
    }

    function findPrevNavigableRowIndexForAlternativeTTSNavigation(currentRowIndex) {
        for (let prevRowIndex = currentRowIndex - 1; prevRowIndex &gt;= DESCRIPTION_OF_OPERATION_ROW_INDEX; prevRowIndex--) {
            if (isRowNavigableInAltTTSNavigation(prevRowIndex)) {
                return prevRowIndex;
            }
        }
        return DESCRIPTION_OF_OPERATION_ROW_INDEX;
    }

    function isRowNavigableInAltTTSNavigation(rowIndex) {
        for (let i = 0; i &lt; presenter.rowsStatusesOfNavigabilityInAltTTSNavigation.length; i++) {
            if (rowIndex === presenter.rowsStatusesOfNavigabilityInAltTTSNavigation[i].rowIndex) {
                return presenter.rowsStatusesOfNavigabilityInAltTTSNavigation[i].isNavigable;
            }
        }
        return false;
    }

    function prevGap() {
        if (presenter.isGapFocused) return;
        if (presenter.keyboardState.gapIndex - 1 &gt; -1 &amp;&amp; presenter.gapElements.length &gt; 0) {
            presenter.keyboardState.gapIndex -= 1;
            const gap = presenter.gapElements[presenter.keyboardState.gapIndex];
            presenter.keyboardState.x = gap.x;
            presenter.keyboardState.y = gap.y;
            selectKeyboardNavigationElement();
        }
    }

    function updateGapIndex() {
        presenter.keyboardState.gapIndex = NO_GAP_INDEX;
        for (let i = 0; i &lt; presenter.gapElements.length; i++) {
            const gapElement = presenter.gapElements[i];
            if (presenter.keyboardState.x === gapElement.x &amp;&amp; presenter.keyboardState.y === gapElement.y) {
                presenter.keyboardState.gapIndex = i;
                break;
            }
        }
    }

    function enter() {
        if (isAlternativeKeyboardNavigationWithTTS() || presenter.keyboardState.y === DESCRIPTION_OF_OPERATION_ROW_INDEX) {
            return;
        }

        const $gap = presenter.tableElements[presenter.keyboardState.y][presenter.keyboardState.x].find('input.writing-calculations-input');
        if ($gap.length &gt; 0) {
            presenter.isGapFocused = true;
            $gap.addClass(presenter.KEYBOARD_SELECTED_CLASS);
            $gap.focus();
        }
    }

    function escape(event) {
        if (!isAlternativeKeyboardNavigationWithTTS() &amp;&amp; presenter.isGapFocused) {
            if (event) {
                event.stopPropagation();
                event.preventDefault();
            }
            const $gap = presenter.tableElements[presenter.keyboardState.y][presenter.keyboardState.x].find('input.writing-calculations-input');
            if ($gap.length &gt; 0) {
                setTimeout(()=&gt;{
                    presenter.isGapFocused = false;
                }, 0);
                $gap.removeClass(presenter.KEYBOARD_SELECTED_CLASS);
                $gap.blur();
            }
        } else {
            exitModuleInTTS();
        }
    }

    function exitModuleInTTS() {
        presenter.isGapFocused = false;
        presenter.keyboardState.x = DESCRIPTION_OF_OPERATION_COLUMN_INDEX;
        presenter.keyboardState.y = DESCRIPTION_OF_OPERATION_ROW_INDEX;
        presenter.keyboardState.gapIndex = NO_GAP_INDEX;
        selectKeyboardNavigationElement();
    }

    function selectKeyboardNavigationElement() {
        if (isAlternativeKeyboardNavigationWithTTS()) {
            document.activeElement.blur();
        }

        presenter.$view.find('.'+presenter.KEYBOARD_SELECTED_CLASS).removeClass(presenter.KEYBOARD_SELECTED_CLASS);
        if (presenter.keyboardState.y === DESCRIPTION_OF_OPERATION_ROW_INDEX) {
            return;
        }
        if (isAlternativeKeyboardNavigationWithTTS()) {
            if (presenter.keyboardState.x === ALT_TEXT_FOR_ROW_COLUMN_INDEX) {
                presenter.tableElements[presenter.keyboardState.y][0].parent().addClass(presenter.KEYBOARD_SELECTED_CLASS);
            } else {
                presenter.isGapFocused = true;
                const $gap = presenter.gapElements[presenter.keyboardState.gapIndex].el.find('input.writing-calculations-input');
                const gap = $gap[0];
                gap.focus();
                gap.setSelectionRange(gap.value.length, gap.value.length);
                $gap.addClass(presenter.KEYBOARD_SELECTED_CLASS);
            }
        } else {
            presenter.tableElements[presenter.keyboardState.y][presenter.keyboardState.x].children().first().addClass(presenter.KEYBOARD_SELECTED_CLASS);
        }
    }

    function isAlternativeKeyboardNavigationWithTTS() {
        return presenter.useAlternativeTTSNavigation &amp;&amp; presenter.isWCAGOn;
    }

    function readSelectedElement() {
        presenter.readSelectedElement(presenter.keyboardState.x, presenter.keyboardState.y);
    }

    presenter.getCellTitle = function (x, y) {
        var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        var title = "";
        do {
            var mod = x % 26;
            title = alphabet[mod] + " " + title;
            x -= mod;
            x = x / 26 - 1;
        } while (x &gt;= 0);
        title += (y+1);
        return title;
    }

    presenter.readSelectedElement = function(x, y) {
        if (!presenter.isWCAGOn) return;
        let data = [];
        if (y &lt; 0) {
            data.push(window.TTSUtils.getTextVoiceObject(presenter.descriptionOfOperation, presenter.langTag));
        } else if (presenter.useAlternativeTTSNavigation &amp;&amp; x === ALT_TEXT_FOR_ROW_COLUMN_INDEX) {
            let navigableRowsNumber = 0;
            for (let rowIndex = 0; rowIndex &lt;= presenter.keyboardState.y; rowIndex++) {
                if (isRowNavigableInAltTTSNavigation(rowIndex)) {
                    navigableRowsNumber++;
                }
            }
            data.push(window.TTSUtils.getTextVoiceObject(presenter.rowsAltTexts[navigableRowsNumber-1], presenter.langTag));
        } else {
            if (!presenter.useAlternativeTTSNavigation) {
                data.push(window.TTSUtils.getTextVoiceObject(presenter.getCellTitle(x,y)));
            }

            const $container = presenter.tableElements[y][x].children().first();
            const containerType = presenter.getContainerType($container);
            let input, inputVoices;

            switch(containerType) {
                case presenter.ELEMENT_TYPE.NUMBER:
                    data.push(window.TTSUtils.getTextVoiceObject($container.html(), presenter.langTag));
                    break;
                case presenter.ELEMENT_TYPE.SYMBOL:
                    const value = $container.data('value');
                    if (value === "*") {
                        data.push(window.TTSUtils.getTextVoiceObject(presenter.speechTexts.multiplicationSign));
                    } else if (value === ":" || value === ")") {
                        data.push(window.TTSUtils.getTextVoiceObject(presenter.speechTexts.divisionSign));
                    } else if (value === "+") {
                        data.push(window.TTSUtils.getTextVoiceObject(presenter.speechTexts.additionSign));
                    } else if (value === "-") {
                        data.push(window.TTSUtils.getTextVoiceObject(presenter.speechTexts.subtractionSign));
                    } else if (value === "#") {
                        data.push(window.TTSUtils.getTextVoiceObject(presenter.speechTexts.equalsSign));
                    }
                    break;
                case presenter.ELEMENT_TYPE.EMPTY_BOX:
                    data.push(window.TTSUtils.getTextVoiceObject(createGapInfoForTTS(x, y, false)));
                    input = $container.find('input');
                    if (input.length &gt; 0) {
                        inputVoices = presenter.getTextVoicesFromInput(input);
                        data = data.concat(inputVoices);
                    }
                    break;
                case presenter.ELEMENT_TYPE.LINE:
                    data.push(window.TTSUtils.getTextVoiceObject(presenter.speechTexts.line));
                    break;
                case presenter.ELEMENT_TYPE.HELP_BOX:
                    data.push(window.TTSUtils.getTextVoiceObject(createGapInfoForTTS(x, y, true)));
                    input = $container.find('input');
                    if (input.length &gt; 0) {
                        inputVoices = presenter.getTextVoicesFromInput(input);
                        data = data.concat(inputVoices);
                    }
                    break;
                default:
                    break;
            }

            const nextSibling = presenter.tableElements[y][x].next();
            if (nextSibling.length &gt; 0 &amp;&amp; presenter.getContainerType(nextSibling.children().first()) == presenter.ELEMENT_TYPE.DOT) {
                data.push(window.TTSUtils.getTextVoiceObject(presenter.speechTexts.decimalSeparator));
            }
        }
        presenter.speak(data);
    }

    function createGapInfoForTTS(x, y, isHelpBox) {
        let gapInfo = isHelpBox ? presenter.speechTexts.additionalGap : presenter.speechTexts.gap;
        if (presenter.useAlternativeTTSNavigation) {
            gapInfo += (getGapIndexInRow(x, y) + 1);
        }
        return gapInfo;
    }

    function getGapIndexInRow(x, y) {
        for (let i = 0; i &lt; presenter.gapElements.length; i++) {
            const gapElement = presenter.gapElements[i];
            if (x === gapElement.x &amp;&amp; y === gapElement.y) {
                return gapElement.gapInRowIndex;
            }
        }
    }

    presenter.getTextVoicesFromInput = function(inputElement) {
        var data =[];
        var value = inputElement.val();
        if (value.length == 0) {
            data.push(window.TTSUtils.getTextVoiceObject(presenter.speechTexts.empty));
        } else {
            data.push(window.TTSUtils.getTextVoiceObject(value, presenter.langTag));
        }
        if (inputElement.hasClass('correct')) {
            data.push(window.TTSUtils.getTextVoiceObject(presenter.speechTexts.correct));
        } else if (inputElement.hasClass('incorrect')) {
            data.push(window.TTSUtils.getTextVoiceObject(presenter.speechTexts.wrong));
        }
        return data;
    };

    presenter.getTextToSpeechOrNull = function (playerController) {
        if (playerController) {
            return playerController.getModule('Text_To_Speech1');
        }

        return null;
    };

    presenter.speak = function (data) {
        if (data.length == 0) return;
        var tts = presenter.getTextToSpeechOrNull(presenter.playerController);

        if (tts &amp;&amp; presenter.isWCAGOn) {
            tts.speak(data);
        }
    }

    presenter.isDeactivationBlocked = function() {
        return presenter.isGapFocused &amp;&amp; !isAlternativeKeyboardNavigationWithTTS();
    };

    return presenter;
}
</presenter></addon>