borisbaer: Wie rufe ich mit URL-Parametern dynamisch Inhalte auf?

0 75

Wie rufe ich mit URL-Parametern dynamisch Inhalte auf?

borisbaer
  • url
  1. 0
    TS
    • ajax
    • client-server
    • url
    1. 0
      borisbaer
  2. 0
    dedlfix
    1. 0
      borisbaer
      1. 0
        Samuel fiedler
        • javascript
        • php
        • url
        1. 0
          Der Martin
          • php
          • url
        2. 0
          Rolf B
          1. 0
            borisbaer
            1. 0
              Rolf B
              1. 0
                borisbaer
                1. 0
                  Rolf B
                  1. 0
                    borisbaer
                    1. 0
                      Rolf B
                      1. 0
                        borisbaer
                        1. 0
                          tk
                          • php
                          • url
                          1. 0
                            borisbaer
                        2. 0
                          Rolf B
                          1. 0
                            borisbaer
                            1. 0
                              Rolf B
                      2. 1
                        tk
                        • php
                        • url
                        1. 0
                          Rolf B
                          1. 0
                            borisbaer
                            1. 0
                              Der Martin
                              1. 0
                                Rolf B
                                1. 0
                                  borisbaer
                                  1. 0
                                    Rolf B
                                    1. 0
                                      borisbaer
                                      1. 0
                                        Rolf B
                                        1. 0
                                          borisbaer
                                          1. 0
                                            Rolf B
                                            1. 0
                                              borisbaer
                                            2. 0
                                              borisbaer
                                              1. 0
                                                Rolf B
                                                1. 0
                                                  borisbaer
                                                  1. 0
                                                    Rolf B
                                                    1. 0
                                                      borisbaer
                                                      1. 0
                                                        Rolf B
                                                        1. 0
                                                          borisbaer
                                                          1. 0
                                                            borisbaer
                                                            1. 0
                                                              Rolf B
                                                              1. 0
                                                                borisbaer
                                                                1. 0
                                                                  Rolf B
                                                                  1. 0
                                                                    borisbaer
                                                            2. 0
                                                              Rolf B
                                                              1. 0
                                                                borisbaer
                                                                1. 0
                                                                  Rolf B
                                                                  1. 0
                                                                    borisbaer
                                                                    1. 0
                                                                      Rolf B
                                                                      1. 0
                                                                        borisbaer
                                                                        1. 0
                                                                          Rolf B
                                                                          1. 0
                                                                            klawischnigg
                                                                            1. 0
                                                                              Rolf B
                                                                              1. 0
                                                                                klawischnigg
                                                                                1. 0
                                                                                  Rolf B
                                                                          2. 1
                                                                            borisbaer
                                2. 0
                                  Der Martin
                                  1. 0
                                    Rolf B
                                    1. 0
                                      Der Martin
                                      1. 0
                                        Rolf B
                                        1. 0
                                          Der Martin
                                          1. 0
                                            Rolf B
                            2. 0
                              Tabellenkalk
                              1. 0
                                borisbaer
        3. 0
          borisbaer
  3. 0
    Rolf B
    1. 0
      borisbaer
      1. 0
        Rolf B
        1. 0
          borisbaer
          1. 0
            Rolf B
            1. 0
              borisbaer
              1. 1
                Rolf B
                1. 0
                  borisbaer
                  1. 0
                    Rolf B
                    1. 0
                      borisbaer

problematische Seite

Hallo, ich habe bei einem älteren Beitrag dargestellt, wie ich Tabs erstellt habe, die mithilfe von AJAX dynamisch den Content wechseln. Diese Methode ist für mich aus zwei Gründen suboptimal: 1. Die Browser-Funktion Seitenquelltext anzeigen zeigt mir den dynamisch geladenen Content nicht an. 2. Der Tab-Content lässt sich nicht gezielt über die URL aufrufen (wobei dies sicherlich auch noch irgendwie zu lösen wäre).

Stattdessen hätte ich gerne, dass z.B. die URL …/games/demons-souls?mods mir auch direkt als Tab-Content den Inhalt von mods.php anzeigt.

Zwar weiß ich, dass das mit URL-Parametern gehen soll, aber leider haben meine bisherigen Recherchen mir nicht sagen können, wie genau ich das bewerkstelligen kann. Falls jemand einen Tipp für mich hat, wo ich ein Tutorial dazu finde, wäre ich sehr dankbar.

akzeptierte Antworten

  1. problematische Seite

    Hello,

    ich habe deinen Beitrag jetzt mindestens fünfmal gelesen und versucht, ihn auch mit Hilfe der verlinkten Seite zu verstehen.

    Die macht leider erstmal meinen ganzen Browserinhalt (Hintergrund nebst Navigation) schwarz, was ich auf den Tod nicht leiden kann.

    Da auch noch niemand Anderes geantwortet hat, schlage ich vor, dass Du uns ein paar Skizzen zeichnest, scannst und uploadest und den zeitlichen Ablauf dazwischen markierst.

    Was soll

    • wann
    • wie
    • wodurch (welche Benutzerhandlungen, welche Daten, ...)

    aussehen?

    Das ist jetzt kein Joke, sondern ein praktikabler Weg, seine Ziele zu formulieren, um dann zu Lösungsvorschlägen zu kommen.

    Die Aufteilung der Requests in POST, GET, URL oder AJAX im Hintergund will durchaus intelligent geplant sein, und dass Du jetzt danach fragst, wie das geht, ist durchaus intelligent!

    Stattdessen hätte ich gerne, dass z.B. die URL …/games/demons-souls?mods mir auch direkt als Tab-Content den Inhalt von mods.php anzeigt.

    Glück Auf
    Tom vom Berg

    --
    Es gibt nichts Gutes, außer man tut es!
    Das Leben selbst ist der Sinn.
    1. problematische Seite

      Entschuldige bitte die späte Antwort, bei meiner Familie und mir ist Corona ausgebrochen mitsamt Grippe-Symptomen.

      Die macht leider erstmal meinen ganzen Browserinhalt (Hintergrund nebst Navigation) schwarz, was ich auf den Tod nicht leiden kann.

      Das habe ich nun geändert, danke für den Hinweis!

      Da auch noch niemand Anderes geantwortet hat, schlage ich vor, dass Du uns ein paar Skizzen zeichnest, scannst und uploadest und den zeitlichen Ablauf dazwischen markierst.

      Was soll

      • wann
      • wie
      • wodurch (welche Benutzerhandlungen, welche Daten, ...)

      aussehen?

      Für Skizzen fehlt mir momentan die Energie, aber ich versuche es besser zu erklären: Ich möchte, dass jeder Tab (Spiel, Releases, Merchandise, Guides usw.) der jeweilige Inhalt aus einer separaten .php-Datei in demselben Ordner geladen wird. Wenn ich also auf Releases klicke, dann soll in ein bestimmtes div der Inhalt der Datei releases.php geladen werden. Bisher funktioniert das mit einem AJAX-Script bei mir, nämlich dem Folgenden:

      // A J A X - T A B S
      
      $( "#tabs .tab" ).click(function() {
      
      	// h i g h l i g h t - c u r r e n t - t a b
      
      	$( this ).addClass( "current" ).siblings().removeClass( "current" );
      
      	// m a i n : a d d - c u r r e n t - i d
      
      	var main = $( "#tabs a[href].current" ).attr( "href" );
      
      	$( "main" ).attr( "id", main );
      
      	// l o a d - a j a x - c o n t e n t
      
      	$.ajax( { url: this.href, success: function( html ) {
      
      		$( "#append" ).empty().append( html );
      
      	}
      
      	});
      
      	return false;
      
      });
      
      //	A C T I V E - A J A X - T A B
      
      var url = $( "#tabs a[href].current" ).attr( "href" );
      
      $.ajax( { url: url, success: function( html ) {
      
      	// m a i n : a d d - c u r r e n t - i d
      
      	var main = $( "#tabs a[href].current" ).attr( "href" );
      
      	$( "main" ).attr( "id", main );
      
      	// l o a d - a j a x - c o n t e n t
      
      	$( "#append" ).empty().append( html );
      
      }
      
      });
      

      Die Aufteilung der Requests in POST, GET, URL oder AJAX im Hintergund will durchaus intelligent geplant sein, und dass Du jetzt danach fragst, wie das geht, ist durchaus intelligent!

      Momentan läuft mehr oder weniger nur diese eine Seite zum Testen. Ich versuche herauszufinden, wie ich den Inhalt der Webseite so strukturieren kann, dass die einzelnen Elemente möglichst dynamisch sind. Sie soll darauf ausgelegt sein, potenziell mehrere hundert Einzelseiten zu beinhalten (sehr ambitioniert, ich weiß).

      Wenn ich dann ein Element irgendwann doch gerne ändern wollte (z.B. dass ich an der Stelle, an der Veröffentlichung steht, doch lieber den Begriff Erscheinungsdatum verwenden möchte), ich diese Änderung nicht bei allen hundert Seiten durchführen muss.

      Leider bin ich echt ein Noob in puncto Programmiersprachen. Ich bin froh, dass ich überhaupt so weit gekommen bin bisher. Ich versuche meine Kenntnisse zu PHP und JavaScript mit der Zeit auszubauen, aber es läuft eher schleppend. War jetzt sehr viel learning by doing. Ich weiß nicht mal genau, was URL Parameter genau sind (sie wurden mir von Rolf B als bessere Lösung zu AJAX vorgeschlagen) und die Tutorials, die ich bisher gesehen bzw. gelesen habe, hinterlassen viele Fragezeichen. Wie dem auch sei, ich hoffe, dass ich irgendwann durchsteige.

      Gruß Boris

  2. problematische Seite

    Tach!

    1. Die Browser-Funktion Seitenquelltext anzeigen zeigt mir den dynamisch geladenen Content nicht an.

    Ja, das ist der Quelltext, der vom Server kam. Daraus erzeugt der Browser das DOM und rendert es anschließend zu dem, was man sieht. Bereits Fehlerkorrekturen der Browser können das DOM anders aussehen lassen als was du im Quelltext stehen hattest.

    Wenn du das DOM manipulierst, was du durch das Nachladen und Einfügen von Inhalt machst, ändert sich nicht der Quelltext, und er wird auch nicht zurückgerechnet. Wenn du dir was anschauen willst, kannst du in den Entwickler-Tools den jeweiligen Stand des DOM inspizieren. Wenn du sehen möchtest, was dein Browser anfragt und was dein Server antwortet, kannst du das im Netzwerk-Tab der Entwickler-Tools untersuchen.

    1. Der Tab-Content lässt sich nicht gezielt über die URL aufrufen (wobei dies sicherlich auch noch irgendwie zu lösen wäre).

    Ja, das ist erstmal so, solange du die History nicht entsprechend anpasst. Der Browser weiß ja ncht, wie relevant deine Manipulationen für die Navigation durch deine Inhalte sind.

    dedlfix.

    1. problematische Seite

      Wenn du das DOM manipulierst, was du durch das Nachladen und Einfügen von Inhalt machst, ändert sich nicht der Quelltext, und er wird auch nicht zurückgerechnet. Wenn du dir was anschauen willst, kannst du in den Entwickler-Tools den jeweiligen Stand des DOM inspizieren. Wenn du sehen möchtest, was dein Browser anfragt und was dein Server antwortet, kannst du das im Netzwerk-Tab der Entwickler-Tools untersuchen.

      Ja, untersuchen kann ich die Seite schon mit den Entwickler-Tools, aber wenn ich view-source: vor die URL schreibe, wir der dynamisch hinzugefügte Inhalt natürlich nicht angezeigt. Ich dachte, das wäre vielleicht anders, wenn man URL-Parameter verwendet.

      1. Der Tab-Content lässt sich nicht gezielt über die URL aufrufen (wobei dies sicherlich auch noch irgendwie zu lösen wäre).

      Ja, ich glaube mit der Verwendung von # kann man es irgendwie einrichten, dass z.B. die URL /games/demons-souls#mods auch immer automatisch die dazugehörige Datei mods.php lädt. Leider weiß ich aber nicht, wie genau das zu programmieren wäre.

      Ja, das ist erstmal so, solange du die History nicht entsprechend anpasst. Der Browser weiß ja ncht, wie relevant deine Manipulationen für die Navigation durch deine Inhalte sind.

      Ich muss gestehen, von so was habe ich gar keine Ahnung. Der Link hat mir leider nicht viel sagen können, da fehlt mir wohl das Grundverständnis …

      1. problematische Seite

        Hallo borisbaer!

        Ich schreib jetzt mal was zu URL-Parametern.

        Allgemein ist ein URL-Parameter etwas, was hinter der Adresse der Seite steht und mit ? anfängt. Als gutes Beispiel können wir die Suche von Suchmaschinen verwenden.
        Auch wenn ich Google hasse, werde ich Google als Beispiel verwenden, einfach, weil es immer noch (😟) die populärste Suchmaschine ist.
        Unser erstes Beispiel wird sein: https://google.com/search?q=selfhtml. Dabei ist das URL-Parameter das ?q=selfhtml. Wie wir sehen, ist das q der „Schlüssel“, und das selfhtml der Wert.
        Bei der Google Suche habe ich die URL direkt aufgerufen. Sie kann aber auch durch ein Formular entstehen. Für eine einfache Google Suche würde das dann so aussehen:

        <form action="https://google.com/search" method="GET">
          <label for="suchbegriff">Suchbegriff: </label>
          <input type="text" name="q" id="suchbegriff"/><br/>
          <button type="submit">Suchen</button>
        </form>
        

        Das ganze habe ich in einem schönen kleinen Fiddle online gestellt. Dort funktioniert das Formularabsenden aber leider nicht, wahrscheinlich erlaubt es Google nicht, in ein iFrame (oder Frame) eingebettet zu sein.
        Wenn man mehr als ein URL-Parameter mitliefern möchte, geht es nicht mit ? weiter, sondern mit &.
        Auch da gibt es das Google-Beispiel: Wenn ich auf die Google Startseite gehe und dort nach „selfhtml“ suche, dann sieht die ganze URL viel komplizierter aus: https://www.google.com/search?q=selfhtml&source=hp&ei=ekkoYpj9DO6Hxc8P2tGR8AM&iflsig=AHkkrS4AAAAAYihXinLuiThCTkx3kUv_6l_KpzG50Hq3&ved=0ahUKEwiYorTZs7j2AhXuQ_EDHdpoBD4Q4dUDCAg&uact=5&oq=selfhtml&gs_lcp=Cgdnd3Mtd2l6EAMyBQgAEIAEMgUIABCABDIFCAAQgAQyBQgAEIAEMgUIABCABDIFCAAQgAQyBQgAEIAEMgUIABCABDIFCAAQgAQyBQgAEIAEOgsILhCABBDHARCjAjoUCC4QgAQQsQMQgwEQxwEQ0QMQ1AI6EQguEIAEELEDEIMBEMcBENEDOggIABCxAxCDAToLCAAQgAQQsQMQgwE6CAguELEDEIMBOgsILhCABBDHARCvAToICC4QgAQQ1AI6CAgAEIAEELEDOgUILhCABDoOCC4QgAQQsQMQgwEQ1AI6DgguEIAEELEDEMcBENEDOgsILhCABBCxAxCDAToICC4QgAQQsQM6CwguEIAEELEDENQCOg4ILhCABBCxAxDHARCjAjoOCC4QgAQQsQMQxwEQrwE6BwgAEIAEEApQ_QJYjAlgyw1oAXAAeACAAWCIAboEkgEBOJgBAKABAbABAA&sclient=gws-wiz

        Nehmen wir das mal auseinander:

        q              selfhtml
        source         hp
        ei             kkoYpj9DO6Hxc8P2tGR8AM
        iflsig         AHkkrS4AAAAAYihXinLuiThCTkx3kUv_6l_KpzG50Hq3
        ved            0ahUKEwiYorTZs7j2AhXuQ_EDHdpoBD4Q4dUDCAg
        uact           5
        oq             selfhtml
        gs_lcp         Cgdnd3Mtd2l6EAMyBQgAEIAEMgUIABCABDIFCAAQgAQyBQgAEIAEMgUIABCABDIFCAAQgAQyBQgAEIAEMgUIABCABDIFCAAQgAQyBQgAEIAEOgsILhCABBDHARCjAjoUCC4QgAQQsQMQgwEQxwEQ0QMQ1AI6EQguEIAEELEDEIMBEMcBENEDOggIABCxAxCDAToLCAAQgAQQsQMQgwE6CAguELEDEIMBOgsILhCABBDHARCvAToICC4QgAQQ1AI6CAgAEIAEELEDOgUILhCABDoOCC4QgAQQsQMQgwEQ1AI6DgguEIAEELEDEMcBENEDOgsILhCABBCxAxCDAToICC4QgAQQsQM6CwguEIAEELEDENQCOg4ILhCABBCxAxDHARCjAjoOCC4QgAQQsQMQxwEQrwE6BwgAEIAEEApQ_QJYjAlgyw1oAXAAeACAAWCIAboEkgEBOJgBAKABAbABAA
        sclient        gws-wiz
        

        Frage mich bitte nicht, was das alles bedeutet, aber alle diese Daten empfängt Google über die URL-Parameter.
        Diese URL-Parameter kann man natürlich auslesen.
        Vorher eine kleine Bemerkung: Das, was im view-source steht, ist NUR das, was der Browser von Server empfängt.
        Wenn du alles über AJAX machst, ist das JavaScript. Das läuft (normalerweise [1]) im Browser. Logischerweise kann JavaScript also auf keinem Weg den view-source ändern 😉 [2]. Er kann vielleicht das Aussehen der Seite ändern, aber das kannst du ja nur im Seiteninspektor sehen.
        Wenn du das aber über PHP machst, sieht das ganze etwas anders aus. PHP läuft nämlich (normalerweise [3]) auf dem Server. Dann empfängt der Browser die Ergebnisse schon so.
        Es ist allerdings schwierig (SEHR schwierig, eigentlich unmöglich), mit PHP den Buttonklick eines Anwenders (oder die Textauswahl) ohne weitere Hilfen festzustellen. Deswegen mein ganzes Gefasel über URL-Parameter.
        Du kannst dein Problem tatsächlich mit URL-Parametern lösen. Dafür musst du aber wissen, wie du sie empfangen kannst.
        Vielleicht sollte ich noch sagen, dass du auch mit <a href="https://google.com/search?q=selfhtml"></a> URL-Parameter empfangen kannst.

        In PHP kannst du alle URL-Parameter mit $_GET empfangen. Vielleicht findet Google den Suchbegriff über $_GET['q'] heraus.
        Jedenfalls könntest du eine PHP-Datei bauen, die mit URL-Parametern funktioniert. Wenn du dann https://example.com/test.php?mode=games hast, dann kannst du mit PHP $_GET['mode'] machen.
        Weiter kannst du das dann mit $modus = $_GET['mode']; in einer Variablen speichern.
        Den Rest darfst du selber konstruieren, da ich mit meiner Tastatur so langsam durchdrehe.

        Ich hoffe, du konntest mir folgen und hast sowohl mich als auch einen neuen Begriff verstanden!

        Au revoir,
        Samuel Fiedler

        --
        Wie wir ja alle wissen, steht das D in Google für Datenschutz und das v für vertrauenswürdig, siehe Zitat #2337.
        Ich würde noch hinzufügen, dass die Gs in Google für Geld stehen!

        1. Es gibt „node.js“. Das soll auf dem Server laufen. Da ich mich damit jedoch nicht beschäftige, kann ich darüber nichts schreiben. ↩︎

        2. Genau genommen kann JavaScript nur das DOM manipulieren, aber eben nicht den view-source. Von allem, was sich im DOM ändert, hat der view-source keinen blassen Schimmer. ↩︎

        3. Keine Ahnung, ob es das auch für PHP gibt, aber es gibt WebPerl. ↩︎

        1. problematische Seite

          Hallo Samuel,

          ergänzend zu deinen Ausführungen:

          Allgemein ist ein URL-Parameter etwas, was hinter der Adresse der Seite steht und mit ? anfängt.

          Genaugenommen ist das Fragezeichen nur ein Trenner. Es trennt die URL-Parameter vom Rest ab (so wie das et-Zeichen mehrere URL-Parameter voneinander trennt), ist aber selbst nicht Teil davon.

          In PHP kannst du alle URL-Parameter mit $_GET empfangen.

          Naja, empfangen ist vielleicht eine unglückliche Wortwahl.
          Ich hätte eher gesagt "darauf zugreifen", denn empfangen sind sie ja längst.

          Weiter kannst du das dann mit $modus = $_GET['mode']; in einer Variablen speichern.

          Das sollte man aber nicht. Das bloße Umkopieren bringt keinen Mehrwert, es verschleiert nur die Herkunft der Daten. Schließlich ist $_GET['mode'] auch schon eine Variable - aber man sieht ihr auf den ersten Blick an, dass es eine Benutzereingabe ist.

          Einen schönen Tag noch
           Martin

          --
          Мир для України.
        2. problematische Seite

          Hallo Samuel,

          ein paar Hinweise noch dazu. Beim Nachlesen stelle ich fest, dass das Posting etwas wirr scheint - tatsächlich diskutiere ich diverse Optionen, die man wählen kann und aus denen man auswählen muss.

          Es gibt übrigens tatsächlich ein "PHP in Browser" Projekt: https://github.com/oraoto/pib, um <script type="text/php"> ausführen zu können. Was genau er da treibt, scheint intransparent, er erzählt was von Docker und verwendet emscripten, den C/C++ to WASM Compiler. Für Anna Normalprogramiererin ist das aber sicherlich der Overkill, und es auch noch Version 0.04, a.k.a. als experimentell zu sehen.

          Wenn ich URL Parameter mit PHP verarbeite, geschieht das am Server. Man müsste also nach Erkennen des Parameters am Server dies tun

          <div id="append">
          <?php
             include "$module.php";
          ?>
          </div>
          

          oder, schlechter:

          <?php
             echo '<div id="append">';
             include "$module.php";
             echo '</div>';
          ?>
          </div>
          

          Um das nutzen zu können, muss jeder Tab-Wechsel über den Server laufen, das ist dann kein Ajax mehr. Es ist aber für den initialen Seitenaufruf interessant, wenn jemand die URL für ein bestimmtes TAB in einem Bookmark hat.

          Wenn man mit URL-Parametern arbeiten will, hätte man in der Adresszeile URLs wie

          http://.../pages/games/demons-souls/
          http://.../pages/games/demons-souls/?game
          http://.../pages/games/demons-souls/?savegame

          Die vorhandene .htaccess Einstellung führt dazu, dass alle diese Abrufe auf der index.php landen. URLs 2 und 3 mit einem entsprechenden Query String, den PHP dann einliest.

          Der Standardfall ist, dass die /games/demons-souls Seite abgerufen wird. Die index.php kann loslaufen und per Default das game.php inkludieren. Wenn der User auf der Seite nun ein anderes Tab klickt, z.B. dieses:

          <a href="?savegame">Savegame</a>
          

          steht der Link auf /games/demons-souls/?savegame im Raum. Den kann man mit einem Click-Handler im Javascript abfangen und einen Ajax-Request auf savegame.php machen. Und mittels History-API in die Adresszeile pushen:

          window.history.pushState(state, "", new URL(href));
          

          state ist ein beliebiges Objekt, das man zum Hinterlegen von Daten nutzen kann (oder halt ein Leerstring). Der 2. Parameter ist ein Dummy, den gab's mal in der Spec und nun kriegt man ihn nicht mehr weg. Der dritte Parameter ist die URL, die in die Browser-Historie soll und dann auch in der Adresszeile steht, nämlich:

          http://.../pages/games/demons-souls/?savegame

          Wird die gebookmarked und später aufgerufen, bekommt index.php diesen Parameter als Query-String.

          Serverseitig ist index.php?savegame aber nicht so leicht zu erkennen. Denn PHPs $_GET Array geht von URL Parametern in der Form name=value aus. Einen "name only" Parameter findet man zwar in $_GET, aber mit einem NULL Wert. Man kann ihn nur mit isset($_GET["savegame"]) entdecken - und das ist lästig, wenn es eine größere Zahl von Tabs gibt. Aber am sichersten, weil gefakte Parameter kein Unheil anrichten können.

          Man könnte auch in $_SERVER['QUERY_STRING'] schauen. Dort würde man "?savegame" finden, könnte das ? entfernen, ein ".php" anhängen und das inkludieren. Whoa - STOP - das ist ein Hackerfest. Vorher ist zu prüfen, ob der so übergebene Modulname auch erlaubt ist, denn man könnte dort auch Namen von php Sourcen einsteuern, gerne auch mit .. und \ angereichert, die niemals nicht ein Tab-Modul sind.

          Auf diese Weise hätte man die aktuelle Seite als serverseitigen URL-Parameter "versorgt". Dies ist auch die sicherste Implementierung, denn wenn JavaScript nicht läuft, wird einfach über den Server das richtige Tab aktiviert. Progressive Enhancement at work.

          Aber ist das schick, dem Anwender eine URL wie /games/demons-souls/?savegame anzuzeigen? Und wo ich gerade dabei bin - /games/demons-souls/#savegame ist auch nicht wirklich schick. Es hat nur den Vorteil, clientseitig mittels hashchange Event die Navigation steuern zu können.

          Die Variante /games/demons-souls/savegame ist eigentlich viel schicker, nur führt die dazu, dass direkt savegame.php abgerufen wird; und das liefert ja nur das Tab aus.

          Das kann man unterschiedlich lösen. Der typische Apache-Ansatz ist ein Eingriff in die .htaccess, um mit mod_rewrite aus /games/game/page den Abruf von /games/game/index.php?page zu machen - wobei man sich in dem Fall auch das Leben erleichtern und den Parameter als ?tab=page generieren kann. Dann muss man im PHP nur $_GET['tab'] abfragen.

          Also - best practice wäre:

          • die URLs in den Tabs ohne Fragezeichen und ohne # notieren. Also genau so, wie sie jetzt sind.
          • am Server in der .htaccess per mod_rewrite dafür sorgen, dass Tab-Namen als ?tab=... an index.php angehängt werden. Falls irgendein Heini direkt die index.php abruft, kommt halt index.php?tab=index.php an. Ja und? Ist ein ungültiger Tab-Name, und:
          • in index.php den tab-Parameter verarbeiten und auf gültige Tab-Namen validieren. Für ungültige oder fehlende Tabnamen das Default-Tab includen
          • als progressive enhancement einen click-Handler auf die Tabs legen, der die Page per Ajax holt und per pushState so tut, als wär's über den Server gelaufen.

          Rolf

          --
          sumpsi - posui - obstruxi
          1. problematische Seite

            Genau das, was du hier beschreibst (mit Ausnahme der Sicherheitsaspekte, von denen ich leider keine Ahnung habe), wollte ich von Anfang an umsetzen.

            • in index.php den tab-Parameter verarbeiten und auf gültige Tab-Namen validieren. Für ungültige oder fehlende Tabnamen das Default-Tab includen

            Ich frage ganz blöd: Wie geht das?

            1. problematische Seite

              Hallo borisbaer,

              was jetzt genau? Die Verarbeitung des Parameters in index.php hast Du doch schon in deiner Demoseite drin.

              Die mod_rewrite-Konfiguration in der .htaccess? Letztlich definierst Du da, dass eine hereinkommende URL x/y/z irgendwie umgebaut werden soll, bevor der Server sie verarbeitet. Der Servername ist nicht mehr Teil dieser URL.

              In deinen /games Ordner kannst Du eine .htaccess Datei stellen, die so aussieht:

              RewriteEngine On
              RewriteRule suchmuster ersetzungsmuster flags
              

              Das Suchmuster ist eine Regex, mit der auf die URL gematcht wird, und zwar ab dem Ordner, in dem die .htaccess Datei steht. Also: Wenn die .htaccess im /games Ordner steht und die URL /games/demons-souls/mods abgerufen wird, wird die Regex auf demons-souls/mods angewendet.

              Man kann mehrere RewriteRules angeben, und sie werden von oben nach unten verarbeitet. Wenn Du meinst, dass eine Rule die URL abschließend verarbeitet, gibt ihr das L Flag ("Last"), dann werden die folgenden Rules nicht mehr verarbeitet. Man kann auch so eine Art von IF-Blöcken bauen, das geht mit RewriteCond, aber das lass ich jetzt mal weg.

              Wenn Du dies hier schreibst:

              RewriteRule ^([^/]*)/([^/]*)$ $1/index.php?tab=$2 [L,QSA]
              

              dann würden alle URLs nach dem Muster /games/spiel/tabname aufgebaut sind, als /games/spiel/index.php?tab=tabname verarbeitet. Alle anderen URLs bleiben unverändert.

              Wieso ist das so? Schauen wir uns das Suchmuster an. Es sieht wüst aus:

              ^([^/]+)/([^/]+)$
              

              Ich schiebe es mal etwas auseinander.

              ^  ([^/]+)  /  ([^/]+)  $
              

              Regexe sind eine Nichtwissenschaft für sich. Wären sie eine Wissenschaft, gäbe es bspw. diese Mehrdeutigkeit des ^ Zeichens nicht.

              Der Begriff "games" taucht im Suchmuster nicht auf. Siehe oben, dieser Teil der URL kommt beim Suchmuster gar nicht an.

              Das erste ^ ist ein Anker. Steht es am Anfang der Regex, verankert es die Suche am Beginn des zu durchsuchenden Strings. Die Regex "ha" würde zwei Treffer in "haha" finden, aber "^ha" nur das erste ha.

              Dann kommt eine Suche nach einer Folge von Zeichen. Dazu wird eine Klasse von Zeichen verwendet, das macht man mit den eckigen Klammern. Innerhalb der Klammern kann man verschiedenes angeben, z.B. [aeiou] für die Standardvokale. Man kann es aber auch andersrum sagen: Alles außer diesem hier, dafür verwendet man - blöderweise - das ^ Zeichen. [^aeiou] findet alles außer Vokalen, und [^/] heißt: Jedes Zeichen, das nicht / ist. Hinter dieser Zeichenklasse steht ein Plus. Das ist eine Wiederholungsangabe und bedeutet: Das, was links vom Plus ist, soll im duchsuchten String einmal bis beliebig oft vorkommen. [^/]+ findet also die Teile einer URL zwischen den / Zeichen.

              Dieses Teilmuster ist in Klammern gesetzt und bildet damit eine Gruppe. Die brauchen wir nachher beim Ersetzen, denn den Inhalt einer gefundenen Gruppe kann man im Ersetzungsmuster einfügen.

              Es folgt ein / - das ist ein Zeichen ohne Sonderbedeutung und verlangt an dieser Stelle einfach ein /.

              Dahinter wird ein weiterer Pfadanteil in einer Gruppe abgefragt und zum Schluss kommt das $. Das $ ist wieder ein Anker, diesmal für das Ende der Zeichenkette.

              Damit passt das Muster auf foo/bar oder f/b, nicht aber auf f oder foo/bar/baz. Diese blieben unverändert. Ein Abruf von /games/demons würde damit funktionieren wie bisher, und /games/demons/versus/zombies würde wohl auf HTTP Status 404 laufen. Wenn Du solche URLs an dein index.php übergeben willst, verwende als Suchmuster ^([^/]*)/(.*) - das .* am Ende sagt: Nimm Alles Was Kommt.

              Nun zum Ersetzungsmuster. Da steht $1/index.php?tab=$2. Das kannst Du sicherlich selbst einordnen. $1 und $2 sind die Inhalte der Gruppen 1 und 2 aus dem Suchmuster, d.h. der Spielname und der Tabname.

              Zum Schluss folgt noch [L,QSA]. Das sind Flags für die RewriteRule. L heißt "Last", d.h. wenn das Suchmuster zutrifft, ist dies die letzte verarbeitete Rewrite-Regel. QSA heißt "Query String Append" - ob Du das brauchst, weiß ich nicht. Was ist mit einer URL wie /games/demons/achievements?mode=3 - das wäre der Wunsch für das Spiel demons das tab achievements zu sehen, und bei diesem Abruf noch einen zusätzlichen URL-Parameter mode=3 zu nutzen. Ohne QSA würde dieser Extraparameter verloren gehen. Mit QSA würde er an deine generierte URL angehängt.

              Ich hoffe, das war verständlich und auch das, was Du wissen wolltest.

              Rolf

              --
              sumpsi - posui - obstruxi
              1. problematische Seite

                Hallo Rolf,

                vielen Dank für die ausführliche Erklärung des Ganzen. Ich muss mir das noch ein paar mal durchlesen, um es wirklich nachvollziehen zu können. Momentan ist mein Kopf wegen Corona total zu. Ich wollte noch sagen, dass ich schon eine htaccess-Datei habe:

                Options +MultiViews
                
                RewriteEngine On
                
                # Rewrite "/games/<anything>" to "/pages/games/<anything>"
                RewriteCond %{REQUEST_URI} !^/pages/games
                RewriteRule ^games($|/.*) pages/$0 [L]
                
                # Remove .php extension from URL
                RewriteCond %{REQUEST_FILENAME} !-d
                RewriteCond %{REQUEST_FILENAME}\.php -f
                RewriteRule ^([^\.]+)/$ $1.php
                

                Alle Seiten sind bei mir erst mal unter games/pages gespeichert, also wäre der eigentliche Pfad, der umgeschrieben werden müsste nicht /games/demons-souls/, sondern /pages/games/demons-souls/.

                Eigentlich wollte ich auch eher wissen, wie ich denn diesen PHP-Code

                <?php 
                
                if (isset($_GET['page'])) {
                	if ($_GET['page'] == 'game') { $path = "game.php"; include($path); } 
                	else if ($_GET['page'] == 'releases') { $path = "releases.php"; include($path); } 
                	else if ($_GET['page'] == 'mods') { $path = "mods.php"; include($path); } 
                }
                else { $path = "game.php"; include($path); }
                
                ?>
                

                so umschreibe, damit ich nicht jedes Mal ein if ergänzen muss, sobald ein neues Tab hinzukommt, wenn du verstehst, was ich meine.

                Boris

                1. problematische Seite

                  Hallo borisbaer,

                  hm, deine Rewrite-Regeln will ich dann nicht weiter anfassen. Wärest Du denn zufrieden, wenn Du mit /games/demons-souls/?tab=dings auf den Tab namens "dings" kommst? Oder möchtest Du sehr gerne URLs wie /games/demons-souls/dings unterstützen können? Dann müsste man sich passende Rewrites überlegen und könnte vielleicht sogar die Prüfung auf gültige Tab-Namen in der .htaccess durchführen 😉

                  damit ich nicht jedes Mal ein if ergänzen muss, wenn du verstehst, was ich meine.

                  ja, schon.

                  Meine Emfehlung wäre, die PHP Scripte für die Tabs so zu benennen, dass sie am Dateinamen als Tab erkennbar sind. Das kann man entweder so machen, dass sie in einen tabs-Unterordner kommen, oder bspw. als game.tab.php benannt werden.

                  Wenn Du dann in %_GET['page'] etwas findest, kannst du:

                  (1) Prüfen, ob der gefundene Wert nur aus Buchstaben besteht (preg_match) (2) Prüfen, ob die .tab.php-Datei dazu existiert (3) Tab laden

                  $page_found = false;
                  if (!empty($_GET["page"]) 
                       && preg_match("/^[a-z]+$/i", $_GET["page"]) == 1)
                  {
                     $pageFile = ".\" . $_GET["page"] . ".tab.php";
                     if (file_exists($pageFile))
                     {
                        include $pageFile;
                        $page_found = true;
                     }
                  }
                  if (!$page_found)
                  {
                     include ".\game.tab.php";
                  }
                  

                  Das ist die ausführliche Version, die man gut lesen kann.
                  Meine persönliche Version sähe so aus 😳 - ich schäme mich ja auch beinahe 😉

                  $pageFile) = ".\" . (empty($_GET["page"] || !preg_match("/^[a-z]+$/i", $_GET["page"]) ? "game" : $_GET["page"]) . ".tab.php";
                  file_exists($pageFile) && include $pageFile;
                  

                  Rolf

                  --
                  sumpsi - posui - obstruxi
                  1. problematische Seite

                    Wärest Du denn zufrieden, wenn Du mit /games/demons-souls/?tab=dings auf den Tab namens "dings" kommst? Oder möchtest Du sehr gerne URLs wie /games/demons-souls/dings unterstützen können? Dann müsste man sich passende Rewrites überlegen und könnte vielleicht sogar die Prüfung auf gültige Tab-Namen in der .htaccess durchführen 😉

                    Ja, die schickere Variante wäre mir schon sehr lieb, aber das mit den Rewrites hat noch keine Priorität für mich.

                    Meine Emfehlung wäre, die PHP Scripte für die Tabs so zu benennen, dass sie am Dateinamen als Tab erkennbar sind. Das kann man entweder so machen, dass sie in einen tabs-Unterordner kommen, oder bspw. als game.tab.php benannt werden.

                    Wenn Du dann in %_GET['page'] etwas findest, kannst du:

                    (1) Prüfen, ob der gefundene Wert nur aus Buchstaben besteht (preg_match) (2) Prüfen, ob die .tab.php-Datei dazu existiert (3) Tab laden

                    $page_found = false;
                    if (!empty($_GET["page"]) 
                         && preg_match("/^[a-z]+$/i", $_GET["page"]) == 1)
                    {
                       $pageFile = ".\" . $_GET["page"] . ".tab.php";
                       if (file_exists($pageFile))
                       {
                          include $pageFile;
                          $page_found = true;
                       }
                    }
                    if (!$page_found)
                    {
                       include ".\game.tab.php";
                    }
                    

                    error

                    Irgendwie hat er ein Problem mit dem Code.

                    Das ist die ausführliche Version, die man gut lesen kann.
                    Meine persönliche Version sähe so aus 😳 - ich schäme mich ja auch beinahe 😉

                    $pageFile) = ".\" . (empty($_GET["page"] || !preg_match("/^[a-z]+$/i", $_GET["page"]) ? "game" : $_GET["page"]) . ".tab.php";
                    file_exists($pageFile) && include $pageFile;
                    

                    Auch diese Version will er mir leider nicht als validen Code annehmen. 😵😵😵

                    1. problematische Seite

                      Hallo borisbaer,

                      ich könnt' ja sagen: Find's raus, ist eine gute Lektion wenn Du es erkennst 😉. Aber ich hab es ja selbst auch nicht gesehen beim Schreiben.

                      $pageFile = ".\" . $_GET["page"] . ".tab.php";
                      

                      Sorry. \ ist ein Escapezeichen und maskiert deshalb das ". Das kommt davon, wenn man Code direkt vom Hirn in den Editor dumpt und nicht PHP befragt, was es davon hält. Das \ muss verdoppelt werden, um sich selbst zu maskieren.

                      $pageFile = ".\\" . $_GET["page"] . ".tab.php";
                      

                      Das gleiche Problem besteht natürlich in dem write-only Einzeiler.

                      Rolf

                      --
                      sumpsi - posui - obstruxi
                      1. problematische Seite

                        ich könnt' ja sagen: Find's raus, ist eine gute Lektion wenn Du es erkennst 😉. Aber ich hab es ja selbst auch nicht gesehen beim Schreiben.

                        Und ich würde es dir sicher auch sagen können, nachdem ich noch ein Zweitstudium in Informatik absolviert habe. Wenn ich mir normale PHP-Tutorials anschaue, werden nie solche Probleme angesprochen, sondern immer alles sehr basal gehalten.

                        Wenn ich dich mal fragen darf: Wie hast du dir das alles angeeignet?

                        Das gleiche Problem besteht natürlich in dem write-only Einzeiler.

                        Den möchte er trotzdem nicht annehmen, es kommt die Fehlermeldung Parse error: Unmatched ')'. Da der Einzeiler für mich absolut kryptisch ist, versuche ich erst gar nicht den Fehler zu suchen.

                        Wie müsste denn der Code aussehen, damit er nicht auf das .tab im Datei-Namen reagiert, sondern z.B. auf einen Unterordner namens tabs?

                        1. problematische Seite

                          Moin,

                          Das gleiche Problem besteht natürlich in dem write-only Einzeiler.

                          Den möchte er trotzdem nicht annehmen, es kommt die Fehlermeldung Parse error: Unmatched ')'. Da der Einzeiler für mich absolut kryptisch ist, versuche ich erst gar nicht den Fehler zu suchen.

                          Wie sieht dein Code aktuell aus? Im zweiten Code aus dem Rolfs Posting von 19:00 Uhr ist nach $pageFile noch eine schließende Klammer zu viel.

                          Wie müsste denn der Code aussehen, damit er nicht auf das .tab im Datei-Namen reagiert, sondern z.B. auf einen Unterordner namens tabs?

                          Du musst eben in $pageFile den Pfad zu den Dateien richtig zusammenbauen, evtl. sowas in die Richtung:

                          $pageFile = __DIR__ . "/tabs/" . $_GET["page"] . ".php";
                          

                          (bezüglich __DIR__ siehe meine Antwort an Rolf)

                          Gruß
                          Tobias

                          1. problematische Seite

                            Wie sieht dein Code aktuell aus?

                            $pageFile = ".\\" . (empty($_GET["page"]) || !preg_match("/^[a-z]+$/i", $_GET["page"]) ? "game" : $_GET["page"]) . ".page.php";
                            file_exists($pageFile) && include $pageFile;
                            

                            Danke! Funktioniert jetzt auch so! Musste zudem noch die Klammer vor empty schließen.

                        2. problematische Seite

                          Hallo borisbaer,

                          ein Zweitstudium in Informatik hilft Dir da weniger, die lernten früher mal Pascal und heute Java. Mit so ekligen Dingen wie PHP gibt sich doch kein hehrer Prof ab. Da musst Du Dir schon jahrelang die Finger in Niederungen wie Selfhtml schmutzig machen, lesen lesen lesen, und hinreichend oft selbst Fehler machen und selbst finden. Programmieren lernen ist wie ein verstauchter Knöchel. Tut weh und braucht Zeit. Und wenn er dann heile ist, rennst Du wie ein junger Gott - bis zum nächsten Stolperstein (d.h. der nächsten SuperDuper Library, die grunzend durch's Dorf gehyped wird) - echt erstaunlich, wie weit diese Metapher reicht 😂.

                          Mein Stolperstein war das \ - Tobias hat ganz recht, das gehört da überhaupt nicht hin weil es nur unter Windows funktioniert. Das universell nutzbare Verzeichnistrennzeichen ist das /

                          Mit welchen Stringoperationen Du einen Pfadnamen mit Unterverzeichnis zusammensetzt - muss ich Dir das wirklich erklären 😨? Du hast das Verzeichnis deines aktuellen Scripts in DIR, du hast den Basisnamen der Page, du möchtest einen Verzeichnisnamen dazwischen schieben und .php hinten anhängen. Das kannst Du bestimmt selbst.

                          Rolf

                          --
                          sumpsi - posui - obstruxi
                          1. problematische Seite

                            ein Zweitstudium in Informatik hilft Dir da weniger, die lernten früher mal Pascal und heute Java. Mit so ekligen Dingen wie PHP gibt sich doch kein hehrer Prof ab. Da musst Du Dir schon jahrelang die Finger in Niederungen wie Selfhtml schmutzig machen, lesen lesen lesen, und hinreichend oft selbst Fehler machen und selbst finden. Programmieren lernen ist wie ein verstauchter Knöchel. Tut weh und braucht Zeit. Und wenn er dann heile ist, rennst Du wie ein junger Gott - bis zum nächsten Stolperstein (d.h. der nächsten SuperDuper Library, die grunzend durch's Dorf gehyped wird) - echt erstaunlich, wie weit diese Metapher reicht 😂.

                            Tja, ich schätze, wenn man ein so komisches Hobby hat wie ich und auch noch eine Webseite dazu erstellen möchte, dann muss man wohl da durch. Vor 5 oder 6 Jahren wusste ich noch nicht mal, was ein div ist. Es gibt furchtbar viel zu wissen und zu lernen. Ich schätze, die nächste große Hürde wird für mich die Suchfunktion und dann evtl. noch das Erstellen von Accounts (phpMyAdmin?) sein. Na ja, zum Glück gibt es ja Menschen wie euch hier, die einem helfen. Ich sollte mir auch möglicherweise mal Bücher zu bestimmten Programmiersprachen kaufen.

                            Mein Stolperstein war das \ - Tobias hat ganz recht, das gehört da überhaupt nicht hin weil es nur unter Windows funktioniert. Das universell nutzbare Verzeichnistrennzeichen ist das /

                            Das habe ich geändert! Danke für den Hinweis von Tobias.

                            1. problematische Seite

                              Hallo borisbaer,

                              das Erstellen von Accounts

                              Dafür brauchst Du eine eigene Userverwaltung, eine Login-Seite und eine Kontrolle des Logins über die Session. Die Userverwaltung kann in einer Datei oder einer Datenbank liegen. Wichtig ist nur: Speichere keine Klartextpassworte. Passworte werden gehasht und man speichert nur den Hash. Beim Loginversucht wird das Passwort gegen den Hash geprüft. Das verhindert, dass jemand deinen Passwortspeicher abgreift. Heißt auch: Wenn Du die User in einer Datei speicherst, darf diese Datei nicht über das Web downloadbar sein. Das kann man durch Ablage außerhalb des Web-Root oder durch eine Sperre desw Ablageordners mittels .htaccess erreichen.

                              Ein Login-System hatten wir mal im Wiki, es wurde aber aus Gründen depubliziert. Hinter dem Link findest Du Tipps und Verweise auf Quellen.

                              Ich sollte mir auch möglicherweise mal Bücher zu bestimmten Programmiersprachen kaufen.

                              Jo, PHP, SQL, HTML, CSS, JavaScript - Literatur dazu veraltet allerdings schneller, als sich ein Bücherwurm .303 durchfressen kann. Du musst immer auch ein Auge auf die Onlinequellen behalten.

                              Rolf

                              --
                              sumpsi - posui - obstruxi
                      2. problematische Seite

                        Moin,

                        Sorry. \ ist ein Escapezeichen und maskiert deshalb das ". Das kommt davon, wenn man Code direkt vom Hirn in den Editor dumpt und nicht PHP befragt, was es davon hält. Das \ muss verdoppelt werden, um sich selbst zu maskieren.

                        Besser wäre es einen normalen Slash („/“) zu verwenden: der Backslash ist Windows-only, ein Slash funktioniert überall. Alternativ gibt es noch die Konstante DIRECTORY_SEPARATOR, die zu verwenden ist aber nicht notwendig, / funktioniert immer (Windows kommt auch mit einer Mischung aus / und \ zurecht).

                        Ich würde auch generell mit absoluten Pfaden verwenden, also:

                        $pageFile = __DIR__ . "/" . $_GET["page"] . ".tab.php";
                        

                        Damit funktioniert das Script nämlich auch wenn das Arbeitsverzeichnis mal ein anderes ist, z.B. wenn das Script von einem anderen Verzeichnis aus eingebunden wird.

                        Gruß
                        Tobias

                        1. problematische Seite

                          Hallo Tobias,

                          du hast natürlich recht, das \ ist ein Windowsismus und DIR ist für eine Adressierung relativ zum einbindenden File eine gute Lösung.

                          Rolf

                          --
                          sumpsi - posui - obstruxi
                          1. problematische Seite

                            Schau mal, das Ganze sieht jetzt so bei mir aus:

                            $page_found = false;
                            
                            if (!empty($_GET["page"])
                            	&& preg_match("/^[a-z]+$/i", $_GET["page"]) == 1)
                            {
                            	$pageFile = __DIR__ . "/page/" . $_GET["page"] . ".php";
                            	if (file_exists($pageFile))
                            	{
                            		include $pageFile;
                            		$page_found = true;
                            	}
                            }
                            
                            if (!$page_found)
                            {
                            	include __DIR__ . "/page/game.php";
                            }
                            

                            Ich glaube, ich habe jetzt auch einigermaßen den Aufbau dieses Scripts verstanden. Ich versuche das mal in Worten zu formulieren und würde gerne noch einige Rückfragen stellen.

                            $page_found = false;
                            

                            Erst mal wir die Variable auf false gestellt, damit ihr Status später vom Script abgefragt werden kann.

                            if (!empty($_GET["page"])
                            	&& preg_match("/^[a-z]+$/i", $_GET["page"]) == 1)
                            {
                            

                            Wenn der URL-Parameter nicht leer ist, stelle die Bedingung auf, dass der URL-Parameter nach der Variable ?page= nur aus Buchstaben von A bis Z besteht. Wozu wird das gemacht? Sicherheit?

                            Das ^ ist ein Anker und setzt, denke ich, dort an, wo der URL-Parameter beginnt, nämlich nach dem ?page=. Ich schätze, das +$ steht wohl für die Dateiendung (also in dem Fall .php), aber was soll das /i bedeuten?

                            {
                            	$pageFile = __DIR__ . "/page/" . $_GET["page"] . ".php";
                            	if (file_exists($pageFile))
                            	{
                            		include $pageFile;
                            		$page_found = true;
                            	}
                            }
                            

                            Danach wird die Variable $pageFile als Pfad definiert. DIR bedeutet, man geht vom aktuellen Verzeichnis aus und hängt dann den Unterordner page dran, dann den Namen des URL-Paramters, der durch den Link aufgerufen wird und schließlich die Datei-Endung.

                            Wenn die Datei existiert, schreibe das include und setze die anfängliche Variable auf true. An dieser Stelle endet das Script, wenn die Datei bzw. Seite gefunden wurde.

                            if (!$page_found)
                            {
                            	include __DIR__ . "/page/game.php";
                            }
                            

                            Sollte die Datei nicht gefunden werden und bleibt die Variable $page_found deshalb auf false, dann wird standardmäßig die Seite game.php aufgerufen.

                            Hier könnte man es wohl noch so schreiben, dass es nicht immer nur mit der Datei game.php funktioniert, sondern z.B. die Datei aufgerufen wird, die den Zusatz .default enthält, also etwa game.default.php, dann wäre man nicht so limitiert, wäre mal keine game.php da.

                            Wie müsste man denn den Einzeiler umschreiben, damit das obige (ausführliche) Script ausgeführt wird. Momentan sieht er so aus:

                            $pageFile = ".//" . (empty($_GET["page"]) || !preg_match("/^[a-z]+$/i", $_GET["page"]) ? "game" : $_GET["page"]) . ".page.php";
                            			file_exists($pageFile) && include $pageFile;
                            

                            Meine Versuche haben bisher leider nicht geklappt.


                            Entschuldigt den langen Posting, aber ich wollte noch auf etwas zurückkommen. Oben hast du als best practice Folgendes geschrieben:

                            Also - best practice wäre:

                            • die URLs in den Tabs ohne Fragezeichen und ohne # notieren. Also genau so, wie sie jetzt sind.
                            • am Server in der .htaccess per mod_rewrite dafür sorgen, dass Tab-Namen als ?tab=... an index.php angehängt werden. Falls irgendein Heini direkt die index.php abruft, kommt halt index.php?tab=index.php an. Ja und? Ist ein ungültiger Tab-Name, und:
                            • in index.php den tab-Parameter verarbeiten und auf gültige Tab-Namen validieren. Für ungültige oder fehlende Tabnamen das Default-Tab includen
                            • als progressive enhancement einen click-Handler auf die Tabs legen, der die Page per Ajax holt und per pushState so tut, als wär's über den Server gelaufen.

                            Ich habe mal gestrichen, was ich bereits leisten kann.

                            Übrig bleibt also die Geschichte mit .htaccess (würde ich zum Schluss angehen) und das mit dem pushState. Ich habe nicht ganz verstande, wie das funktioniert und wozu das gut ist (Sicherheit?).

                            Boris

                            1. problematische Seite

                              Hallo,

                              if (!empty($_GET["page"])
                              	&& preg_match("/^[a-z]+$/i", $_GET["page"]) == 1)
                              {
                              

                              Wenn der URL-Parameter nicht leer ist, stelle die Bedingung auf, dass der URL-Parameter nach der Variable ?page= nur aus Buchstaben von A bis Z besteht. Wozu wird das gemacht? Sicherheit?

                              in gewisser Weise ja. Damit ist sichergestellt, dass der Parameter nicht irgendwelche Zeichen enthält (Slash, Punkte o.ä.), mit denen man im Verzeichnisbaum rumwandern und ganz superinteressante Daten abgreifen könnte.

                              Das ^ ist ein Anker und setzt, denke ich, dort an, wo der URL-Parameter beginnt, nämlich nach dem ?page=.

                              Einfacher gesagt: Am Stringanfang.

                              Ich schätze, das +$ steht wohl für die Dateiendung (also in dem Fall .php)

                              Nein. Das Pluszeichen bezieht sich auf das verhergehende Zeichen (in diesem Fall die Zeichenklasse [a-z]) und bedeutet: Mindestens eins davon, gern aber auch mehr.

                              aber was soll das /i bedeuten?

                              Case insensitive, so dass dein Suchmuster auch Großbuchstaben matcht.

                              Sollte die Datei nicht gefunden werden und bleibt die Variable $page_found deshalb auf false, dann wird standardmäßig die Seite game.php aufgerufen.

                              Und warum so umständlich? Ein einfaches else würde genügen, und du bräuchtest dich und andere Leser nicht mit einer zusätzlichen Flag-Variablen zu verwirren.

                              Einen schönen Tag noch
                               Martin

                              --
                              Мир для України.
                              1. problematische Seite

                                Hallo Martin,

                                Ein einfaches else würde genügen,

                                nein, genügt nicht, weil es ja ein zweistufiges if ist. Es gibt unterschiedliche Auslöser für den Einsatz des Default-Tab.

                                Wenn man auf Flags verzichten will, und mindestens PHP 7 hat, könnte man mit Hilfe des null coalescing Operator ?? die Sache schick machen. Ich würde dann aber auch noch ein paar Funktionen benutzen.

                                function getTabFile($pageName)
                                {
                                   if ($pageName == null)
                                      return null;
                                
                                   if (preg_match("/^[a-z]+$/i", $pageName) != 1)
                                      return null;
                                
                                   $fileName = getTabFileName($pageName);
                                   if (file_exists($fileName))
                                      return $fileName;
                                
                                   return null;
                                }
                                
                                function getDefaultTabFile()
                                {
                                   return getTabFileName('default');
                                }
                                
                                function getTabFileName($tab)
                                {
                                   return __DIR__ . "/page/$tab.php";
                                }
                                
                                include (getTabFile($_GET['page'] ?? null) ?? getDefaultTabFile());
                                

                                Von innen nach außen:

                                $x = $_GET['page'] ?? null ist die Kurzfassung von

                                if (empty($_GET['page']))
                                   $x = null;
                                else
                                   $x = $_GET['page']);
                                

                                Man nennt das ?? den "null coalescing operator" - der kommt aus C# und Java und ist eine kompakte Methode, für NULL einen Alternativwert zu erzeugen. In diesem Fall ist die Alternative wieder null, der Zweck des ?? ist lediglich, einen fehlenden page Eintrag im $_GET nicht als Fehler aufscheinen zu lassen.

                                Die Funktion getTabFile bekommt den so ermittelten Tab-Namen als Parameter und prüft der Reihe nach:

                                • ist er null - gib null zurück. Was dann passiert, schreibe ich gleich.
                                • passt er auf das zulässige Namensmuster. Wenn nicht, gib null zurück
                                • Bilde nun den Dateinamen für den Tab. Gibt's die Datei, gib den Namen zurück. Der Dateiname wird mit Hilfe einer Funktion gebildet, weil das in der nächsten Funktion auch gebraucht wird. DRY Code - Don't Repeat Yourself.
                                • Sonst gib Null zurück

                                Heißt also: getTabFile liefert entweder den Namen einer zulässigen Tab-Datei, oder NULL. Und nun kommt ein weiterer ?? Operator, der das behandelt:

                                getTabFile(...) ?? getDefaultTab()

                                Die Funktion getDefaultTab() hat die Aufgabe, den Namen des Default-Tabs zurückzugeben. Im einfachsten Fall ist das eine Konstante, wie oben gezeigt. Im Ergebnis habe ich also nun den vom Client angegebenen Tab-Namen validiert und als Dateiname umgewandelt, oder den Default-Tab als Dateinamen. Und das wird dann an include weitergegeben.

                                include ( getTabFile(...) ?? getDefaultTab() );

                                Wenn man den Default-Tab flexibler benennen möchte, wird es komplizierter. Wenn der Default-Name bspw. game.default.tab ist, dann muss getTabFile damit umgehen können. Der Browser würde das game Tab anfordern, und der Dateiname könnte nun game.default.php oder game.php heißen. Man müsste nun beide Dateinamen abfragen. Und getDefaultTab müsste im page-Ordner mit glob oder scandir oder opendir/readdir/closedir nach einer Datei zum Muster "*.default.php" suchen. Geht natürlich, ist aber lästig. Ich persönlich würde das nicht tun. Der Client kann ja gerne ein Tab namens 'game' anfordern. Aber eine game.php Datei gibt's nicht, und er bekommt die default.php - fertig.

                                Rolf

                                --
                                sumpsi - posui - obstruxi
                                1. problematische Seite

                                  Sieh mal: http://54598532.swh.strato-hosting.eu/games/demons-souls/savegame

                                  Jetzt funktioniert es sowohl mit den URL-Parametern als auch mit der RewriteRule für den Query-String. Also erst mal: Yay~! Ein großes Stück bewältigt! Vielen, vielen Dank! 🙂

                                  Nun habe ich jedoch das Problem, dass die class .current nicht mehr an die Tabs gehängt wird, um den jeweils aktiven Tab hervorzuheben.

                                  Für deine Best-Practice-Lösung fehlt dann eigentlich nur noch eine Sache:

                                  • als progressive enhancement einen click-Handler auf die Tabs legen, der die Page per Ajax holt und per pushState so tut, als wär's über den Server gelaufen.
                                  1. problematische Seite

                                    Hallo borisbaer,

                                    Nun habe ich jedoch das Problem, dass die class .current nicht mehr an die Tabs gehängt wird,

                                    Es gibt da zwei Aspekte.

                                    (1) Wenn die Seite initial per URL geladen wird, muss das .current vom PHP an das Tab gesetzt werden.

                                    Alternativ müsstest Du JavaScript laufen lassen, das das beim Laden der Seite tut, was aber nicht trivial ist; du müsstest dann aus der URL auf das Tab rückschließen. In PHP ist es einfacher.

                                    In PHP heißt das auch: Du musst den angeforderten Tab-Namen ermitteln, bevor Du die Links ausgibst, sonst weißt Du bei deren Ausgabe nicht, wo das class='current' hingehört. Beim bisher diskutierten Code wurde das nicht bedacht. D.h. da steht ein Umbau an. Good Luck!

                                    (2) Derzeit ist dein Ajax außer Funktion, der Tabwechsel erfolgt rein über PHP. Dein click-Handler auf den Links wird nicht aufgerufen. Warum?

                                    <nav id="tabs">
                                      <a class="tab" href="game">Spiel</a>
                                      <a class="tab" href="releases">Releases</a>
                                      ...
                                    </nav>
                                    
                                    $("body").on("click", ".tabs .tab", function() { ... } );
                                    

                                    Na? Verdien Dir ein Fleißkärtchen. Was ist falsch? Warum wird die Funktion nicht aufgerufen?

                                    Der jetztige click-Handler ergibt auch keinen Sinn mehr. Er setzt die current-Class als Reaktion auf einen Klick auf einen Link, was bedeutet: gleich als nächstes lädt der Browser die Seite neu und dein Werk löst sich in Staub auf. Ohne den Ajax-Call kannst Du die Reaktion auf click auch weglassen.

                                    Der Ajax-Abruf muss natürlich berücksichtigen, dass die Tab-Scripte nun in einem page-Ordner stehen, und das in die URL einbauen. Und dann muss er die Seite laden, und dem Browser sagen, dass er den Klick auf den Link nicht verarbeiten soll. Das hattest Du doch schon mal drin, oder?

                                    Rolf

                                    --
                                    sumpsi - posui - obstruxi
                                    1. problematische Seite

                                      In PHP heißt das auch: Du musst den angeforderten Tab-Namen ermitteln, bevor Du die Links ausgibst, sonst weißt Du bei deren Ausgabe nicht, wo das class='current' hingehört. Beim bisher diskutierten Code wurde das nicht bedacht. D.h. da steht ein Umbau an. Good Luck!

                                      Werde ich brauchen. Irgendwie muss PHP erkennen, welche Seite geladen wurde.

                                      <nav id="tabs">
                                        <a class="tab" href="game">Spiel</a>
                                        <a class="tab" href="releases">Releases</a>
                                        ...
                                      </nav>
                                      
                                      $("body").on("click", ".tabs .tab", function() { ... } );
                                      

                                      Na? Verdien Dir ein Fleißkärtchen. Was ist falsch? Warum wird die Funktion nicht aufgerufen?

                                      Na ja, also erst mal steht oben id und unten .tabs, also als class. Ich weiß nicht, ob du darauf hinauswolltest.

                                      Zum anderen reicht nun $("#tabs .tab").click(function() zu schreiben.

                                      Der Ajax-Abruf muss natürlich berücksichtigen, dass die Tab-Scripte nun in einem page-Ordner stehen, und das in die URL einbauen. Und dann muss er die Seite laden, und dem Browser sagen, dass er den Klick auf den Link nicht verarbeiten soll.

                                      Hm, den Ajax-Abruf gibt’s ja jetzt gar nicht mehr.

                                      Das hattest Du doch schon mal drin, oder?

                                      Ich glaube nicht, ich weiß aber nicht genau, was du meinst.

                                      Boris

                                      1. problematische Seite

                                        Hallo borisbaer,

                                        genau, der Selektor passte nicht.

                                        Einen Tab-Wechsel per Ajax-Aufruf hattest Du drin. Das weiß ich ziemlich genau, das war doch der Einstieg in den Thread. Und dann wolltest Du den Tab-Namen in der URL haben.

                                        Der Code, den du vor 3 Tagen um 14:40 gepostet hast, enthielt noch den Ajax Aufruf. Oh, hey, der enthielt auch den #tabs Selektor…

                                        Willst du den direkten Tab-Wechsel ohne Neuaufbau der Seite jetzt nicht mehr? Ich dachte, das wäre dein Ziel gewesen.

                                        Rolf

                                        --
                                        sumpsi - posui - obstruxi
                                        1. problematische Seite

                                          genau, der Selektor passte nicht.

                                          🙂

                                          Einen Tab-Wechsel per Ajax-Aufruf hattest Du drin. Das weiß ich ziemlich genau, das war doch der Einstieg in den Thread. Und dann wolltest Du den Tab-Namen in der URL haben.

                                          Genau!

                                          Der Code, den du vor 3 Tagen um 14:40 gepostet hast, enthielt noch den Ajax Aufruf. Oh, hey, der enthielt auch den #tabs Selektor…

                                          Hm, ich hatte einmal #tabs .tab und einmal .tabs .tab. Ich weiß, nicht sehr kreativ. Das mit der id war für die AJAX-Tabs gedacht und das mit der class für diese nicht-dynamischen Tabs (unter dem Tab Savegame (du hattest mich mal darauf angesprochen)).

                                          Willst du den direkten Tab-Wechsel ohne Neuaufbau der Seite jetzt nicht mehr? Ich dachte, das wäre dein Ziel gewesen.

                                          Ja, sicher doch, aber puh, ich weiß nicht mal, wie ich da ansetzen sollte. 😵🤯

                                          Boris

                                          1. problematische Seite

                                            Hallo borisbaer,

                                            Ja, sicher doch, aber puh, ich weiß nicht mal, wie ich da ansetzen sollte.

                                            Du hattest es doch fast schon. Was Du jetzt in PHP gemacht hast, ist wichtig für den Erstabruf, falls ein Tab gebookmarked wurde, und für den Fall, dass JavaScript beim User nicht läuft (weil abgeschaltet oder weil main.js auf der Leitung versumpft ist).

                                            Es fehlt nur noch

                                            • Reaktivieren des Ajax Calls im Click Handler
                                            • der Update der URL mittels pushState (siehe hier)
                                            • und ggf. noch eine Ajax-Reaktion auf den "Zurück" Button, das ist aber die Kür und nicht unbedingt nötig. Dafür musst Du das popState Event verarbeiten und den Tab-Namen aus der URL herauslutschen.

                                            Rolf

                                            --
                                            sumpsi - posui - obstruxi
                                            1. problematische Seite

                                              Es fehlt nur noch

                                              • Reaktivieren des Ajax Calls im Click Handler
                                              $( "#tabs .tab" ).click(function() {
                                                  $.ajax( { url: this.href, success: function( html ) {
                                                      //
                                                  }
                                                  });
                                              });
                                              

                                              Meinst du das hier?

                                              • der Update der URL mittels pushState (siehe hier)
                                              window.history.pushState(state, "", new URL(href));
                                              

                                              Ich verstehe nicht ganz, was ich mit der Zeile machen soll.

                                              Was soll denn bei state rein? Das ist ja eine Variable. Wozu benötige ich sie?

                                              Also, ich versuche, die aktuelle Seite in meine URL-History zu pushen, verhindere zuvor aber mithilfe von event.preventDefault();, dass sie aufgerufen. Und wozu das Ganze?

                                              • und ggf. noch eine Ajax-Reaktion auf den "Zurück" Button, das ist aber die Kür und nicht unbedingt nötig. Dafür musst Du das popState Event verarbeiten und den Tab-Namen aus der URL herauslutschen.

                                              Leider keine Ahnung, wie das gehen soll. Wie kann ich eine Ajax-Reaktion erstellen?

                                              Sorry, ich stehe echt auf dem Schlauch.

                                            2. problematische Seite

                                              So, ich habe jetzt verstanden, was wir hier machen. Ich dachte, es ginge immer noch um die Hervorhebung des aktiven Tabs.

                                              Mit PHP wird jetzt also immer die default-Seite dynamisch geladen, sobald man die Seite aufruft. Das PHP-Script ist zudem eine Art Fallback, falls Javascript deaktiviert sein sollte. Ansonsten soll der dynamische Inhalt wieder über Ajax aufgerufen werden. Folgenden Code habe ich dafür jetzt zusammen:

                                              	// L O A D - D Y N A M I C - C O N T E N T
                                              	$( "#tabs .tab" ).click(function( event ) {
                                              		event.preventDefault();
                                              		$( this ).addClass( "current" ).siblings().removeClass( "current" );
                                              		var href = $( this ).attr( "href" );
                                              		window.history.pushState( null, "", href );
                                              		$.ajax( { url: this.href, success: function( html ) {
                                              			$( ".append" ).html( html );
                                              		}
                                              		});
                                              		return false;
                                              	});
                                              

                                              Leider wird jetzt mit diesem Code, die gesamte Seite in <div class="append"></div> geladen, also zusammen mit header und footer usw. Was läuft da falsch?

                                              Außerdem funktioniert der Zurück-Button nicht, wie er soll.

                                              Boris

                                              1. problematische Seite

                                                Hallo borisbaer,

                                                Ansonsten soll der dynamische Inhalt wieder über Ajax aufgerufen werden.

                                                Genau. Ich war der Meinung, dass genau das dein Ziel war.

                                                Mit PHP wird jetzt also immer die default-Seite dynamisch geladen, sobald man die Seite aufruft. Das PHP-Script ist zudem eine Art Fallback, falls Javascript deaktiviert sein sollte.

                                                Jein. Ja, weil die Defaultseite damit geladen wird und ja, weil der Fallback ist, und Nein, weil nicht immer die Defaultseite geladen wird.

                                                Ich habe das so verstanden, dass Du diese URLs hast (jeweils mit https://domain davor):

                                                /games/demons-souls/                Default-Abruf - Zeigt game Tab
                                                /games/demons-souls/game            Expliziter Abruf des game Tab
                                                /games/demons-souls/releases        Expliziter Abruf des releases Tab
                                                ...
                                                /games/demons-souls/savegame        Expliziter Abruf des savegame Tab
                                                

                                                Deine .htaccess würde die URLs so umwandeln

                                                /pages/games/demons-souls/index.php
                                                /pages/games/demons-souls/index.php?page=game
                                                /pages/games/demons-souls/index.php?page=releases
                                                ...
                                                /pages/games/demons-souls/index.php?page=savegame
                                                

                                                BTW - eine URL wie /pages/games/demons-souls/, wie sie in deiner Angabe der problematischen URL steht, darfst Du nicht publizieren, denn da funktioniert die Seitennavigation nicht. Dafür ist keine RewriteRule da, wie es scheint. Also: Die URL immer ohne /pages, dann greifen deine Rewrites sauber.

                                                Nur der erste Abruf liefert die Defaultseite mit dem Default-Tab, die übrigen liefern die Seite mit einem explizit angegebenen Tab.

                                                Aber das ist ja okay so, das erzeugt die Bookmark-Fähigkeit und es ermöglicht die Navigation ohne JavaScript-Support.

                                                		var href = $( this ).attr( "href" );
                                                		window.history.pushState( null, "", href );
                                                		$.ajax( { url: this.href, success: function( html ) { ... } );
                                                

                                                Hier wird es nun falsch, denn Du hast einen Satz von mir übersehen. Darauf hättest Du auch selbst kommen können, wenn Du das Prinzip deiner Seitenkonstruktion ganz verstanden hättest.

                                                Ich schrieb:

                                                Der Ajax-Abruf muss natürlich berücksichtigen, dass die Tab-Scripte nun in einem page-Ordner stehen, und das in die URL einbauen.

                                                D.h. wenn Du /games/demons-souls/releases abrufst, dann läuft dein index.php an und liefert die komplette Seite mit dem releases-Tab. Dein Ajax möchte aber nur den Inhalt des releases-Tab sehen. Wie heißt die URL, die Dir nur den Tab-Inhalt liefert? Das musst Du als URL für den Ajax-Aufruf verwenden. Das unterscheidet sich aber notwendigerweise von der URL, die Du dem Browser für die Adresszeile geben musst, weil beide URLs unterschiedliche Inhalte liefern müssen.

                                                Außerdem funktioniert der Zurück-Button nicht, wie er soll.

                                                Wenn ich den unvergesslichen Cheatah zitieren darf…

                                                Was soll denn da zum Funktionieren kommen? Gips das schon irgendwo online?

                                                Rolf

                                                --
                                                sumpsi - posui - obstruxi
                                                1. problematische Seite

                                                  Hallo Rolf,

                                                  Genau. Ich war der Meinung, dass genau das dein Ziel war.

                                                  zu Beginn wusste ich nicht, dass bei einem Wechsel des URL-Parameters die Webseite neu geladen wird. Das wurde mir erst später bewusst. Deswegen habe ich mich gedanklich schon von der Ajax-Lösung verabschiedet.

                                                  Jein. Ja, weil die Defaultseite damit geladen wird und ja, weil der Fallback ist, und Nein, weil nicht immer die Defaultseite geladen wird.

                                                  Stimmt, bei refresh wird nicht die Default-Seite geladen.

                                                  Ich habe das so verstanden, dass Du diese URLs hast (jeweils mit https://domain davor):

                                                  /games/demons-souls/                Default-Abruf - Zeigt game Tab
                                                  /games/demons-souls/game            Expliziter Abruf des game Tab
                                                  /games/demons-souls/releases        Expliziter Abruf des releases Tab
                                                  ...
                                                  /games/demons-souls/savegame        Expliziter Abruf des savegame Tab
                                                  

                                                  Genau.

                                                  Deine .htaccess würde die URLs so umwandeln

                                                  /pages/games/demons-souls/index.php
                                                  /pages/games/demons-souls/index.php?page=game
                                                  /pages/games/demons-souls/index.php?page=releases
                                                  ...
                                                  /pages/games/demons-souls/index.php?page=savegame
                                                  

                                                  Das tut sie, ja.

                                                  BTW - eine URL wie /pages/games/demons-souls/, wie sie in deiner Angabe der problematischen URL steht, darfst Du nicht publizieren, denn da funktioniert die Seitennavigation nicht. Dafür ist keine RewriteRule da, wie es scheint. Also: Die URL immer ohne /pages, dann greifen deine Rewrites sauber.

                                                  Ja, zu dem Zeitpunkt hatte ich noch nicht die richtigen Rewrites parat.

                                                  		var href = $( this ).attr( "href" );
                                                  		window.history.pushState( null, "", href );
                                                  		$.ajax( { url: this.href, success: function( html ) { ... } );
                                                  

                                                  Hier wird es nun falsch, denn Du hast einen Satz von mir übersehen. Darauf hättest Du auch selbst kommen können, wenn Du das Prinzip deiner Seitenkonstruktion ganz verstanden hättest. Ich schrieb:

                                                  Der Ajax-Abruf muss natürlich berücksichtigen, dass die Tab-Scripte nun in einem page-Ordner stehen, und das in die URL einbauen.

                                                  Ich erinnere mich wieder daran, dass du das sagtest. Und ich war mir schon ziemlich sicher, dass genau dies das Problem ist, nämlich dass sich die Seiten jetzt in einem Unterordner befinden, der da heißt subpages.

                                                  D.h. wenn Du /games/demons-souls/releases abrufst, dann läuft dein index.php an und liefert die komplette Seite mit dem releases-Tab. Dein Ajax möchte aber nur den Inhalt des releases-Tab sehen. Wie heißt die URL, die Dir nur den Tab-Inhalt liefert? Das musst Du als URL für den Ajax-Aufruf verwenden. Das unterscheidet sich aber notwendigerweise von der URL, die Du dem Browser für die Adresszeile geben musst, weil beide URLs unterschiedliche Inhalte liefern müssen.

                                                  Ja, genau das habe ich die ganze Zeit versucht irgendwie in Code umzuwandeln. Leider verstehe ich nicht wirklich, wie das zu schreiben ist. Könntest du mir das vielleicht weiterhelfen und für einen AHA-Moment bei mir sorgen?

                                                  Außerdem funktioniert der Zurück-Button nicht, wie er soll.

                                                  Ja, blöd ausgedrückt. Er srpingt einfach nicht zurück auf die vorherige Seite bzw. nur in der URL-Zeile, aber der Klick auf den Zurück-Button wird nicht so verarbeitet, dass die Seite neu geladen wird.

                                                  Boris

                                                  1. problematische Seite

                                                    Hallo borisbaer,

                                                    Könntest du mir das vielleicht weiterhelfen und für einen AHA-Moment bei mir sorgen?

                                                    Erzähl mir nicht, dass Du dieses Detail vorgekaut bekommen musst.

                                                    Du liest den Pagename aus dem href Attribut. D.h. in href steht z.B. "savegames" drin.

                                                    Aus Sicht des Browsers ist das die relative URL, auf die gewechselt werden muss. D.h. wenn Du - aus Browsersicht - im Ordner "/games/demon-souls/" oder auch "/games/demon-souls/game" bist und "savegames" in die History drückst, setzt der Browser das in "/games/demon-souls/savegames" um. PRIMA.

                                                    Der Tab-Inhalt kommt aber von "/games/demon-souls/subpages/savegames". D.h. Du musst dem Ajax-Aufruf etwas mitgeben, woraus der Browser diese URL macht. Das folgt den gleichen Regeln wie das Umsetzen der relativen Link-URL in eine absolute URL.

                                                    Allerdings - es ist nur die halbe Miete. Denn dein Server muss daraus dann ja noch "/pages/games/demon-souls/subpages/savegames.php" machen.

                                                    Schlechte Lösung - nicht machen: Du pfeifst auf eine RewriteRule und erzeugst diese URL direkt am Client. Das könnte funktionieren (es sei denn, irgendeine RewriteRule schießt quer). ABER - das ist schlechte Architektur. Es ist ganz schlecht, wenn dein JavaScript-Code wissen muss, welche RewriteRules auf dem Server gelten. Das solltest Du nicht tun, sowas geht bei der ersten Änderung kaputt, weil Du nämlich garantiert vergisst, dass Du an 2 Stellen ändern musst. Zumindest würde mir das so gehen.

                                                    Sinnvolle Lösung: Du belässt die URL-Spielereien da, wo sie hingehören, auf dem Server. Und baust eine weitere RewriteRule, die URLs zum Pattern /games/([^/]+)/subpages/([^/]+) passend umbaut. Der erste Klammerteil ist der Spielename, der zweite Teil der Pagename - ohne das .php. Und daraus muss dann /pages/games/$1/subpages/$2.php werden. Wenn das klappt, kannst Du /games/demon-souls/subpages/savegames abrufen und bekommst den Inhalt der Subpage

                                                    Come on, das kriegst Du hin. Ich schlaf mich derweil aus, letzte Nacht war laaaaang 🥱

                                                    Rolf

                                                    --
                                                    sumpsi - posui - obstruxi
                                                    1. problematische Seite

                                                      Erzähl mir nicht, dass Du dieses Detail vorgekaut bekommen musst.

                                                      Doch, leider schon.

                                                      Der Tab-Inhalt kommt aber von "/games/demon-souls/subpages/savegames". D.h. Du musst dem Ajax-Aufruf etwas mitgeben, woraus der Browser diese URL macht. Das folgt den gleichen Regeln wie das Umsetzen der relativen Link-URL in eine absolute URL.

                                                      Ja, aber wie mache ich das? Es bringt mir nichts, mit meinen Kenntnisse über ein Problem nachzugrübeln, wenn ich zu wenig Ahnung von der Programmiersprache selbst habe. Ich komme leider von selbst wirklich nicht darauf.

                                                      Sinnvolle Lösung: Du belässt die URL-Spielereien da, wo sie hingehören, auf dem Server. Und baust eine weitere RewriteRule, die URLs zum Pattern /games/([^/]+)/subpages/([^/]+) passend umbaut. Der erste Klammerteil ist der Spielename, der zweite Teil der Pagename - ohne das .php. Und daraus muss dann /pages/games/$1/subpages/$2.php werden. Wenn das klappt, kannst Du /games/demon-souls/subpages/savegames abrufen und bekommst den Inhalt der Subpage

                                                      Ich glaube, das wird für mich noch der einfachste Teil sein.

                                                      1. problematische Seite

                                                        Hallo borisbaer,

                                                        Ja, aber wie mache ich das?

                                                        Du erinnerst mich an einen Tag in meiner Kindheit. Ich war bestimmt noch keine 10 Jahre, extrem schüchtern, und meine Mutter wollte mich schicken, um Kaffee zu kaufen. Doch, wirklich, in den 70ern wurde einem Kind sowas zugetraut. Da gab's noch keinen Supermarkt bei uns und die Bäckereien verkauften den. Und ich? Ich war in Panik bei dem Gedanken, da allein vor der Theke zu stehen und auch noch etwas sagen zu müssen und habe immer nur wiederholt "Ich weiß aber nicht was ich da sagen soll" und die Versuche meiner Mutter, mir zu erklären, was genau ich der Verkäuferin sagen muss um den Kaffee zu bekommen, sind komplett in den Wirbel, der sich in meinem Kopf befand, verpufft. Sie ist irgendwann zu mir durchgedrungen - es hat möglicherweise einer therapeutischen Ohrfeige bedurft - ich habe den Kaffee bekommen und gelernt, dass meine Angst komplett idiotisch war, aber das ist mir als einer der schrecklicheren Momente meiner Kindheit hängengeblieben.

                                                        Bei deinem "Wie mache ich das" komm ich mir vor wie meine Mutter damals. Fühle Dich also therapeutisch geohrfeigt, und leg die Panik weg.

                                                        Vor allem: Guck nicht nur nach Codeschnipseln zum rauskopieren. Ich hab jetzt viel geschrieben, um Dir zu erklären, was zu tun ist. Ein fertiger Schnipsel ist nicht dabei!

                                                        Schauen wir noch mal hier drauf.

                                                        		var href = $( this ).attr( "href" );
                                                        		window.history.pushState( null, "", href );
                                                        		$.ajax( { url: this.href, success: function( html ) {{
                                                        			$( ".append" ).html( html );
                                                        		});
                                                        

                                                        Das ist die 2. Hälfte aus deinem gestern nachmittag geposteten click-Handler für die Tabs. Vorher hast Du preventDefault() aufgerufen - korrekt - und die current Klasse gesetzt (bei der man sogar noch diskutieren könnte ob man da für besser Zugänglichkeit mit dem aria-current Attribut arbeiten sollte, aber dieses Thema will ich jetzt nicht auch noch aufmachen).

                                                        Zeile 1: Bestücken der href Variablen mit dem Wert des href Attributs. Tatsächlich machst Du das an der Stelle zu umständlich. 2 Zeilen tiefer ist es viel einfacher gemacht: this.href. Dies ist ein Eventhandler für einen click auf einen Link, er ist auf den Links registriert, und der Browser ruft ihn so auf, dass this auf das DOM Element zeigt, auf dem das Event registriert wurde.

                                                        Wenn der Link so aussieht: `<a href="savegames">Savegames</a>, dann findest Du nun in href den String "savegames".

                                                        Zeile 2: Dieser String wird in die URL History gepusht. Du bist für den Browser auf Seite "/games/demons-souls/" oder vielleicht "/demons-souls/game", und deswegen, wie schon beschrieben, interpretiert der Browser den pushState(null, "", "savegames") so als ob Du /games/demons-souls/savegames gepusht hättest. Gut so.

                                                        Zeile 3: Vielleicht sollte man diese Zeile nochmal näher erklären, wenn es Dir noch an Verständnis für JavaScript mangelt. Das ist der Aufruf der Methode ajax, die jQuery mitbringt. Diese Methode bekommt ein Objekt übergeben, und zwar dieses:

                                                        {
                                                           url: ...
                                                           success: ...
                                                        }
                                                        

                                                        Man nennt das in JavaScript ein "Objektliteral" - also sowas wie ein fertiges Objekt, das im Quellcode steht und vom Programm an der Stelle als Wert verwendet werden kann. Es ist ein Objekt mit zwei Eigenschaften: url und success. Das Objekt, das Du hier an .ajax übergibst, kann noch viel mehr Eigenschaften haben - was Du hier siehst ist so ziemlich das Minimum. Der Artikel im jQuery Handbuch, der das erklärt, ist ein halber Roman.

                                                        Diese Schreibweise mit dem Objektliteral gibt es auch in PHP, da sähe das so aus (das hilft Dir für JavaScript nichts, es ist nur eine Analogie für's Verständnis der JavaScript-Schreibweise):

                                                        $xxx = [
                                                           "url" => ....
                                                           "success" => function() { ... }
                                                        ];
                                                        

                                                        Ja, tatsächlich, eine Funktion als Wert geht in neueren PHP Versionen auch. Man kann es mit älterer Syntax auch so schreiben:

                                                        $xxx = ARRAY(
                                                           "url" => ...,
                                                           "success" => ...
                                                        );
                                                        

                                                        Gleicher Wein, anderer Schlauch. Zurück zu JavaScript.

                                                        Unter success steht die Funktion, die bei einem erfolgreichen Ajax-Abruf aufgerufen wird, um das Ergebnis zu verarbeiten. Die hast Du und die tut, was sie soll. Haken dran.

                                                        Unter url muss die abzurufende URL stehen. Wenn Du hier this.href hinschreibst, wird das href-Attribut des Links verwendet (this zeigt auf das Link-Element). So, wie der Aufruf jetzt ist, wird der url Eigenschaft also "savegames" zugewiesen, der Browser interpretiert das als relative URL und macht /games/demons-souls/savegames draus. Möööp - falsch.

                                                        Du brauchst /games/demons-souls/subpages/savegames - und das heißt, dass an die url Eigenschaft der Wert "subpages/savegames" zugewiesen werden muss. Wie macht man aus "savegames" den Wert "subpages/savegames"?!

                                                        Ach so. Ist das hier vielleicht dein Problem? In PHP verkettet man Strings mit dem . Operator. In JavaScript nicht. Da verwendet man das +.

                                                        Eigentlich sollte es Dir jetzt nicht mehr schwerfallen, die Sternchen durch den richtigen Code zu ersetzen. Den Ajax-Aufruf verteile ich mal auf ein paar mehr Zeilen, dann erkennt man besser, wie sich das zusammensetzt.

                                                        		var href = this.href;
                                                        		window.history.pushState( null, "", href );
                                                        		$.ajax(
                                                        			{
                                                        				url: *********,
                                                        				success: function( html ) {
                                                        					...
                                                        				} 
                                                        			});
                                                        

                                                        Rolf

                                                        --
                                                        sumpsi - posui - obstruxi
                                                        1. problematische Seite

                                                          Bei deinem "Wie mache ich das" komm ich mir vor wie meine Mutter damals. Fühle Dich also therapeutisch geohrfeigt, und leg die Panik weg.

                                                          Danke, jetzt kann ich wieder klar denken. 😨

                                                          Wenn der Link so aussieht: `<a href="savegames">Savegames</a>, dann findest Du nun in href den String "savegames".

                                                          Das wusste ich immerhin schon!

                                                          Zeile 3: Vielleicht sollte man diese Zeile nochmal näher erklären, wenn es Dir noch an Verständnis für JavaScript mangelt. Das ist der Aufruf der Methode ajax, die jQuery mitbringt. Diese Methode bekommt ein Objekt übergeben, und zwar dieses:

                                                          {
                                                             url: ...
                                                             success: ...
                                                          }
                                                          

                                                          Also, diesen Teil des Codes habe ich ebenfalls bereits verstehen können.

                                                          Unter url muss die abzurufende URL stehen. Wenn Du hier this.href hinschreibst, wird das href-Attribut des Links verwendet (this zeigt auf das Link-Element). So, wie der Aufruf jetzt ist, wird der url Eigenschaft also "savegames" zugewiesen, der Browser interpretiert das als relative URL und macht /games/demons-souls/savegames draus. Möööp - falsch.

                                                          Genau. In meinem Kopf steht this für /games/demons-souls/ und href für das href attr des Links, bspw. savegame. Wenn das stimmt, muss ich nur noch + ("/subpages/") + zwischen die beiden obigen Teil quetschen, sprich:

                                                          url: this + "/subpages/" + href,
                                                          

                                                          So weit bin ich gestern sogar schon selber gekommen.

                                                          Doch steht als GET in der Konsole dann: http://localhost/games/demons-souls/releases/subpages/http://localhost/games/demons-souls/releases.

                                                          Du brauchst /games/demons-souls/subpages/savegames - und das heißt, dass an die url Eigenschaft der Wert "subpages/savegames" zugewiesen werden muss. Wie macht man aus "savegames" den Wert "subpages/savegames"?!

                                                          Jaaa …

                                                          Ach so. Ist das hier vielleicht dein Problem? In PHP verkettet man Strings mit dem . Operator. In JavaScript nicht. Da verwendet man das +.

                                                          Das habe ich versucht, ich schwöre es!

                                                          Weißt du, dann habe ich mir gedacht, ich mach es jetzt mal ganz dumm und schreibe den absoluten Pfad hin:

                                                          url: "/games/demons-souls/subpages/" + href
                                                          

                                                          Die Konsole sagt: „Hey, du willst scheinbar das aufrufen:“

                                                          http://localhost/games/demons-souls/subpages/http://localhost/games/demons-souls/savegame

                                                          Das href steht – warum auch immer – für die komplette URL. Das war gestern schon die ganze Zeit so. Sonst hätte ich es vermutlich schon hingekriegt. Im Grunde funktioniert es nur, wenn ich einen vollständig absoluten Pfad hinschreibe, z.B.:

                                                          url: "/games/demons-souls/subpages/game"
                                                          

                                                          Sowohl bei alert(this) als auch bei alert(href) kommt beide Male die vollständige URL raus, nicht nur savegame oder mods oder so. Irgendwas läuft da falsch und der Fehler sitzt immer vorm PC.

                                                          Boris

                                                          1. problematische Seite

                                                            Ich habe gemerkt, dass ich einen blöden Fehler oben gemacht habe, und zwar habe ich einen relativen Pfad mit einem / angefangen – autsch. 😳

                                                            Es funktioniert jetzt sogar mit dem folgenden Code:

                                                            $(function () {
                                                            
                                                            	$("#tabs .tab").click(function(event) {
                                                            		event.preventDefault();
                                                            		$(this).addClass("current").siblings().removeClass("current");
                                                            		var href = $(this).attr("href");
                                                            		window.history.pushState(null, "", href);
                                                            		$.ajax(
                                                            			{
                                                            				url: "subpages/" + href,
                                                            				success: function(html) {
                                                            					$(".append").html(html);
                                                            				}
                                                            			});
                                                            	});
                                                            });
                                                            

                                                            ABER, wie du siehst, musste ich die Variable href wieder zu $(this).attr("href") umschreiben. Bei this.href hat sie immer den ganzen Pfad vor dem href-Attribut ausgegeben, also nicht nur bspw. savegame. Warum ist das so?

                                                            Ich gehe mal jetzt schon mal einen Schritt weiter und frage: Sollte ich jetzt wieder den Teil "subpages/" aus der Ajax-URL löschen und stattdessen die .htaccess so umschreiben, dass URLs nach dem Muster /games/$1/$2 automatisch /games/$1/subpages/$2 aufrufen?

                                                            Und noch weiter gefragt: Der Ajax-Call funktioniert also wieder, aber wenn ich auf den Zurück-Button klicke, geht zwar oben in der Adresszeile die URL zurück auf den vorherigen string, doch der Inhalt natürlich noch nicht. Hier kommt vermutlich dieses popstate-Funktion zum Einsatz. Wie ich das verstanden habe, reagiert popstate auf Veränderungen in der URL bzw. in der History. Wenn also jemand auf den Zurück-Button klickt, dann sollte mit popstate erkannt werden, dass sich was in der URL-Zeile verändert hat und der entsprechende Inhalte aufgerufen werden soll.

                                                            	window.onpopstate = function() {
                                                            
                                                            		/* ein erneuter Ajax-Call? 
                                                                Aber dieses Mal muss er ohne das href des Links auskommen, 
                                                                sondern sich wohl auf die window.history verlassen. */
                                                            
                                                            	};
                                                            

                                                            Mein lieber Herr Gesangsvereien. 🤯

                                                            1. problematische Seite

                                                              Hallo borisbaer,

                                                              In meinem Kopf steht this für /games/demons-souls/

                                                              Schlag Dir das gleich wieder aus dem Kopf.

                                                              this ist in JavaScript ganz grob gesagt der aktuelle Kontext, in dem eine Funktion oder Methode läuft. Aber: Man hat beim Aufrufen einer Funktion durchaus Möglichkeiten, diesen Kontext zu manipulieren. Und das tut jQuery, wenn es einen Eventhandler aufruft. Es sorgt dafür, dass Du in this das Objekt für das HTML Element findest, auf dem der Eventhandler registriert wurde. Und weil Du den click-Handler auf dem Link definierst, ist das das HTML Elementobjekt zum geklickten Link. Also: this ist der Link.

                                                              Bei this.href hat sie immer den ganzen Pfad vor dem href-Attribut ausgegeben, also nicht nur bspw. savegame. Warum ist das so?

                                                              this ist der Link, und this.href ist die verlinkte URL. Und genau hier bin ich böse reingefallen - du darfst mir die Ohrfeige nun gerne zurückgeben. Es gibt einen Unterschied zwischen dem href Attribut und der href-Eigenschaft des Link-Objekts. Das href Attribut steht für den Text, der im HTML steht. Und die href-Eigenschaft ist das, wie der Browser dieses Attribut deutet. Das habe ich übersehen; ich dachte, in der href-Eigenschaft stünde der Attributinhalt. Aber nein - da steht bereits die vollständig aufbereitete URL, auf die der Link verweist.

                                                              Deswegen ist this.href für den pushState okay. Aber für das Bauen der Ajax-URL steht schon zu viel drin, deswegen ist es da ganz sinnvoll, wenn Du mit $(this).attr("href") den Attributinhalt ausliest.

                                                              die .htaccess so umschreiben, dass URLs nach dem Muster /games/$1/$2 automatisch /games/$1/subpages/$2 aufrufen?

                                                              Nein, tu das nicht. Die URLs ohne "subpages" drin sind die, die der Browser für den Erstabruf der Seite braucht. Die werden von htaccess in index.php?page=... umgesetzt.

                                                              Die URL mit "subpages" sind die, mit denen Du den reinen Inhalt der Subpage bekommst und die Du für Ajax brauchst - das sind zwei verschiedene Dinge.

                                                              wenn ich auf den Zurück-Button klicke, geht zwar oben in der Adresszeile die URL zurück auf den vorherigen string, doch der Inhalt natürlich noch nicht.

                                                              Ups? Noch ein Hirnschaden von mir? Ich hätte erwartet, dass der Browser dann diese URL abruft. Um zu sehen, was da genau passiert, müsste ich deinen Stand online sehen - geht das? Ich mag es nicht nicht nachprogrammieren. Im Moment scheint bei Dir was eine Endlosschleife zu drehen - entweder der Hoster oder deine Seite. Direkte Aufrufe auf /subpages Seiten gehen, aber Aufrufe über die index.php bleiben hängen.

                                                              Rolf

                                                              --
                                                              sumpsi - posui - obstruxi
                                                              1. problematische Seite

                                                                Hallo Rolf,

                                                                this ist in JavaScript ganz grob gesagt der aktuelle Kontext, in dem eine Funktion oder Methode läuft. Aber: Man hat beim Aufrufen einer Funktion durchaus Möglichkeiten, diesen Kontext zu manipulieren. Und das tut jQuery, wenn es einen Eventhandler aufruft. Es sorgt dafür, dass Du in this das Objekt für das HTML Element findest, auf dem der Eventhandler registriert wurde. Und weil Du den click-Handler auf dem Link definierst, ist das das HTML Elementobjekt zum geklickten Link. Also: this ist der Link.

                                                                Ja, das stimmt. Habe ich dann auch gemerkt, kurz nachdem ich den Murks verfasst habe. War nur zu spät zum Editieren.

                                                                this ist der Link, und this.href ist die verlinkte URL. Und genau hier bin ich böse reingefallen - du darfst mir die Ohrfeige nun gerne zurückgeben.

                                                                🙂

                                                                Es gibt einen Unterschied zwischen dem href Attribut und der href-Eigenschaft des Link-Objekts. Das href Attribut steht für den Text, der im HTML steht. Und die href-Eigenschaft ist das, wie der Browser dieses Attribut deutet. Das habe ich übersehen; ich dachte, in der href-Eigenschaft stünde der Attributinhalt. Aber nein - da steht bereits die vollständig aufbereitete URL, auf die der Link verweist.

                                                                Danke für die Klarstellung. Jetzt habe ich es verstanden.

                                                                Deswegen ist this.href für den pushState okay. Aber für das Bauen der Ajax-URL steht schon zu viel drin, deswegen ist es da ganz sinnvoll, wenn Du mit $(this).attr("href") den Attributinhalt ausliest.

                                                                Doch nicht so dumm!

                                                                Nein, tu das nicht. Die URLs ohne "subpages" drin sind die, die der Browser für den Erstabruf der Seite braucht. Die werden von htaccess in index.php?page=... umgesetzt.

                                                                Die URL mit "subpages" sind die, mit denen Du den reinen Inhalt der Subpage bekommst und die Du für Ajax brauchst - das sind zwei verschiedene Dinge.

                                                                Also lasse ich die .htaccess so, wie sie ist?

                                                                Ups? Noch ein Hirnschaden von mir? Ich hätte erwartet, dass der Browser dann diese URL abruft. Um zu sehen, was da genau passiert, müsste ich deinen Stand online sehen - geht das? Ich mag es nicht nicht nachprogrammieren. Im Moment scheint bei Dir was eine Endlosschleife zu drehen - entweder der Hoster oder deine Seite. Direkte Aufrufe auf /subpages Seiten gehen, aber Aufrufe über die index.php bleiben hängen.

                                                                Na klar, ich habe die Webseite aktualisiert

                                                                http://54598532.swh.strato-hosting.eu/games/demons-souls/

                                                                Wie du siehst, funktioniert nun sogar das Hervorheben des aktiven Tabs.

                                                                <nav id="tabs">
                                                                    <a class="tab <?= $current == null || $current == 'game' ? 'current' : ''; ?>" href="game">Spiel</a>
                                                                    <a class="tab <?= $current == 'releases' ? 'current' : ''; ?>" href="releases">Releases</a>
                                                                    <a class="tab <?= $current == 'merchandise' ? 'current' : ''; ?>" href="merchandise">Merchandise</a>
                                                                    <a class="tab <?= $current == 'guides' ? 'current' : ''; ?>" href="guides">Guides</a>
                                                                    <a class="tab <?= $current == 'emulation' ? 'current' : ''; ?>" href="emulation">Emulation</a>
                                                                    <a class="tab <?= $current == 'mods' ? 'current' : ''; ?>" href="mods">Mods</a>
                                                                    <a class="tab <?= $current == 'savegame' ? 'current' : ''; ?>" href="savegame">Savegame</a>
                                                                </nav>
                                                                

                                                                Kurz vorher explodiert noch die aktuelle URL …

                                                                <?php
                                                                $url = htmlspecialchars($_SERVER['REQUEST_URI'], ENT_QUOTES, 'UTF-8');
                                                                $urlParts = explode('/', $url);
                                                                $current = $urlParts[count($urlParts)-1];
                                                                ?>
                                                                

                                                                Ich weiß nicht, ob das eine gute Lösung ist, aber es ist eine Lösung.

                                                                Boris

                                                                1. problematische Seite

                                                                  Hallo borisbaer,

                                                                  das, was ich online sehe, hat aber kein Ajax drin. Das ist die reine PHP Lösung ohne JavaScript-Navigation.

                                                                  Zur Explosion: Die REQUEST_URI brauchst Du nicht durch htmlspecialchars zu jagen. Du gibst weder die URI noch die $urlParts noch den abgetrennten $current-Teil aus. htmlspecialchars sollte man wirklich nur für die Werte anwenden, die man auch wirklich zum Browser schickt.

                                                                  Also lasse ich die .htaccess so, wie sie ist?

                                                                  Glaube schon. Bis zum Beweis des Gegenteils...

                                                                  Rolf

                                                                  --
                                                                  sumpsi - posui - obstruxi
                                                                  1. problematische Seite

                                                                    das, was ich online sehe, hat aber kein Ajax drin. Das ist die reine PHP Lösung ohne JavaScript-Navigation.

                                                                    Ups, habe vergessen das JS-Script zu aktualisieren. Ist behoben!

                                                                    Siehste mal, hier kommt dein advanced engineering progressive enhancement zum Einsatz. JavaScript funktioniert nicht, also kommt PHP! 😄

                                                                    Zur Explosion: Die REQUEST_URI brauchst Du nicht durch htmlspecialchars zu jagen. Du gibst weder die URI noch die $urlParts noch den abgetrennten $current-Teil aus. htmlspecialchars sollte man wirklich nur für die Werte anwenden, die man auch wirklich zum Browser schickt.

                                                                    Danke für den Hinweis!

                                                                    Glaube schon. Bis zum Beweis des Gegenteils...

                                                                    Na dann!

                                                            2. problematische Seite

                                                              Hallo borisbaer,

                                                              Update - ich habe nochmal Doku gelesen. Tatsächlich ein Hirnschaden meinerseits.

                                                              Ein history.pushState assoziert die neu dargestellte URL mit dem geladenen Dokument, lädt die URL aber nicht. Sozusagen reine Augenwischerei.

                                                              Ein Klick auf "zurück" wechselt darum die URL, aber da Du eine gefakte URL verlässt, bleibt das Dokument gleich und deshalb lädt der Browser nichts.

                                                              Also, das popstate Event muss an den Start. Es wird immer dann geworfen (gerade ausprobiert), wenn Du zu oder von einer URL wechselst, die die mit pushState entstanden ist. Immer wenn popstate fliegt, hat der Browser nichts geladen, und du hast die Chance, selbst was zu tun.

                                                              Gerade probiert - müsste klappen:

                                                              Zeile 2: aktuellen Path-Teil der URL auslesen (also /games/demons-souls/...)
                                                              Zeile 3: Position des letzten / finden
                                                              Zeile 4: Den Text hinter dem / lesen, das ist der Name des Tabs. ODER LEER!
                                                              Zeile 6-12: Zwei Fälle möglich.
                                                              Zeile 8-9 Wenn page leer ist, kennst den Namen der Default-Page nicht. Wenn Du also per popstate zu /games/demons-souls/ zurückkehrst, musst Du Dir den Namen der Default-Page aus dem ersten Tab herausholen. Als Nebeneffekt holst Du Dir einen Verweis auf das erste <a class="tab"> Element
                                                              Zeile 11: Hast Du einen tab-Namen, musst Du trotzdem das passende Link-Element finden.
                                                              Zeile 13: current-Klasse auf den Link zum aktuellen Tab setzen
                                                              Zeile 15: Tab-Inhalt holen

                                                               1   window.onpopstate = function(popEvent) {
                                                               2      let urlPath = window.location.pathName;
                                                               3      let slash = page.lastIndexOf("/");
                                                               4      let page = urlPath.substr(slash+1);
                                                               5
                                                               6      let $tab;
                                                               7      if (page='') {
                                                               8         $tab = $("#tabs .tab:first-of-type");
                                                               9         page = $tab.attr("href");
                                                              10      else {
                                                              11         $tab = $("#tabs .tab[href="+CSS.escape(page)+"])
                                                              12      }
                                                              13      $tab.addClass("current").siblings().removeClass("current");
                                                              14
                                                              15      $.ajax({url: "subpages/"+page, function(html) {
                                                              16         ...
                                                              17      } });
                                                              18   }
                                                              

                                                              So ganz blöd angemerkt: Diese Logik könnte man auch beim normalen Laden der Seite anwenden und das Erstbefüllen des Tab mit PHP überflüssig machen. ABER - das würde zu einem Zucken auf der Anzeige führen, weil die Seite erst mit leerem Tab geladen wird und es dann erst füllt. D.h. die PHP Logik ist für eine schönere Darstellung sinnvoll.

                                                              Rolf

                                                              --
                                                              sumpsi - posui - obstruxi
                                                              1. problematische Seite

                                                                Ich habe meine Webseite mit deinem Code aktualisiert, musste allerdings noch zwei Syntax-Fehler beheben:

                                                                window.onpopstate = function(popEvent) {
                                                                	let urlPath = window.location.pathName;
                                                                	let slash = page.lastIndexOf("/");
                                                                	let page = urlPath.substr(slash+1);
                                                                	let $tab;
                                                                
                                                                	if (page = '') {
                                                                		$tab = $("#tabs .tab:first-of-type");
                                                                		page = $tab.attr("href");
                                                                	}
                                                                
                                                                	else { $tab = $("#tabs .tab[href="+CSS.escape(page)+"]") }
                                                                
                                                                	$tab.addClass("current").siblings().removeClass("current");
                                                                
                                                                	$.ajax({url: "subpages/"+page, function(html) {
                                                                		$(".append").html(html);
                                                                	} });
                                                                }
                                                                

                                                                Einmal musste das if geschlossen werden (fehlendes }) und ein andermal noch der jQuery-Selektor nach $tab (fehlendes " nach [href="+CSS.escape(page)+"]). Jetzt sollte es zumindest syntaktisch passen, aber leider passiert nichts, wenn ich das Script einbinde.

                                                                1. problematische Seite

                                                                  Hallo borisbaer,

                                                                  die Tab-Navigation "vorwärts" funktioniert prima.

                                                                  Dass bei der Browser-Navigation nichts passiert, ist ein Irrtum. Mach die Entwicklerwerkzeuge auf, guck in die Konsole, das hier passiert:

                                                                  foo.js:38 Uncaught ReferenceError: Cannot access 'page' before initialization
                                                                      at window.onpopstate (foo.js:38:15)
                                                                  

                                                                  Das ist natürlich kein Fehler meinerseits, sondern ein Test für Dich. Du sollst es ja nicht zu leicht haben 😝 (laber, rumschwatz, rausred...)

                                                                  Da man aus Fehlern am meisten lernt, auch wenn andere sie gemacht haben: Überlege, was der Code tun soll, das habe ich ja aufgeschrieben, und worauf er dafür in dieser Zeile zugreifen müsste.

                                                                  Rolf

                                                                  --
                                                                  sumpsi - posui - obstruxi
                                                                  1. problematische Seite

                                                                    foo.js:38 Uncaught ReferenceError: Cannot access 'page' before initialization
                                                                        at window.onpopstate (foo.js:38:15)
                                                                    

                                                                    Da man aus Fehlern am meisten lernt, auch wenn andere sie gemacht haben: Überlege, was der Code tun soll, das habe ich ja aufgeschrieben, und worauf er dafür in dieser Zeile zugreifen müsste.

                                                                    Wenn ich das richtig interpretiere, dann kann das Script nicht auf die Variable page zugreifen, bevor diese nicht davor irgendwo eingeführt wurde? An welcher Stelle sollte man denn diese Variable einführen und wie soll sie definiert werden? Ich habe jetzt hin und her überlegt, finde aber keinen Ansatz.

                                                                    Edit: Was ich sehe (oder glaube zu sehen), ist, dass slash bereits auf page zugreifen möchte, noch bevor diese definiert ist. Aber wenn ich page vor slash definiere, dann kommt: Uncaught TypeError: urlPath is undefined.

                                                                    Bzw. greift ja auch page auf slash zu, also keine Ahnung.

                                                                    1. problematische Seite

                                                                      Hallo borisbaer,

                                                                      ich schrub als Absichtserklärung:

                                                                      Zeile 2: aktuellen Path-Teil der URL auslesen (also /games/demons-souls/...) Zeile 3: Position des letzten / finden Zeile 4: Den Text hinter dem / lesen, das ist der Name des Tabs. ODER LEER!

                                                                      Gecodet (oder eher gekotet) habe ich

                                                                      	let urlPath = window.location.pathName;
                                                                      	let slash = page.lastIndexOf("/");
                                                                      	let page = urlPath.substr(slash+1);
                                                                      

                                                                      Worin muss ich denn den letzten / suchen, um den Tab-Namen herauszubekommen?

                                                                      Ist das Problem die Reihenfolge, in der ich Dinge tue? Oder greife ich vielleicht im richtigen Moment auf die falschen Daten zu?

                                                                      Ich sollte vielleicht Quizmaster werden 😂

                                                                      Rolf

                                                                      --
                                                                      sumpsi - posui - obstruxi
                                                                      1. problematische Seite

                                                                        Zeile 2: aktuellen Path-Teil der URL auslesen (also /games/demons-souls/...)

                                                                        let urlPath = window.location.pathName;
                                                                        

                                                                        Bei alert(urlPath) kommt aber undefined heraus. Nur wenn ich das .pathName weglasse, kommt die abgerufene URL heraus. Eben sehe ich, das Programm schlägt mir .pathname vor. Damit wird auch die URL tatsächlich ausgegeben. Ist das case-sensitive?

                                                                        Zeile 3: Position des letzten / finden

                                                                        Zeile 4: Den Text hinter dem / lesen, das ist der Name des Tabs. ODER LEER!

                                                                        	let urlPath = window.location.pathName;
                                                                        	let slash = page.lastIndexOf("/");
                                                                        	let page = urlPath.substr(slash+1);
                                                                        

                                                                        Worin muss ich denn den letzten / suchen, um den Tab-Namen herauszubekommen?

                                                                        Ich würde sagen, in urlPath. Dort suchst du von hinten das erste / der URL-Zeile.

                                                                        Ist das Problem die Reihenfolge, in der ich Dinge tue? Oder greife ich vielleicht im richtigen Moment auf die falschen Daten zu?

                                                                        Ja, nämlich oben. Wenn man es so schreibt, funktioniert zumindest das Finden des strings:

                                                                        let urlPath = window.location.pathname;
                                                                        let slash = urlPath.lastIndexOf( "/" );
                                                                        let page = urlPath.substr(slash+1);
                                                                        

                                                                        Jetzt muckt er aber an dieser Stelle rum: $tab = $("#tabs .tab[href="+CSS.escape(page)+"]")

                                                                        Fehlermeldung: Uncaught Error: Syntax error, unrecognized expression: #tabs .tab[href=]

                                                                        Hm? Stimmt doch eigentlich? Habe das Script hochgeladen!

                                                                        1. problematische Seite

                                                                          Hallo borisbaer,

                                                                          oh Mann, heute ist nicht mein Tag. Aber ich krieg Dich zum Mitdenken, das ist prima.

                                                                          .pathname

                                                                          Sehr gut gesehen. Das hatte ich falsch aus der Konsole der Entwicklertools abgeschrieben.

                                                                          $tab = $("#tabs .tab[href="+CSS.escape(page)+"]") Fehlermeldung: Uncaught Error: Syntax error, unrecognized expression: #tabs .tab[href=]

                                                                          Hä, wie? In den Teil läuft er doch nur, wenn page nicht leer ist.
                                                                          Wieso zum grundgütigen Gei - eijeijeijeijei 🤮

                                                                          ROLF, DU VOLLPFOSTEN

                                                                          if (page = '') 
                                                                             hier lang wenn in page nichts drin steht
                                                                          else
                                                                             mach was mit page, wenn in page was drin steht
                                                                          

                                                                          Siehst Du's? Warum page leer ist UND er in den else Zweig läuft? Das könnte auch PHP sein (dann müsste man die Variable $page nennen, klar), aber es wäre exakt der gleiche Blödsinn mit exakt den gleichen Folgen.

                                                                          Falls Du einen Tipp brauchst - Was ist Wahrheit?.

                                                                          Rolf

                                                                          --
                                                                          sumpsi - posui - obstruxi
                                                                          1. problematische Seite

                                                                            Hi there,

                                                                            ROLF, DU VOLLPFOSTEN

                                                                            Naja, das mußt Du selbst beurteilen, aber erfrischend und angenehm ist es zu sehen, daß nicht nur ich ab und an immer wieder auf den selben (Schlampigkeits-) Fehler hereinfalle.

                                                                            Wobei man sagen muß, daß es durchaus auch Programmiersprachen gibt, die zwischen Vergleich und Zuweisung erst gleich gar nicht groß unterscheiden...

                                                                            1. problematische Seite

                                                                              Hallo klawischnigg,

                                                                              Wobei man sagen muß, daß es durchaus auch Programmiersprachen gibt, die zwischen Vergleich und Zuweisung erst gleich gar nicht groß unterscheiden...

                                                                              Du meinst, sie verwenden = sowohl für Zuweisung als auch für Vergleich?

                                                                              Ja, aber diese Sprachen erlauben auch nicht, die Zuweisung als Ausdruck zu verwenden. Spontan fallen mir BASIC und COBOL ein - da ist ein = zwischen IF und THEN grundsätzlich ein Vergleich.

                                                                              Dieser größenwahnsinnig gewordene Assembler namens "C" und seine Abkömmlinge dagegen...

                                                                              Ich habe dann etwas im Netz gestöbert und Tante Google hat mir diesen Artikel von Altmeister Niklas Wirth präsentiert: "Good Ideas, Through The Looking Glass" (von 2004, ein PDF). Sehr interessant 😀

                                                                              Rolf

                                                                              --
                                                                              sumpsi - posui - obstruxi
                                                                              1. problematische Seite

                                                                                Hi there,

                                                                                Wobei man sagen muß, daß es durchaus auch Programmiersprachen gibt, die zwischen Vergleich und Zuweisung erst gleich gar nicht groß unterscheiden...

                                                                                Du meinst, sie verwenden = sowohl für Zuweisung als auch für Vergleich?

                                                                                Genau so. Wobei es bspw. um es noch skuriler zu machen in späteren dBase-Derivaten beim Vergleich einen Unterschied zwischen "=" und "==" gegeben hat, aber nur dergestalt, daß doppeltes Ist-gleich-Zeichen ein exakter Stringvergleich war und einfaches "=" etwas mehr "fuzzy" war (soweit ich's noch im Kopf habe was die Länge des Strings und/oder Groß- und Kleinschreibe betraf.)

                                                                                Ja, aber diese Sprachen erlauben auch nicht, die Zuweisung als Ausdruck zu verwenden.

                                                                                Wie auch, da kommt ja der Interpreter/Compiler mächtig ins Straucheln😉.

                                                                                Ich habe dann etwas im Netz gestöbert und Tante Google hat mir diesen Artikel von Altmeister Niklas Wirth präsentiert: "Good Ideas, Through The Looking Glass" (von 2004, ein PDF). Sehr interessant 😀

                                                                                Ja, nur so überblättert, werd ich mir aber noch detaillierter 'reinziehen, danke für den Link...

                                                                                1. problematische Seite

                                                                                  Hallo klawischnigg,

                                                                                  dBase

                                                                                  Oh mein Gott. The 80s are calling 😂 - und das schlimmste ist: ich kenn den Ruf auch noch. dBase IV, dann Clipper 5 und der BLinker um die 640KB zu sprengen, hach was waren das gruselige Zeiten.

                                                                                  Rolf

                                                                                  --
                                                                                  sumpsi - posui - obstruxi
                                                                          2. problematische Seite

                                                                            .pathname

                                                                            Sehr gut gesehen. Das hatte ich falsch aus der Konsole der Entwicklertools abgeschrieben.

                                                                            Das war reines Glück. Hätte ich ein anderes Tool verwendet, hätte ich das nie im Leben gemerkt. Und hätte mir stundenlang den Kopf zerbrochen, warum es nicht geht.

                                                                            if (page = '') 
                                                                               hier lang wenn in page nichts drin steht
                                                                            else
                                                                               mach was mit page, wenn in page was drin steht
                                                                            

                                                                            Siehst Du's? Warum page leer ist UND er in den else Zweig läuft? Das könnte auch PHP sein (dann müsste man die Variable $page nennen, klar), aber es wäre exakt der gleiche Blödsinn mit exakt den gleichen Folgen.

                                                                            Wir haben kein == benutzt, sondern nur ein = als Gleichsetzungszeichen.

                                                                            So weit, so gut. Über die Netzwerkanalyse sehe ich nun auch, dass die page geladen wird … doch man sieht sie nicht! Komisch, oder?

                                                                            Edit: Alles klar, das machst du doch absichtlich, Rolf. 🤪 Es steht kein success: function(html) im Ajax-Teil. Jetzt funktioniert es! Juchee!

                                2. problematische Seite

                                  Hallo Rolf,

                                  Ein einfaches else würde genügen,

                                  nein, genügt nicht, weil es ja ein zweistufiges if ist. Es gibt unterschiedliche Auslöser für den Einsatz des Default-Tab.

                                  ja, du formulierst insgesamt drei Bedingungen, die alle erfüllt sein müssen. Aber du verteilst sie auf zwei if-Statements anstatt eine UND-Verknüpfung aller drei Bedingungen in einem if-Ausdruck zu formulieren.

                                  Ich würde das daher zusammenfassen und vereinfachen:

                                  if (!empty($_GET["page"])
                                   && preg_match("/^[a-z]+$/i", $_GET["page"])==1
                                   && file_exists($pageFile = __DIR__ . "/page/" . $_GET["page"] . ".php"))
                                   { include $pageFile;
                                   }
                                  else
                                   { include __DIR__ . "/page/game.php";
                                   }
                                  

                                  Das könnte man unter Verwendung des ternären Operators sogar noch zu einem Ausdruck aufhübschen.

                                  Einen schönen Tag noch
                                   Martin

                                  --
                                  Мир для України.
                                  1. problematische Seite

                                    Hallo Martin,

                                    file_exists($pageFile = __DIR__ . "/page/" . $_GET["page"] . ".php"))
                                    

                                    😲

                                    Gerade zum

                                    aufhübschen

                                    habe ich dein Konstrukt nicht gewählt. Ich hatte es mal. Und dann dachte ich: Rolf, schreib lesbaren Code. Dass ich auch zur unlesbaren Kompaktvariante fähig bin, habe ich ja mit dem Zweizeiler belegt. Das sollte aber abschrecken und nicht als Muster dienen.

                                    Es gibt Programmiersprachen, in denen man einzeilige Programme beliebiger Komplexität verfassen kann. APL zum Beispiel. C gehört auch dazu. Aber in PHP, vor allem wenn ein Nichtexperte den Code verwenden soll, sollte man das nicht tun. Ein Schalter ist nervig, ja. Aber eine Zuweisung in den Argumenten einer Funktion zu verstecken ist etwas, das man einem Minifizierer überlassen und keinesfalls als musterhaften Code präsentieren sollte. Dann lieber meine funktionale Variante.

                                    Rolf

                                    --
                                    sumpsi - posui - obstruxi
                                    1. problematische Seite

                                      Hallo Rolf,

                                      file_exists($pageFile = __DIR__ . "/page/" . $_GET["page"] . ".php"))
                                      

                                      😲

                                      was denn?? Ich bilde damit nur das Denkschema aus dem wirklichen Leben ab: Einen Ausdruck berechnen und für weitere Verwendung vormerken.

                                      Gerade zum

                                      aufhübschen

                                      habe ich dein Konstrukt nicht gewählt. Ich hatte es mal.

                                      Und warum hast du es aufgegeben? Es ist intuitiv, es entspricht der natürlichen Denkweise.

                                      Und dann dachte ich: Rolf, schreib lesbaren Code.

                                      Genau das habe ich getan. Jedenfalls nach meinen Maßstäben.

                                      Es gibt Programmiersprachen, in denen man einzeilige Programme beliebiger Komplexität verfassen kann. APL zum Beispiel. C gehört auch dazu.

                                      Es geht nicht darum, Einzeiler zu produzieren. Es geht darum, Programmcode so zu schreiben, dass er den zugrundeliegenden Denkprozess möglichst gut wiedergibt. Deswegen habe ich den vereinfachten Vorschlag gebracht.

                                      Aber in PHP, vor allem wenn ein Nichtexperte den Code verwenden soll, sollte man das nicht tun.

                                      Warum nicht??

                                      Ein Schalter ist nervig, ja. Aber eine Zuweisung in den Argumenten einer Funktion zu verstecken ist etwas, das man einem Minifizierer überlassen und keinesfalls als musterhaften Code präsentieren sollte.

                                      Da bin ich komplett anderer Meinung.

                                      Einen schönen Tag noch
                                       Martin

                                      --
                                      Мир для України.
                                      1. problematische Seite

                                        Hallo Martin,

                                        Ich bilde damit nur das Denkschema aus dem wirklichen Leben ab: Einen Ausdruck berechnen und für weitere Verwendung vormerken.

                                        Das kannst Du ja tun. Aber dann bitte als eigene Zeile. Es in einem Funktionsaufruf zu tun, der dazu noch in einer Bedingung drin steckt, das ist zu viel auf einmal.

                                        den zugrundeliegenden Denkprozess möglichst gut wiedergibt

                                        $name = __DIR__ . "foo.php";
                                        if file_exists($name) { }
                                        

                                        und

                                        if file_exists($name = __DIR__ . "foo.php") { }
                                        

                                        sind durchaus Zeichen verschiedener Denkprozesse. Der erste besagt: Ich baue mir den Namen zusammen, und damit mach ich dann weiter. Der zweite besagt: Ich muss den Dateinamen prüfen - ach, den muss ich ja zusammenbauen - ups, merken muss ich ihn mir auch noch.

                                        Der erste Prozess ist strukturiert und geplant, der zweite ist spontan und chaotisch. Es sieht zumindest so aus. Natürlich hast Du nachgedacht bevor Du das geschrieben hast. Aber Du hast Dich durch eine Mikrooptimierung einfangen lassen.

                                        Es ist auch eine Frage der Erwartungshaltung. In einer Abfrage erwarte ich, das ein Zustand geprüft wird. Bei einem Funktionsaufruf erwarte ich, dass vorhandene Werte übergeben werden. In beiden Fällen erwarte ich ausdrücklich nicht, dass Werte als Seiteneffekt verändert werden.

                                        Beim Programmieren muss man schon genug nachdenken. Es durch ein Suchspiel anzureichern, wo sich Zuweisungen verstecken könnten, ist kontraproduktiv.

                                        Wie gesagt und gezeigt - wenn der Schalter stört, baut man den Code besser funktional um. Der kleine Assemblerprogrammierer in mir schreit dabei natürlich auf. „Aber die Laufzeit!!!“. Quark. Der file_exists braucht um Größenordnungen mehr Zeit.

                                        Rolf

                                        --
                                        sumpsi - posui - obstruxi
                                        1. problematische Seite

                                          Hallo,

                                          Ich bilde damit nur das Denkschema aus dem wirklichen Leben ab: Einen Ausdruck berechnen und für weitere Verwendung vormerken.

                                          Das kannst Du ja tun. Aber dann bitte als eigene Zeile.

                                          nein, das eben gerade nicht. Es ist ein gedanklicher Schritt. Ihn in zwei oder mehr Programmzeilen zu splitten, macht die Sache schwerer nachvollziehbar, weil zusammengehörende Dinge getrennt werden.

                                          Es in einem Funktionsaufruf zu tun, der dazu noch in einer Bedingung drin steckt, das ist zu viel auf einmal.

                                          Es ist die mehr oder weniger exakte Abbildung meiner Denkweise.

                                          $name = __DIR__ . "foo.php";
                                          if file_exists($name) { }
                                          

                                          und

                                          if file_exists($name = __DIR__ . "foo.php") { }
                                          

                                          sind durchaus Zeichen verschiedener Denkprozesse.

                                          Eben. Der erste ist zersplittert, der zweite homogen.
                                          Oder anders: Der erste ist Englischunterricht in der fünften Klasse, der zweite ist Shakespeare.

                                          Der erste Prozess ist strukturiert und geplant, der zweite ist spontan und chaotisch.

                                          Nicht chaotisch, nur systematisch geschachtelt.

                                          Einen schönen Tag noch
                                           Martin

                                          --
                                          Мир для України.
                                          1. problematische Seite

                                            Hallo Martin,

                                            let's agree to disagree...

                                            Der erste ist Englischunterricht in der fünften Klasse, der zweite ist Shakespeare.

                                            Ein sehr nützliches Bild. Genausowenig wie Du einem Fünftklässler Shakespeare vorsetzt, solltest Du borisbaer deine geschachtelte Denke vorsetzen.

                                            Two beer or not two beer? Prost!

                                            Rolf

                                            --
                                            sumpsi - posui - obstruxi
                            2. problematische Seite

                              Hallo,

                              An dieser Stelle endet das Script, wenn die Datei bzw. Seite gefunden wurde.

                              Diese Formulierung stört mich.

                              Vielleicht meinst das richtige, aber das Script weiß nicht, dass du jetzt zufrieden bist und endet deshalb, sondern läuft brav bis zum Schluss, auch wenns da nix mehr zu tun gibt.

                              Gruß
                              Kalk

                              1. problematische Seite

                                Hast recht, habe ich mir hinterher auch gedacht!

        3. problematische Seite

          Huch, entschuldige, hätte ich nur früher gesehen, dass hier noch ein Beitrag kam. Also, super erklärt, konnte alles gut nachvollziehen. Jetzt weiß ich immerhin, dass man URL-Parameter mit PHP verarbeiten kann. Wie das im Einzelfall genau geht, finde ich hoffentlich bald heraus.

  3. problematische Seite

    Hallo borisbaer,

    Stattdessen hätte ich gerne, dass z.B. die URL …/games/demons-souls?mods mir auch direkt als Tab-Content den Inhalt von mods.php anzeigt.

    Das setzt voraus, dass mods.php keine vollständige HTML Seite liefert, sondern nur das HTML Fragment, das in den Tab hinein soll.

    Im Haupt-PHP Script, das die Rahmenseite liefert, kannst Du mit isset($_GET['mods']) abfragen, ob ein Parameter namens mods vorhanden ist. Das ist allerdings umständlich, weil Du für jede Page eine eigene Abfrage brauchst. Wenn Du nur den Pagename angeben willst, guck Dir $_SERVER['QUERY_STRING'] an und prüfe, ob der Wert in einer Liste erlaubter Pages steht, bevor Du die Page inkludierst.

    Wenn der Parameter dann identifiziert ist, kannst Du im PHP an der Stelle, wo die Page eingebunden werden muss, sie einfach mit include hereinholen.

    Clientseitig kannst Du, wie Dedlfix sagte, durch Zugriffe auf das History-API dafür sorgen, dass ein Page-Wechsel sich in URL und URL-Historie niederschlägt.

    ABER

    Ich würde das nicht tun. Bei einer Ajax-getriebenen Seite kannst Du auch URLs mit Hash-Teil vewenden: …/games/demons-souls#mods

    Diesen Hash-Teil kannst Du am Client auswerten und die Page per AJAX hereinholen. Das musst Du beim Start der Seite tun, und bei URL-Änderungen die Page anpassen. Dazu gibt's die hashchange und popstate Events auf dem window Objekt.

    Rolf

    --
    sumpsi - posui - obstruxi
    1. problematische Seite

      Das setzt voraus, dass mods.php keine vollständige HTML Seite liefert, sondern nur das HTML Fragment, das in den Tab hinein soll.

      Genau so ist es auch.

      Im Haupt-PHP Script, das die Rahmenseite liefert, kannst Du mit isset($_GET['mods']) abfragen, ob ein Parameter namens mods vorhanden ist. Das ist allerdings umständlich, weil Du für jede Page eine eigene Abfrage brauchst. Wenn Du nur den Pagename angeben willst, guck Dir $_SERVER['QUERY_STRING'] an und prüfe, ob der Wert in einer Liste erlaubter Pages steht, bevor Du die Page inkludierst.

      Tut mir echt leid, leider verstehe ich nur Bahnhof. Bisher kenne ich nur zwei Methoden, Inhalte dynamisch zu laden, nämlich über ein AJAX-Script oder über den PHP-Befehl<?php $path = $_SERVER['DOCUMENT_ROOT']; $path .= "/placeholder.php"; include($path); ?>. Ich habe bisher nicht verstehen können, wie man URL-Parameter überhaupt einsetzt, wo man sie im Code hinschreibt, das ganze Prinzip im Grunde nicht. Und die bisherigen Tutorials, die ich mir angeschaut habe, haben mir da auch irgendwie nicht weitergeholfen. Ich bräuchte ein Tutorial für richtige Dummies.

      Wenn der Parameter dann identifiziert ist, kannst Du im PHP an der Stelle, wo die Page eingebunden werden muss, sie einfach mit include hereinholen.

      Siehe oben.

      Clientseitig kannst Du, wie Dedlfix sagte, durch Zugriffe auf das History-API dafür sorgen, dass ein Page-Wechsel sich in URL und URL-Historie niederschlägt.

      Ich möchte das auch eigentlich nicht über eine URL-Historie lösen, sondern …

      ABER

      Ich würde das nicht tun. Bei einer Ajax-getriebenen Seite kannst Du auch URLs mit Hash-Teil vewenden: …/games/demons-souls#mods

      … wenn schon AJAX, dann so, wie du das hier geschrieben hast, nämlich mit Hash.

      Diesen Hash-Teil kannst Du am Client auswerten und die Page per AJAX hereinholen. Das musst Du beim Start der Seite tun, und bei URL-Änderungen die Page anpassen. Dazu gibt's die hashchange und popstate Events auf dem window Objekt.

      Unter den Beitrag von TS habe ich den kompletten Code kopiert, der für das dynamische Laden der Seitenteile zuständig ist. Könntest du mir sagen, wie ich hier mit den von dir genannten Events es so einrichte, dass der Hash-Teil automatisch den entsprechenden Seitenteil lädt?

      1. problematische Seite

        Hallo borisbaer,

        ich finde deinen Code durch die Doppelzeiligkeit schwer lesbar. Aber ok, jedem sein Stil.

        URL Parameter am Server interessieren Dich zur Zeit nicht, wenn ich das richtig verstehe. Darum schreibe ich dazu jetzt nichts. Wichtig sind die Hashes.

        Was mir nicht klar ist: Auf der Savegame Seite hast Du eine Subnavigation, bei der Beute. Möchtest Du deren Seiten dauerhaft komplett laden, oder soll das auf AJAX umgestellt werden? In dem Fall wird die Hash-Behandlung etwas schwieriger. Nicht viel, aber doch. Ich gehe erstmal davon aus, dass Du das nicht machst.

        Mein Vorschlag wäre:

        • Mach ein Backup von dem, was Du hast…
        • Ergänze deine Links um ein #, also href="#game" statt href="game"
        • Reagiere nicht auf den Klick des Links, sondern auf das hashchange Event des window-Objekts
        • Bau dein Script in etwa so:
        $(function() {
           let currentHash = '';
        
           // loadCurrentPage als hashchange-Handler registrieren
           window.addEventListener("hashchange", loadCurrentPage);
        
           // und gleich beim Start der Seite aufrufen um die Default-Page zu laden
           loadCurrentPage();
        
           function loadCurrentPage() {
              let newHash = window.location.hash;
              let $newTab;
        
              if (newHash.length == 0) {
                 $newTab = $("#tabs .tab:first-child");
                 newHash = $newTab.attr("href");
              }
              else {
                 $newTab = $("#tabs .tab[href="+newHash+"]");
                 if ($newTab.length == 0) {
                    // Fehler, den Tab gibt's nicht
                 }
              } 
        
        
              if (newHash != currentHash) {
                 currentHash = newHash;
                 $newTab.addClass("current").siblings().removeClass("current");
        
                 let tabName = newHash.substr(1);   // das # steht in newHash drin
                 $("main".attr("id", tabName);
        
                 $ajax(tabName)
                 .then(function(html) { $("#append").html(html); });
              }
           }
        });
        

        Das ist jetzt so aus der Hüfte geschossen und ungetestet. Teils habe ich Code von Dir übernommen. Geändert habe ich:

        • $(document).ready(function() { ... }) ist in jQuery missbilligt, die einzig korrekte Installation eines Ready-Handlers erfolgt mit $(function() { ... })
        • Der aktuelle Tab-Link wird in einer Variablen ($newTab) gespeichert. Diese Variable beginnt mit einem $, das ist meine Methode, um darzustellen, dass hier ein jQuery Set drin ist.
        • Statt des success-Callback verwende ich die modernere Deferred-technik von jQuery (in vanilla javascript auch Promise genannt)
        • statt .empty().append(html) verwende ich .html(html), das tut das gleiche in einem Schritt.

        Das sollte die Zeilen 65-114 deines main.js abdecken. Durch die Navigation mittels hashchange entfällt die Reaktion auf eine click der Links.

        Was fehlt, ist die Behandlung des Fehlers, wenn der genannte Hash nicht existiert. Das kann passieren, wenn der User den Hash editiert oder Du Dich vertippst hast.

        Man könnte auch ganz auf jQuery verzichten, aber da Du ein Freund davon bist, habe ich es dringelassen.

        Rolf

        --
        sumpsi - posui - obstruxi
        1. problematische Seite

          Hallo Rolf,

          Was mir nicht klar ist: Auf der Savegame Seite hast Du eine Subnavigation, bei der Beute. Möchtest Du deren Seiten dauerhaft komplett laden, oder soll das auf AJAX umgestellt werden? In dem Fall wird die Hash-Behandlung etwas schwieriger. Nicht viel, aber doch. Ich gehe erstmal davon aus, dass Du das nicht machst.

          nein, das soll nicht auf AJAX umgestellt werden.

          Das sollte die Zeilen 65-114 deines main.js abdecken. Durch die Navigation mittels hashchange entfällt die Reaktion auf eine click der Links.

          Einige Syntax-Fehler waren scheinbar drin, habe sie hoffentlich alle korrigiert, ansonsten läuft dein Code jetzt so:

          let currentHash = '';
          
          // loadCurrentPage als hashchange-Handler registrieren
             window.addEventListener("hashchange", loadCurrentPage);
          
          // und gleich beim Start der Seite aufrufen um die Default-Page zu laden
          loadCurrentPage();
          
          function loadCurrentPage() {
          	let newHash = window.location.hash;
          	let $newTab;
          
          	if (newHash.length == 0) {
          		$newTab = $("#tabs .tab:first-child");
          		newHash = $newTab.attr("href");
          	}
          
          	else {
          		$newTab = $("#tabs .tab[href="+newHash+"]");
          
          		if ($newTab.length == 0) {
          			// Fehler, den Tab gibt's nicht
          		}
          	}
          
          	if (newHash != currentHash) {
          		currentHash = newHash;
          		$newTab.addClass("current").siblings().removeClass("current");
          
          		let tabName = newHash.substr(1);   // das # steht in newHash drin
          
          		$("main").attr("id", tabName);
          
          		$.ajax(tabName).then(function(html) { $("#append").html(html); });
          	}
          }
          
          

          Es funktioniert, dass die erste Unterseite (game) jetzt direkt angezeigt wird. Was jedoch nicht funktioniert, ist das Umschalten auf einen neuen Tab. Es erscheint die Fehlermeldung: Uncaught Error: Syntax error, unrecognized expression: #tabs .tab[href=#releases]

          In der URL-Leiste steht z.B.: http://localhost/games/demons-souls/#releases

          Ich schätze mal, htaccess schießt hier irgendwie quer? Der eigentliche Pfad lautet nämlich: http://localhost/pages/games/demons-souls/index.php#releases

          Ich habe htaccess so eingestellt:

          Options +MultiViews
          
          RewriteEngine On
          
          # Rewrite "/games/<anything>" to "/pages/games/<anything>"
          RewriteCond %{REQUEST_URI} !^/pages/games
          RewriteRule ^games($|/.*) pages/$0 [L]
          
          # Remove .php extension from URL
          RewriteCond %{REQUEST_FILENAME} !-d
          RewriteCond %{REQUEST_FILENAME}\.php -f
          RewriteRule ^([^\.]+)/$ $1.php
          

          Was fehlt, ist die Behandlung des Fehlers, wenn der genannte Hash nicht existiert. Das kann passieren, wenn der User den Hash editiert oder Du Dich vertippst hast.

          Ja, so was wollte ich auch noch einbauen, danke!

          Man könnte auch ganz auf jQuery verzichten, aber da Du ein Freund davon bist, habe ich es dringelassen.

          Was meinst du damit? Lieber nur Standard-Javascript verwenden?

          Boris

          1. problematische Seite

            Hallo borisbaer,

            Was meinst du damit? Lieber nur Standard-Javascript verwenden?

            Ja. Das meiste, was Du da tust, geht heute auch mit vanilla DOM Methoden - außer der .siblings-Nummer. Aber dafür kann man zur Not auch ein Schleifchen klöppeln. Es ist aber deine Entscheidung, welchen Toolstack Du nutzen willst. Wenn du mit jQuery zufrieden bist, nimm es. Statt $.ajax kannst Du auch das fetch-API benutzen, wenn Du auf den IE verzichten kannst.

            Uncaught Error: Syntax error, unrecognized expression: #tabs .tab[href=#releases]

            Ugh, ja, das hab ich verpennt. Das # ist in einem CSS Selektor ein Zeichen mit spezieller Bedeutung, so wie auch Punkt oder eckige Klammern, und muss in maskiert werden. Dafür gibt's eine Funktion, sie heißt CSS.escape. Nicht im Internet Explorer - dem schlägst Du damit einen weiteren Nagel in den Sarg.

            $newTab = $("#tabs .tab[href="+CSS.escape(newHash)+"]");
            

            Wenn Du auf den IE nicht verzichten kannst, mach es so:

            $newTab = $("#tabs .tab[href="+newHash.replace("#", "\\#")+"]");
            

            Wichtig ist der doppelte Backslash, weil JavaScript den selbst schon zum Escapen nutzt.

            Rolf

            --
            sumpsi - posui - obstruxi
            1. problematische Seite

              Hallo Rolf,

              Ja. Das meiste, was Du da tust, geht heute auch mit vanilla DOM Methoden - außer der .siblings-Nummer. Aber dafür kann man zur Not auch ein Schleifchen klöppeln. Es ist aber deine Entscheidung, welchen Toolstack Du nutzen willst. Wenn du mit jQuery zufrieden bist, nimm es.

              jQuery kann ich leichter nachvollziehen.

              Statt $.ajax kannst Du auch das fetch-API benutzen, wenn Du auf den IE verzichten kannst.

              Welchen Vorteil bietet das? Ist es schwierig? Schreibe ich dann einfach nur fetch(tabName) { $(".append").html(html); }; stattdessen hin?

              Hier noch einmal der vollständige, funktionierende Code:

              $(function () {
              
              	let currentHash = '';
              
              	window.addEventListener("hashchange", loadCurrentPage);
              
              	loadCurrentPage();
              
              	function loadCurrentPage() {
              		let newHash = window.location.hash;
              		let $newTab;
              
              		if (newHash.length == 0) {
              			$newTab = $("#tabs .tab:first-child");
              			newHash = $newTab.attr("href");
              		}
              
              		else {
              			$newTab = $("#tabs .tab[href="+CSS.escape(newHash)+"]");
              
              			if ($newTab.length == 0) {
              
              				// e r r o r
              
              			}
              		}
              
              		if (newHash != currentHash) {
              			currentHash = newHash;
              			$newTab.addClass("current").siblings().removeClass("current");
              
              			let tabName = newHash.substr(1);
              
              			$("main").attr("id", tabName);
              
              			$.ajax(tabName).then(function(html) { $(".append").html(html); });
              		}
              	}
              
              });
              

              Wofür ist denn in let tabName = newHash.substr(1); das substr(1) da? Was würde passieren, wenn man den Teil weglässt?


              Ich habe endlich das Grundprinzip von URL Parametern nachvollziehen können. Im Grunde können Variablen in der URL einen bestimmten PHP-Code auf der Seite auslösen.

              In ganz einfacher Form habe ich mal eine Testseite erstellt: http://54598532.swh.strato-hosting.eu/

              Im div mit der class append steht folgender PHP-Code:

              <?php 
              
              if ($_GET['subpage'] == 'foo') { 
              
                  $path = $_SERVER['DOCUMENT_ROOT']; $path .= "foo.php"; include($path); 
              
                      } else if ($_GET['subpage'] == 'bar') { 
              
                  $path = $_SERVER['DOCUMENT_ROOT']; $path .= "bar.php"; include($path); 
              
                      } else { echo " Startseite "; }
              
              ?>
              

              Seltsamerweise funktioniert das auf meinem lokalen Server, aber auf dem Strato-Server plötzlich nicht mehr. Hast du eine Ahnung, warum?

              Dafür zeigt mir die Seite auf dem lokalen Server folgende Fehlermeldung an:

              Warning: Undefined array key "subpage" in E:\Webseiten\games\index.php on line 37
              
              Warning: Undefined array key "subpage" in E:\Webseiten\games\index.php on line 41
              

              Auf dem Strato-Server hingegen erscheint diese Fehlermeldung nicht …

              Edit: Es lag am Pfad. Ich musste ein slash bei /foo.php und /bar.php setzen, damit es den ROOT-Ordner anpeilt. Die Fehlermeldung auf dem lokalen Server bleibt aber.

              1. problematische Seite

                Hallo borisbaer,

                fetch(tabName) { $(".append").html(html); };

                Du schreibst

                fetch(tabName)
                .then(function(response) {
                  if (response.ok)
                    return response.text();
                  else
                    throw 'Tab konnte nicht geladen werden';
                })
                .then(function(html) {
                  $(".append").html(html);
                })
                .catch(function(message) {
                   // Fehlerbehandlung
                });
                

                Fetch funktioniert in 3 Schritten. jQuery kapselt Dir das.

                Schritt 1: Absenden des HTTP Requests. Das macht fetch(tabName). Du bekommst ein Promise zurück; ein Objekt, das Dir das Versprechen macht, irgendwann Daten zu liefern.

                Auf diesem Promise rufst Du die Methode .then() auf. Mit then registrierst Du eine Funktion - einen Callback - der aufgerufen wird, wenn das Versprechen eingelöst wird.

                Schritt 2: Dieser Callback bekommt aber noch nicht die fertigen Daten übergeben, sondern erstmal nur ein "Response" Objekt. Je nachdem, ob der Server mit HTTP 200 oder 40x geantwortet hat, ist die ok Eigenschaft der Response auf true oder false. Das fragst Du ab, und wenn es ok war, DANN kannst Du den Text der Response lesen. Das geschieht wiederum asynchron (weil erstmal von Netzwerk gelesen werden muss), und response.text() liefert Dir ein neues Promise, mit dem Versprechen auf den Text, den der Server geliefert hat.

                Schritt 3: Und DER geht dann an den Callback des zweiten .then Aufrufs.

                Das ist ein Grundsatzmechanismus von Promises. Die .then Methode liefert immer ein neues Promise zurück, d.h. Du kannst .then Aufrufe aneinander hängen. Genau das passiert oben:

                fetch().then().then();
                

                Es ist nur anders aufgeschrieben, so dass die .then jeweils am Zeilenanfang stehen. Deswegen gibt's auch kein Semikolon hinter fetch(...).

                Wenn ein then Handler einfach nur einen Wert zurückliefert, geht der 1:1 weiter an den nächsten then. Aber wenn ein Promise zurückkommt, dann wird das Ergebnis dieses Promise an den nächsten then Handler übergeben, sobald sich das Promise erfüllt. Man kann auf diese Weise also mehrere asynchrone Abläufe aneinander koppeln.

                Im Fehlerfall steht da throw Error(). Das ist der JavaScript Exception-Mechanismus. Eine mit throw geworfener Wert kann mit einem try-catch Konstrukt gefangen werden (-> Selfwiki). Darum kümmert sich bei Promises die then-Funktion (then selbst, nicht dein Callback, den Du then übergeben hast). Sie fängt den geworfenen Wert und bricht die then-Kette ab. Um den Error verarbeiten zu können, brauchst Du noch einen .catch Aufruf auf der Promise-Kette, um einen Callback für den Fehlerfall zu registrieren.

                fetch()
                .then()
                .then()
                .catch(function(error) { ... });
                

                Den .catch-Handler musst Du nicht registrieren. Lässt Du ihn weg, wird ein Error in der Konsole der Entwicklerwerkzeuge notiert, aber das Programm läuft weiter. Aber wenn doch, bekommst Du genau das Ding übergeben, das mit throw geworfen wurde.

                Warning: Undefined array key "subpage" in E:\Webseiten\games\index.php on line 37

                Das sollte nur kommen, wenn der subpage Parameter nicht bei PHP ankommt. Kommt das nur, wenn Du die Seite ohne Parameter aufrufst? Wenn ja, und es bei Strato nicht kommt, kann das auch am unterschiedlichen error reporting der PHP Installationen liegen. Zeigt PHP auf dem Server überhaupt Warnungen an? Was liefert Dir die Funktion error_reporting(), bzw. ini_get("error_reporting") - vergleiche das mal mit deiner lokalen Kiste.

                $_SERVER['DOCUMENT_ROOT']

                Musst Du das überhaupt tun? Ein include sollte doch, meine ich, zuerstmal in dem Ordner schauen, in dem das PHP Script gestartet wurde. Wenn also Includierer und Includierter im gleichen Ordner stehen, müsste es auch ohne expliziten Pfad klappen. Das PHP Handbuch schreibt:

                Dateien werden unter dem angegebenen Pfad gesucht oder, wenn keiner gegeben ist, im include_path. Wenn die Datei auch im include_path nicht gefunden werden kann, sucht include noch im Verzeichnis der aufrufenden Datei und im aktuellen Arbeitsverzeichnis.

                include_path ist eine Einstellung in der php.ini.

                Rolf

                --
                sumpsi - posui - obstruxi
                1. problematische Seite

                  Das ist ein Grundsatzmechanismus von Promises.

                  Vielen Dank für die Erklärung!

                  Kommt das nur, wenn Du die Seite ohne Parameter aufrufst?

                  Ja.

                  Wenn ja, und es bei Strato nicht kommt, kann das auch am unterschiedlichen error reporting der PHP Installationen liegen. Zeigt PHP auf dem Server überhaupt Warnungen an? Was liefert Dir die Funktion error_reporting(), bzw. ini_get("error_reporting") - vergleiche das mal mit deiner lokalen Kiste.

                  Okay, also jetzt zeigt es auf Strato auch folgenden Fehler an:

                  Warning: Undefined array key "subpage" in /mnt/web521/b1/32/54598532/htdocs/index.php on line 42 
                  Warning: Undefined array key "subpage" in /mnt/web521/b1/32/54598532/htdocs/index.php on line 44
                  

                  $_SERVER['DOCUMENT_ROOT']

                  Musst Du das überhaupt tun? Ein include sollte doch, meine ich, zuerstmal in dem Ordner schauen, in dem das PHP Script gestartet wurde. Wenn also Includierer und Includierter im gleichen Ordner stehen, müsste es auch ohne expliziten Pfad klappen. Das PHP Handbuch schreibt:

                  Nein, da hast du recht, das ist nur ein Überbleibsel. Ich habe es wieder entfernt.


                  Wie ist das denn, wenn ich jetzt einen URL-Parameter als Variable für den path haben möchte? Ist das möglich?

                  Boris

                  1. problematische Seite

                    Hallo borisbaer,

                    was ich vergaß zu schreiben:

                    if (!empty($_GET['subpage'])) {
                       // prüfe parameterwert
                    }
                    

                    beseitigt die Warnung. Entweder empty, oder isset.

                    Rolf

                    --
                    sumpsi - posui - obstruxi
                    1. problematische Seite

                      Hat geklappt, danke für die Hilfe, Rolf!

                      Es scheint mir tatsächlich die bessere Lösung zu sein, Tabs mit URL-Parametern abzurufen.

                      Wie du bereits oben sagtest, müsste ich halt für jeden Tab-Namen einen Befehl mit if bzw. else if schreiben. Du erwähntest $_SERVER['QUERY_STRING'] als Workaround? Ich habe nur leider nicht verstanden, wie genau das funktionieren soll. Könntest du genauer erklären, wie das gemeint war? Und mal wirklich ein großes Dankeschön, dass du mir hier so weiterhilfst. Ich bin dir echt sehr dankbar!