Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
cloze-element.ts 4.69 KiB
import {
  CompoundElement, InputElement, InputElementValue, UIElement
} from '../uI-element';
import {
  ClozePart, FontElement, LikertColumn, LikertRow
} from '../../interfaces/UIElementInterfaces';
import { TextFieldElement } from '../text-field-element';
import { TextAreaElement } from '../text-area-element';
import { CheckboxElement } from '../checkbox-element';
import { DropdownElement } from '../dropdown-element';
import { DropListElement } from './drop-list';
import { initFontElement } from '../../util/unit-interface-initializer';

// TODO styles like em dont continue after inserted components

export class ClozeElement extends CompoundElement implements FontElement {
  text: string = '<p>Lorem ipsum dolor \\z</p>';

  parts: ClozePart[][] = [];
  childElements: InputElement[] = [];

  fontColor: string = 'black';
  font: string = 'Roboto';
  fontSize: number = 20;
  lineHeight: number = 120;
  bold: boolean = false;
  italic: boolean = false;
  underline: boolean = false;

  constructor(serializedElement: UIElement) {
    super(serializedElement);
    Object.assign(this, serializedElement);
    Object.assign(this, initFontElement(serializedElement));
    this.height = 200;
    this.width = 500;
  }

  setProperty(property: string, value: InputElementValue | string[] | LikertColumn[] | LikertRow[]): void {
    super.setProperty(property, value);

    if (property === 'text') {
      this.createParts(value as string);
    }
  }

  private createParts(htmlText: string): void {
    const elementList = ClozeElement.readElementArray(htmlText);

    this.parts = [];
    elementList.forEach((element: HTMLParagraphElement | HTMLHeadingElement, i: number) => {
      this.parseParagraphs(element, i);
    });
    // console.log('PARTS:', this.parts);
  }

  private static readElementArray(htmlText: string): (HTMLParagraphElement | HTMLHeadingElement)[] {
    const el = document.createElement('html');
    el.innerHTML = htmlText;
    return Array.from(el.children[1].children) as (HTMLParagraphElement | HTMLHeadingElement)[];
  }

  private parseParagraphs(element: HTMLParagraphElement | HTMLHeadingElement, partIndex: number): void {
    this.parts[partIndex] = []; // init array to be able to push
    let [nextSpecialElementIndex, nextElementType] = ClozeElement.getNextSpecialElement(element.innerHTML);
    let indexOffset = 0;

    while (nextSpecialElementIndex !== -1) {
      nextSpecialElementIndex += indexOffset;
      this.parts[partIndex].push({
        type: element.localName,
        value: element.innerHTML.substring(indexOffset, nextSpecialElementIndex),
        style: element.style.cssText
      });

      const newElement = ClozeElement.createElement(nextElementType);
      this.childElements.push(newElement);
      this.parts[partIndex].push({ type: nextElementType, value: newElement });

      indexOffset = nextSpecialElementIndex + 2; // + 2 to get rid of the marker, i.e. '\b'
      [nextSpecialElementIndex, nextElementType] =
        ClozeElement.getNextSpecialElement(element.innerHTML.substring(indexOffset));
    }
    this.parts[partIndex].push({
      type: element.localName,
      value: element.innerHTML.substring(indexOffset),
      style: element.style.cssText
    });
  }

  private static getNextSpecialElement(p: string): [number, string] {
    const x = [];
    if (p.indexOf('\\d') > 0) {
      x.push(p.indexOf('\\d'));
    }
    if (p.indexOf('\\i') > 0) {
      x.push(p.indexOf('\\i'));
    }
    if (p.indexOf('\\z') > 0) {
      x.push(p.indexOf('\\z'));
    }

    const y = Math.min(...x);
    let nextElementType = '';
    switch (p[y + 1]) {
      case 'd': nextElementType = 'dropdown'; break;
      case 'i': nextElementType = 'text-field'; break;
      case 'z': nextElementType = 'drop-list'; break;
      default: return [-1, 'unknown'];
    }
    return [y, nextElementType];
  }

  private static createElement(elementType: string): InputElement {
    const elementModel: UIElement = { type: elementType } as UIElement;
    let newElement: InputElement;
    switch (elementModel.type) {
      case 'text-field':
        newElement = new TextFieldElement(elementModel);
        (newElement as TextFieldElement).label = '';
        break;
      case 'text-area':
        newElement = new TextAreaElement(elementModel);
        break;
      case 'checkbox':
        newElement = new CheckboxElement(elementModel);
        break;
      case 'dropdown':
        newElement = new DropdownElement(elementModel);
        break;
      case 'drop-list':
        newElement = new DropListElement(elementModel);
        newElement.height = 30;
        newElement.width = 100;
        break;
      default:
        throw new Error(`ElementType ${elementModel.type} not found!`);
    }
    return newElement;
  }
}