Felder sperren und freigeben
Die Basis liefert globale Skript-Funktionen, mit denen sich einzelne Felder zur Laufzeit sperren
und wieder freigeben lassen, je Eingabeart ein Paar. Sie setzen nicht nur
disabled, sondern auch die Gestaltung des FMH-Feldgerüsts, damit ein gesperrtes
Feld auch aussieht wie eines.
Angesprochen wird immer über den Eigenschaftsnamen aus dem ViewModel, nicht über einen Selektor. Die vollständige Liste steht unter Referenz.
Je Eingabeart ein Funktionspaar
Textfeld, Auswahlbox, Radio-Gruppe, Mehrfachauswahl und Schaltfläche – jeweils gesperrt und wieder freigegeben. Angesprochen wird über den Eigenschaftsnamen aus dem ViewModel.
@model ElementStatusBeispielViewModel
@* Je Eingabeart ein Abschnitt: Überschrift, die beiden Funktionen, das Feld und die Schaltflächen.
Die Schaltflächen tragen den Eigenschaftsnamen und die Eingabeart als data-Attribute; welche
Funktion daraus wird, entscheidet der Listener im Tab „JavaScript". Die alte Schreibweise mit
onclick="disableTextField('Name')" tut dasselbe, verlangt aber eine gelockerte
Content-Security-Policy – deshalb hier die Fassung mit externem Skript. *@
<h4 class="h5">Textfeld</h4>
<p class="small text-muted mb-2">
<code>disableTextField('Name')</code> · <code>enableTextField('Name')</code><br />
Sperrt das Eingabefeld und setzt <code>fmh-text-field--disabled</code> an sein Feldgerüst.
</p>
<div class="mb-4">
@Html.EditorFor(m => m.Name)
<div class="btn-group btn-group-sm mt-2">
@Html.Fmh(Fmh.Button()
.Text("Sperren")
.Type(ButtonType.Button)
.Color(ButtonColor.Secondary)
.Outline()
.Attributes(new { data_status = "sperren", data_status_typ = "TextField", data_status_feld = "Name" }))
@Html.Fmh(Fmh.Button()
.Text("Freigeben")
.Type(ButtonType.Button)
.Color(ButtonColor.Secondary)
.Outline()
.Attributes(new { data_status = "freigeben", data_status_typ = "TextField", data_status_feld = "Name" }))
</div>
</div>
<hr />
<h4 class="h5">Auswahlbox</h4>
<p class="small text-muted mb-2">
<code>disableDropdown('DossierId')</code> · <code>enableDropdown('DossierId')</code><br />
Wirkt für beide Formen der Auswahlbox – die klassische <code>select</code> wie die Variante von
Material Design.
</p>
<div class="mb-4">
@Html.EditorFor(m => m.DossierId)
<div class="btn-group btn-group-sm mt-2">
@Html.Fmh(Fmh.Button()
.Text("Sperren")
.Type(ButtonType.Button)
.Color(ButtonColor.Secondary)
.Outline()
.Attributes(new { data_status = "sperren", data_status_typ = "Dropdown", data_status_feld = "DossierId" }))
@Html.Fmh(Fmh.Button()
.Text("Freigeben")
.Type(ButtonType.Button)
.Color(ButtonColor.Secondary)
.Outline()
.Attributes(new { data_status = "freigeben", data_status_typ = "Dropdown", data_status_feld = "DossierId" }))
</div>
</div>
<hr />
<h4 class="h5">Radio-Gruppe</h4>
<p class="small text-muted mb-2">
<code>disableRadio('RadioAuswahl')</code> · <code>enableRadio('RadioAuswahl')</code><br />
Eine Gruppe besteht aus mehreren Eingabefeldern – die Funktion geht alle Optionen durch.
</p>
<div class="mb-4">
@Html.EditorFor(m => m.RadioAuswahl)
<div class="btn-group btn-group-sm mt-2">
@Html.Fmh(Fmh.Button()
.Text("Sperren")
.Type(ButtonType.Button)
.Color(ButtonColor.Secondary)
.Outline()
.Attributes(new { data_status = "sperren", data_status_typ = "Radio", data_status_feld = "RadioAuswahl" }))
@Html.Fmh(Fmh.Button()
.Text("Freigeben")
.Type(ButtonType.Button)
.Color(ButtonColor.Secondary)
.Outline()
.Attributes(new { data_status = "freigeben", data_status_typ = "Radio", data_status_feld = "RadioAuswahl" }))
</div>
</div>
<hr />
<h4 class="h5">Mehrfachauswahl</h4>
<p class="small text-muted mb-2">
<code>disableCheckbox('CheckBoxAuswahl')</code> · <code>enableCheckbox('CheckBoxAuswahl')</code><br />
Wie bei der Radio-Gruppe: Alle Kästchen der Gruppe werden geschaltet.
</p>
<div class="mb-4">
@Html.EditorFor(m => m.CheckBoxAuswahl)
<div class="btn-group btn-group-sm mt-2">
@Html.Fmh(Fmh.Button()
.Text("Sperren")
.Type(ButtonType.Button)
.Color(ButtonColor.Secondary)
.Outline()
.Attributes(new { data_status = "sperren", data_status_typ = "Checkbox", data_status_feld = "CheckBoxAuswahl" }))
@Html.Fmh(Fmh.Button()
.Text("Freigeben")
.Type(ButtonType.Button)
.Color(ButtonColor.Secondary)
.Outline()
.Attributes(new { data_status = "freigeben", data_status_typ = "Checkbox", data_status_feld = "CheckBoxAuswahl" }))
</div>
</div>
<hr />
<h4 class="h5">Schaltfläche</h4>
<p class="small text-muted mb-2">
<code>disableButton('btnStatusZiel')</code> · <code>enableButton('btnStatusZiel')</code><br />
<strong>Der Sonderfall:</strong> Enthält der Wert einen Punkt, wird als <em>Klasse</em> gesucht –
sonst als Id. Damit lassen sich mehrere Schaltflächen auf einmal schalten.
</p>
<div class="mb-2">
@Html.Fmh(Fmh.Button()
.Text("Beispiel-Schaltfläche")
.Type(ButtonType.Button)
.Attributes(new { id = "btnStatusZiel" }))
</div>
<div class="btn-group btn-group-sm">
@Html.Fmh(Fmh.Button()
.Text("Sperren")
.Type(ButtonType.Button)
.Color(ButtonColor.Secondary)
.Outline()
.Attributes(new { data_status = "sperren", data_status_typ = "Button", data_status_feld = "btnStatusZiel" }))
@Html.Fmh(Fmh.Button()
.Text("Freigeben")
.Type(ButtonType.Button)
.Color(ButtonColor.Secondary)
.Outline()
.Attributes(new { data_status = "freigeben", data_status_typ = "Button", data_status_feld = "btnStatusZiel" }))
</div>
@Html.PostScript(@<script src="~/js/demos/element-status.js" asp-append-version="true"></script>)
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using FMH.Komponente.Basis.Ui.Controls;
using FMH.Komponente.Basis.Ui.Validation.DataAnnotation;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace FMH.Komponente.WebUI.Areas.Basis.Models
{
/// <summary>
/// Je ein Feld der vier Eingabearten, die sich über die Skript-Funktionen der Basis sperren
/// und wieder freigeben lassen.
/// </summary>
/// <remarks>
/// Die Funktionen sprechen die Felder über den **Eigenschaftsnamen** an – deshalb ist hier
/// wichtig, wie die Eigenschaften heissen, nicht was sie enthalten.
/// </remarks>
public class ElementStatusBeispielViewModel
{
/// <summary>
/// Holt oder setzt das Textfeld; angesprochen über <c>disableTextField('Name')</c>.
/// </summary>
[Display(Name = "Name")]
public string Name { get; set; } = "Muster";
/// <summary>
/// Holt oder setzt die Auswahlbox; angesprochen über <c>disableDropdown('DossierId')</c>.
/// </summary>
[Display(Name = "Dossier")]
[UIHint(UiEditor.Basis.DropdownList)]
[AdditionalMetadata("Selection", nameof(Dossiers))]
public int? DossierId { get; set; } = 4;
/// <summary>
/// Holt die Auswahlliste der Auswahlbox.
/// </summary>
public IEnumerable<SelectListItem> Dossiers { get; } = new List<SelectListItem>
{
new ("Dossier 2024", "2"),
new ("Dossier 2025", "3"),
new ("Dossier 2026", "4"),
};
/// <summary>
/// Holt oder setzt die Radio-Auswahl; angesprochen über <c>disableRadio('RadioAuswahl')</c>.
/// </summary>
[Display(Name = "Anrede")]
[UIHint(UiEditor.Basis.RadioList)]
[AdditionalMetadata("Selection", nameof(Anreden))]
[AdditionalMetadata("Direction", FlexDirection.Row)]
public string RadioAuswahl { get; set; } = "f";
/// <summary>
/// Holt die Auswahlliste der Radio-Gruppe.
/// </summary>
public IEnumerable<SelectListItem> Anreden { get; } = new List<SelectListItem>
{
new ("Frau", "f"),
new ("Herr", "m"),
new ("Keine Angabe", "x"),
};
/// <summary>
/// Holt oder setzt die Mehrfachauswahl; angesprochen über
/// <c>disableCheckbox('CheckBoxAuswahl')</c>.
/// </summary>
[Display(Name = "Zustellwege")]
[UIHint(UiEditor.Basis.CheckboxList)]
[AdditionalMetadata("Selection", nameof(Zustellwege))]
[AdditionalMetadata("Direction", FlexDirection.Row)]
public IEnumerable<int> CheckBoxAuswahl { get; set; } = new[] { 1 };
/// <summary>
/// Holt die Auswahlliste der Mehrfachauswahl.
/// </summary>
public IEnumerable<SelectListItem> Zustellwege { get; } = new List<SelectListItem>
{
new ("Post", "1"),
new ("E-Mail", "2"),
new ("Portal", "3"),
};
}
}
// Beispiel «Felder sperren und freigeben» (Basis).
//
// Die Funktionen sind global und kommen aus jquery-fmh-validation.js der Basis-Komponente. Sie
// erwarten den **Eigenschaftsnamen** des Feldes, nicht einen Selektor, ein führendes '#' wird
// zwar toleriert, gehört aber nicht hin.
//
// Ausnahme: disableButton/enableButton suchen als Klasse, sobald der Wert einen Punkt enthält,
// sonst als Id.
var statusFunktionen = {
'TextField:sperren': disableTextField,
'TextField:freigeben': enableTextField,
'Dropdown:sperren': disableDropdown,
'Dropdown:freigeben': enableDropdown,
'Radio:sperren': disableRadio,
'Radio:freigeben': enableRadio,
'Checkbox:sperren': disableCheckbox,
'Checkbox:freigeben': enableCheckbox,
'Button:sperren': disableButton,
'Button:freigeben': enableButton,
};
// Delegation an das document: Der Zeitpunkt des Renderns spielt damit keine Rolle.
document.addEventListener('click', function (ereignis) {
var knopf = ereignis.target.closest('[data-status]');
if (knopf === null) {
return;
}
var funktion = statusFunktionen[knopf.dataset.statusTyp + ':' + knopf.dataset.status];
if (funktion) {
funktion(knopf.dataset.statusFeld);
}
});
// Dieselbe Umsetzung mit jQuery. Diese Datei wird NICHT geladen, sie steht nur im Code-Tab
// «jQuery». Würden beide Fassungen laufen, liefe jeder Klick doppelt.
//
// Die Funktionen selbst sind in beiden Fassungen dieselben: Sie stammen aus
// jquery-fmh-validation.js der Basis-Komponente und setzen jQuery ohnehin voraus.
var statusFunktionen = {
'TextField:sperren': disableTextField,
'TextField:freigeben': enableTextField,
'Dropdown:sperren': disableDropdown,
'Dropdown:freigeben': enableDropdown,
'Radio:sperren': disableRadio,
'Radio:freigeben': enableRadio,
'Checkbox:sperren': disableCheckbox,
'Checkbox:freigeben': enableCheckbox,
'Button:sperren': disableButton,
'Button:freigeben': enableButton,
};
// $(document).on(ereignis, selektor, ...) ist die jQuery-Form der Delegation.
// .data('statusTyp') liest data-status-typ, jQuery wandelt die Bindestrich-Schreibweise selbst um.
$(document).on('click', '[data-status]', function () {
var $knopf = $(this);
var funktion = statusFunktionen[$knopf.data('statusTyp') + ':' + $knopf.data('status')];
if (funktion) {
funktion($knopf.data('statusFeld'));
}
});
Darstellung
Textfeld
disableTextField('Name') · enableTextField('Name')
Sperrt das Eingabefeld und setzt fmh-text-field--disabled an sein Feldgerüst.
Auswahlbox
disableDropdown('DossierId') · enableDropdown('DossierId')
Wirkt für beide Formen der Auswahlbox – die klassische select wie die Variante von
Material Design.
Radio-Gruppe
disableRadio('RadioAuswahl') · enableRadio('RadioAuswahl')
Eine Gruppe besteht aus mehreren Eingabefeldern – die Funktion geht alle Optionen durch.
Mehrfachauswahl
disableCheckbox('CheckBoxAuswahl') · enableCheckbox('CheckBoxAuswahl')
Wie bei der Radio-Gruppe: Alle Kästchen der Gruppe werden geschaltet.
Schaltfläche
disableButton('btnStatusZiel') · enableButton('btnStatusZiel')
Der Sonderfall: Enthält der Wert einen Punkt, wird als Klasse gesucht –
sonst als Id. Damit lassen sich mehrere Schaltflächen auf einmal schalten.
Felder zur Laufzeit sperren und wieder freigeben, abhängig von einer anderen Eingabe, einer Rolle oder einem Zustand, den erst der Browser kennt.
disableTextField('Name'); // sperren
enableTextField('Name'); // wieder freigeben
Je Eingabeart ein Paar; die vollständige Liste steht unter Referenz.
Der Parameter ist der Eigenschaftsname
Nicht der Selektor. Die Funktionen bauen daraus selbst die Id des Feldes und die Id des Feldgerüsts:
disableDropdown('DossierId')
→ $('#DossierId').prop('disabled', true)
→ $('#field_DossierId').addClass('fmh-select--disabled')
Ein führendes # wird entfernt und stört deshalb nicht, die alte Doku zeigt beide Schreibweisen.
Verlassen sollte man sich darauf nicht: Bereinigt wird nur das erste Vorkommen.
Warum nicht einfach disabled setzen
Weil zum gesperrten Zustand mehr gehört als das Attribut: Die FMH-Templates rendern ein Feldgerüst mit Beschriftung, Rahmen und Hilfetext. Erst dessen Klasse macht sichtbar, dass ein Feld nicht bedienbar ist. Radio- und Checkbox-Gruppen bestehen zudem aus mehreren Eingabefeldern, die Funktionen gehen sie alle durch.
Was zu beachten ist
- Gesperrte Felder senden nichts. Wie bei jedem
disabled-Feld fehlt der Wert im POST. Wer ihn braucht, führt ihn zusätzlich als verstecktes Feld mit. - Die Funktionen brauchen das Feldgerüst der Templates. An einem selbst geschriebenen
<input>greift nur dasdisabled. disableButtonist der Sonderfall mit der Id-oder-Klasse-Regel, siehe Referenz.- Die Validierung wird nicht angepasst. Ein gesperrtes Pflichtfeld bleibt ein Pflichtfeld; wer
das nicht will, arbeitet stattdessen mit
RequiredIfund dessen OptionenDisableIfNotRequiredundHideIfNotRequired.