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
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
- beim Aufbau der Seite, direkt nach dem Anlegen der Dropzone,
- nach jedem Hinzufügen einer Datei,
- 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 daswindow. - 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 seinenready-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
detailje nach Version am Ereignis oder amoriginalEventliegen —ereignis.detail || ereignis.originalEvent.detaildeckt beide Fälle ab.