Javascript tabelle dynamisch erstellen
der henry
- html
- javascript
Hallo,
ich möchte das erste mal eine Tabelle für Datenbankdaten dynamisch erstellen, um damit dann die einzelnen Felder zu editieren, löschen, hinzufügen ... (Daten aus der Datenbank werden beim Start per php dem Javascript zugeführt)
Hier habe ich mich schlau gemacht, vermutlich nicht schlau genug 🤔
Ich habe eine Tabelle (nur die <th> Zeile) fest im html code integriert. Wenn ich nun die Tabelle erzeuge, werden die rows und cols erstellt, sehe ich, jedoch ohne "Textinhalt".
Dies sollte eigentlich mit td.textContent = value; passieren.
value bringt bei console.log(value) die richtigen Werte.
function onloadStart()
{
table = document.getElementById("myTable");
tbody = document.getElementById("myTable").getElementsByTagName('tbody')[0];
addRow(datapoints);
}
function addRow(rowData)
{
const tr = document.createElement("tr");
const td = document.createElement("td");
const row = tbody.insertRow();
let x; let cell;
for (let i = 0; i < rowData.length; i++)
{
const row = tbody.insertRow();
cell = row.insertCell();
td.textContent = 'add';
tr.appendChild(td);
td.textContent = 'edit';
tr.appendChild(td);
td.textContent = 'del';
tr.appendChild(td);
Object.entries(rowData[i]).forEach(([key, value]) => {
cell = row.insertCell();
td.textContent = value;
tr.appendChild(td);
});
}
table.appendChild(tbody);
Was könnte hier die Ursache sein ??
Zusätzlich hätte ich noch die Frage, wie den jeweiligen Zellen unterschiedlich ein "onclick" event dynamisch hinzufügen ?
Vielen Dank !!!
Hallo Henry,
es gibt zwei mögliche Vorgehensweisen, und wenn man sie miteinander mischt, muss man aufpassen.
(1) tr und td Elemente mit createElement erzeugen und mit appendChild sinnvoll einfügen
(2) tr und td Elemente mit insertRow und insertCell erzeugen
Du erzeugst einmalig mit createElement ein tr und ein td Element und hantierst dann damit herum. Das ist aber sinnlos, weil Du ja eigentlich von row=tbody.insertRow() ein neues tr Element in diesem tbody bekommst und von row=insertCell() ein neues td Element in dieser Zeile.
Du erzeugst pro Datensatz ein row Element. Das ist richtig. Und dann erzeugst Du genau ein cell Element. Das ist nicht richtig, du musst pro Datenspalte einen insertCell() Aufruf machen.
Sodann machst Du dreimal hintereinander eine Zuweisung an td.textContent und fügst td mit appendChild an tr an. Das ist aber jedesmal die gleiche Zelle, du hast kein neues td Element erzeugt. Deswegen hast Du in tr am Ende nur ein td Element drin (der appendChild reißt es erstmal von seinem Parent weg und fügt es dann neu ein) und der textContent entspricht der letzten Zuweisung: del.
Darauf folgt dann die Iteration über die Objekteigenschaften. Du rufst zwar pro Eigenschaft einmal row.insertCell() auf, tust mit der so erhaltenen cell (ein td-Element) aber nichts. Statt dessen überschreibst Du erneut dein Einmal-td.
Und dann machst Du mit der nächsten Zeile weiter, hast aber die Row nicht an die Table angehängt.
Schmeiß die Variablen tr und td weg und den createElement Aufruf dazu auch.
insertRow() hängt dem tbody eine row an und liefert Dir das tr Element. insertCell() hängt der row eine cell an und liefert Dir das td Element.
Rufe pro gewünschter Spalte in der Row einmal cell=row.insertCell() auf und speichere den Text, der da rein soll, dann mit cell.textContent. Ein appendChild ist weder für row noch cell nötig, das machen die insert-Methoden automatisch.
Und, ach ja, such Dir das tbody-Element mit document.querySelector("#myTable tbody") heraus. Das ist der effizienteste Weg, den ersten (oder einzigen oder impliziten) tbody einer Tabelle mit id="myTable" zu finden.
Sortiere deinen Code besser. Die Funktion heißt addRow, aber sie fügt alle Rows hinzu. Sie müsste also addRows heißen. Das würde ich aber nicht tun, statt dessen sollte es so aussehen:
function onloadstart() {
const myBody = document.querySelector("#myTable tbody");
dataPoints.forEach((dataPoint, index) => addRow(myBody, dataPoint, index));
myBody.addEventListener("click", handleDatapointClick);
}
function addRow(tbody, dataPoint, index) {
const row = tbody.insertRow();
row.dataset.index = index; // Zeilenindex als data-index Attribut speichern
// insertCell fügt ein td ein und gibt es zurück, d.h. man kann sofort
// eine Zuweisung an textContent machen und muss es nicht speichern
row.insertCell().textContent = "add";
row.insertCell().textContent = "edit";
row.insertCell().textContent = "del";
Object.values(dataPoint)
.forEach(value => row.insertCell().textContent = value);
}
function handleDatapointClick(event) {
const cell = event.target.closest("td");
if (!cell) return;
const row = cell.parentElement;
const index = row.dataset.index;
...
}
Es ist sinnvoll, finde ich, die Interation über die dataPoints und das Einfügen eines dataPoint in eine Row zu separieren. Und für den Eventhandler ist es nützlich, den Index der Tabellenzeile im tr-Element zu speichern. Man KANN sich zwar auch auf die rowIndex-Eigenschaft des tr-Elements verlassen, aber ich mache solche Dinge lieber explizit.
Wenn Dich row.insertCell().textContent = ... irritiert - ich hätte auch zu Beginn mit let cell; eine Variable namens cell definieren können und dann jeweils
cell = row.insertCell();
cell.textContent = ...
schreiben können. Aber, wie im Kommentar geschrieben, braucht man die Variable nicht und es geht in einer Zeile, wenn man lediglich textContent setzen muss.
Zum Thema click-Handler pro Row oder gar pro Cell: Das würde ich gar nicht erst versuchen. Registriere genau einen click-Handler auf der Table. Klicks in der Tabelle werden zwar auf td Elementen ausgelöst, blubbern dann aber im DOM nach oben und kommen beim table Element an. In diesem click-Handler kannst Du leicht ermitteln, in welcher Row und in welcher Cell geklickt wurde. Siehe Code oben.
Wenn Du dann Rows hinzufügst, musst Du Dich nicht darum kümmern, einen Eventhandler hinzuzufügen, das Bubbling erledigt das automatisch. Ausführliche Infos zur Ereignisbehandlung findest Du im Wiki.
Rolf
@@Rolf B
Zum Thema click-Handler pro Row oder gar pro Cell: Das würde ich gar nicht erst versuchen. Registriere genau einen click-Handler auf der Table. Klicks in der Tabelle werden zwar auf td Elementen ausgelöst,
So? Werden sie das? Wie bitte soll ich einen Click auf ein td-Element auslösen, wenn ich nicht zu den priviligierten Nutzern gehöre, die eine Maus o.ä. verwenden können?
Für interaktive Schaltflächen muss man <button>s verwenden:
<td>
<button>…</button>
</td>
Mit CSS kann man denen ihren Rahmen und Hintergrund nehmen. Beispiel (die buttons werden dort mit JavaScript generiert)
Und ja, die Click-Events der Buttons bubblen hoch zum table-Element.
🖖 Live long and prosper
Hi there,
Für interaktive Schaltflächen muss man
<button>s verwenden:
Ok, und was schlägst Du vor, wenn nicht die Spaltenüberschrift sondern das einzelne td-Element, also die Zelle, interaktiv sein soll? Einen Button mit dem Zelleninhalt als Beschriftung (die vermutlich auch einmnal ausführlicher sein kann) oder eine zusätzliche Reihe mit korrespondierenden Buttons (darunter oder darüber)...?
@@klawischnigg
Ok, und was schlägst Du vor, wenn nicht die Spaltenüberschrift sondern das einzelne td-Element, also die Zelle, interaktiv sein soll? Einen Button mit dem Zelleninhalt als Beschriftung (die vermutlich auch einmnal ausführlicher sein kann) oder eine zusätzliche Reihe mit korrespondierenden Buttons (darunter oder darüber)...?
It depends… 😎
🖖 Live long and prosper
Hallo Gunnar,
ja, natürlich. Die drei Zellen mit add, edit und del darin müssen nicht mit textContent, sondern mit innerHTML befüllt werden und dann als Button. Dieser kann mit CSS auf die Größe des td aufgeblasen werden (falls die Datenspalten mehrzeilig sein können).
row.insertCell().innerHTML = "<button type='button' name='add'>Add</button>";
und der erste Versuch im Click-Handler muss sein:
const button = event.target.closest("button");
Ist das null, muss man überlegen, ob die Behandlung eines Klick-Events in einer der anderen Zellen Sinn ergibt UND wie man diese Aktion alternativ auslösen kann. Aber da dort nur Strings abgelegt werden, dürften nur die Buttons von Belang sein.
Die EDIT Aktion könnte die Textinhalte der Zeile in input-Elemente verpacken. Aber dann braucht man weitere Buttons: Save und Cancel. Diese könnten die existierenden Add/Edit/Del Buttons ersetzen.
Die Semantik des Add Buttons ist mir eh nicht klar geworden, ich habe aber nicht lange drüber nachgedacht und mich auf die Erstellung der Tabelle beschränkt. Soll ein Add eine neue Zeile UNTER der geklickten Zeile anlegen? Das wäre ein INSERT und damit wäre mein Gedanke mit dataset.index Unsinn (weil die Indexe bei einem Insert ins dataPoints Array nicht mehr stimmen). Oder doch eher am Ende der Tabelle (dann gehört der Button aber über die Tabelle und nicht zu den Zeilen)? Da ist noch einiges an Konzeption zu leisten.
@klawischnigg - so ganz verstehe ich Dich nicht, die Buttons, von denen hier die Rede ist, sind Row-Buttons für Row-Aktionen. Wenn Du Aktionen pro Zelle haben möchtest, wird das Ganze aufwändiger, dann müsste man vielleicht doch den Zellen einen tabindex geben, damit man per Tastatur durchkommt, und sich eine passende Tastaturbedienung für die Zellaktionen ausdenken. Oder aber doch einen Mini-Button oben rechts in jede Zelle setzen, der für die Zelle ein Aktionsmenü öffnet. Oder doch ein Zeilenbutton für Aktionen. UI Design bietet viele Möglichkeiten…
Rolf
@@Rolf B
Dieser [Button] kann mit CSS auf die Größe des td aufgeblasen werden
Ja, aber das muss man gar nicht tun.
und der erste Versuch im Click-Handler muss sein:
const button = event.target.closest("button");
Muss er das? Mann kann doch auch auf event.target.closest("td") gehen. Das button-Element stellt sicher, dass Nutzer das Event auch per Tastatur-, Sprach- oder Was-weiß-ich-für-Steuerung auslösen können.
☞ Beispiel
🖖 Live long and prosper
Hi there,
@klawischnigg - so ganz verstehe ich Dich nicht[...]
Ja, das geht vielen so, passiert mir sogar manchmal selbst...😉...
[...] die Buttons, von denen hier die Rede ist, sind Row-Buttons für Row-Aktionen. Wenn Du Aktionen pro Zelle haben möchtest, wird das Ganze aufwändiger, dann müsste man vielleicht doch den Zellen einen tabindex geben, damit man per Tastatur durchkommt, und sich eine passende Tastaturbedienung für die Zellaktionen ausdenken.
War ja nur eine prinzipielle Frage. Ich hatte das Problem/den Fall schon öfter, und hab es mit einem Eventhandler auf das td-Element selbst gelöst (weil es um Webapplikationen ging, die auf einem Smartphone erstens nichts verloren hätten und dort ohnehin nicht funktioniert hätten). Ich halte die Lösung von Gunnar für Spaltenüberschriften für prinzipiell gut und richtig, aber was mich eben interessiert hätte wäre wie oder ob man das auf Zell-Ebene besser lösen könnte...
Hallo klawischnigg,
ein click Handler auf table Ebene kann alle Klicks in der table behandeln.
Du musst nur herausfinden, worauf geklickt wurde, und dafür kann es ganz nützlich sein, zumindest auf tr Ebene ein data-Attribut zu setzen, das dir sagt, zu welchem Datensatz die Row gehört. Ansonsten helfen auch die cellIndex und rowIndex Propertys (sic!) von td/th und tr.
Wie man closest() nutzt, habe ich ja schon gezeigt.
Knifflig wird es nur bei colspan oder rowspan Zellen, dann stimmen die Indexe nicht mehr unbedingt.
Rolf
Lieber henry,
ich möchte das erste mal eine Tabelle für Datenbankdaten dynamisch erstellen, um damit dann die einzelnen Felder zu editieren, löschen, hinzufügen ...
warum nicht phpMyAdmin verwenden? Warum willst Du das selbst bauen?
Liebe Grüße
Felix Riesterer
Guten Morgen,
vielen Dank euch allen.
@Rolf B: Vielen Dank, sehr ausführlich und veständlich
@Felix Riesterer: Ich nutze schon phpMyAdmin, genauso wie dbeaver. Jedoch möchte ich eine Eingabeüberprüfung und Eingabebegrenzung aus verschiedenen Kombinationen der Eingaben erstellen. Einfaches Add / Delete sollte auch möglich sein. Die "Dateneingabe" sollte später ohne "Diplom" möglich sein 🤪
Jetzt bin ich auf ein Problem bzw. für mich Problem gestoßen. Die erzeugte Tabelle reagiert nicht auf das "geladene css"
<link rel="stylesheet" type="text/css" href="datapoints.css">
Jetzt habe ich ein wenig recherchiert und habe nachfolgendes ausprobiert, "css nachladen"
leider auch ohne Erfolg ... Schön wäre ein css, das nur für diese Tabelle gilt, sonst von der restlichen Webseite ignoriert wird.
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = "datapoints.css";
document.head.appendChild(link);
Wie kann/muss ich hier vorgehen.
Vielen Dank !!!
Hallo,
Schön wäre ein css, das nur für diese Tabelle gilt, sonst von der restlichen Webseite ignoriert wird.
ich verwende dafür inzwischen verschachteltes CSS:
https://blog.selfhtml.org/2023/sep/25/schicke-schachteln-css-nesting-ist-in-den-browsern-angekommen
Gruß
Jürgen
Hallo,
Jetzt bin ich auf ein Problem bzw. für mich Problem gestoßen. Die erzeugte Tabelle reagiert nicht auf das "geladene css"
<link rel="stylesheet" type="text/css" href="datapoints.css">
Hier musst du uns mehr vom Code zeigen, das funktioniert normalerweise.
Deine weiteren gezeigten Versuche das Problem zu umgehen, sind Umwege.
Gruß
Kalk
@@der henry
Schön wäre ein css, das nur für diese Tabelle gilt, sonst von der restlichen Webseite ignoriert wird.
Wie kann/muss ich hier vorgehen.
🖖 Live long and prosper
Hallo Gunnar,
Zum einen: @scope ist noch sehr neu, und die Spec dazu ist Working Draft. Es gibt 2 Versionen davon: 2021, wo @scope noch anders aussah, und 2024. Firefox hat @scope erst im Dezember 2025 bereitgestellt. Ich würde das nicht in produktiven Webseiten verwenden. Auf Seiten, die nur für mich sich, mag das anders sein.
Zum anderen: Was ist denn das für ein 💩 - nicht dein Beispiel, sondern die Cascade-6 Spec, die @scope einführt. Die HTML Spec sagt ausdrücklich: style ist ein Elementtyp der Content-Kategorie Metadata, und das einzige Element, in dem Metadata-Content erlaubt ist, ist <head>.
Dass man <style> im <body>-Bereich für lokale Styles verwenden darf, war mal eine Idee, wurde aber meines Wissens wieder verworfen. Und nun kommt die Cascade-6 Spec um die Ecke und bringt das ganz nebenbei als Beispiel, als wäre es das natürlichste von der Welt. MDN übernimmt es auf der @scope-Seite, aber auf der Seite, wo <style> beschrieben ist, sagen sie ganz klar: <style> ist nur im <head> erlaubt. Was denn nun?!
Rolf