Kapitel 5 des verbindlichen Modulhandbuchs für das Referenzmodul myInvoices.
8. Formtemplate
Datei tpl/htm/invoice-form.htm:
<div id="dbxForm_{i}" class="dbx-panel dbxForm_wrapper dbx-ajax-root">
<div class="dbx-panel-head d-flex justify-content-between align-items-center">
<h2 class="h5 mb-0">{form_title}</h2>
<a class="btn btn-outline-secondary btn-sm" href="{list_url}">
Zur Liste
</a>
</div>
<form action="{action}" method="post" class="dbxAjax"
data-ajax-target="dbxForm_{i}" data-ajax-replace="target">
<div class="dbx-panel-body">
{obj:form_msg}
<div class="row g-3">
[dbx:form]
</div>
</div>
<div class="dbx-panel-foot">
<button class="btn btn-primary" type="submit">
<i class="bi bi-save"></i> Speichern
</button>
</div>
[dbx:js]
</form>
</div>
Ohne JavaScript funktioniert derselbe Submit normal. Mit dbxAjax ersetzt die Antwort nur dbxForm_{i} und initialisiert die enthaltenen Features neu.
9. Äußerer Rechnungsreport
Datei tpl/htm/invoice-report.htm:
<div class="dbx-panel dbxReport dbx-ajax-root"
id="dbx_target_{i}"
data-dbx="lib=report|form=0">
<div class="dbx-panel-head d-flex justify-content-between align-items-center">
<h2 class="h5 mb-0">Rechnungen</h2>
<a class="btn btn-primary btn-sm"
href="?dbx_modul=myInvoices&dbx_run1=form&rid=new">
<i class="bi bi-plus-lg"></i> Neu
</a>
</div>
<form action="{action}" method="post" id="dbx_form_{i}" class="dbxAjax">
[dbx:form]
[tpl=dbx|report-form-select]
[dbx:pagination]
<div class="table-responsive dbx-report-table-scroll">
<table class="table table-striped table-bordered table-hover align-middle">
<thead>
<tr class="{tr-class}">[rpt:row]</tr>
</thead>
<tbody>
<hr class="dbx_split">
<tr class="{tr-class}">[rpt:row]</tr>
<tr class="dbx-invoice-items-row">
<td colspan="{rpt:col_count}" class="p-3 bg-body-tertiary">
{positions_call}
</td>
</tr>
<hr class="dbx_split">
</tbody>
<tfoot>
<tr>
<th colspan="{rpt:colspan}" class="text-end">
Endsumme der angezeigten Rechnungen
</th>
<th class="text-end">{invoice_report_total}</th>
</tr>
</tfoot>
</table>
</div>
[dbx:js]
</form>
</div>
[rpt:row] erzeugt im table-Modus Header und Datenzellen aus _rflds. Die zweite <tr> gehört zur gleichen Rechnung. Ihr colspan="{rpt:col_count}" deckt auch dann alle Spalten ab, wenn sich der Report später ändert. {positions_call} wurde vorher vom Record-Callback gesetzt und wird nach dem Reportlauf vom dbxInterpreter ausgeführt.
Der Footer verwendet {rpt:colspan}. dbxReport berechnet damit automatisch „alle Spalten außer der letzten Wertespalte“. Eine eigene Footer-Methode ist nicht erforderlich; der Record-Callback aktualisiert die Endsumme spät über add_rep().
10. Eingebetteter Positionsreport
Datei tpl/htm/invoice-items-report.htm:
<div class="dbxReport" id="dbx_invoice_items_{i}">
<h3 class="h6">Positionen zu Rechnung {invoice_no}</h3>
<div class="table-responsive">
<table class="table table-sm table-bordered mb-0">
<thead>
<tr class="{tr-class}">[rpt:row]</tr>
</thead>
<tbody>
<hr class="dbx_split">
<tr class="{tr-class}">[rpt:row]</tr>
<hr class="dbx_split">
</tbody>
<tfoot>
<tr>
<th colspan="{rpt:colspan}" class="text-end">
Endsumme der Positionen
</th>
<th class="text-end">{report_total}</th>
</tr>
</tfoot>
</table>
</div>
</div>
Der Positionsreport enthält bewusst kein eigenes <form>: Er liegt innerhalb des Formulars des äußeren Reports, und verschachtelte HTML-Formulare wären ungültig. Er besitzt trotzdem eine eindeutige {i}-ID und eine vollständige dbxReport-Pipeline.
Weil positions() keine Pagination aktiviert, sieht der automatische invoice_items_report_next_record()-Callback jede Position der Rechnung. Der per add_rep() gesetzte Footerwert ist damit die vollständige Rechnungssumme der geladenen Positionen, nicht nur eine Seitensumme.
11. Zeilenaktionen mit Confirm und Ajax
Datei tpl/htm/invoice-row-action.htm:
<div class="btn-group btn-group-sm" role="group">
<a class="btn btn-outline-primary" href="{edit_url}">
<i class="bi bi-pencil"></i>
<span class="visually-hidden">Bearbeiten</span>
</a>
<a class="btn btn-outline-danger dbxAjax dbxConfirm"
href="{delete_url}"
data-confirm-title="{delete_title}"
data-confirm="{delete_question}"
data-confirm-hint="{delete_hint}"
data-confirm-buttons="yesno">
<i class="bi bi-trash"></i>
<span class="visually-hidden">Löschen</span>
</a>
</div>
Der Ablauf bleibt systemweit einheitlich:
- confirm.js fragt nur bei der markierten Mutation nach.
- Bei „Nein“ entsteht kein Request.
- Bei „Ja“ übernimmt ajax.js den vorhandenen Link.
- dbxWebApp prüft vor dem Modulstart Route, RID-Bindung und Token.
- dbxDB startet eine Transaktion auf dem gemeinsamen DD-Server.
- Positionen und Rechnung werden über ihre DDs gelöscht und getraced.
- Bei einem Fehler rollt rollback() beide Schritte zurück.
- Der neu gerenderte Report ersetzt denselben Ajax-Root.