HTML/CSS/JS: Warum funktioniert meine bindDelete-Funktion nicht und wie kann ich die Preisangaben untereinander formatieren?

Hi, ich habe an einen Spezialisten eine Frage:

import Order from './Order.js';
import ArticleModel from './ArticleModel.js';

export default function UserOrderViewHandler(articleModel) {
  this.articleModel = articleModel;
  this.order = new Order(-1, null, [], 0, 0, 0, 0, 0, false);
}

UserOrderViewHandler.prototype.renderPage = function() {
  let body = document.getElementsByTagName('body')[0];
  body.innerHTML = "";
  body.innerHTML = `<div class="container">
    <sidebar>
      <h1 class="mb-4">Unser Sortiment</h1>
      <div id="articleList">
        <div class="row space-between">
          <div class="col-md-4 mb-4">
            <div class="card" data-index="0">
              <div>
                <h2>Salamipizza</h2>
                <p>Halbe hausgemachte Pizza mit Käse und regionaler Salami</p>
                <p><strong>6.50 €</strong></p>
                <button class="addBtnClss btn btn-primary">Artikel hinzufügen</button>
              </div>
            </div>
          </div>
          <div class="col-md-4 mb-4">
            <div class="card" data-index="1">
              <div>
                <h2>Hamburger</h2>
                <p>Burger mit österreichischem Rindfleisch, Ketchup und Senf</p>
                <p><strong>6.00 €</strong></p>
                <button class="addBtnClss btn btn-primary">Artikel hinzufügen</button>
              </div>
            </div>
          </div>
          <div class="col-md-4 mb-4">
            <div class="card" data-index="2">
              <div>
                <h2>Wochenmenü</h2>
                <p>Frittatensuppe --- Spaghetti Bolognese --- Muffin</p>
                <p><strong>12.50 €</strong></p>
                <button class="addBtnClss btn btn-primary">Artikel hinzufügen</button>
              </div>
            </div>
          </div>
        </div>
      </div>
    </sidebar>
    <sidebar>
      <h1 class="mb-4 extramargin">Bestellung</h1>
      <ul id="order"></ul>
      <div class="form-group">
        <label for="pickupTime">Gewünschte Abholzeit:</label>
        <input type="time" id="pickupTime" class="form-control">
      </div>
    </sidebar>
  </div>`;
}

UserOrderViewHandler.prototype.renderOrder = function() {
  let orderElement = document.getElementById('order');
  orderElement.innerHTML = "";
  orderElement.className = "list-group";

  let i = 0;

  for (let article of this.order.articleList) {
    let articleElement = document.createElement('li');
    articleElement.className = "list-group-item d-flex justify-content-between align-items-center";
    articleElement.innerHTML = `
      <div>1 x ${article.name} &emsp; &emsp; &emsp;${article.price.toFixed(2)} €</div>
      <button class="delBtn btn btn-danger btn-sm" data-index="${i}">Entfernen</button>
    `;
    orderElement.appendChild(articleElement);
    i++;
  }
}

UserOrderViewHandler.prototype.bindAddBtn = function() {
  let buttons = document.getElementsByClassName('addBtnClss');
  const that = this;

  for (let button of buttons) {
    button.addEventListener('click', function() {
      let index = this.closest('.card').getAttribute('data-index');
      let article = that.articleModel.getByIndex(index);

      if (article) {
        that.order.addArticle(article);
        that.renderOrder();
      }
    });
  }
}

UserOrderViewHandler.prototype.bindDeleteBtn = function() {
  let orderElement = document.getElementById('order');
  orderElement.addEventListener('click', function(event) {
    if (event.target.classList.contains('delBtn')) {
      let index = event.target.getAttribute('data-index');
      that.order.deleteArticle(index);
      that.renderOrder();
    }
  });
}
  1. Warum funktioniert meine bindDelete-Funktion nicht, um die li-Elemente wieder zu löschen?
  2. Wie kann ich es schaffen, dass der Preis von jedem li-Element genau so untereinander ist, sodass man ihn schön zusammenrechen könnte:

Danke für die Hilfe.

...zur Frage
Warum funktioniert meine bindDelete-Funktion nicht, um die li-Elemente wieder zu löschen?

Die Variable that wird von dir nicht definiert. Entweder du packst die Definition mit an den Funktionsanfang:

const that = this;

oder du nutzt zum einen eine Arrow-Funktion, um den Event Handler zu definieren und zum anderen this statt that.

button.addEventListener('click', () => {
  /* ... */
  this.order.addArticle(article);
  this.renderOrder();
Wie kann ich es schaffen, dass der Preis von jedem li-Element genau so untereinander ist, sodass man ihn schön zusammenrechen könnte:

Es gibt verschiedene Wege:

  • Verwende keine Liste, sondern eine Tabelle mit festen Spaltenbreiten; ordne Name, Preis und Button dabei in unterschiedliche Spalten ein
  • Packe die jeweiligen Angaben (Name, Preis) in einzelne (span-)Elemente. Gib denen mit CSS (width) eine entsprechende Breite oder ordne die Elemente mit einem CSS Grid an (via grid-template-columns kannst du die Spaltenbreiten vorgeben)

Zusätzlich wäre es wohl besser, die Preise mit CSS rechtsbündig auszurichten (text-align: right).

Zuletzt noch ein paar Anmerkungen zu deinem restlichen geteilten Code:

1) Diese Zeile ist überflüssig:

body.innerHTML = "";

Du überschreibst den Inhalt des body-Elements doch im Anschluss gleich noch einmal.

Wenn der Inhalt, den du in renderPage setzt, im Übrigen initial beim Laden der Seite angezeigt werden soll, würde ich ihn auch nicht dynamisch mit JavaScript einbinden, sondern direkt von Anfang an im body-Bereich ausliefern.

Mehr Sinn würde es m.E. machen, die konkreten Inhalte (Artikelname, Beschreibung, Preis, ID) in ein JSON-Objekt auszulagern und dann die einzelnen Artikelelemente via Schleife zu generieren. Statt mit innerHTML zu arbeiten, würde sich ein template-Element für diesen Fall gut eignen.

2) Es gibt kein HTML-Element namens sidebar. Naheliegend zu dem wäre ein aside-Element.

3) Solltest du strong allein aus dem Grund nutzen, den Preis visuell zu formatieren, dann entferne es und style den Text stattdessen mit CSS (siehe font-weight).

...zur Antwort

Ein Weg wäre die Installation des DuckDB CLI. Lade die passende Version für dich herunter und entpacke sie. Die Datenbankdatei würde ich im Anschluss (der Einfachheit halber) direkt mit in das Verzeichnis kopieren.

Öffne im Anschluss die Kommandozeile deines Betriebssystems (meine folgenden Code-Snippets beziehen sich auf Windows OS) und navigiere zu dem entpackten Ordner, in dem die duckdb.exe liegt.

cd "c:/path/to/duckdb/folder"

Dann kannst du auch schon eine Verbindung herstellen:

duckdb hotelkette.db

Sollte deine Datenbankdatei doch noch in einem anderen Pfad liegen, gib stattdessen den absoluten Dateipfad an. Am besten umklammert von Anführungszeichen, falls einer der Verzeichnisnamen ein Leerzeichen enthalten sollte.

duckdb "c:/path/to/hotelkette.db"

Sobald sich der Client mit der Datenbank verbunden hat, solltest du via SQL Daten auslesen oder schreiben können. Der beste Startpunkt wäre wohl, erst einmal in Erfahrung zu bringen, was für Tabellen überhaupt in der Datenbank liegen und wie sie strukturiert sind.

Die information_schema_tables-View beinhaltet bspw. Informationen über alle verfügbaren Tabellen in der Datenbank:

SELECT table_name FROM information_schema.tables

Die Struktur einer bestimmten Tabelle kannst du in Erfahrung bringen, indem du in der information_schema.columns-View nach der Tabelle suchst:

SELECT * FROM information_schema.columns WHERE table_name = 'name of a table'

Mehr zu den information_schema-Views kannst du in der Dokumentation nachschlagen.

Alternativ zu diesem Weg macht es dir DBeaver an der Stelle natürlich deutlich einfacher. Auf deinem ersten Screenshot hast du die Datenbank ja bereits geöffnet. Wenn du im linken Dialog (Datenbanknavigator) die Knoten von beispielsweise main oder pg_catalog einmal aufklappen würdest, solltest du ebenso die Tabellen finden, die in der Datenbank so vorliegen.

Explizit relevant für dich sind natürlich nur die Tabellen, die irgendetwas mit Hotels zutun haben.

Sollte dir nun aber noch Wissen zu dem Aufbau von Datenbanktabellen fehlen (z.B. Was Constraints oder Datentypen sind), würde ich dir empfehlen, dich dazu erst einmal zu belesen. Hilfreiche Artikel gibt es zum Beispiel auf datenbanken-verstehen.de. Bezüglich der SQL-Syntax solltest du in der DuckDB-Dokumentation nachschlagen.

(...) um an dieser Datei neue Dinge hinzuzufügen (...)

Neue Daten kannst du mittels INSERT INTO zu einer bestehenden Tabelle hinzufügen.

Beispiel:

Angenommen, wir haben eine Tabelle hotelpersonal, die das Hotelpersonal auflisten soll. Es gibt die Spalten vorname (VARCHAR), nachname (VARCHAR), gehalt (DECIMAL) und position (VARCHAR).

Das Hinzufügen eines neuen Datensatzes könnte folgendermaßen aussehen:

INSERT INTO hotelpersonal (vorname, nachname, gehalt, position)
VALUES ('Zéro', 'Moustafa', 2400, 'Page');

Für Weiteres dazu würde ich erneut auf die DuckDB-Dokumentation verweisen.

...zur Antwort

Schau nach, wo die Variable vorkommt (z.B. mittels Volltextsuche o.ä. Editorfunktionen) und wie ihr Werdegang ist. Bei einer Fehlermeldung seitens des Programms solltest du auch schon Informationen dazu bekommen, zu welchem Zeitpunkt die Variable ohne Wert ist.

Letzten Endes musst du dann natürlich schauen, dass ihr ein entsprechender Wert zugewiesen wird.

...zur Antwort
HTML: Wie kann ich die Titelbox verschieben?

Hey,

ich verzweifle mittlerweile an meinen eigenen Sinnen und hab schon manches probiert. Das Einbinden von KI (ChatGPT) hat mir dann den Rest gegeben, da alles was davon kam einfach keinen Sinn ergab.

Ich habe zwei Container, also left-colum und right-colum.

Wie man in dem Code sehen kann, wird die Klasse "whatis-titlebox" im ersten Teil korrekt angezeigt. Es wird das Szenario eintreten, dass die linke Spalte leer beiben wird und nur die rechte mit Text gefüllt wird.

Dazu dachte ich mir, kopiere ich den Standardteil und spreche die Kopie dann mit "rightinput" um eben "whatis-titlebox" aus dem 2. Teil, also "rightinput whatis-titlebox" über die rechte colum zu legen, sodass der Text darin linksbündig mit dem Text sichtbar wird. Fehlanzeige, ich habe es nicht geschafft. Vielleicht ist mein Code auch einfach nur falsch geschrieben.

Ich blicke da nicht mehr durch und hoffe, dass ich hier Hilfe erhalte.

HTML:

<div class="leidfadenmain-input">
    <div class="whatis">
        <div class="whatis-titlebox">
            <h2 class="whatis-title-txt">Über</h2>
        </div>
        <div class="container">
            <div class="left-column">
                <!-- Linker Text -->
                <p class="whatis-txt"></p>
                <p class="whatis-txt"></p>
            </div>
            <div class="divider"></div>
            <div class="right-column">
                <!-- Rechter Text bei Bedarf -->
                <p class="whatis-txt"></p>
            </div>
        </div>
    </div>

    <div class="whatis">
        <div class="rightinput whatis-titlebox">
            <h2 class="whatis-title-txt">Über</h2>
        </div>
        <div class="container">
            <div class="left-column">
                <!-- Linker Text -->
            </div>
            <div class="divider"></div>
            <div class="right-column">
                <!-- Rechter Text bei Bedarf -->
                <p class="whatis-txt"></p>
            </div>
        </div>
    </div>
</div>

CSS:

.leidfadenmain-input {
    padding-top: 100px;
    display: flex;
    flex-direction: column;
}

.whatis {
    display: flex;
    flex-direction: column; /* Ändert die Ausrichtung auf vertikal */
    width: 100%;
    max-width: 1000px;
    margin: 0 auto;
}

.whatis-txt {
    padding-bottom: 15px;
}

.whatis .whatis-titlebox {
    position: relative;
    margin-bottom: 20px;
    top: 0;
}

.rightinput .whatis-titlebox {
    margin-bottom: 20px;
    top: 0;
    left: 50%; /* Zentriert die Überschrift horizontal */
    transform: translateX(-50%); /* Zentriert die Überschrift horizontal */
}

.whatis .whatis-title-txt {
    font-size: 1.5em;
    font-weight: bold;
    margin-bottom: 10px;
}

.whatis .left-column,
.whatis .right-column {
    width: 45%;
}

.whatis .divider {
    width: 1px;
    height: auto;
    background-color: black; /* Ändere die Farbe bei Bedarf */
    margin: 0 10px;
}

.whatis .container {
    display: flex;
    align-items: stretch;
    width: 100%;
}

Einfach erklärt.: ".rightinput .whatis-titlebox" soll, sofern gebraucht, über right-colum platziert werden.

Sofern ihr mir weiterhelfen könntet, wäre ich euch dankbar!

Vielen Dank!

Liebe Grüße

...zur Frage

Wenn die linke Spalte nicht gebraucht wird, sollte sie auch nicht herausgerendert werden. Das heißt, der Browser sollte in diesem Fall nur so ein Markup für den Container erhalten:

<div class="container">
  <div class="right-column">
    <!-- ... -->
  </div>
</div>

Da du das HTML offensichtlich selbst definierst, sollte das kein Problem darstellen.

Im Notfall könnte man auch die linke Spalte mitsamt Trenner via CSS ausblenden, vorausgesetzt, das Element ist tatsächlich leer (keinerlei Kindelemente oder Text, inklusive Leerzeichen):

.whatis .left-column:empty,
.whatis .left-column:empty + .divider {
  display: none
}

Die linke Spalte hingegen leer, aber sichtbar mitsamt Trenner herauszurendern und die Überschrift über die rechte Spalte zu verschieben, erscheint für mich unlogisch. Zum einen zeigst du dann überflüssige Inhalte an (die von einem Seitenbesucher vermutlich als unästhetisch und fehlerhaft gewertet werden) und zum anderen unterbricht es den Lesefluss.

Wenn du es aber unbedingt erzwingen möchtest, wäre es besser, der Titelbox (.whatis-titlebox) eine zusätzliche Modifikatorklasse zuzuweisen, mit der sie verschoben wird.

Beispiel:

<div class="whatis-titlebox whatis-titlebox-right">
  <h2 class="whatis-title-txt">Über</h2>
</div>

CSS:

.whatis-titlebox-right {
  margin-left: calc(45% + 1px);
}

Elemente zu duplizieren und dann übereinanderzuschieben ist keine gute Idee.

...zur Antwort

PyCharm verwendet eine eigene Konsolenimplementation, die sich nicht direkt so verhält, wie du es bei der Windows Konsole erwarten könntest. Aufrufe wie cls funktionieren in ihr nicht. Wenn du den Fensterinhalt leeren möchtest, musst du auf die GUI-Funktionalitäten von PyCharm zurückgreifen (z.B. Rechtsklick in das Konsolenfenster > Clear All).

Die IDE stellt allerdings auch eine Einstellung bereit, mit der eine OS-Konsole zumindest emuliert werden kann (siehe: Emulate terminal in output console in den Run/Debug Configurations). Wenn du die aktivierst, sollte ein Aufruf wie

import os
os.system("cls")

die Konsole leeren.

Für eine tatsächliche Sicherheit, dass all deine Anweisungen an die Konsole wie erwartet funktionieren, solltest du dein Python-Skript allerdings in der echten Konsole von Windows ausführen.

...zur Antwort

Direkt schreiben/definieren kannst du Methoden immer nur innerhalb einer Klasse oder einem Interface. Objekte sind die Instanzen von Klassen, die sich später auf bestimmte Methoden beziehen können, die in der Klasse (oder einer Basisklasse) definiert wurden.

Beispiel:

class Car {
  public void drive() {
    System.out.println("Wrumm wrumm");
  }
}

// main:
Car herbie = new Car();
herbie.drive();

Hier wird eine Klasse Car angelegt, in der eine Methode drive definiert wird. Später kann ein Objekt dieser Klasse kreiert werden. Dieses Objekt hat Zugriff auf die Methode drive.

Weiterführend wird bei Methoden, die du in einer Klasse definierst, grundsätzlich zwischen objektgebundenen (Instanz-) und klassengebundenen (statischen) Methoden unterschieden. Letztere werden mit dem static-Modifier gekennzeichnet.

Beispiel:

class Car {
  public static int compareCars(Car car1, Car car2) {
    // ...
  }
}

// main:
Car herbie = new Car();
Car docHudson = new Car();
Car.compareCars(herbie, docHudson);

Der wesentliche Unterschied bei statischen Methoden liegt darin, dass sie über die Klasse aufgerufen werden. Das heißt, du brauchst kein Objekt für einen Aufruf und innerhalb ihres Körpers gibt es keine this-Referenz (d.h. es besteht kein Zugriff auf objektgebundene Elemente), so wie in objektgebundenen Methoden.

Welche Methodenart je Fall passend ist, kann logisch erfragt werden. Verhaltensweisen/Aktionen, die ein Objekt ausmachen, sollten bspw. objektgebunden definiert sein.

...zur Antwort

a) Es gibt Dateiversionierungssyteme wie Git oder Mercurial, deren Repository zentral gehostet werden kann (siehe Bitbucket, GitLab, GitHub, Sourceforge). Das Repository beinhaltet all eure Projektdateien und ebenso eine Verlaufsgeschichte über jegliche Änderungen, die in ihm einmal registriert wurden.

Jeder in eurem Team kann sich folgend dieses (Remote) Repository klonen und in diesem seine Änderungen einfügen (Commits). Später werden diese auf das zentrale Repository geschoben. Sollten zwei Personen zeitgleich an einer Datei gearbeitet haben, ist es notwendig, die Änderungen zusammenzuführen (Merge).

Die Arbeit mit solchen Versionierungssystemen ist gängiger Standard in der Industrie.

b) Für einige Entwicklungsumgebungen/Editoren gibt es Kollaborationssoftware, über die man in Echtzeit gemeinsam an einem Projekt arbeiten kann. Für Eclipse gibt es da z.B. das Saros Plugin oder für Visual Studio und VS Code gibt es Live Share.

c) Zu guter Letzt steht euch natürlich noch offen, via Kommunikationssoftware (Microsoft Teams, Skype, o.ä.) Pair Programming zu organisieren.

...zur Antwort

1) Dein HTML ist an verschiedenen Stellen invalid.

  • select-Elemente kennen kein Attribut test
  • Das erste Anführungszeichen muss raus
  • option-Elemente kennen kein Attribut href

2) Bei nur einer Option in der Auswahlbox hat der Nutzer nicht die Möglichkeit, die Auswahl zu ändern (und somit ein change-Event zu triggern).

3) Da dein option-Element kein value-Attribut definiert, wird das Label (in deinem Fall -test) als Wert verwendet.

Eine korrigierte Fassung könnte folgendermaßen aussehen:

<select name="test" onchange="window.open(this.value, '_self'); return false;">
  <option selected disabled>Please choose a target</option>
  <option value="index.html">-test</option>
</select>
...zur Antwort

Sofern der Container als Rahmen für das Bild agieren soll, wäre es die bessere Lösung, dem Bild via CSS einen Rahmen zu geben.

Beispiel:

.image {
  border: 16px solid blue;
}

Andernfalls passt die initiale Darstellungsform des Containers nicht. Blockelemente orientieren sich für die Berechnung ihrer Größe an den Elternelementen, nicht ihrem Inhalt.

Man kann nun entweder mit

width: fit-content;

die Containergröße an die Breite der Content-Box anpassen (lies hier mehr dazu) oder stattdessen die Darstellungsform ändern:

<!doctype html>
<head>
  <title>Example</title>
  <style>
    .image-container {
      background: blue;
      display: inline-block;
      padding: 16px;
    }
  </style>
</head>
<body>
  <div class="image-container">
    <img alt="..." height="..." src="..." width="...">
  </div>
</body>

Die Größe des Containers wird auch hier durch seinen Inhalt (und den Innenabstand) bestimmt. Wenn du nun trotzdem eine (maximale) Breite (max-width / width) vorgeben möchtest, musst du beachten, dass das Bild über den Rand läuft, wenn es größer als der Container sein sollte. Daher wäre es in diesem Fall besser, noch für das Bild eine Skalierung vorzugeben:

.image {
  height: auto;
  width: 100%;
}

Je Bildschirmbreite / Breite des Containers wird das Bild nun größer/kleiner skalieren (ohne sein ursprüngliches Größenverhältnis zu verlieren).

...zur Antwort

Das ist eher eine Aufgabe für HTML + JavaScript. Mit CSS kannst du nur das Aussehen von Seitenelementen beeinflussen.

Eine einfache Umsetzung könnte so aussehen:

<!doctype html>
<title>Example</title>
<button onclick="prompt('Dialog title', 'Field default value')" type="button">Click me</button>

Der klickbare Knopf wird mittels eines button-Elements umgesetzt. Dieses Element bekommt einen Event Handler zugeordnet, der bei Klick auf den Button aufgerufen wird. Der Handler ruft die prompt-Funktion auf, die einen Dialog mit Textfeld anzeigt.

(...) aber ich glaube das ist egal was man nutzt oder?

Es ist nur wichtig, einen Texteditor zu haben, mit dem man Text speichern kann.

...zur Antwort

Der Connectionstring gibt an, mit welcher Datenbank du dich verbindest. Wenn der Pfad, den du als DataSource angibst, relativ ist, geht er von dem Ordner aus, in dem sich dein auszuführendes Programm befindet. Builds wandern typischerweise in den bin-Ordner.

...zur Antwort

Mindestens der Text sollte in einen eigenen Container. Bei dem Bild würde ich allerdings ebenso einen Container drumherum setzen, um die Skalierung leichter handzuhaben.

Welche Elemente dabei konkret zum Einsatz kommen, hängt vom Kontext ab. Wenn der Text beispielsweise das Bild beschreibt und diese Einheit essentiell zum Hauptinhalt der Seite beiträgt, dann wäre ein figure-Element passend.

<figure class="container">
  <div class="container__image-box">
    <img alt="..." class="container__image" height="..." src="..." width="...">
  </div>
  <figcaption class="container__text">Some text</figcaption>
</figure>    

Die Elemente kannst du nun mittels Flexbox oder einem Grid horizontal nebeneinander anordnen.

CSS:

.container {
  column-gap: 10px;
  display: flex;
  margin: 0;
}

.container__text {
  flex: 1 1 0px;
  text-align: right;
}

.container__image {
  height: auto;
  width: 100%;
}

Zu beiden Features findest du hier ausführliche Guides:

  • A Complete Guide to Flexbox
  • A Complete Guide to CSS Grid
...zur Antwort

1) Du setzt mehrmals die Schriftart für Komponenten falsch.

// falsch
entry= tk.Entry(root, width= 20, font("Arial", 20), bd=20, justify= tk.RIGHT)

// richtig
entry= tk.Entry(root, width=20, font="Arial 20", bd=20, justify=tk.RIGHT)

// auch richtig
entry= tk.Entry(root, width=20, font=("Arial", 20), bd=20, justify=tk.RIGHT) 

Bei font handelt es sich um ein keyword argument. Dieses wird mittels Gleichheitszeichen von seinem Wert getrennt.

2) Hinter dem Kopf einer Kontrollstruktur kommt stets ein Doppelpunkt

// falsch
if column_val> 3

// richtig
if column_val > 3:

Es ist für eine bessere Lesbarkeit im Übrigen sinnvoll, Operatoren und Terme via Leerzeichen zu trennen.

3) Die Einrückungen in deiner vorletzten Schleife sind offensichtlich falsch. Wenn du einen neuen Kontext öffnen möchtest (z.B. if-Körper), dann rücke um vier Leerzeichen nach rechts ein. Soll dieser Kontext wieder geschlossen werden, geht es wieder vier Leerzeichen nach links.

4) Der Aufruf von mainloop sollte insgesamt nur einmal erfolgen. Er gehört also nicht in den Schleifenkörper.

5) Bei entry.grid handelt es sich um einen Methodenaufruf. Folglich ist der Zuweisungsoperator vor der Klammer (Argumentenliste) falsch.

6) Es gibt kein Argument stickx für die grid-Methode.

...zur Antwort

Versuche die unterschiedlichen Ebenen (Styles, Markup, Skripte) so gut wie möglich zu trennen. Bei JavaScript und CSS dürfte das am einfachsten fallen, da du beide Sprachen in eigene Dateien auslagern kannst.

Bezüglich PHP wäre es vorteilhaft, ein MVC-Framework mit Template-Engine zu nutzen (z.B. Laravel, Phalcon, Symfony, Yii, o.ä.), denn ein solches gibt schon gut durchdachte Strukturen vor.

Aber auch ohne Framework kann man sich erste Ansätze schaffen, eine stärkere Trennung vorzunehmen.

  • Entwickle, wo es geht, objektorientiert
  • Lagere deine Klassen und Funktionen in eigene Dateien aus und inkludiere sie nur in andere Dateien; Anwendungslogik hat in Views (HTML) nichts zu suchen
  • Lege dir eine klare Ordnerstruktur an (z.B. models, views, controllers, ...)
  • Bau dir ein Routingsystem (es gibt etliche Webartikel dazu)
  • Verzichte auf globale Variablen
  • Wenn du PHP und HTML vermischen musst, verwende die alternative Syntax, die PHP zur Verfügung stellt
  • Verzichte möglichst darauf, HTML in PHP-Strings zu verwenden; inkludiere stattdessen den PHP-Code in HTML, so wie hier:
<?php $name = "Josef"; ?>
<p>My name is <?= $name ?></p>
  • Lagere wiederverwendbare Komponenten (Bsp.: Footer) in eigene Dateien aus und binde sie wo benötigt nur noch ein

Ansonsten ist, wie dir hier bereits geschrieben wurde, eine IDE nützlich (z.B. PhpStorm oder Visual Studio), die dich mit Syntax Highlighting u.ä. unterstützen kann.

...zur Antwort
NextJS-Backend/Datenbank?

Ich habe eine Anwendung, wo sich ein Nutzer mit Auth0 registrieren kann.

Es zeigt den Namen und die E-Mail ganz normal an und mit

/api/auth/login
/api/auth/logout

kann man sich anmelden/abmelden.

Nun sollen alle Nutzer XP-Punkte haben (bei 0 starten) und durch Fragen beantworten im Level aufsteigen können. Bzw. ich möchte ganz simpel erst einmal mit einem Buttonklick 25 XP-Punkte dazu verdienen.

Das Ganze kriege ich normalerweise mit PHP und einer normalen SQL-Datenbank, die ich in phpMyAdmin einrichte, hin, aber da ich in NextJS neu bin, wollte ich wissen, wie das da abläuft.

Vercel liefert soweit ich weiß keine Datenbank, wo ich Nutzerdaten hinschicken kann und Auth0 kann auch nicht mehr, als nur ein sicheres Loginsystem bieten.
Also müsste ich die SQL-Datenbank wie immer anlegen und mit dem user-Objekt von Auth0 arbeiten und die "sid" benutzen, um die Punkte dem richtigen User zuzuschreiben?

Ich weiß, dass MongoDB beliebt ist, aber das ist doch auch wieder extern irgendwo und kostet Geld und ist nicht auf einem "Standard" Webhosting inklusive, wie PHP/SQL-Datenbank.

Wie benutzt man MongoDB und ist das sinnvoll?

Für Next brauche ich kein VPS, was praktisch ist. Keine zusätzlichen Kosten und lange Einrichtung. Dafür SSR.

NextJS ist auch selber das Backend soweit ich weiß, also ich kann direkt in der Serverkomponente eine Datenbankabfrage machen und mit Node etwas zu der Datenbank hinzufügen, wie das mit dem Klick +25 XP.

Von Firebase habe ich gehört, dass es beides kann: Sowohl sichere Authentication als auch Datenbank für Nutzerdaten.

Aber ob das bei wenigen Nutzern Geld kostet weiß ich nicht.

...zur Frage
(...) und Auth0 kann auch nicht mehr, als nur ein sicheres Loginsystem bieten.

In Auth0 können ebenso profilbezogene Daten (user_metadata) gespeichert werden.

Also müsste ich die SQL-Datenbank wie immer anlegen und (...)

Wenn du die Nutzerdaten in einer eigenen Datenbank ablegen möchtest: Ja. Eine Tabelle mit zwei Spalten dürfte ausreichen. Eine Spalte beinhaltet eine Nutzer-ID/E-Mail (= Primärschlüssel), die zweite die aktuellen Erfahrungspunkte.

Die sid von Auth0 wäre allerdings nicht als Nutzer-ID geeignet, denn die beschreibt die aktuelle Session-ID. Sie ist also nicht persistent, sondern ändert sich mit jeder neuen Authentifizierung. Nimm stattdessen die user_id oder (sofern die in deinem Fall eindeutig ist) die E-Mail-Adresse.

(...) Vercel liefert soweit ich weiß keine Datenbank, wo ich Nutzerdaten hinschicken kann (...)

Vercel bietet verschiedene Formen der Datenspeicherung. Das Angebot inkludiert eine Postgres-Datenbank, die du auch im Hobby-Plan nutzen kannst.

(...) aber da ich in NextJS neu bin, wollte ich wissen, wie das da abläuft.

Wie in PHP, kannst du auch mit unterschiedlichen DBMS kommunizieren. Schau auf NPM, dort findest du passende Client-Bibliotheken.

Zum Beispiel:

  • mongodb (MongoDB)
  • mysql2 (MySQL)
  • pg (Postgres)
  • prisma (ein ORM, welches Konnektoren zu verschiedenen DBMS bietet; bei Bedarf auch eine GUI zur Verwaltung der Datenbank > siehe Prisma Studio)
  • sqlite3 (SQLite)

Wenn du explizit phpMyAdmin weiterverwenden möchtest, musst du es entsprechend einrichten (Webserver mit PHP-Konfiguration, phpMyAdmin installieren und konfigurieren). In einer Webhosting-Umgebung, in der die entsprechenden Zugriffsrechte fehlen, klappt das natürlich nicht so gut. Du könntest dir das alles auf dem eigenen Rechner einrichten und später einen Dump auf das gehostete System ziehen.

Ich weiß, dass MongoDB beliebt ist, aber das ist doch auch wieder extern irgendwo und kostet Geld und ist nicht auf einem "Standard" Webhosting inklusive, wie PHP/SQL-Datenbank.
  • MongoDB ist frei/kostenlos nutzbar
  • MongoDB kann selbst oder in einer Cloud gehostet werden

Lies dazu hier.

Node.js + MongoDB könntest du bei Heroku oder NodeChef hosten. Andernfalls eignen sich Cloud Hosting-Anbieter wie AWS, Azure, Digital Ocean, usw. ... oder du holst dir einen vServer (z.B. bei IONOS, netcup, Strato, o.ä.) und richtest dir auf dem alles Notwendige selbst ein. Das keine dieser Lösungen kostenlos ist, sollte verständlich sein.

Wie benutzt man MongoDB (...)?

Schau in die Dokumentation.

(...) und ist das sinnvoll?

Wir reden in deinem Fall von keiner komplexen oder außergewöhnlichen Struktur, die zur Verwaltung komplizierter Operationen (Joins, o.ä.) bedarf. Du hast nur eine einfache Liste an Schlüsselwertpaaren, die in beiden Datenbanksystemen problemlos abgebildet werden kann.

Worüber man sich wenn eher Gedanken machen müsste, wäre die Update-Strategie, wobei auch das erst wirklich relevant werden sollte, wenn permanent tausende Anfragen in der Minute bearbeitet werden müssen. Belies dich für so einen Fall zu Load Balancing/Skalierung und schau außerdem, ob du nicht schon über eine geänderte Programmlogik für Entlastung sorgen kannst.

Irgendwelche Highscore-Tabellen/Rankingsysteme, o.ä. berechnet man am besten über einen separaten Job in bestimmten Zeitintervallen (z.B. an jedem Tag um 1 Uhr).

Von Firebase habe ich gehört, (...)

Auch zu Firebase gibt es eine offizielle Dokumentation.

  • Produkte
  • Preismodelle
...zur Antwort

1) In der Zeile, in der du das Array initialisierst, fehlt ein Semikolon am Zeilenende.

2) Deine Bezeichner würde ich nochmals überarbeiten:

  • Das Array könnte mit einem Namen wie numbers konkreter beschrieben werden.
  • inputs ist eine Pluralform, aber du liest in diesem Schritt nur eine Eingabe
  • Statt snake_style würde ich empfehlen, die übliche PascalCase-Konvention von C# einzuhalten. Aus min_Max_Sum wird demzufolge minMaxSum.
  • Für Zählervariablen wäre es besser, entweder einen passenden, aussagekräftigen Namen zu wählen (z.B. index) oder zumindest einen gängigen Namen wie i (= index/iteration).

3) Wenn wir einmal fest davon ausgehen, dass jede Eingabe ausnahmslos eine valide Ganzzahl ist, dann wäre eine for-Schleife besser geeignet, da dann die Zählervariable nur für den Kontext angelegt werden muss, in dem sie tatsächlich benötigt wird.

for (int index = 0; index < numbers.Length; ++index)
{
  var input = Console.ReadLine();
  numbers[index] = int.Parse(input);
}

In der Praxis ist es aber tatsächlich besser, sich gerade bei Eingaben von außen abzusichern. Bei einer invaliden Eingabe würde die Parse-Methode einen unbehandelten Ausnahmefall auslösen, der zum Programmabsturz führt.

Die TryParse-Methode ist die sichere Option. Wenn sie die Eingabe lesen kann, wird das Resultat in das out-Argument geschrieben und die Methode selbst liefert den Wert true zurück. Bei Misserfolg wäre die Rückgabe false.

Schlussendlich könnte man hier nun gut die do-while-Schleife nutzen:

var index = 0;

do
{
  var input = Console.ReadLine();

  if (int.TryParse(input, out var number))
  {
    numbers[index] = number;
    ++index;
  }
}
while (index < numbers.Length);

Natürlich sind while oder for ebenso möglich:

for (var index = 0; index < numbers.Length;)
{
  var input = Console.ReadLine();

  if (int.TryParse(input, out var number))
  {
    numbers[index] = number;
    ++index;
  }
}

Ich halte in diesem Fall aber die do-while (zumindest im Vergleich zu for) für die bessere Option, denn man kann an ihr schneller die verfolgte Absicht herauslesen.

Die for-Schleife steht üblicherweise für einen kontrollierten Ablauf, bei dem von vornherein klarer definiert ist, wie oft die Schleife ihren Körper wiederholen wird. Bei while/do-while ist das offener, da der Kopf/Fuß nur eine Bedingung aufnimmt.

Das Ergebnis von ReadLine schreibe ich übrigens in eine eigene Variable, damit man bei Bedarf leichter prüfen kann (z.B. via Debugger), was für ein tatsächlicher Wert von der Konsole gelesen wurde.

4) Deine Lösung, via Linq die Extremwerte herauszusuchen, ist natürlich schön kurz und bei den fünf Zahlen absolut kein Problem.

Behalte dennoch im Auge, dass du damit mehrmals über das Array läufst, obwohl du die gesuchten Werte auch schon beim Einlesen ermitteln könntest. Immerhin bräuchtest du lediglich zwei Variablen (min/max) die mit jeder neuen Eingabe verglichen werden. Ist die Eingabe größer/kleiner, dann wird der Wert der jeweiligen Variable überschrieben.

Müsstest du die Werte nicht in einem Array speichern, sollten Minimum und Maximum definitiv in der Eingabeschleife ermittelt werden.

...zur Antwort
JavaScript

Es wäre meiner Meinung nach aufgrund mehrerer Gründe besser, mit JavaScript anzufangen.

  • JavaScript ist weiter verbreitet als TypeScript
  • Es hilft später, bestimmte Verhaltensweisen besser zu verstehen (JS basiert bspw. auf einem prototypbasierten OOP-Modell, auf die nun eine Sprache wie TypeScript versucht, ein klassenbasiertes OOP-Modell draufzustülpen)

Bezüglich interessanter Themen für später:

  • Web-APIs (s. bspw. Canvas, DOM, Fetch, File, Intersection Observer, Mutation Observer, Push, WebSockets, Web Storage)
  • Die Features der Webentwicklungstools deines genutzten Browsers (Chrome/Firefox/Safari)
  • JS Design Patterns
  • Adaptive & Responsive Design (Vorgehensweisen, technische Mittel, ...)
  • Node.js, NPM und Bundling Tools (wie Vite, webpack, o.ä.)
  • Es wäre gut, sich in ein/e populäre/s JS-Bibliothek/Framework (z.B. Angular / NextJS / React / Svelte / Vue) schon einmal tiefer eingearbeitet zu haben
  • Das Gleiche gilt für CSS-Frameworks/Toolkits (s. Bootstrap, Tailwind, UIkit, ...)
  • CSS-Erweiterungen (wie LESS oder SCSS)
  • CSS Namenskonventionen (siehe BEM, SMACSS)
  • Storybooks
  • Test-Frameworks (wie Cypress, Jest, Mocha, Playwright, ...)
  • WCAG
  • Auch wenn es nicht mehr direkt in den Bereich Frontend gehört: Einmal mit einem CMS zu arbeiten, wäre nicht schlecht

Auch wenn ich bzgl. Tools oben meist mehrere Optionen nenne, reicht es vollkommen aus, sich jeweils nur eines herauszugreifen, um das darumliegende Konzept kennenzulernen. Sich direkt in alles einarbeiten zu wollen (z.B. alle genannten Testframeworks), wäre übertrieben. Womit du bei einem Arbeitgeber letztendlich tatsächlich einmal konfrontiert wirst, ist nicht absehbar.

(...) z.B. höre ich immer wie wichtig REST-APIs sind (...)

REST gibt Regeln vor, wie eine Schnittstelle aussehen soll, über die ein Client mit einem Server kommuniziert. Webartikel, die diese Architektur genauer beschreiben, gibt es viele (Beispiel). Ein einfaches Einstiegstutorial mit Node.js findest du beispielsweise im Postman Blog (einen Postman-Account benötigst du m.E. nicht; die Anfragen an die API kannst du genauso gut mittels Fetch stellen).

...zur Antwort

Mit dem Style gibst du vor, dass das Bild in der Breite 90% des Viewports einnehmen soll und setzt dazu noch einen Innenabstand. So entsteht eine größere Fläche als benötigt wird mit zusätzlichen Weißraum.

Ich würde mal sagen, dass du sicherlich die komplette CSS-Regel nicht brauchst. Gib allenfalls mit den Attributen width und height das Bildgrößenverhältnis vor.

Beispiel bei einem Bild mit den Maßen 500x200 Pixel:

<a href="..."><img class="rx6400" src="RX6400.png" alt="..." height="200" width="500"></a>

Mit dynamischer Ausrichtung an der Containergröße (in CSS):

.rx6400 { height: auto; width: 100% }

Um einen Abstand drumherum zu erhalten, solltest du nicht den Innenabstand des Bildes oder des Links ändern. Setze entweder einen Container ein und definiere für diesen einen Innenabstand (der Kürze halber hier nur als Inline-Style):

<div style="padding: 19%">
  <a href="..."><img ...></a>
</div>

oder nutze für den Link das margin-Property, um einen Außenabstand festzulegen.

Der Wert des alt-Attributs sollte im Übrigen aussagekräftiger sein. In deinem Fall übernimmt es die Rolle des Linktitels. Also wäre so etwas wie: Gehe zu RX6400 Produktseite passend.

...zur Antwort