Entwicklung ·SAPUI5-API ·Usability

Validierungen in Fiori Teil 3

Mach es bitte richtig – und selber

Ich möchte wieder über Validierungen in Fiori schreiben. In meinem ersten Beitrag dieser Reihe riss ich bereits komplexe Eingaben an, wie beispielsweise eine Personalnummer. Die von anspruchsvolleren Eingaben möchte ich nun wieder aufgreifen und als Thema dieses Beitrags behandeln.

Es ist wieder ein Meeting beim Kunden. Ich konstruiere jetzt mal ein Beispiel, dass in der Fiori irgend eine Form von Personalnummer eingegeben werden muss, da das HR-System nicht direkt mit dem SAP Gateway der Fiori verbunden ist. Diese Fiori wird nur intern verwendet, so dass die Anwender ihre Nummer kennen (sollten) und sie Nummer freihändig im Formular mit angeben müssen. Man erklärte mir, dass das Schema der Personalnummer wie folgt aussieht:

  1. Genau 2 Buchstaben
  2. Bindestrich
  3. Genau 5 Ziffern
  4. Bindestrich
  5. Genau 1 Buchstabe

Mögliche Kombinationen wären beispielsweise KL-00302-S, ga-11431-q oder HH-04711-F. Es dürfen Groß- oder Kleinbuchstaben verwendet werden, da diese im SAP Gateway vereinheitlicht werden sollen. Wie so oft heißt es: “ist halt so gewachsen” ;). Man könnte sich übrigens überlegen, die Eingabe über drei Felder zu verteilen und im Controller zusammen zu setzen, aber ich möchte nun bewusst den Weg über ein Eingabefeld verfolgen.

 Eine einfache Nummer

Bevor ich nun die Möglichkeiten von Fiori vorstelle, wie die oben beschriebene Regel geprüft werden können, möchte ich erst einmal einen subtilen, indirekten und unaufdringlichen Weg vorstellen, um den Anwender selbstverantwortlich zu einer korrekten Eingabe bringen zu können. Konkret ist es das Attribut “placeholder”, welches unter Anderem für das Control sap.m.Input anwendbar ist.

<Label required="true" text="Personalnummer"/>
<Input 
  placeholder="AB-12345-C" 
  value="{ path: 'StaffNumber'}"/>

persnr_placeholder
Mittels “placeholder” werden, wie es der Name schon sagt, Platzhalter-Werte in einem Eingabefeld angezeigt. Der Anwender bekommt dadurch bereits einen sehr guten Eindruch, in welcher Form die Eingabe aussehen sollte. Eine recht effiziente Lösung wie ich finde, die hier nicht unerwähnt bleiben sollte.

Kontrolle ausdrücklich erwünscht

Wie in den vorherigen Teilen dieser Reihe, möchte ich eine direkte Validierung über JavaScript und den alternativen Möglichkeiten des SAPUI5-Frameworks vorstellen. Wieder beginne ich mit einer JavaScript-Lösung, die direkt die API der Controls verwenden soll.

<Label required="true" text="Personalnummer"/>
<Input 
  placeholder="AB-12345-C" 
  value="{ path: 'StaffNumber'}"
  change="onStaffNumberChange"/>
onStaffNumberChange: function(oEvent){
  var oSource = oEvent.getSource();
  var sStaffNumber = oSource.getValue();

  if (this._isValidStuffNumber(sStaffNumber)) {
    oSource.setValueState("None");
  } else {
    oSource.setValueState("Error");
    oSource.setValueStateText("'" + sStaffNumber + "' ist keine valide Personalnummer (AB-12345-C)");
  }
},

_isValidStuffNumber : function (sStaffNumber) {
  var staffNumberRegex = /^[a-zA-Z]{2}-\d{5}-[a-zA-Z]{1}$/;
  return sStaffNumber.match(staffNumberRegex);
},

Wie bereits zuvor, implementiere ich für das Personalnummer-Feld ein change() Event. Im Controller verarbeitet onStaffNumberChange() die Eingabe und prüft mittels einem regulären Ausdruck den übergebenen Wert. Analog zum älteren Beitrag, verwende ich hier ebenfalls eine Kombination aus ValueState und ValueStateText.
persnr_fehler
Eingaben, die das Schema im regulären Ausdruck verletzen, versetzen das Input-Control in einen Fehlerstatus.
persnr_richtig
Korrekte Werte lösen keinen Fehler aus.

Alles in Allem, eine recht effiziente Möglichkeit die Anforderung des Kunden zu realisieren. Als nächste Lösung zeige ich den Ansatz über sap.ui.model.type.String und den Einsatz von Constraints.

Technisch korrekt, aber…

Okay, das sollte schnell gehen. Ich überarbeite die XML-View für den Einsatz eines String Model Type und benutze den Constraint “search”, der laut Doku einen regulären Ausdruck beinhalten kann. Anschließend entferne ich das change() Event wieder.

<Label required="true" text="Personalnummer"/>
<Input 
  placeholder="AB-12345-C" 
  value="{ path : 'StaffNumber', 
           type : 'sap.ui.model.type.String' ,
           constraints : {
             search : '^[a-zA-Z]{2}-\d{5}-[a-zA-Z]{1}$'
           }
         }
  change="onStaffNumberChange"/>

Und … geht nicht 😛 Nach langer Suche kam heraus, dass ich das Escape-Zeichen “\” mit einem weiteren “\” escapen muss, um den Regex in der XML-View einsetzen zu können (ಠ_ಠ)

<Label required="true" text="Personalnummer"/>
<Input 
  placeholder="AB-12345-C" 
  value="{ path : 'StaffNumber', 
           type : 'sap.ui.model.type.String' ,
           constraints : {
             search : '^[a-zA-Z]{2}-\\d{5}-[a-zA-Z]{1}$'
           }
         }"/>

Nach diesem Erlebnis wurde die View wieder geladen und ich konnte das Ergebnis der Validierung zu sehen…
persnr_regex
… Also diese Fehlermeldung ist zwar technisch korrekt, aber für jeden Anwender natürlich völlig unzumutbar. Bleibt es daher bei der JavaScript-Lösung, oder gibt es hier noch einen dritten Weg?

Genau mein Typ(e)

Zum Glück lassen sich die archetypischen Model Types wie Date, Number, String, etc., als Unterklassen erweitern. Ein erweiterter Type gibt mir die Möglichkeit, die doch arg technische Fehlermeldung für die Personalnummer zu überschreiben. Ein eigener Typ wird in einem Controller oder einer Library definiert und in der View angesprochen.

<Label required="true" text="Personalnummer"/>
<Input 
  placeholder="AB-12345-C" 
  value="{ path : 'StaffNumber', 
           type : 'sap.ui.model.type.String',
           type : '.typeStaffNumber',
           constraints : {
             search : '^[a-zA-Z]{2}-\\d{5}-[a-zA-Z]{1}$'
           }
         }"/>

In der View tausche ich den bislang verwendeten Type mit einem Eigenen, dem “typeStaffNumber”, aus. Durch den Punkt-Operator greife ich auf den Controller zu. Dort muss nun der erweiterte Type definiert werden.

sap.ui.define([
        [...]
	'sap/ui/model/type/String',
	'sap/ui/model/ValidateException'
], function([...]. StringType, ValidateException) {

Es ist zwar optional, aber ich empfehle sap.ui.model.type.String und sap.ui.model.ValidateException als Abhängigkeiten im Controller aufzunehmen.

typeStaffNumber: StringType.extend("staffNumber", {
  validateValue: function(oValue) {
    if (oValue.match(this.oConstraints.search)) {
      return oValue;
    } else {
      throw new ValidateException("'" + oValue + "' ist keine valide Personalnummer (AB-12345-C)");
    }
  }
}),

Im Controller kann ich nun den StringType erweitern. Nach etwas Recherche in den APIs zeigte sich, dass die function validateValue() überschrieben werden kann, um die Fehlermeldung auszutauschen. Die in der View definierten Constraints lassen sich über this.oConstraints ansteuern. Ich prüfe den search-Constraint (also den dort abgelegten regulären Ausdruck) gegen den übergebenen Wert “oValue” und werfe im Falle eines Fehlers eine ValidateException. Dadurch wird die sperrige Fehlermeldung des UI5-Frameworks mit der von mir geschriebenen Meldung ersetzt.

persnr_fehler222

Wer auf komplexe Constraints in der View ganz verzichten möchte, dann diese Prüfungen auch komplett in den eigenen Type auslagern. Eine mögliche Lösung könnte so aussehen:

  
<Label required="true" text="Personalnummer"/>
<Input 
  placeholder="AB-12345-C" 
  value="{ path: 'StaffNumber', 
           type: '.typeStaffNumber',
           constraints : {
             search : '^[a-zA-Z]{2}-\\d{5}-[a-zA-Z]{1}$'
           }
         }"/>

In der View entferne ich die constraints und muss nie wieder escape-Zeichen escapen 😉 .

typeStaffNumber: StringType.extend("staffNumber", {
  STAFF_NUMBER_REGEX : /^[a-zA-Z]{2}-\d{5}-[a-zA-Z]{1}$/,

  validateValue: function(oValue) {
    if (this._isValidStuffNumber(oValue)) {
      return oValue;
    } else {
      throw new ValidateException("'" + oValue + "' ist keine valide Personalnummer (AB-12345-C)");
    }
  },

  _isValidStuffNumber : function (sStaffNumber) {
    return sStaffNumber.match(this.STAFF_NUMBER_REGEX);
  }
}),

In der Überarbeitung befindet sich der Regex direkt im Type typeStaffNumber und nähert sich wieder stärker an die ursprüngliche JavaScript-Version an, die ich in diesem Beitrag als Erstes gezeigt hatte.

  
<Label required="true" text="Personalnummer"/>
<Input 
  placeholder="AB-12345-C" 
  value="{ path: 'StaffNumber', 
           type: '.mySharedTypes.typeStaffNumber'
         }"/>

Der interessanteste Vorteil ist wohl die Möglichkeit solche eigenen Types in einer Library (z.B. “mySharedTypes”) zu sammeln, wenn die Validierungen über mehrere Views und Controller erfolgen sollen. Ferner können für eigene Types selbstdefinierte Constraints in den Views verwendet werden. Ebenfalls eine sehr interessante Möglichkeit, wie ich finde.

Jetzt habe ich tolle Fehlermeldungen, aber der Anwender kann das Formular immer noch falsch abschicken. Wie das verhindert werden kann möchte ich im folgenden Beitrag dieser Reihe zeigen.

Schreibe einen Kommentar