Artykuł ten oparty jest o prezentację wygłoszoną na WordUp Trójmiasto #3. Jest to swoista transkrypcja tej prelekcji, bo idealnie nadaje się ona również na poradnik pisany.

AJAX

To w skrócie Asynchronous JavaScript and XML. W domyślne dane zwracane z serwera miały być zakodowane do XMLa. Po co jednak utrudniać sobie życie – przeważnie zwraca się dane w json, którego JS lubi i się z nim rozumie.

AJAX służy nam do tego, aby asynchronicznie, czyli bez przeładowania strony wykonać jakąś akcję, do której potrzebujemy serwera. W naszym przypadku będzie to wysłanie maila, czego nie zrobimy z poziomu javascriptu.

Aby AJAX działał potrzeba nam 3 rzeczy. Żeby działał dobrze, potrzeba 5. Są nimi:

  • obiekt (na którym będziemy wykonywać akcje)
  • skrypt js
  • akcja WordPressa
  • zabezpieczenie
  • obsługa błędów

 

To wszystko zawarłem w jednej wtyczce.

Obiekt

Wtyczkę napisałem obiektowo, ale nie ma w tym nic strasznego. W tym prostym przypadku służy to jako taka pseudo przestrzeń nazw (nazwy funkcji nie wchodzą w konflikt z niczym innym). Bowiem jedynie klasa musi mieć unikalną nazwę w obrębie całego systemu, jej metody już nie.

Wpinamy się więc do konstruktora klasy z pierwszym filtrem: the_content. Pozwala ona dowolnie filtrować treść postów.

public function __construct() {

    add_filter( 'the_content', array( $this, 'report_button' ), 10, 1 );
	
}

Metoda, którą podpiąłem pod ten filtr wygląda tak:

public function report_button( $content ) {

    // display button only on posts
    if ( ! is_single() ) {
        return $content;
    }

    $content .= '<div class="report-a-bug">
                    <button class="show-form" data-post_id="' . get_the_ID() . '">' . 
                        __( 'Report a bug', 'reportabug' ) . 
                    '</button>
                    <textarea class="report-a-bug-message" placeholder="' . __( 'Describe what\'s wrong...', 'reportabug' ) . '"></textarea>
                    <p class="report-a-bug-response"></p>
                </div>';

    return $content;

}

Dodajemy sobie div ze wszystkimi elementami, których potrzebujemy:

  • Przycisk
  • Pole tekstowe
  • Kontener na odpowiedź – przyda się później

 

Do przycisku dodajemy od razu ID posta, którego dotyczy zgłoszenie. Wykorzystałem do tego atrybut data który bardzo fajnie można wykorzystać do przekazywania informacji do JS.

Mimo, że w moim przypadku przycisk jest już ostylowany, bo korzystałem z motywu 2015, to musimy ukryć pole tekstowe i dodać marginesy. Korzystamy więc z akcji wp_enqueue_scripts, którą również podpinamy w konstruktorze klasy. A w metodzie scripts() wywołujemy funkcję wp_enqueue_style() wskazując odpowiedni plik w katalogu wtyczki:

public function __construct() {

    add_filter( 'the_content', array( $this, 'report_button' ), 10, 1 );
    
    add_action( 'wp_enqueue_scripts', array( $this, 'scripts' ) );
    
}

function scripts() {

    wp_enqueue_style( 'report-a-bug', plugin_dir_url( __FILE__ ) . 'css/style.css' );

}

Jeszcze tylko kawałek CSS:

.report-a-bug-message {
	display: none;
	margin-top: 1em;
}

.report-a-bug-response {
	margin-top: 1em;
}

I nasz przycisk prezentuje się tak:

Picture1

 

Skrypt

Ze skryptem podpinamy się do metody scripts(), tak samo jak w przypadku styli, ale wykorzystujemy tym razem funkcję wp_enqueue_script().

function scripts() {

    wp_enqueue_style( 'report-a-bug', plugin_dir_url( __FILE__ ) . 'css/style.css' );

    wp_enqueue_script( 'report-a-bug', plugin_dir_url( __FILE__ ) . 'js/scripts.js', array( 'jquery' ), null, true );

    // set variables for script
    wp_localize_script( 'report-a-bug', 'settings', array(
        'send_label' => __( 'Send report', 'reportabug' )
    ) );

}

Zwróć uwagę na null, który jest w miejscu parametru odpowiadającego za wersję skryptu. Tylko null spowoduje, że parametr ten nie będzie doklejany do adresu skryptu. Podanie false sprawi, że będzie tam po prostu aktualna wersja WordPressa.

Dodajemy również od razu lokalizację skryptu. Funkcja wp_localize_script() tworzy zaraz nad wywołaniem adresu skryptu obiekt o podanej nazwie. Jego pola są kodowane do jsona i dobrać możemy się do nich bardzo łatwo już w skrypcie. Ja utworzyłem od razu pole z etykietką przycisku, który będzie wysyłał wiadomość.

Sam skrypt wygląda na razie tak:

jQuery(document).ready(function($) {

	$('.report-a-bug').on('click', '.show-form', function(event) {

		// change label and switch class
		$(this).text( settings.send_label ).removeClass('show-form').addClass('send-report');

		// show textarea
		$('.report-a-bug-message').slideDown('slow');

	});

});

Nasłuchujemy kliknięcia w przycisk o klasie show-form, który może znajdować się w kontenerze o klasie report-a-bug. Dlatego tak pokracznie, a nie przez metodę click()? Ponieważ kiedy obiekt o klasie show-form byłby zwrócony dynamicznie z AJAXa to wtedy funkcja by się nie uruchomiła. Warto o tym pamiętać.

Więc po kliknięciu zmieniamy jego etykietę (korzystając ze zdefiniowanego wcześniej obiektu) i podmieniamy klasy. Pokazujemy też pole tekstowe. Teraz po drugim kliknięciu będziemy mogli „wysłać formularz”.

Przycisk zaczyna żyć.

bug-html

 

Dodajmy więc do niego AJAXa.

Sam AJAX wymaga tylko jednego parametru, jest niż adres pliku odpytywanego z JavaScriptu. W WordPressie zawsze będzie nim plik /wp-admin/admin-ajax.php. Problem jest tylko taki, że o ile w panelu administratora w każdym skrypcie możemy od razu użyć zmiennej ajaxurl tak na froncie musimy sami sobie ją zdefiniować. Wykorzystajmy do tego obiekt, który już mamy:

wp_localize_script( 'report-a-bug', 'settings', array(
    'ajaxurl'    => admin_url( 'admin-ajax.php' ),
    'send_label' => __( 'Send report', 'reportabug' )
) );

Pora wpiąć się pod nasz przycisk ze zmienioną klasą.

$('.report-a-bug').on('click', '.send-report', function(event) {

    var $button = $(this);

    $button.width( $button.width() ).text('...');

    // set ajax data
    var data = {
        'action' : 'send_bug_report',
        'post_id': $button.data('post_id'),
        'report' : $('.report-a-bug-message').val()
    };

    $.post(settings.ajaxurl, data, function(response) {

        console.log( 'ok' );

    });

});

[alert type=notice]
Pokaż użytkownikowi, że coś się dzieje na stronie. Kiedy nie będzie żadnej reakcji na kliknięcie przycisku, to prawdopodobnie zostanie on kliknięty drugi raz.
[/alert]

Ja zrobiłem prosty „loader”, który zmienia etykietę przycisku na wielokropek. Przy okazji przepisuję jako styl inline szerokość przycisku, aby uniknąć brzydkich skoków we względu na węższą treść.

Definiujemy więc obiekt data, w którym będziemy przekazywać do PHP nasze dane. WordPress wymaga tutaj tylko pozycji action, przez którą będzie wywoływał dany hak akcji. Czyli w WordPressie do AJAXa wymagane są: adres skryptu PHP oraz oraz obiekt z polem action przekazanym jako argument. W callbacku dostajemy obiekt, który wrócił z serwera.

Metoda post() dobrze może sugerować wysyłkę zapytania POST. AJAXa można w ogóle rozumieć jak wysłanie takiego zwykłego formularza na serwer.

Bardzo przydatną funkcją jest tutaj też metoda console.log() do której możemy podać dowolny obiekt. Bardzo ułatwia to debugowanie, ponieważ wszystko wyświetli się w konsoli przeglądarki.

Na tym etapie dorobiliśmy się czegoś takiego:

bug-preajax

 

Zadbajmy więc o jakąś sensowną odpowiedź.

Akcja

W WordPressie do akcji AJAXa możemy podpiąć się dwoma hakami:

wp_ajax_nopriv_{$akcja}
wp_ajax_{$akcja}

W których zmienna $akcja wskazuje na to pole action zdefiniowane w obiekcie data w JavaScripcie.

Pierwsza z nich wywoływana jest wyłącznie dla użytkowników niezalogowanych, a druga dla użytkowników zalogowanych.

Dodajemy więc do naszego konstruktora obie akcje.

public function __construct() {

    add_filter( 'the_content', array( $this, 'report_button' ), 10, 1 );
    
    add_action( 'wp_enqueue_scripts', array( $this, 'scripts' ) );

    add_action( 'wp_ajax_nopriv_send_bug_report', array( $this, 'send_bug_report' ) );
    add_action( 'wp_ajax_send_bug_report', array( $this, 'send_bug_report' ) );
    
}

Metoda obsługująca to pytanie wygląda tak:

function send_bug_report() {

    $data = $_POST;

    $post_title = get_the_title( intval( $data['post_id'] ) );

    wp_mail( '[email protected]', 'Bug report in post: ' . $post_title, $data['report'] );

    wp_send_json_success( __( 'Thanks for reporting!', 'reportabug' ) );

}

Pobieramy tytuł naszego posta, oraz wysyłamy wiadomość email do administratora.

Na końcu warto zwrócić jakąś odpowiedź do JavaScriptu. Do tego użyjemy funkcji wp_send_json_success(), która nie dość, że zakoduje podane dane do jsona to jeszcze na końcu zrobi wymagane die().

Nie potrzeba robić tutaj:

echo json_encode( $data );
die();

Oraz później parsować to w JS. WordPress udostępnia do tego narzędzia, więc warto z nich skorzystać.

Do JavaScriptu dodajmy teraz obsługę tego co wróciło z serwera:

$.post(settings.ajaxurl, data, function(response) {

    // remove button and textarea
    $button.remove();
    $('.report-a-bug-message').remove();

    // display success message
    $('.report-a-bug-response').html( response.data );

});

Usuwamy przycisk oraz pole tekstowe i wyświetlamy zdefiniowaną wiadomość w przygotowanym na samym początku paragrafie.

Zabezpieczenie

Nonce

Bardzo prostym, ale też efektywnym zabezpieczeniem jest nonce. Jest to prosty kod generowany na podstawie ciągu znaków, który później jest weryfikowany.

Dodajmy więc go do naszego przycisku jako jeszcze jeden atrybut data.

$nonce = wp_create_nonce( 'report_a_bug_' . get_the_ID() );

$content .= '<div class="report-a-bug">
                <button class="show-form" data-nonce="' . $nonce . '" data-post_id="' . get_the_ID() . '">' . 
                    __( 'Report a bug', 'reportabug' ) . 
                '</button>
                <textarea class="report-a-bug-message" placeholder="' . __( 'Describe what\'s wrong...', 'reportabug' ) . '"></textarea>
                <p class="report-a-bug-response"></p>
            </div>';

Nasz nonce to nie jest stały ciąg, postanowiłem wzmocnić go ID postu. To sprawi, że każdy post będzie miał inny hash.

Wystarczy go przekazać jeszcze w obiekcie data w JavaScripcie:

// set ajax data
var data = {
    'action' : 'send_bug_report',
    'post_id': $button.data('post_id'),
    'nonce'  : $button.data('nonce'),
    'report' : $('.report-a-bug-message').val()
};

I zweryfikować w PHP za pomocą specjalnie do tego przeznaczonej funkcji check_ajax_referer().

function send_bug_report() {

    $data = $_POST;

    // check the nonce
    if ( check_ajax_referer( 'report_a_bug_' . $data['post_id'], 'nonce', false ) == false ) {
        wp_send_json_error();
    }

    $post_title = get_the_title( intval( $data['post_id'] ) );

    wp_mail( '[email protected]', 'Bug report in post: ' . $post_title, sanitize_text_field( $data['report'] ) );

    wp_send_json_success( __( 'Thanks for reporting!', 'reportabug' ) );

}

Jej pierwszy argument to ten sam ciąg użyty go wygenerowania hasha, drugi wskazuje na klucz w tablicy REQUEST (to połączenie tablicy GET i POST) pod którym będzie szukał hasha, a trzecim możemy kontrolować czy funkcja automatycznie zrobi wp_die(). Nie chcemy, żeby to zrobiła, bo zamiast tego wyślemy z powrotem użytkownikowi wiadomość, że coś poszło nie tak za pomocą funkcji wp_send_json_error().

Zauważ również sanityzację treści emaila. Funkcja sanitize_text_field() zadba o to, żebyśmy nie wysłali w mailu złamań linii, tagów html itd.

Jeszcze tylko opakujmy nasze działanie w instrukcję warunlkową if, aby sprawdzić czy aby wszystko zostało zrobione pomyślnie.

$.post(settings.ajaxurl, data, function(response) {

    if ( response.success == true ) {

        // remove button and textarea
        $button.remove();
        $('.report-a-bug-message').remove();

        // display success message
        $('.report-a-bug-response').html( response.data );

    }

});

Zablokowanie przycisku

Kiedy użytkownik kliknąłby dwa razy w przycisk, to zostałyby wysłane dwa takie same emaile. To ze względu, że AJAX jest domyślnie asynchroniczny.

Więc po kliknięciu w przycisk zablokujmy go ustawiając mu atrybut disabled. Odblokujemy go dopiero po otrzymaniu odpowiedzi od serwera.

$('.report-a-bug').on('click', '.send-report', function(event) {

    var $button = $(this);

    $button.width( $button.width() ).text('...').prop('disabled', true);

    // set ajax data
    var data = {...};

    $.post(settings.ajaxurl, data, function(response) {

        if ( response.success == true ) {...}

        // enable button
        $button.prop('disabled', false);

    });

});

Obsługa błędów

Walidacja

A co jeśli użytkownik będzie chciał wysłać pustą wiadomość? Zablokujmy taką możliwość.

W JavaScripcie wystarczy dodać prostą walidację pola textowego. Jeśli po kliknięciu w przycisk będzie ono puste to jego ramka zmieni się na czerwony kolor. Jeśli za drugim razem wiadomość będzie wpisana to zmieni się ona na kolor domyślny.

$('.report-a-bug').on('click', '.send-report', function(event) {

    var $button = $(this);

    // check if message is not empty
    if ( $('.report-a-bug-message').val().length === 0 ) {
        $('.report-a-bug-message').css('border', '1px solid red');
        return false;
    } else {
        $('.report-a-bug-message').css('border', '1px solid rgba(51, 51, 51, 0.1)');
    }

    $button.width( $button.width() ).text('...').prop('disabled', true);

    // set ajax data
    var data = {...};

    $.post(settings.ajaxurl, data, function(response) {

        if ( response.success == true ) {...}

        // enable button
        $button.prop('disabled', false);

    });

});

Rezultat wywołanej funkcji

Problem może być z samym wysłaniem maila. Jeśli korzystamy z serwera SMTP może być on niedostępny akurat w tej chwili kiedy przyjdzie prośba o jego użycie. Na szczęście funkcja wp_mail zwraca wartość true w przypadku powodzenia i false w przypadku niepowodzenia. W zależności od tego zwracamy odpowiednią informację do JS.

function send_bug_report() {

    $data = $_POST;

    // check the nonce
    if ( check_ajax_referer( 'report_a_bug_' . $data['post_id'], 'nonce', false ) == false ) {
        wp_send_json_error();
    }

    $post_title = get_the_title( intval( $data['post_id'] ) );

    $result = wp_mail( '[email protected]', 'Bug report in post: ' . $post_title, sanitize_text_field( $data['report'] ) );

    if ( $result == false ) {
        wp_send_json_success( __( 'Thanks for reporting!', 'reportabug' ) );
    } else {
        wp_send_json_error();
    }

}

Dowolne wiadomości możemy również zdefiniować podczas lokalizacji skryptów. Tak też zrobiłem ja:

// set variables for script
wp_localize_script( 'report-a-bug', 'settings', array(
    'ajaxurl'    => admin_url( 'admin-ajax.php' ),
    'send_label' => __( 'Send report', 'reportabug' ),
    'error'      => __( 'Sorry, something goes wrong. Please try again', 'reportabug' )
) );

W JS jeśli wróci nam error pola zostają tak jak były, a jedynie wyświetlany jest błąd. Formularz można spróbować wysłać ponownie. Zmieniamy również etykietkę na taką jak była pokazując, że AJAX już skończył swoje działanie.

$.post(settings.ajaxurl, data, function(response) {

    if ( response.success == true ) {

        // remove button and textarea
        $button.remove();
        $('.report-a-bug-message').remove();

        // display success message
        $('.report-a-bug-response').html( response.data );

    } else {

        // display error message
        $('.report-a-bug-response').html(settings.error);

    }

    // enable button and revert label
    $button.text( settings.send_label ).prop('disabled', false);

});

Działanie

Poniżej prezentacja jak działa walidacja i obsługa błędów:

bugerror

A kiedy wszystko będzie ok:

bugok

I to już wszystko. Udostępniam również całą wtyczkę, którą można kopiować w swoich projektach. Częstuj się!

Jeśli uważasz, że ten artykuł był pomocny, proszę udostępnij go!

Opublikowany przez Kuba Mikita

Miłośnik minimalizmu i prostoty, bo nie potrafi stworzyć niczego ładnego. Ma kołdrę, na której wypisane są funkcje WordPressa.

12 odpowiedzi na “AJAX w WordPress – kompletny przewodnik”

    1. Raczej jest przykładem, ale można ją użyć na stronie bo działa.

      Brakuje w niej tylko jakiejś captchy albo innego zabezpieczenia antybotowego.

  1. Fajny wpis, czegoś takiego mi właśnie tutaj brakowało :)
    Ale warto wspomnieć, że Google przestaje indeksować treści zawarte w Ajax. Czyli warto przemyśleć do czego lepiej z niego nie korzystać.

    1. A czy kiedykolwiek indeksowało? :) bo odkąd pamiętam to treści dynamiczne nigdy nie były indeksowane

  2. Cześć, sam mogę zgłosić drobny błąd, nie uruchomił się shortcode. chodzi o alert=”notice” w tym artykule

    1. Cześć Piotr, co rozumiesz przez „nie uruchomił się shortcode”? Widzisz po prostu treść shortcode?

      Jeśli tak to czy na pewno wtyczka jest aktywna i użyłeś go w miejscu gdzie shortcody są renderowane?

  3. Hej Kuba, dzięki za wpis. Mała prośba – link pod „Pobierz wtyczkę „zgłoś błąd” do pobrania wtyczki nie działa. Możesz to poprawić? U mnie jest skierowany do aktualnej strony.

Możliwość komentowania została wyłączona.