extensions/interactions/SetInput/directives/oppia-interactive-set-input.component.ts

Summary

Maintainability
A
25 mins
Test Coverage
// Copyright 2014 The Oppia Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Component for the SetInput interaction.
 *
 * IMPORTANT NOTE: The naming convention for customization args that are passed
 * into the component is: the name of the parameter, followed by 'With',
 * followed by the name of the arg.
 */

import {Component, Input, OnInit} from '@angular/core';
import {InteractionAttributesExtractorService} from 'interactions/interaction-attributes-extractor.service';
import {CurrentInteractionService} from 'pages/exploration-player-page/services/current-interaction.service';
import {SetInputRulesService} from './set-input-rules.service';
import eq from 'lodash/eq';
import {SetInputCustomizationArgs} from 'interactions/customization-args-defs';
import {Schema} from 'services/schema-default-value.service';
import {downgradeComponent} from '@angular/upgrade/static';
import {SetInputAnswer} from 'interactions/answer-defs';

@Component({
  selector: 'oppia-interactive-set-input',
  templateUrl: './set-input-interaction.component.html',
})
export class InteractiveSetInputComponent implements OnInit {
  // These properties are initialized using Angular lifecycle hooks
  // and we need to do non-null assertion. For more information, see
  // https://github.com/oppia/oppia/wiki/Guide-on-defining-types#ts-7-1
  @Input() buttonTextWithValue!: string;
  @Input() savedSolution!: SetInputAnswer;
  errorMessage!: string;
  answer!: SetInputAnswer;
  schema!: {
    type: string;
    items:
      | {
          type: string;
        }
      | Schema;
    ui_config: {
      add_element_text: string;
    };
  };

  buttonText!: string;

  constructor(
    private currentInteractionService: CurrentInteractionService,
    private interactionAttributesExtractorService: InteractionAttributesExtractorService,
    private setInputRulesService: SetInputRulesService
  ) {}

  private hasDuplicates(answer: SetInputAnswer): boolean {
    for (var i = 0; i < answer.length; i++) {
      for (var j = 0; j < i; j++) {
        if (eq(answer[i], answer[j])) {
          return true;
        }
      }
    }
    return false;
  }

  private hasBlankOption(answer: SetInputAnswer): boolean {
    return answer.some(element => {
      return element === '';
    });
  }

  updateAnswer(answer: SetInputAnswer): void {
    this.answer = answer;
    this.errorMessage = this.hasDuplicates(answer)
      ? 'I18N_INTERACTIONS_SET_INPUT_DUPLICATES_ERROR'
      : '';
  }

  getSchema(): Schema {
    return this.schema as Schema;
  }

  ngOnInit(): void {
    const {buttonText} =
      this.interactionAttributesExtractorService.getValuesFromAttributes(
        'SetInput',
        {buttonTextWithValue: this.buttonTextWithValue}
      ) as SetInputCustomizationArgs;
    this.buttonText = buttonText.value.unicode;
    this.schema = {
      type: 'list',
      items: {
        type: 'unicode',
      },
      ui_config: {
        // TODO(mili): Translate this in the HTML.
        add_element_text: 'I18N_INTERACTIONS_SET_INPUT_ADD_ITEM',
      },
    };
    if (this.buttonText) {
      this.schema.ui_config.add_element_text = this.buttonText;
    }

    // Adds an input field by default.
    this.answer = this.savedSolution !== undefined ? this.savedSolution : [''];

    this.currentInteractionService.registerCurrentInteraction(
      () => this.submitAnswer(this.answer),
      () => this.isAnswerValid()
    );
  }

  submitAnswer(answer: SetInputAnswer): void {
    if (this.hasDuplicates(answer)) {
      this.errorMessage = 'I18N_INTERACTIONS_SET_INPUT_DUPLICATES_ERROR';
    } else {
      this.errorMessage = '';
      this.currentInteractionService.onSubmit(
        answer,
        this.setInputRulesService
      );
    }
  }

  isAnswerValid(): boolean {
    return this.answer.length > 0 && !this.hasBlankOption(this.answer);
  }
}

angular.module('oppia').directive(
  'oppiaInteractiveSetInput',
  downgradeComponent({
    component: InteractiveSetInputComponent,
  })
);