Likertskala

Texte um die Skala

Header, Introduction und Footer rahmen die Skala, DescriptionColumn beschriftet die erste Spalte und LikertSkalaRow.Header setzt eine Überschrift in die Zeile. Alle vier nehmen HTML entgegen.

@model LernzielViewModel

<div class="alert alert-secondary py-2 px-3 mb-3">
    <strong>Rolle: @Model.RolleName</strong><br />
    <span class="small">@Model.RolleHinweis</span>
</div>

@* Vier Textstellen umrahmen die Skala, alle optional und alle als HTML ausgegeben: Header und
   Introduction darüber, Footer darunter, DescriptionColumn als Überschrift der ersten Spalte. Die
   fünfte steht in den Daten: LikertSkalaRow.Header setzt eine fette Zeile über den Zeilentext.

   Die Spaltenbreite ist keine Textfrage, gehört aber hierher: WidthInPixel am Ergebnis von For
   verhindert, dass lange Beschriftungen die Skala auseinanderziehen. *@
<form method="post" asp-area="Kompetenzen" asp-controller="LikertSkalaBeispiel" asp-action="Texte">
    @Html.HiddenFor(m => m.Id)
    @Html.HiddenFor(m => m.Rolle)

    @Html.Fmh(Fmh<int>.LikertSkala()
        .Id("lk-texte")
        .Header("<h5>Kompetenzen aus dem Weiterbildungsprogramm</h5>")
        .Introduction("Bitte alle Zeilen beurteilen. Die Einschätzung lässt sich bis zum Abschluss der Periode ändern.")
        .Footer("<small class=\"text-muted\">Stand: Zwischenevaluation 2026</small>")
        .DescriptionColumn("Lernziel")
        .AssessmentType(Model.Rolle)
        .Columns(spalten =>
        {
            spalten.For(1, "Trifft zu")
                .WidthInPixel(160);

            spalten.For(2, "Teilweise")
                .WidthInPixel(160);

            spalten.For(3, "Trifft nicht zu")
                .WidthInPixel(160);
        })
        .Bind(Model.Likertskala, Html.NameFor(m => m.Likertskala).ToString()))

    @Html.Fmh(Fmh.Button()
        .Text("Bewerten")
        .Size(ButtonSize.Sm)
        .Attributes(new { @class = "mt-3" }))
</form>

<div class="mt-3">
    @Html.Fmh(Fmh.Button()
        .Text("Ausgangslage wiederherstellen")
        .Size(ButtonSize.Sm)
        .Outline()
        .Action("Zuruecksetzen", "LikertSkalaBeispiel", new { area = "Kompetenzen", beispiel = "texte" }))
</div>
using System.Collections.Generic;
using FMH.Komponente.Kompetenzen.Ui.Controls.LikertSkala.Daten;

namespace FMH.Komponente.WebUI.Areas.Kompetenzen.Models
{
    /// <summary>
    /// Das ViewModel einer einzelnen Likertskala. Es enthält nur, was die Skala braucht: die Zeilen
    /// und – je nach Beispiel – die Spaltendefinition.
    /// </summary>
    /// <remarks>
    /// Die Zeilen sind vom Typ <see cref="LikertSkalaRow{T}" /> der Komponente. Fachliche Daten
    /// werden beim Aufbau der Seite darauf abgebildet; ein eigener Zeilentyp ist nicht vorgesehen,
    /// weil der Builder genau diesen Typ bindet.
    /// </remarks>
    public class LernzielViewModel
    {
        /// <summary>
        /// Holt oder setzt die fachliche Id, an der der POST hängt (Zeugnis, Revision, …). Sie
        /// gehört nicht zur Skala, wird aber wie in einem Projekt als Hidden-Feld mitgeführt.
        /// </summary>
        public int Id { get; set; }

        /// <summary>
        /// Holt oder setzt die Zeilen der Skala. Der Name dieser Eigenschaft ist der zweite
        /// Parameter von <c>Bind(...)</c> – daraus entstehen die Feldnamen
        /// <c>Likertskala[0].SelfAssessmentValue</c>, an denen das Model-Binding die Antworten
        /// wiederfindet.
        /// </summary>
        public IList<LikertSkalaRow<int>> Likertskala { get; set; } = new List<LikertSkalaRow<int>>();

        /// <summary>
        /// Holt oder setzt die Rolle, in der die Skala gerade ausgefüllt wird. Sie steuert die
        /// Skala (<c>AssessmentType</c>), den Hinweis über ihr und – nach dem Absenden – welchen
        /// der beiden Werte der Controller übernimmt. Als Hidden-Feld geht sie mit dem POST mit.
        /// </summary>
        public Editable Rolle { get; set; } = Editable.SelfAssessment;

        /// <summary>
        /// Holt den Hinweistext zur aktuellen Rolle.
        /// </summary>
        public string RolleHinweis => Rollen.Hinweis(this.Rolle);

        /// <summary>
        /// Holt den Kurznamen der aktuellen Rolle.
        /// </summary>
        public string RolleName => Rollen.Kurzname(this.Rolle);

        /// <summary>
        /// Holt oder setzt die Spalten der Skala. Nur die Beispiele, die ihre Spalten aus den Daten
        /// beziehen, füllen sie – sonst stehen die Spalten im Markup (<c>Columns(spalten => …)</c>).
        /// </summary>
        public IList<LikertSkalaHeaderColumn<int>> Spalten { get; set; } = new List<LikertSkalaHeaderColumn<int>>();
    }
}
using System.Collections.Generic;
using FMH.Komponente.Kompetenzen.Ui.Controls.LikertSkala;
using FMH.Komponente.Kompetenzen.Ui.Controls.LikertSkala.Daten;

namespace FMH.Komponente.WebUI.Areas.Kompetenzen.Models
{
    /// <summary>
    /// Die Testdaten der Demo. Ohne Datenbank, dafür fachlich nahe an einem Zeugnis: Lernziele mit
    /// Code, Text und den bereits erfassten Einschätzungen.
    /// </summary>
    /// <remarks>
    /// Jede Methode liefert bewusst eine **neue** Liste. Die Zeilen sind veränderlich, und der POST
    /// eines Beispiels bindet direkt auf sie – eine prozessweit geteilte Liste würde sich zwischen
    /// den Seiten gegenseitig überschreiben.
    /// </remarks>
    public static class LernzielDaten
    {
        /// <summary>
        /// Die Skalenwerte als Spaltendefinition, wie sie aus einer Konfigurationstabelle käme.
        /// </summary>
        /// <remarks>
        /// **Die Reihenfolge ist fachlich bedeutsam**: Der Erfüllungsgrad vergleicht die Position
        /// der Spalten, nicht deren Werte. Die beste Stufe steht deshalb zuvorderst, «nicht
        /// beurteilbar» als Nicht-Stufe zuhinterst.
        /// </remarks>
        /// <returns>Die Spalten der Skala.</returns>
        public static IList<LikertSkalaHeaderColumn<int>> Stufen()
        {
            return new List<LikertSkalaHeaderColumn<int>>
                   {
                       new LikertSkalaHeaderColumn<int> { Value = 4, Text = "Stufe 4", Width = 130 },
                       new LikertSkalaHeaderColumn<int> { Value = 3, Text = "Stufe 3", Width = 130 },
                       new LikertSkalaHeaderColumn<int> { Value = 2, Text = "Stufe 2", Width = 130 },
                       new LikertSkalaHeaderColumn<int> { Value = 1, Text = "Stufe 1", Width = 130 },
                       new LikertSkalaHeaderColumn<int> { Value = 99, Text = "nicht beurteilbar", Width = 150 },
                   };
        }

        /// <summary>
        /// Lernziele mit einer angefangenen Selbsteinschätzung – die Ausgangslage des Grundfalls.
        /// </summary>
        /// <returns>Die Zeilen der Skala.</returns>
        public static IList<LikertSkalaRow<int>> Lernziele()
        {
            return new List<LikertSkalaRow<int>>
                   {
                       new LikertSkalaRow<int> { Code = 5, Text = "Patientinnen und Patienten umfassend beraten und begleiten", SelfAssessmentValue = 1 },
                       new LikertSkalaRow<int> { Code = 7, Text = "Befunde strukturiert dokumentieren", SelfAssessmentValue = 2 },
                       new LikertSkalaRow<int> { Code = 9, Text = "Im interprofessionellen Team zusammenarbeiten" },
                       new LikertSkalaRow<int> { Code = 11, Text = "Eigene Grenzen erkennen und Hilfe anfordern", SelfAssessmentValue = 1 },
                       new LikertSkalaRow<int> { Code = 13, Text = "Regelmässig an Weiterbildungen teilnehmen" },
                   };
        }

        /// <summary>
        /// Lernziele mit Überschrift je Zeile – Grundlage des Beispiels «Texte um die Skala».
        /// </summary>
        /// <returns>Die Zeilen der Skala.</returns>
        public static IList<LikertSkalaRow<int>> LernzieleMitUeberschrift()
        {
            return new List<LikertSkalaRow<int>>
                   {
                       new LikertSkalaRow<int>
                       {
                           Code = 5,
                           Header = "Betreuung",
                           Text = "Kann Patientinnen und Patienten umfassend beraten, begleiten und betreuen und dabei deren Autonomie gebührend respektieren",
                           SelfAssessmentValue = 2,
                       },
                       new LikertSkalaRow<int>
                       {
                           Code = 7,
                           Header = "Kommunikation",
                           Text = "Kann komplexe Sachverhalte gegenüber Angehörigen verständlich darstellen",
                       },
                       new LikertSkalaRow<int>
                       {
                           Code = 9,
                           Header = "Zusammenarbeit",
                           Text = "Kann die eigene Rolle im interprofessionellen Team einnehmen und Aufgaben delegieren",
                           SelfAssessmentValue = 1,
                       },
                   };
        }

        /// <summary>
        /// Kompetenzen mit Selbst- und Fremdeinschätzung – Grundlage der Beispiele
        /// «Selbst- und Fremdeinschätzung» und «Erfüllungsgrad».
        /// </summary>
        /// <remarks>
        /// <c>DegreeOfAchievementValue</c> ist die **geforderte** Stufe. Bewusst gemischt: erfüllt,
        /// übererfüllt, nicht erreicht, ohne Anforderung und «nicht beurteilbar» – nur so lässt sich
        /// die Prozentzahl im Kopf der Skala nachrechnen.
        /// </remarks>
        /// <returns>Die Zeilen der Skala.</returns>
        public static IList<LikertSkalaRow<int>> Kompetenzen()
        {
            return new List<LikertSkalaRow<int>>
                   {
                       new LikertSkalaRow<int>
                       {
                           Code = 5,
                           Text = "Akutes Lungenödem «A»",
                           SelfAssessmentValue = 4,
                           ExternalAssessmentValue = 4,
                           DegreeOfAchievementValue = 4,
                       },
                       new LikertSkalaRow<int>
                       {
                           Code = 7,
                           Text = "Lungenembolie «A»",
                           SelfAssessmentValue = 3,
                           ExternalAssessmentValue = 4,
                           DegreeOfAchievementValue = 3,
                       },
                       new LikertSkalaRow<int>
                       {
                           Code = 9,
                           Text = "Akuter Myokardinfarkt ACS «A»",
                           SelfAssessmentValue = 3,
                           ExternalAssessmentValue = 2,
                           DegreeOfAchievementValue = 3,
                       },
                       new LikertSkalaRow<int>
                       {
                           Code = 11,
                           Text = "Hypertensive Krise «A»",
                           SelfAssessmentValue = 3,
                           ExternalAssessmentValue = 1,
                       },
                       new LikertSkalaRow<int>
                       {
                           Code = 13,
                           Text = "Arrhythmien und Rhythmusstörungen «A»",
                           SelfAssessmentValue = 3,
                           ExternalAssessmentValue = 99,
                           DegreeOfAchievementValue = 2,
                       },
                       new LikertSkalaRow<int>
                       {
                           Code = 15,
                           Text = "Aortendissektion (Erwachsene) «B»",
                           SelfAssessmentValue = 2,
                           ExternalAssessmentValue = 2,
                           DegreeOfAchievementValue = 2,
                       },
                   };
        }

        /// <summary>
        /// Zwei Skalen mit unterschiedlichen Spalten als eine Gruppe – Grundlage des Beispiels
        /// «Mehrere Skalen gruppieren».
        /// </summary>
        /// <returns>Das Modell der Gruppe.</returns>
        public static LikertSkalaGroupModel Gruppe()
        {
            var gruppe = new LikertSkalaGroupModel
                         {
                             // Der Bearbeitungsmodus steht an der Gruppe, nicht an der einzelnen
                             // Skala: Wer eine Gruppe ausfüllt, tut das durchgehend in einer Rolle.
                             AssessmentType = Editable.ExternalAssessment,
                             GroupHeader = "Zwischenevaluation 2026",
                             Header = "Kompetenzen und Verhalten",
                             Introduction = "Bitte beide Tabellen vollständig ausfüllen.",
                             Footer = "Die Einschätzung wird mit der Kandidatin oder dem Kandidaten besprochen.",
                         };

            var fachlich = new LikertSkalaModel
                           {
                               Id = 10,
                               LikerstSkalaHeader = "<h5>Fachliche Kompetenzen</h5>",
                               Header = "Kompetenz",
                               Introduction = "Vier Stufen, dazu «nicht beurteilbar».",
                               ShowDegreeOfAchievement = true,
                               Columns = Stufen(),
                               Likertskala = Kompetenzen(),
                           };

            var verhalten = new LikertSkalaModel
                            {
                                Id = 12,
                                LikerstSkalaHeader = "<h5>Verhalten im Team</h5>",
                                Header = "Merkmal",
                                Footer = "Erfüllt bedeutet: im Alltag durchgehend beobachtbar.",

                                // Kein Skalenwert 0: Eine noch nicht beurteilte Zeile hat als int
                                // ebenfalls 0 und sähe sonst aus wie eine bewusste Antwort.
                                Columns = new List<LikertSkalaHeaderColumn<int>>
                                          {
                                              new LikertSkalaHeaderColumn<int> { Value = 1, Text = "Erfüllt", Width = 150 },
                                              new LikertSkalaHeaderColumn<int> { Value = 2, Text = "Nicht erfüllt", Width = 150 },
                                          },
                                Likertskala = new List<LikertSkalaRow<int>>
                                              {
                                                  new LikertSkalaRow<int> { Code = 21, Text = "Pünktlichkeit", ExternalAssessmentValue = 1 },
                                                  new LikertSkalaRow<int> { Code = 23, Text = "Übergaben vollständig", ExternalAssessmentValue = 2 },
                                                  new LikertSkalaRow<int> { Code = 25, Text = "Rückfragen rechtzeitig gestellt" },
                                              },
                            };

            gruppe.LikertSkalaGroup.Add(fachlich);
            gruppe.LikertSkalaGroup.Add(verhalten);

            return gruppe;
        }
    }
}
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using FMH.Komponente.Kompetenzen.Ui.Controls.LikertSkala;
using FMH.Komponente.Kompetenzen.Ui.Controls.LikertSkala.Daten;

namespace FMH.Komponente.WebUI.Areas.Kompetenzen.Models
{
    /// <summary>
    /// Der Stand der Beispiele. Ohne ihn zeigte die Seite nach dem Absenden wieder die
    /// Ausgangsdaten – die Wirkung einer Änderung wäre nicht zu sehen.
    /// </summary>
    /// <remarks>
    /// Statt einer Datenbank eine prozessweite Ablage je Beispiel; nach einem Neustart der
    /// Anwendung steht wieder die Ausgangslage. In einem Projekt steht an dieser Stelle das
    /// Repository – der Ablauf ist derselbe.
    /// <para>
    /// <b>Übernommen wird über den <c>Code</c>, nicht die Position.</b> Aus dem Formular kommen nur
    /// Code und die Einschätzung der aktuellen Rolle zurück; die geforderte Stufe, die Überschriften
    /// und die Spalten stammen weiterhin aus der eigenen Quelle. Genau so gehört es auch in einem
    /// Projekt gemacht: Der POST liefert Antworten, nicht die Konfiguration.
    /// </para>
    /// </remarks>
    public static class LernzielZustand
    {
        private static readonly ConcurrentDictionary<string, LikertSkalaGroupModel> Gruppen =
            new ConcurrentDictionary<string, LikertSkalaGroupModel>();

        private static readonly ConcurrentDictionary<string, IList<LikertSkalaRow<int>>> Skalen =
            new ConcurrentDictionary<string, IList<LikertSkalaRow<int>>>();

        /// <summary>
        /// Der aktuelle Stand einer einzelnen Skala; beim ersten Aufruf die Ausgangslage.
        /// </summary>
        /// <param name="beispiel">Der Schlüssel des Beispiels.</param>
        /// <param name="ausgangslage">Liefert die Ausgangsdaten, wenn noch nichts erfasst wurde.</param>
        /// <returns>Die Zeilen der Skala.</returns>
        public static IList<LikertSkalaRow<int>> Skala(string beispiel, Func<IList<LikertSkalaRow<int>>> ausgangslage)
        {
            return Skalen.GetOrAdd(beispiel, _ => ausgangslage());
        }

        /// <summary>
        /// Der aktuelle Stand einer Gruppe; beim ersten Aufruf die Ausgangslage.
        /// </summary>
        /// <param name="beispiel">Der Schlüssel des Beispiels.</param>
        /// <param name="ausgangslage">Liefert die Ausgangsdaten, wenn noch nichts erfasst wurde.</param>
        /// <returns>Das Modell der Gruppe.</returns>
        public static LikertSkalaGroupModel Gruppe(string beispiel, Func<LikertSkalaGroupModel> ausgangslage)
        {
            return Gruppen.GetOrAdd(beispiel, _ => ausgangslage());
        }

        /// <summary>
        /// Übernimmt die Antworten eines POST in den Stand einer einzelnen Skala.
        /// </summary>
        /// <param name="beispiel">Der Schlüssel des Beispiels.</param>
        /// <param name="gepostet">Die Zeilen aus dem Formular.</param>
        /// <param name="rolle">Die Rolle, in der erfasst wurde.</param>
        /// <returns>Die Anzahl geänderter Zeilen.</returns>
        public static int Uebernehmen(string beispiel, IList<LikertSkalaRow<int>> gepostet, Editable rolle)
        {
            if (Skalen.TryGetValue(beispiel, out var bestand) == false)
            {
                return 0;
            }

            return Uebernehmen(bestand, gepostet, rolle);
        }

        /// <summary>
        /// Übernimmt die Antworten eines POST in den Stand einer Gruppe.
        /// </summary>
        /// <param name="beispiel">Der Schlüssel des Beispiels.</param>
        /// <param name="gepostet">Die Gruppe aus dem Formular.</param>
        /// <returns>Die Anzahl geänderter Zeilen über alle Skalen.</returns>
        public static int UebernehmenGruppe(string beispiel, LikertSkalaGroupModel gepostet)
        {
            if (Gruppen.TryGetValue(beispiel, out var bestand))
            {
                // Die Rolle steht am Bestand: AssessmentType ist Konfiguration und wird nicht
                // gepostet – am geposteten Modell stünde der Standardwert.
                var anzahl = 0;

                for (var idx = 0; idx < bestand.LikertSkalaGroup.Count; idx++)
                {
                    var geposteteSkala = gepostet?.LikertSkalaGroup.ElementAtOrDefault(idx);

                    anzahl += Uebernehmen(
                        bestand.LikertSkalaGroup[idx].Likertskala,
                        geposteteSkala?.Likertskala,
                        bestand.AssessmentType);
                }

                return anzahl;
            }

            return 0;
        }

        /// <summary>
        /// Verwirft den erfassten Stand eines Beispiels; der nächste Aufruf zeigt wieder die
        /// Ausgangslage.
        /// </summary>
        /// <param name="beispiel">Der Schlüssel des Beispiels.</param>
        public static void Zuruecksetzen(string beispiel)
        {
            Skalen.TryRemove(beispiel, out _);
            Gruppen.TryRemove(beispiel, out _);
        }

        private static int Uebernehmen(IList<LikertSkalaRow<int>> bestand, IList<LikertSkalaRow<int>> gepostet, Editable rolle)
        {
            // In dieser Rolle sind die Radiobuttons gesperrt und senden nichts – ein POST darf den
            // Stand deshalb nicht anfassen.
            if (rolle == Editable.ExternalAssessmentReadOnly || gepostet == null)
            {
                return 0;
            }

            var anzahl = 0;

            foreach (var zeile in gepostet)
            {
                var ziel = bestand.FirstOrDefault(vorhanden => vorhanden.Code == zeile.Code);

                if (ziel == null)
                {
                    continue;
                }

                if (rolle == Editable.ExternalAssessment)
                {
                    if (ziel.ExternalAssessmentValue != zeile.ExternalAssessmentValue)
                    {
                        ziel.ExternalAssessmentValue = zeile.ExternalAssessmentValue;
                        anzahl++;
                    }
                }
                else if (ziel.SelfAssessmentValue != zeile.SelfAssessmentValue)
                {
                    ziel.SelfAssessmentValue = zeile.SelfAssessmentValue;
                    anzahl++;
                }
            }

            return anzahl;
        }
    }
}
using FMH.Komponente.Basis.Erweiterungen;
using FMH.Komponente.Basis.Ui.Feedback.Alert;
using FMH.Komponente.Kompetenzen.Ui.Controls.LikertSkala.Daten;
using FMH.Komponente.WebUI.Areas.Kompetenzen.Models;
using Microsoft.AspNetCore.Mvc;

namespace FMH.Komponente.WebUI.Areas.Kompetenzen.Controllers.Beispiele
{
    /// <summary>
    /// Die Gegenstelle der Beispiele. Die Skala ist ein Eingabe-Steuerelement – ohne einen POST, der
    /// die Antworten übernimmt, bliebe die halbe Erklärung offen.
    /// </summary>
    /// <remarks>
    /// Alle Actions arbeiten nach dem PRG-Muster: übernehmen, Meldung merken, zurück auf die
    /// Demo-Seite. Der Stand liegt in LernzielZustand, damit die Seite nach dem Absenden das
    /// Erfasste zeigt und nicht wieder die Ausgangsdaten.
    /// <para>
    /// Übernommen wird über den Code der Zeile und nur der Wert der aktuellen Rolle. Alles andere –
    /// die geforderte Stufe, die Spalten, die Texte – steht in keinem Formularfeld und kommt
    /// weiterhin aus der eigenen Quelle.
    /// </para>
    /// </remarks>
    [Area("Kompetenzen")]
    public class LikertSkalaBeispielController : Controller
    {
        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Einschaetzung(LernzielViewModel modell)
        {
            // Die Rolle kommt als Hidden-Feld mit und entscheidet, welcher der beiden Werte
            // geschrieben wird. Der jeweils andere reist unverändert in seinem Hidden-Feld mit.
            this.MeldeUebernahme(LernzielZustand.Uebernehmen("einschaetzung", modell.Likertskala, modell.Rolle), modell.Rolle);

            return this.RedirectToAction("Einschaetzung", "Demo", new { area = "Kompetenzen", rolle = modell.Rolle });
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Erfuellungsgrad(LernzielViewModel modell)
        {
            this.MeldeUebernahme(LernzielZustand.Uebernehmen("erfuellungsgrad", modell.Likertskala, modell.Rolle), modell.Rolle);

            return this.Zurueck(nameof(this.Erfuellungsgrad));
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Grundfall(LernzielViewModel modell)
        {
            this.MeldeUebernahme(LernzielZustand.Uebernehmen("grundfall", modell.Likertskala, modell.Rolle), modell.Rolle);

            return this.Zurueck(nameof(this.Grundfall));
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Gruppe(KompetenzGruppeViewModel modell)
        {
            // Die Rolle steht bei einer Gruppe am Modell und wird nicht gepostet – der Zustand
            // nimmt sie deshalb aus dem eigenen Bestand.
            var anzahl = LernzielZustand.UebernehmenGruppe("gruppe", modell.Tabellen);

            this.AddAlert(
                anzahl == 0
                    ? "Keine Änderung – es wurde nichts anders bewertet als zuvor."
                    : $"{anzahl} Bewertung(en) über alle Skalen übernommen.",
                anzahl == 0 ? AlertType.Info : AlertType.Success,
                autoHide: true);

            return this.Zurueck(nameof(this.Gruppe));
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Texte(LernzielViewModel modell)
        {
            this.MeldeUebernahme(LernzielZustand.Uebernehmen("texte", modell.Likertskala, modell.Rolle), modell.Rolle);

            return this.Zurueck(nameof(this.Texte));
        }

        /// <summary>
        /// Verwirft den erfassten Stand eines Beispiels und zeigt wieder die Ausgangsdaten.
        /// </summary>
        /// <param name="beispiel">Der Schlüssel des Beispiels, zugleich der Name der Demo-Action.</param>
        /// <param name="rolle">Die Rolle, in die zurückgekehrt wird (nur beim Beispiel «einschaetzung»).</param>
        /// <returns>Die Umleitung auf die Demo-Seite.</returns>
        public IActionResult Zuruecksetzen(string beispiel, Editable rolle = Editable.SelfAssessment)
        {
            LernzielZustand.Zuruecksetzen(beispiel);

            this.AddAlert("Ausgangslage wiederhergestellt.", AlertType.Info, autoHide: true);

            return this.RedirectToAction(DemoAction(beispiel), "Demo", new { area = "Kompetenzen", rolle });
        }

        /// <summary>
        /// Der Schlüssel des Beispiels ist der kleingeschriebene Name der Demo-Action.
        /// </summary>
        /// <param name="beispiel">Der Schluessel des Beispiels.</param>
        /// <returns>Der Name der Demo-Action.</returns>
        private static string DemoAction(string beispiel)
        {
            switch (beispiel)
            {
                case "einschaetzung":
                    return "Einschaetzung";
                case "erfuellungsgrad":
                    return "Erfuellungsgrad";
                case "gruppe":
                    return "Gruppe";
                case "texte":
                    return "Texte";
                default:
                    return "Grundfall";
            }
        }

        private void MeldeUebernahme(int anzahl, Editable rolle)
        {
            if (Rollen.IstBearbeitbar(rolle) == false)
            {
                // Gesperrte Radiobuttons senden nichts – ein POST aus dieser Rolle darf den Stand
                // nicht anfassen.
                this.AddAlert("Nur-Ansicht-Rolle – es wurde nichts übernommen.", AlertType.Warning, autoHide: true);

                return;
            }

            this.AddAlert(
                anzahl == 0
                    ? $"Keine Änderung an «{Rollen.Kurzname(rolle)}» – es wurde nichts anders bewertet als zuvor."
                    : $"{anzahl} Bewertung(en) für «{Rollen.Kurzname(rolle)}» übernommen.",
                anzahl == 0 ? AlertType.Info : AlertType.Success,
                autoHide: true);
        }

        private IActionResult Zurueck(string action)
        {
            return this.RedirectToAction(action, "Demo", new { area = "Kompetenzen" });
        }
    }
}

Darstellung

Rolle: Self-assessment
Erfasst wird «Self-assessment»; «definitive assessment» steht als Markierung daneben.
Kompetenzen aus dem Weiterbildungsprogramm
Bitte alle Zeilen beurteilen. Die Einschätzung lässt sich bis zum Abschluss der Periode ändern.
Lernziel Trifft zu Teilweise Trifft nicht zu
Betreuung
Kann Patientinnen und Patienten umfassend beraten, begleiten und betreuen und dabei deren Autonomie gebührend respektieren
Kommunikation
Kann komplexe Sachverhalte gegenüber Angehörigen verständlich darstellen
Zusammenarbeit
Kann die eigene Rolle im interprofessionellen Team einnehmen und Aufgaben delegieren

Um die Skala herum stehen vier Textstellen zur Verfügung, alle optional:

Aufruf Ort CSS-Klasse
.Header(html) über der Skala lk-header, lk-header-<id>
.Introduction(html) darunter, vor der Tabelle lk-introduction, lk-introduction-<id>
.DescriptionColumn(text) Überschrift der ersten Spalte –
.Footer(html) unter der Tabelle lk-footer, lk-footer-<id>

Die fünfte steht in den Daten: LikertSkalaRow.Header setzt eine fette Zeile über den Text der Zeile (div.fmh-lk-row-header, zusätzlich fmh-lk-row-header-<code>). Sinnvoll, wenn die Zeilen zu Themen gehören, die genannt werden sollen, ohne dafür eine eigene Skala aufzumachen.

Alle nehmen HTML entgegen

Header, Introduction, Footer, die Spaltenüberschrift, der Zeilentext und die Zeilenüberschrift gehen als Markup in die Seite – so lässt sich eine Einleitung gestalten oder ein Begriff hervorheben. Stammt der Text aus einer Eingabe, gehört er vorher bereinigt.

Spaltenbreite

For(...) liefert die Spalte zurück, WidthInPixel(px) setzt ihre Breite:

spalten.For(1, "Trifft zu")
    .WidthInPixel(160);

Ohne Angabe sind es 125 px. Die Breite landet als style="width:…px" am th und wirkt nur in der breiten Darstellung; in der schmalen klappt die Tabelle ohnehin um.

Was zu beachten ist

  • Überschrift und Zeilentext kommen gemeinsam im Feld Text zurück («Betreuung Kann Patientinnen …»). Wer beide Teile getrennt braucht, holt sie über den Code aus der eigenen Datenquelle.
  • Der Text der Zeile wird zusätzlich unterhalb der Überschrift ausgegeben; Überschrift und Text sollen sich also ergänzen, nicht wiederholen.