From a10eae9cd6d3bbbb75045ba7a27336ee2301c5fb Mon Sep 17 00:00:00 2001
From: jojohoch <joachim.hoch@iqb.hu-berlin.de>
Date: Thu, 27 Oct 2022 10:39:52 +0200
Subject: [PATCH] Implement dynamic rows for textarea based on expected number
 of chars

---
 docs/release-notes-editor.txt                 |  3 +++
 docs/unit_definition_changelog.txt            |  2 ++
 .../input-elements/text-area.component.ts     |  6 +++++-
 .../elements/input-elements/text-area.ts      |  4 ++++
 .../pipes/update-textarea-rows.pipe.spec.ts   |  8 ++++++++
 .../common/pipes/update-textarea-rows.pipe.ts | 19 +++++++++++++++++++
 projects/common/shared.module.ts              |  4 +++-
 .../element-model-properties.component.html   | 15 ++++++++++++++-
 projects/editor/src/assets/i18n/de.json       |  4 +++-
 9 files changed, 61 insertions(+), 4 deletions(-)
 create mode 100644 projects/common/pipes/update-textarea-rows.pipe.spec.ts
 create mode 100644 projects/common/pipes/update-textarea-rows.pipe.ts

diff --git a/docs/release-notes-editor.txt b/docs/release-notes-editor.txt
index e3c859fb3..afb2f7936 100644
--- a/docs/release-notes-editor.txt
+++ b/docs/release-notes-editor.txt
@@ -7,6 +7,9 @@ Editor
   Die Bereiche können als Ellipsen, Rechtecke und Dreiecke angelegt werden.
   Ihre Position, Größe, Drehung, Farbe und Rahmenbreite kann eingestellt werden.
   Das Element kann als Pflichtfeld ausgezeichnet werden.
+- Ermöglicht für Eingabebereichen die dynamische Anpassung der Anzahl von Zeilen
+  Auf Grundlage der Angabe der erwarteten Zeichen für die Antwort des Users wird
+  für das gegebene Seitenformat die Anzahl der Zeilen berechnet.
 
 1.34.0
 - Implement GeoGebra applet element
diff --git a/docs/unit_definition_changelog.txt b/docs/unit_definition_changelog.txt
index e1191b805..0cd16e574 100644
--- a/docs/unit_definition_changelog.txt
+++ b/docs/unit_definition_changelog.txt
@@ -41,3 +41,5 @@ iqb-aspect-definition@1.0.0
 
 3.8.0
 - +HotspotImageElement
+- Textarea: +hasDynamicRowCount: boolean;
+            +expectedCharactersCount: number;
diff --git a/projects/common/components/input-elements/text-area.component.ts b/projects/common/components/input-elements/text-area.component.ts
index 05670801d..f50ec9c48 100644
--- a/projects/common/components/input-elements/text-area.component.ts
+++ b/projects/common/components/input-elements/text-area.component.ts
@@ -26,8 +26,12 @@ import { FormElementComponent } from '../../directives/form-element-component.di
                 autocapitalize="none"
                 autocorrect="off"
                 spellcheck="false"
-                rows="{{elementModel.rowCount}}"
                 value="{{elementModel.value}}"
+                [rows]="elementModel.rowCount | updateTextareaRows :
+                        elementModel.expectedCharactersCount :
+                        elementModel.hasDynamicRowCount :
+                        input.offsetWidth :
+                        elementModel.styling.fontSize"
                 [attr.inputmode]="elementModel.showSoftwareKeyboard ? 'none' : 'text'"
                 [formControl]="elementFormControl"
                 [readonly]="elementModel.readOnly"
diff --git a/projects/common/models/elements/input-elements/text-area.ts b/projects/common/models/elements/input-elements/text-area.ts
index 2c4505c84..aa7166cd4 100644
--- a/projects/common/models/elements/input-elements/text-area.ts
+++ b/projects/common/models/elements/input-elements/text-area.ts
@@ -12,7 +12,9 @@ import { TextAreaComponent } from 'common/components/input-elements/text-area.co
 export class TextAreaElement extends InputElement implements PositionedUIElement {
   appearance: 'fill' | 'outline' = 'outline';
   resizeEnabled: boolean = false;
+  hasDynamicRowCount: boolean = false;
   rowCount: number = 3;
+  expectedCharactersCount: number = 300;
   inputAssistancePreset: InputAssistancePreset = null;
   inputAssistancePosition: 'floating' | 'right' = 'floating';
   hasArrowKeys: boolean = false;
@@ -30,6 +32,8 @@ export class TextAreaElement extends InputElement implements PositionedUIElement
     if (element.appearance) this.appearance = element.appearance;
     if (element.resizeEnabled) this.resizeEnabled = element.resizeEnabled;
     if (element.rowCount) this.rowCount = element.rowCount;
+    if (element.hasDynamicRowCount) this.hasDynamicRowCount = element.hasDynamicRowCount;
+    if (element.expectedCharactersCount) this.expectedCharactersCount = element.expectedCharactersCount;
     if (element.inputAssistancePreset) this.inputAssistancePreset = element.inputAssistancePreset;
     if (element.inputAssistancePosition) this.inputAssistancePosition = element.inputAssistancePosition;
     if (element.restrictedToInputAssistanceChars !== undefined) {
diff --git a/projects/common/pipes/update-textarea-rows.pipe.spec.ts b/projects/common/pipes/update-textarea-rows.pipe.spec.ts
new file mode 100644
index 000000000..0c99a0bbe
--- /dev/null
+++ b/projects/common/pipes/update-textarea-rows.pipe.spec.ts
@@ -0,0 +1,8 @@
+import { UpdateTextareaRowsPipe } from './update-textarea-rows.pipe';
+
+describe('UpdateRowsPipe', () => {
+  it('create an instance', () => {
+    const pipe = new UpdateTextareaRowsPipe();
+    expect(pipe).toBeTruthy();
+  });
+});
diff --git a/projects/common/pipes/update-textarea-rows.pipe.ts b/projects/common/pipes/update-textarea-rows.pipe.ts
new file mode 100644
index 000000000..24f6119a0
--- /dev/null
+++ b/projects/common/pipes/update-textarea-rows.pipe.ts
@@ -0,0 +1,19 @@
+import { Pipe, PipeTransform } from '@angular/core';
+
+@Pipe({
+  name: 'updateTextareaRows'
+})
+export class UpdateTextareaRowsPipe implements PipeTransform {
+  transform(staticRowCount: number,
+            expectedCharactersCount: number,
+            hasDynamicRowCount: boolean,
+            inputWidth: number,
+            fontSize: number
+  ): number {
+    if (hasDynamicRowCount && expectedCharactersCount && inputWidth) {
+      const averageCharWidth = fontSize / 2; // s. AverageCharWidth of dotNet
+      return (Math.ceil((expectedCharactersCount * averageCharWidth) / inputWidth));
+    }
+    return staticRowCount;
+  }
+}
diff --git a/projects/common/shared.module.ts b/projects/common/shared.module.ts
index 36c93011c..d17113c90 100644
--- a/projects/common/shared.module.ts
+++ b/projects/common/shared.module.ts
@@ -71,6 +71,7 @@ import { GeometryComponent } from './components/geometry/geometry.component';
 import { MathAtanPipe } from './pipes/math-atan.pipe';
 import { MathDegreesPipe } from './pipes/math-degrees.pipe';
 import { ArrayIncludesPipe } from './pipes/array-includes.pipe';
+import { UpdateTextareaRowsPipe } from './pipes/update-textarea-rows.pipe';
 
 @NgModule({
   imports: [
@@ -132,7 +133,8 @@ import { ArrayIncludesPipe } from './pipes/array-includes.pipe';
     GeometryComponent,
     MathAtanPipe,
     MathDegreesPipe,
-    ArrayIncludesPipe
+    ArrayIncludesPipe,
+    UpdateTextareaRowsPipe
   ],
   exports: [
     CommonModule,
diff --git a/projects/editor/src/app/components/properties-panel/model-properties-tab/element-model-properties.component.html b/projects/editor/src/app/components/properties-panel/model-properties-tab/element-model-properties.component.html
index 1c3ca293c..8a1c6eff5 100644
--- a/projects/editor/src/app/components/properties-panel/model-properties-tab/element-model-properties.component.html
+++ b/projects/editor/src/app/components/properties-panel/model-properties-tab/element-model-properties.component.html
@@ -82,13 +82,26 @@
     {{'propertiesPanel.resizeEnabled' | translate }}
   </mat-checkbox>
 
-  <mat-form-field *ngIf="combinedProperties.rowCount != null"
+  <mat-checkbox *ngIf="combinedProperties.hasDynamicRowCount !== undefined"
+                [checked]="$any(combinedProperties.hasDynamicRowCount)"
+                (change)="updateModel.emit({ property: 'hasDynamicRowCount', value: $event.checked })">
+    {{'propertiesPanel.hasDynamicRowCount' | translate }}
+  </mat-checkbox>
+
+  <mat-form-field *ngIf="combinedProperties.rowCount != null && combinedProperties.hasDynamicRowCount === false"
                   appearance="fill" class="mdInput textsingleline">
     <mat-label>{{'rows' | translate }}</mat-label>
     <input matInput type="number" [value]="$any(combinedProperties.rowCount)"
            (input)="updateModel.emit({ property: 'rowCount', value: $any($event.target).value })">
   </mat-form-field>
 
+  <mat-form-field *ngIf="combinedProperties.expectedCharactersCount != null && combinedProperties.hasDynamicRowCount"
+                  appearance="fill" class="mdInput textsingleline">
+    <mat-label>{{'propertiesPanel.expectedCharactersCount' | translate }}</mat-label>
+    <input matInput type="number" [value]="$any(combinedProperties.expectedCharactersCount)"
+           (input)="updateModel.emit({ property: 'expectedCharactersCount', value: $any($event.target).value })">
+  </mat-form-field>
+
   <aspect-button-properties [combinedProperties]="combinedProperties"
                             (updateModel)="updateModel.emit($event)">
   </aspect-button-properties>
diff --git a/projects/editor/src/assets/i18n/de.json b/projects/editor/src/assets/i18n/de.json
index d68588678..43bc4abb1 100644
--- a/projects/editor/src/assets/i18n/de.json
+++ b/projects/editor/src/assets/i18n/de.json
@@ -179,7 +179,9 @@
     "customToolBar": "Anpassung Werkzeugleiste",
     "customToolbarHelp": "Hier kann definiert werden, welche Elemente auf der Werkzeigleiste angezeigt werden sollen. Für Details bitte die Dokumentation konsultieren.",
     "hotspots": "Aktive Bereiche",
-    "newHotspot": "Neuer Bereich"
+    "newHotspot": "Neuer Bereich",
+    "hasDynamicRowCount": "Dynamische Zeilen",
+    "expectedCharactersCount": "Erwartete Zeichenanzahl"
   },
   "hotspot": {
     "top": "Abstand von oben",
-- 
GitLab