WordPress: SiteOrigin Layout mit Ajax in andere Seite laden

Tl; dr;


Ich brauchte einen Weg, den Inhalt einer WordPress-Seite dynamisch in eine andere WordPress-Seite zu laden. Das habe ich geschafft. Den ganzen Code zum rauskopiern findste unten.

Hintergrund


Diese Seite war anfangs nicht als eine professionelle Seite gedacht, hatte also kein Portfolio, Lebenslauf oder Zeux. Jetz' kam die Zeit, wo das notwendig wurde und ich vor allem ein professionelles Portfolio brauchte. Dabei is es ja nun mal so, dass ich die Rechte an den Projekten, an denen ich im Zuge meines Jobs gearbeitet habe, nicht besitze. Deshalb darf ich die auf einer privaten Seite eigentlich nicht präsentieren. Das war meinem Arbeitgeber wichtig.


Kompromiss

Wie man sich denken kann, is das für die Jobsuche echt ungünstig, wenn ich meine professionelle Arbeit nicht präsentieren kann. Mein Chef, übrigens ein saucooler Dude, hat mir dann angeboten, dass ich das Ganze mit einem Passwort verstecke - als Kompromiss. Am wichtigsten war, dass "die Verbindung zwischen meinem Arbeitgeber, den Projekten und mir nicht öffentlich zugänglich ist".


Ja, was soll ich sagen? Dieser Arbeitgeber ist halt etwas altmodisch. Was soll's, ich hab's ja hinbekommen

Alternativen


Seiten lassen sich über die WordPress Seiteneinstellungen sehr einfach mit einem Passwort schützen, also hab' ich's erstmal über diesen Weg probiert. Was raus kam war, um's milde auszudrücken, ein Usability-Disaster! Jede Seite hatte ihr eigenes Passwort, was der User für jedes Projekt neu eingeben musste, und die Navigation war ein Alptraum.


Als nächstes habe ich versucht über diverse WordPress-Plugins einen Weg zu finden, nur bestimmte Bereiche einer Seite mit einem Passwort zu schützen. Da ich allerdings den SiteOrigin Pagebuilder verwende, um meine Seiten zusammenzubauen, funktionierten die ebenfalls nicht.


Als letztes gab ich meinem eigenen Tüftlerdrang nach und hab die ganze Sache einfach selbst programmiert, auch wenn's etwas länger gedauert hat.

Der Weg


Der Weg zum funktionierenden Portfolio war mit einigen Steinen übersät. Einen Teil des steinigen Weges kann man hier (Englisch!) etwas nachvollziehen. Ich gehe auf alles nochmal genauer ein.


Ajax

Ajax wird benutzt, um mit dem Server zu kommunizieren, nachdem eine Homepage bereits geladen wurde. Man kann damit Daten anfordern oder die Seite verändern, ohne dass sie komplett neu geladen werden muss.

Ich konnte Ajax also benutzen, um mit Javascript eine PHP Funktion auf dem Server aufzurufen, die mir dann das entsprechende SiteOrign-Layout zurückgibt. Das konnte ich dann in ein dafür vorgesehenen Div-Container im Html packen und voilà.


Php

<?php
    
    // Die beiden folgenden Zeilen gehören in die functions.php des Themes oder dahin,
    // wo Scripte für ein Plugin oder Widget eingebunden werden. "portfolio-page.js" ist
    // das Script, von dem aus ich die Ajax-Aufrufe mache.
    
    // Das ist der Wordpress-Weg, um Scripte in eine Seite einzubinden.
    wp_enqueue_script('portfolio-page', get_template_directory_uri() . '/js/portfolio-page.js', array('jquery'), false, false);
    
    // Das bedeutet: "Erlaube portfolio-page Zeug mit Ajax aufzurufen."
    wp_localize_script('portfolio-page', 'ajaxAdmin', array( 'ajaxurl' => admin_url( 'admin-ajax.php' )));



    // Diese beiden Zeilen gehören ebenfalls in die functions.php. Sie mappen den Ajax-Aufruf auf die
    // entsprechende Php methode. Es ist wichtig, dass die parameter genau zueinander passen, wie hier
    // "tnet_load_post_content" (ja, inklusive dem wp_ Zeugs).
    add_action( 'wp_ajax_tnet_load_post_content', 'tnet_load_post_content' );
    add_action( 'wp_ajax_nopriv_tnet_load_post_content', 'tnet_load_post_content' );

   
   
    // Das hier ist die tatsächliche Methode, die dann vom Javascript aufgerufen wird.
    function tnet_load_post_content()
    {
        $id = $_POST['pageId']; // So kommt man an Parameter, die vom Javascript mitgeschickt werden.
        
        // Hier wird geschaut, ob es sich bei der Seite mit der Id 'pageId' um eine mit einem SiteOrigin Layout handelt.
        $meta = get_post_meta( $id, 'panels_data', true );
        if( class_exists( 'SiteOrigin_Panels' ) && $meta )
        {
            // Ja: Benutze SiteOrigin-Magie, um an das Seitenlayout zu kommen.
            $renderer = SiteOrigin_Panels::renderer();
            $content = $renderer->render( $id );
        }
        else
        {
            // Nein: Hol das Layout im standard Wordpress Weg
            $content = apply_filters( 'the_content', get_page($id)->post_content );
        }
        
        // Gib das Layout zurück an den Aufrufer.
        echo $content;
        
        // Wordpress fügt ohne das hier eine "0" zu der Antwort hinzu.
        die(); 
    }
?>

Javascript

function loadContent(id)
{
    // Hier baue ich ein Objekt zusammen, was mit dem Ajax call mitgeschickt wird.
    // Es beinhaltet die Php-Methode, die aufgerufen werden soll, und die Id der
    // Wordpress-Seite, die geladen werden soll.
    var data = { 
        action: "tnet_load_post_content",
        pageId: id
        };
        
    // Baut den Ajax-Aufruf zusammen, führt ihn aus und ruft bei Erfolg onContentLoaded auf.
    useAjax(data, onContentLoaded);
}

function useAjax(data, onSuccess)
{
    $.ajax({
        type: "post",
        cache: false,
        
        // Das Ziel für Wordpress-Ajax-Aufrufe, wurde über wp_localize_script() im Php-Code freigegeben.
        url: ajaxAdmin.ajaxurl,
        
        // Die Paramter, die mitgeschickt werden sollen.
        data: data, 
        
         // Die Methode, die bei Erfolg aufgerufen wird.
        success: onSuccess
    });
}

function onContentLoaded(response)
{
    // Der geladene Inhalt wird in den vorgesehen container gepackt.
    contentContainer.html(response);
}

Ich bin eigentlich kein Webentwickler und, um ehrlich zu sein, finde ich HTML, CSS und vor allem PHP ziemlich behindert! Ich hab' diese Seite total blauäugig zusammengeschustert und hatte daher auch keinen Plan was Ajax eigentlich is oder wie's funktioniert.


Falsches CSS

Ich hatte also geschafft, eine Seite meiner Wahl in eine andere Seite zu laden. Aber irgendwas, war faul. Margins, hatten nicht gestimmt, Bilder sahen komisch aus. Was war los? SiteOrigin gibt seinen Elemente Klassen und Ids, abgeleitet von der Seite zu der diese gehören, und bindet dann entsprechende CSS-Styles mit in die Seite ein.

Beispielsweise befand ich mich jetzt auf der Seite mit der Id 1234 und lade das Layout der Seite 9876. Wenn ich die Seite 1234 aufrufe, verteilt Siteorigin die Id 1234 an alle HTML-Elemente und lädt das CSS für die Seite 1234. Jetzt lade ich 9876 in 1234, was dann keine Styles hat, weil sich die Styles auf 1234 beziehen. Kommst noch mit?


Php

// Hol den Inhalt und die Styles der gewünschten Seite von Siteorigin.
$renderer = SiteOrigin_Panels::renderer();
$content = $renderer->render( $id );
$css = $renderer->generate_css( $id );

// Schick beides als Json zum Javascript
echo json_encode(array($content, $css));

Javascript

function onContentLoaded(response)
{
    // Parse das JSON zu einem Javascript Objekt.
    var data = JSON.parse(response);

    // Der Inhalt gehört in den vorgesehenen Container.
    contentContainer.html(data[0]);
    
    // Die Styles werden ersetzt.
    addMissingStyles(data[1]);
}

function addMissingStyles(css)
{
    if(css == '')
    {
        return;
    }
    
    // Zuerst entferne ich die alten, falschen Styles.
    var siteOriginId = "#siteorigin-panels-layouts-head";
    tryRemove(siteOriginId);
    
    // Dann binde ich sie komplett neu ein.
    var styles = '<style type="text/css" media="all" id="'+siteOriginId+'">'+css+'</style>';
    $('head').append(styles);
}

function tryRemove(id)
{
    var element = document.getElementById(id);
    if(element !== null)
    {
        element.parentNode.removeChild(element);
    }
}

Ich habe sogar einen Webentwickler-Kollegen nach einem besseren Weg gefragt, aber er kam zum selben Schluss: Ich musste also nicht nur das Layout der anderen Seite, sondern auch deren CSS laden und nachträglich hinzufügen. Damit hatte ich die beiden größten Herausforderungen bezwungen.

Weiteres


Nachdem das Hauptfeature schon mal funktionierte, gab es noch ein paar kleinere Unstimmigkeiten, die ich nachtragen wollte.


Das Gallery Widget

Für diese Seite habe ich meinem Theme ein eigenes Gallerie Widget hinzugefügt, was ein Javascript benötigt. Ich musste also das rein geladene Layout noch überprüfen und das benötigte Script nachträglich hinzufügen.


Javascript

function addMissingScripts()
{
    // Ich warte etwas, weil das Widget noch nicht geladen sein könnte.
    setTimeout(function()
    {
        if( $(".gallery-widget").length ) //<- Das heißt: Existiert "gallery-widget"?
        {
            if( $("#gallery-widget-js").length ) //<- Das heißt: Ist das Gallery Widget Javascript schon da?
            {
                return;
            }
            
            // Hier binde ich das Script aus meinem Theme manuell ein.
            $("head").append('<script type="text/javascript" src="https://taijj.net/wp-content/themes/tnet/js/gallery-widget.js?ver=5.5" id="gallery-widget-js"></script>');
        }
    }, 1000);
}

Sprachwechsel

Ich benutze das Polylang Plugin, um zwei Buttons für die beiden Sprachen anzuzeigen. Diese laden, über einen Link, die Seite neu in der anderen Sprache. Ich musste deren Links noch anpassen, dass sie auf den entsprechenden Inhalt verweisen.


Javascript

function updateLanguageButtons()
{
    $('.language-switcher .lang-item a').each(updateUrl);
    $('.header-language-section .lang-item a').each(updateUrl);

    function updateUrl()
    {
        var item = $(this);
        var url = item.attr('href');

        var hashIndex = url.indexOf('#');
        if(hashIndex >= 0)
        {
            url = url.substr(0, hashIndex);
        }
        item.attr('href', url+window.location.hash);
    }
}

Das Passwort-Feature

Darauf gehe ich vielleicht in einem nächsten Post mal ein. Ich musste schauen, wie ich die Form anzeige, Cookies setze und generell das Passwort anzeige.

Fazit


Es war ein langer Weg, doch ich glaube, dass es sich gelohnt hat. Hier nochmal der relevante Code in seiner Gesamtheit:


Php

add_action( 'wp_ajax_tnet_load_post_content', 'tnet_load_post_content' );
add_action( 'wp_ajax_nopriv_tnet_load_post_content', 'tnet_load_post_content' );

function tnet_load_post_content() {

    $id = $_POST['pageId'];
    $meta = get_post_meta( $id, 'panels_data', true );

    if( class_exists( 'SiteOrigin_Panels' ) && $meta )
    {
        $renderer = SiteOrigin_Panels::renderer();
        $content = $renderer->render( $id );
        $css = $renderer->generate_css( $id );
    }
    else
    {
        $css = '';
        $content = apply_filters( 'the_content', get_page($id)->post_content );
    }

    echo json_encode(array($content, $css));
    die();
}

Javascript

Das komplette Javascript ist etwas länger und beinhaltet noch viel mehr. Du kannst es aber gern hier anschauen.


Troubleshooting

  • Beim Einbinden von Javascripts, nach dem einfügen des Layouts, war bei mir ein Timeout notwendig, damit alles richtig funktioniert.
  • Seitenlayouts können nicht über diesen Weg eingebunden werden, wenn die Seite über WordPress selbst Passwort geschützt ist.