dynamisches Rechnungsformular mit vielen Aktionen

wireless-dj

Grünschnabel
Hallo,

Als PHP-Einsteiger kenne ich mich mit JavaScript leider so gar nicht aus.
Ich stehe nun aber vor einem Problem, wo ich wohl ohne das nicht weiter komme.

Ich möchte mit PHP eine Rechnung schreiben, und diese dann in einer MariaDB speichern.
Die Kundenverwaltung, sowie die Artikelverwaltung funktionieren bereits, nun geht
es an das Erstellen der Rechnung.

Die Rechnung selbst ist eine html-Tabelle mit 8 Spalten, in die für jede Position der
Rechnung 3 Zeilen eingefügt werden sollen - entweder durch Klick auf einen Button,
oder durch die Auswahl des Artikels aus einem DropDown selbst.

Die Tabelle selbst sieht momentan so aus:
Bildschirmfoto 2022-05-30 um 14.18.46.png
Wenn man eine neue Rechnung erstellt, sieht man also erstmal nur die Spaltenüberschriften, sowie die Fußzeile mit der <datalist> -Auswahl.
Jetzt onclick() oder "onAuswahl" muss ja aber etliches passieren:
1. der Ausgewählte Artikel muss mit seinen einzelnen Feldern in den richtigen Feldern erscheinen.
2. die noch leeren Felder [Pos], [Menge] und Gesamt müssen sich automatisch mit Werten füllen
3. die DropDown-Artikelauswahl rutscht - bereit für den nächsten Artikel - an das Ende der Tabelle.
4. evtl. wird die Rechnung an dieser Stelle dann schon das erste Mal in einer temp-Datenbank abgelegt?!
5. in der Spalte Optionen finden sich Icons, die die Position wieder löschen können.
6. Die Pos.1 wurde erstellt.

Und das muss beliebig oft wiederholbar sein, wobei Pos. dann automatisch hoch zählt.
Der Code ( experimentell ) sieht mometan so aus:

Code:
<?

include "core/header.php";

$select = $db->query("SELECT `id`, `firma`, `lastname`, `firstname`, `street`, `street_no`, `plz`, `ort` FROM `kunden` ORDER by `firma`, `lastname` ");
$kunden = $select->fetchAll(PDO::FETCH_BOTH);

$select = $db->query("SELECT `id`, `name`, `kurz`, `kategorie`, `ean`, `einheit`, `preis_netto`, `lang` FROM `artikel` ");
$artikels = $select->fetchAll(PDO::FETCH_BOTH);

$i=0;

?>

<br><br>

<table width="1000" border="1">
    <tr>
        <td>Pos:</td>
        <td>Menge:</td>
        <td>Einheit:</td>
        <td>Artikel:</td>
        <td>MwSt:</td>
        <td>Netto:</td>
        <td>Gesamt:</td>
        <td>Optionen:</td>
    </tr>
    
<tbody>
    <tr>
        <td><input type="text" size="10" id="ArtikelId" name="id" value=""></td>
        <td><input type="text" size="10" id="ArtikelMenge" name="menge" value=""></td>
        <td><input type="text" size="10" id="ArtikelEinheit" name="id" value=""></td>
        <td><input type="text" size="50" id="ArtikelName" name="name" value=""></td>
        <td><input type="text" size="10" id="ArtikelMwSt" name="mwst" value="19"></td>
        <td><input type="text" size="20" id="ArtikelNetto" name="netto" value=""></td>
        <td>&nbsp;</td>
        <td>&nbsp;</td>
    </tr>
    <tr>
        <td colspan="3">&nbsp;</td>
        <td><input type="text" size="50" id="ArtikelKurz" name="kurz" value=""></td>
        <td colspan="3">&nbsp;</td>
        <td>&nbsp;</td>
    </tr>
    <tr>
        <td colspan="3">&nbsp;</td>
        <td><textarea cols="48" rows="6" id="ArtikelLang" name="lang"></textarea></td>
        <td colspan="3">&nbsp;</td>
        <td>&nbsp;</td>
    </tr>
</tbody>

    <tr>
        <td colspan="8">
            <form action="#">
                  Artikel wählen:
                  <input type="search" list="Artikel" style="width: 350px;">
                      <datalist id="Artikel">
                    
                    <?
                    foreach($artikels as $artikel) {

                     echo '<option value='.$artikel['id'].'>'.$artikel['name'].'';
                    }
                    ?>
    
                      </datalist>
                <input type="button" id="erstellen" value="Eintragen" onclick="add TableRow($i)">
            </form>
        </td>
    </tr>
</table>

Da die ganze fertige Rechnung quasi ein "Affenformular" wird, steckt sie also komplett in einer <form>...</form>,
und wird am Ende beim Speichern in der Datenbank gespeichert. Es soll dann noch ein PDF daraus werden,
aber da bin ich noch lange nicht.

Kann mir hier bei der dynamisch wachsenden Tabelle Jemand helfen?

Danke & Gruß,

Volker
 

Sempervivum

Erfahrenes Mitglied
Hallo Volker und willkommen im Forum!
Diese Aufgabe ist schon etwas umfangreicher aber nicht allzu schwierig.

Ich empfehle, die Daten der jeweiligen Pos. mit Ajax bzw. fetch vom Server zu holen. Dann brauchst Du kein extra Formular für die Artikelauswahl.
Du wirst einen Eventlistener für den Eintragen-Button brauchen:
HTML DOM Element addEventListener() Method

Dann, wenn der Listener feuert, die Daten mit fetch vom Server holen. Hier eine Demo aus der Schublade:
Code:
        // Neues FormData-Objekt erzeugen:
        const params = new FormData();
        // ... und die Parameter, die übertragen werden sollen, eintragen:
        params.append('param1', 'some-param');
        params.append('param2', 'some-other-param');
        params.append('numparam', '1.24');
        // Parameter mit der Methode POST an das Skript testpost.php schicken:
        fetch('testpost.php', {
            method: 'post',
            body: params
        }).then(res => {
            // Die Antwort vom Server wird als JSON ausgewertet:
            return res.json();
        }).then(res => {
            // Antwort vom Server in der Console ausgeben:
            console.log(res);
            // ... und in HTML-Element eintragen:
            document.getElementById('out').innerHTML = res;
        });
In deinem Fall brauchst Du natürlich nur einen Parameter, die ID des Artikels.

Da die drei Zeilen, die eingefügt werden müssen, ein wenig komplex sind, würde ich empfehlen, dafür ein Template anzulegen:
Code:
<template id="tpl-pos">
hier das HTML der drei Tabellenzeilen
</template>
In dem Template dann Platzhalter für die variablen Werte, die aus der Datenbank kommen, z. B.:
Code:
<td>{{var1}}</td>
Im then für die Antwort des Servers im fetch kannst Du dann das HTML des Template lesen und die Platzhalter durch die Werte vom Server bzw. aus der Datenbank ersetzen:
Code:
let htmlPos = document.getElementById('tpl-pos')
    .replace('{{var1}}', res.var1)
    .replace('{{var2}}', res.var2)
    // usw.
;
Dann bist Du fast am Ziel und kannst das HTML vor der Zeile mit der Datalist und dem Eintragen-Buttton eintragen:
Element.insertAdjacentHTML() - Web API Referenz | MDN

Was die Serverseite betrifft, müsstest Du die Daten des betr. Artikels aus der DB in ein ass. Array eintragen und JSON-kodieren:
Code:
echo json_encode($dasArrayAusDerDb);

Versuche, ob Du mit diesen Hinweisen weiter kommst und melde dich wieder, wenn es irgend wo hakt.
 
Zuletzt bearbeitet:

wireless-dj

Grünschnabel
Hallo Sempervivum,

Vielen Dank für den freundlichen Willkommensgruß, und die schnelle Antwort! :)
Momentan - und auf die schnelle betrachtet, verstehe ich allerdings nicht ein Wort von dem, was Du mir da geantwortet hast. Ich bin mir aber auch nicht sicher, ob ich mein Anliegen verständlich formuliert habe.

Vielleicht können wir da Schritt für Schritt durch gehen?
Ich empfehle, die Daten der jeweiligen Pos. mit Ajax bzw. fetch vom Server zu holen. Dann brauchst Du kein extra Formular für die Artikelauswahl.
Da fängt es schon an.
Ich bin gerade recht froh, dass ich es schaffe, so wie im ersten Post beschrieben, die Werte aus der Datenbank zu holen, und im DropDown darzustellen. Und selbst damit bin ich im Grunde noch auf Kriegsfuß, da ich den datalist() noch nicht wirklich durchschaut habe.
Ich hatte das schon mit select2() probiert, das gefiel mir eigentlich besser, aber ich hab mir sagen lassen, dass das nicht der State-of-the-Art - Weg ist.
Ich muss vielleicht auch noch erwähnen, dass ich den ganzen Teil zur Erstellung der Rechnung in einer einzigen Datei habe! Ich habe das mit den Kunden und den Artikeln ebenso gemacht, und rufe die jeweiligen Abschnitte über einen Request auf.

Also sprich:
if $page=overview {
... mach was.
}

if $page=new {
... mach was Anderes
}

Bisher komme ich damit gut zurecht, und verstehe, was ich da baue.
Aber, ich werde mir das die Tage in Ruhe anschauen, und versuchen, es zu verstehen, was Du da schreibst. Ich hab immer nur ein paar Momente Zeit, mich damit zu beschäftigen.

Danke & Gruß,

Volker
 

Sempervivum

Erfahrenes Mitglied
Tut mir leid, dass meine Erklärungen unverständlich sind aber nach kurzem Überfliegen deines PHP hatte ich den Eindruck, dass Du nicht unerfahren im Programmieren bist. In Javascript gibt es jedoch einige Dinge, die es in anderen Programmiersprachen nicht gibt, hier Ajax.
Das Wesentliche von Ajax kannst Du z. B. hier nachlesen:
JavaScript/Ajax – SELFHTML-Wiki

Mit diesem:
"Dann brauchst Du kein extra Formular für die Artikelauswahl."
meinte ich nur, dass Du kein form-Tag brauchst, um die Auswahl und den Button auszuwerten. Dies genügt:
Code:
    <tr id="tr-select-article">
        <td colspan="8">
                    Artikel wählen:
                    <input id="input-article" type="search" list="Artikel" style="width: 350px;">
                    <datalist id="Artikel">

            <?
            foreach($artikels as $artikel) {

            echo '<option value='.$artikel['id'].'>'.$artikel['name'].'';
            }
            ?>

            </datalist>
            <input type="button" id="erstellen" value="Eintragen">
        </td>
    </tr>
Ich muss vielleicht auch noch erwähnen, dass ich den ganzen Teil zur Erstellung der Rechnung in einer einzigen Datei habe! Ich habe das mit den Kunden und den Artikeln ebenso gemacht, und rufe die jeweiligen Abschnitte über einen Request auf.
Verstehe, aber gerade in diesem Fall würde der Aufbau als Affenformular das Ganze sehr unübersichtlich machen. Verwendest Du Ajax brauchst Du die Seite nicht neu zu laden wenn Du die Daten des Artikels vom Server holst. Du brauchst jedoch ein getrenntes PHP-Skript, das nur die Daten aus der DB holt und JSON-kodiert ausgibt.

Ich hatte das schon mit select2() probiert, das gefiel mir eigentlich besser, aber ich hab mir sagen lassen, dass das nicht der State-of-the-Art - Weg ist.
select2 kenne ich ganz gut und es hat sich bewährt. Wo hast Du das denn her, dass das nicht State-of-the-Art ist und wurde dazu auch eine Begründung geliefert? Ich kann dich nur ermutigen, es einzusetzen.

Das Ganze etwas genauer ausgearbeitet würde so aussehen:
Code:
    <script>
        // Eventlistener für Klick auf den Erstellen-Button registrieren:
        document.getElementById('Erstellen').addEventListener('click', event => {
            // ID des Artikels aus dem Eingabefeld lesen:
            const id = document.getElementById('input-article').value;
            // Jetzt kommt der Teil mit Ajax:
            // Neues FormData-Objekt erzeugen:
            const params = new FormData();
            // ... und die ID, die übertragen werden soll, eintragen:
            params.append('id', id);
            // Parameter mit der Methode POST an das Skript get-article-data.php schicken:
            fetch('get-article-data.php', {
                method: 'post',
                body: params
            }).then(res => {
                // Die Antwort vom Server ist angekommen,
                // wir werten sie als JSON aus:
                return res.json();
            }).then(data => {
                // data enthält jetzt die dekodierte Antwort vom Server
                // als Javascript-Objekt:
                console.log(data);
                
                // Jetzt das HTML aus dem Template lesen
                // und die Platzhalter durch die Daten vom Server ersetzen
                // wie früher gepostet

                // Und das Ergebnis vor der Tabellenzeile für die Auswahl
                // des Artikels einfügen:
                document.getElementById('tr-select-article')
                    .insertAdjacentHTML('beforebegin', htmlPos);
            });
        });
    </script>
 

Sempervivum

Erfahrenes Mitglied
PS: Ich kann mir die Begründung für die Ablehnung von select2 denken:
1. Es basiert auf jQuery
2. Mit datalist geht es heute auch ohne jQuery und select2
Das hat schon eine gewisse Berechtigung aber select2 hat auch Vorteile:
1. Es sieht in allen Browsern gleich aus
2. Es lässt sich leicht stylen
3. Es unterstützt Ajax
 

wireless-dj

Grünschnabel
Muss Dir nicht leid tun, alles gut…!
Ich bin ja froh, dass mir überhaupt Jemand hilft.
Ist ja nicht selbstverständlich…!

ok, das liesst sich erstmal etwas verständlicher.
Ich werde morgen mal den Code posten, den ich bisher mit select2() hatte.
Da hatte ich es soweit geschafft, dass ich zumindest in eine vorgegebene
Tabelle einen Artikel auswählen und eintragen konnte.
Vielleicht schaffe ich es ja, Deinen Code da mit zu verwenden…

Wenn ich Dich richtig verstehe, wird die „wachsende Tabelle“ für den
Rechnungskörper ( wo also die einzelnen Positionen stehen ) über eine
externe Datei gemacht, die dann z.B. per include() in der Hauptdatei eingebunden wird?
Das wäre für mich ok, das verstehe ich soweit.

Die Struktur mit dem Request, wo ich alle Bereiche in einer Datei habe, gefällt mir
deswegen gut, weil ich dann halt alles in nur einer Datei habe. Wenn man irgendwann
mal was nacharbeiten will, muss man halt nicht in den verschachtelten Strukturen
danach suchen, und darüber hinaus habe ich bereits die komplett funktionierende
Kundenverwaltung und Artikelverwaltung mit diesem Prinzip realisiert, da würde ich
die Struktur gerne beibehalten, damit es halt einheitlich im gesamten Projekt ist.
Ist das machbar? Sonst müsste ich die anderen Teile ja wieder umschreiben…

Ja, so ziemlich genau waren die Begründungen wegen select2(). ;-)

Wie gesagt, morgen geht es weiter, für heute ist Feierabend.
Bis hierher aber schonmal ganz herzlichen Dank für Deine Zeit und Deine Mühe!!!

Gruß,

Volker
 

Sempervivum

Erfahrenes Mitglied
Wenn ich Dich richtig verstehe, wird die „wachsende Tabelle“ für den
Rechnungskörper ( wo also die einzelnen Positionen stehen ) über eine
externe Datei gemacht, die dann z.B. per include() in der Hauptdatei eingebunden wird?
Nein, es verhält sich etwas anders: Dieses Skript, das ich als 'get-article-data.php' bezeichnet habe wird nicht über include() eingebunden sondern unabhängig von der Hauptdatei durch das fetch ausgeführt.

da würde ich
die Struktur gerne beibehalten, damit es halt einheitlich im gesamten Projekt ist.
Ist das machbar? Sonst müsste ich die anderen Teile ja wieder umschreiben…
Ja, das ist machbar, Du brauchst nicht alles umzuschreiben. Nur das Template hinzu fügen und das Javascript.
 

wireless-dj

Grünschnabel
Guten morgen,

So, ich hab nun ein wenig Luft, und versuche mal zu verstehen, was Du geschrieben hast.
Wenn Du sagst, dass das eine gute Wahl ist, würde ich gerne bei select2() bleiben.

Das was ich bisher mit select2() erreicht habe, sieht so aus:

Code:
<?

include "core/header.php";

$select = $db->query("SELECT `id`, `name`, `kurz`, `kategorie`, `ean`, `einheit`, `preis_netto`, `lang` FROM `artikel` ");
$artikels = $select->fetchAll(PDO::FETCH_BOTH);

?>

<script type="text/javascript">
function calc(){
    var netto = document.getElementById('preis_netto');
    var mwst = document.getElementById('mwst');
    var fieldmwst_euro = document.getElementById('mwst_euro');
    
    fieldmwst_euro.value = (!isNaN(parseFloat(netto.value)) && !isNaN(parseFloat(mwst.value))) ? (parseFloat(netto.value) / 100 *  parseFloat(mwst.value)).toFixed(2) : "";
    
    var fieldpreis_brutto = document.getElementById('preis_brutto');
    netto.value = netto.value.replace(/,/,".");
    mwst_euro.value = mwst_euro.value.replace(/,/,".");
    mwst.value = mwst.value.replace(/,/,".");
    netto.value = netto.value.replace(/[^\d.-]/g,"");
    mwst.value = mwst.value.replace(/[^\d.-]/g,"");
    fieldpreis_brutto.value = (!isNaN(parseFloat(netto.value)) && !isNaN(parseFloat(mwst_euro.value))) ? (parseFloat(netto.value) + parseFloat(mwst_euro.value)).toFixed(2) : "";
}


function change(){
    var id = document.getElementById('id');
    var mwst = document.getElementById('mwst');
    var fieldmwst_euro = document.getElementById('mwst_euro');
    
    
}

</script>

<br><br>

<table width="1000" border="1">
    <tr>
        <td>Pos:</td>
        <td>Menge:</td>
        <td>Einheit:</td>
        <td>Artikel:</td>
        <td>MwSt:</td>
        <td>Netto:</td>
        <td>Gesamt:</td>
        <td>Optionen:</td>
    </tr>
    <tr>
        <td><input type="text" size="10" id="ArtikelId" name="id" value=""></td>
        <td><input type="text" size="10" id="ArtikelMenge" name="menge" value=""></td>
        <td><input type="text" size="10" id="ArtikelEinheit" name="id" value=""></td>
        <td><input type="text" size="50" id="ArtikelName" name="name" value=""></td>
        <td><input type="text" size="10" id="ArtikelMwSt" name="mwst" value="19"></td>
        <td><input type="text" size="20" id="ArtikelNetto" name="netto" value=""></td>
        <td>&nbsp;</td>
        <td>&nbsp;</td>
    </tr>
    <tr>
        <td colspan="3">&nbsp;</td>
        <td><input type="text" size="50" id="ArtikelKurz" name="kurz" value=""></td>
        <td colspan="3">&nbsp;</td>
        <td>&nbsp;</td>
    </tr>
    <tr>
        <td colspan="3">&nbsp;</td>
        <td><textarea cols="48" rows="6" id="ArtikelLang" name="lang"></textarea></td>
        <td colspan="3">&nbsp;</td>
        <td>&nbsp;</td>
    </tr>
    <tr>
        <td colspan="8"><select id="ArtikelDropdown" style="textbox" name="">
    <option value="artikel">Artikel wählen:</option>
    <?php

    foreach($artikels as $artikel) {

         echo '<option value="'.$artikel['id'].';'.$artikel['name'].';'.$artikel['kurz'].';'.$artikel['einheit'].';'.$artikel['preis_netto'].';'.$artikel['lang'].' ">'.$artikel['name'].'</option>';
    }

    ?>
    </select></label>
    
    <script>
    $("#ArtikelDropdown").select2();

      // Read selected option
      $('#ArtikelDropdown').change(function(){
         let value = $('#ArtikelDropdown').val();
         let parts = value.split(';');
          $('#ArtikelId').val(parts[0]);        // = '.$artikel['id'].'
          $('#ArtikelName').val(parts[1]);        // = '.$artikel['name'].'
          $("#ArtikelKurz").val(parts[2]);        // = '.$artikel['kurz'].'
          $("#ArtikelEinheit").val(parts[3]);    // = '.$artikel['einheit'].'
          $("#ArtikelNetto").val(parts[4]);        // = '.$artikel['preis_netto'].'
          $("#ArtikelLang").text(parts[5]);        // = '.$artikel['lang'].'
          
          
          
      });
     </script><input type="button" id="erstellen" value="Eintragen" onclick="add TableRow($i)"></td>
    </tr>
</table>

Der Eintragen Button ist hier noch ohne Wirkung. Bei der Auswahl eines Artikels wird er momentan in die Bestandteile des value gesplittet, und dann in die leeren Textfelder geschrieben.
Die beiden JavaScripte am Anfang sind hier noch ohne Funktion.
Ich werde jetzt mal versuchen, Deinen Code mit in diesen einzubauen, und mal schauen, ob sich was bewegt.
Deine get-article-data.php enthält dass also nur das Template?
Dann wird die also quasi bei jedem Artikel aufgerufen, und das ausgefüllte Template von dort mit in die Hauptdatei geschrieben, richtig?

Danke & Gruß,

Volker
 

Sempervivum

Erfahrenes Mitglied
OK, so weit klar, wie Du das machst. Du hast die Daten des Artikels durch ";" getrennt im value abgelegt, dann stehen sie sofort zur Verfügung. Wenn Du es so machst, brauchst Du gar kein Ajax um die Daten zu holen weil sie ja sofort verfügbar sind.

Mit diesem:
Code:
          $('#ArtikelId').val(parts[0]);        // = '.$artikel['id'].'
          $('#ArtikelName').val(parts[1]);        // = '.$artikel['name'].'
          $("#ArtikelKurz").val(parts[2]);        // = '.$artikel['kurz'].'
          $("#ArtikelEinheit").val(parts[3]);    // = '.$artikel['einheit'].'
          $("#ArtikelNetto").val(parts[4]);        // = '.$artikel['preis_netto'].'
          $("#ArtikelLang").text(parts[5]);        // = '.$artikel['lang'].'
sehe ich jedoch Probleme weil, wenn ich das bisher richtig verstanden habe, die Felder mehrfach auftreten, denn es gibt ja mehrere Blöcke in der Tabelle mit unterschiedlichen Artikeln. Und eine ID muss dokumentweit eindeutig sein. Hier würde ich eher empfehlen, mein Verfahren mit dem Template anzuwenden.