DokumentListe

Anzahl per Ereignis auswerten

Das Ereignis documentsuploaded meldet nach jeder Änderung den Feldnamen, die aktuelle Anzahl Dokumente und die Obergrenze.

@model DokumentListeEreignisBeispielViewModel

@* Das Skript hört auf das Ereignis "documentsuploaded". Es steht am Ende des Partials und damit
   vor den BodyIncludes des Layouts — dort registriert das Template erst seinen ready-Block. Der
   Listener ist also bereits vorhanden, wenn das Ereignis beim Seitenaufbau das erste Mal fällt. *@

<form asp-area="FileUpload" asp-controller="DokumentListeEreignisBeispiel" asp-action="Index" method="post">
    <div asp-validation-summary="All" class="text-danger"></div>

    <p id="dokumentliste-ereignis-ausgabe" class="alert alert-info py-2">
        Noch kein Ereignis empfangen.
    </p>

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

<script src="~/js/demos/dokumentliste-ereignis.js" asp-append-version="true"></script>
using System.ComponentModel.DataAnnotations;
using FMH.Komponente.Basis.Ui.Validation.DataAnnotation;
using FMH.Komponente.FileUpload.Models;
using FMH.Komponente.Infrastruktur;

namespace FMH.Komponente.WebUI.Areas.FileUpload.Models
{
    public class DokumentListeEreignisBeispielViewModel
    {
        // Der Eigenschaftsname bestimmt den Namen im Ereignis: detail.name ist "Kursbelege".
        // Ohne [UniqueId] entspricht er 1:1 der Html-Id, ist also im Skript vorhersagbar.
        [UIHint("DokumentListe")]
        [Display(Name = "Kursbelege")]
        [AdditionalMetadata(MvcConst.MetaDataUploadUrl, "/FileUpload/DokumentListeEreignisBeispiel/Upload")]
        [AdditionalMetadata(MvcConst.MaxFileSizeInMb, "1")]
        [AdditionalMetadata(MvcConst.MaxFiles, "5")]
        [AdditionalMetadata(MvcConst.MetaDataDownloadActionName, "Download")]
        [AdditionalMetadata(MvcConst.MetaDataDownloadControllerName, "DokumentListeEreignisBeispiel")]
        public DokumentListeViewModel Kursbelege { get; set; } = new DokumentListeViewModel();
    }
}
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using FMH.Komponente.Basis.Erweiterungen;
using FMH.Komponente.Basis.Ui.Feedback.Alert;
using FMH.Komponente.FileUpload.Models;
using FMH.Komponente.WebUI.Areas.FileUpload.Models;
using Microsoft.AspNetCore.Mvc;

namespace FMH.Komponente.WebUI.Areas.FileUpload.Controllers.Beispiele
{
    // Serverseitig unterscheidet sich dieses Beispiel nicht vom Grundfall — der Lerninhalt liegt
    // im Skript des Partials, das auf das Ereignis «documentsuploaded» hört. Der Controller liefert
    // nur die Ausgangslage samt Endpunkten.
    [Area("FileUpload")]
    public class DokumentListeEreignisBeispielController : Controller
    {
        private static readonly ConcurrentDictionary<int, string> Dokumente =
            new ConcurrentDictionary<int, string>(
                new Dictionary<int, string> { [5] = "Kurs.pdf", [6] = "Kurs2.pdf" });

        private static int letzteId = 100;

        // Auch die Doku-Seite zeigt die gefüllte Liste — deshalb öffentlich.
        public static DokumentListeEreignisBeispielViewModel ErstelleModell()
        {
            var modell = new DokumentListeEreignisBeispielViewModel();

            modell.Kursbelege.Dokumente = Dokumente
                .OrderBy(eintrag => eintrag.Key)
                .Select(eintrag => new DokumentEintrag
                                   {
                                       DokumentEintragId = eintrag.Key,
                                       Name = eintrag.Value,
                                       Groesse = 30000 + (eintrag.Key * 100),
                                   })
                .ToList();

            return modell;
        }

        [HttpGet]
        public IActionResult Index()
        {
            return this.View(ErstelleModell());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Index(DokumentListeEreignisBeispielViewModel modell)
        {
            var anzahlNeu = modell.Kursbelege.DokumentIds?.Length ?? 0;

            this.AddAlert(
                $"Formular übertragen – {anzahlNeu} neue(s) Dokument(e).",
                AlertType.Success,
                autoHide: true);

            return this.RedirectToAction(nameof(this.Index));
        }

        /// <summary>
        /// Der Upload-Endpunkt dieses Beispiels. Nach jedem erfolgreichen Upload ruft das Template
        /// <c>checkMaxUploadedFiles</c> auf — und genau dabei entsteht das Ereignis
        /// <c>documentsuploaded</c>, auf das das Skript des Beispiels hört.
        /// </summary>
        /// <returns>Json mit der vergebenen Id.</returns>
        [HttpPost]
        public IActionResult Upload()
        {
            if (this.Request.Headers.ContainsKey("IsDropzoneRequest") == false || this.Request.Form.Files.Count == 0)
            {
                return this.Json(new { id = 0 });
            }

            var datei = this.Request.Form.Files[0];
            var bezeichnung = this.Request.Form["DateiBezeichnung"].ToString();
            var name = string.IsNullOrWhiteSpace(bezeichnung) ? datei.FileName : bezeichnung;

            var id = Interlocked.Increment(ref letzteId);
            Dokumente[id] = name;

            return this.Json(new { id });
        }

        /// <summary>
        /// Der Download-Endpunkt dieses Beispiels.
        /// </summary>
        /// <param name="id">Die Id des Dokuments aus der Tabellenzeile.</param>
        /// <returns>Die Datei zum Herunterladen.</returns>
        [HttpGet]
        public IActionResult Download(int id)
        {
            if (Dokumente.TryGetValue(id, out var name) == false)
            {
                return this.NotFound();
            }

            var inhalt = Encoding.UTF8.GetBytes($"Platzhalter-Inhalt für «{name}» (Id {id}).");

            return this.File(inhalt, "text/plain", name);
        }
    }
}
// Beispiel «Anzahl per Ereignis auswerten» (Area FileUpload, EditorTemplate DokumentListe).
// Das EditorTemplate ruft nach jeder Änderung checkMaxUploadedFiles auf; diese Funktion löst am
// window das Ereignis "documentsuploaded" aus. Im detail stehen der Feldname, die aktuelle Anzahl
// Dokumente und die konfigurierte Obergrenze.
(function () {
    "use strict";

    var AUSGABE_ID = "dokumentliste-ereignis-ausgabe";

    // Das Ereignis fällt bereits beim Aufbau der Seite, danach bei jedem Hinzufügen und jedem
    // Entfernen eines Dokuments. Die Registrierung erfolgt sofort, nicht erst nach DOMContentLoaded
    // — sonst ginge das erste Ereignis verloren.
    window.addEventListener("documentsuploaded", function (ereignis) {
        var ausgabe = document.getElementById(AUSGABE_ID);

        if (ausgabe === null || !ereignis.detail) {
            return;
        }

        ausgabe.textContent = "Feld «" + ereignis.detail.name + "»: " + ereignis.detail.total +
            " von " + ereignis.detail.maxFiles + " Dokument(en).";
    });
})();
// Dieselbe Umsetzung mit jQuery. Diese Datei wird bewusst nicht geladen — sie dient nur dem
// Code-Tab «jQuery», sonst liefen beide Varianten gleichzeitig.
$(window).on("documentsuploaded", function (ereignis) {
    var ausgabe = $("#dokumentliste-ereignis-ausgabe");

    // jQuery ersetzt das native Ereignis durch ein eigenes Objekt. Die Eigenschaft detail des
    // CustomEvent liegt deshalb je nach jQuery-Version direkt am Ereignis oder am originalEvent.
    var detail = ereignis.detail || (ereignis.originalEvent && ereignis.originalEvent.detail);

    if (ausgabe.length === 0 || !detail) {
        return;
    }

    ausgabe.text("Feld «" + detail.name + "»: " + detail.total + " von " + detail.maxFiles + " Dokument(en).");
});

Darstellung

Noch kein Ereignis empfangen.

Description Size Actions
Kurs.pdf 29.79 KB
Kurs2.pdf 29.88 KB
attach_file

Drag files here or click here to upload.

report_problem

You can not upload any more files.

Nach jeder Änderung an der Dokumentliste löst das Template am window das Ereignis documentsuploaded aus. Damit lassen sich abhängige Anzeigen oder Schaltflächen steuern, ohne die Dropzone selbst anzufassen.

[UIHint("DokumentListe")]
[Display(Name = "Kursbelege")]
[AdditionalMetadata(MvcConst.MetaDataUploadUrl, "/FileUpload/DokumentListeEreignisBeispiel/Upload")]
[AdditionalMetadata(MvcConst.MaxFiles, "5")]
public DokumentListeViewModel Kursbelege { get; set; } = new DokumentListeViewModel();
window.addEventListener("documentsuploaded", function (ereignis) {
    // ereignis.detail = { name, total, maxFiles }
    console.log(ereignis.detail.name, ereignis.detail.total, ereignis.detail.maxFiles);
});

Was im Ereignis steht

Feld Bedeutung
detail.name Der Feldname der Eigenschaft (Html.NameFor), z. B. Kursbelege. Bei mehreren Listen auf einer Seite das Unterscheidungsmerkmal.
detail.total Die aktuelle Anzahl Dokumente: bereits zugeordnete plus in dieser Sitzung hochgeladene.
detail.maxFiles Die konfigurierte Obergrenze (MvcConst.MaxFiles, Standard 100).

Wann es ausgelöst wird

Das Ereignis entsteht in der Funktion checkMaxUploadedFiles. Sie läuft

  1. beim Aufbau der Seite, direkt nach dem Anlegen der Dropzone,
  2. nach jedem Hinzufügen einer Datei,
  3. nach jedem Entfernen eines Dokuments aus der Tabelle.

Was zu beachten ist

  • Das Ereignis fällt am window, nicht am Feld. Mit jQuery also $(window).on("documentsuploaded", …).
  • Es blubbert (bubbles: true), lässt sich aber nirgends sinnvoll weiter oben abfangen — der Auslöser ist bereits das window.
  • Zeitpunkt der Registrierung: Das erste Ereignis fällt schon beim Seitenaufbau. Ein Skript, das im Beispiel-Partial per <script src> geladen wird, ist rechtzeitig da — das Template registriert seinen ready-Block erst über die BodyIncludes des Layouts, also nach dem Partial. Wird der Listener dagegen erst nach dem Template registriert, ist nur noch jede weitere Änderung sichtbar.
  • Mit jQuery kann detail je nach Version am Ereignis oder am originalEvent liegen — ereignis.detail || ereignis.originalEvent.detail deckt beide Fälle ab.