jQuery w WordPress

W tym artykule poruszę temat korzystania z jQuery w WordPress. Na nasze potrzeby stworzymy prostą wtyczkę z listą zadań TO DO do której będziemy dodawać tematy i oznaczać je jako wykonane.. Tematy oznaczone będą znikać z listy. Do przechowywania danych użyjemy bazy danych WordPress, a żeby uatrakcyjnić ją graficznie skorzystamy z biblioteki bootstrap. Zanim zaczniemy zdefiniujmy najpierw czym jest jQuery i jak się go używa.

jQuery

jQuery to biblioteka języka JavaScript, która znacznie ułatwia pisanie skryptów po stronie klienta w przeglądarkach internetowych. Jest to szybkie, małe i bogate w funkcje narzędzie, które umożliwia łatwiejsze wykonywanie zadań takich jak manipulowanie dokumentem HTML, obsługa zdarzeń, animowanie elementów oraz komunikacja asynchroniczna z serwerem przy użyciu AJAX. jQuery zostało stworzone, aby uprościć skomplikowane zadania w czystym JavaScript, oferując bardziej przejrzysty i skondensowany kod.

Dla porządku dodam, że wszystko co można osiągnąć za pomocą biblioteki jQuery, można również osiągnąć korzystając ze zwykłego JavaScript (to w końcu bibliotek napisana w JavaScript). Jeżeli kiedyś spotkasz się z terminem, że ktoś używa vanilla.js zamiast np. jQuery oznacza to, że korzysta z czystego JavaScript bez zewnętrznych bibliotek (taki żargon).

Asynchroniczne wysyłanie żądania

W uproszczeniu jQuery ułatwia manipulowanie elementami HTML na stronie w trakcie jej użytkowania. Ponadto pozwala na uruchomienie zewnętrznych skryptów bez przerywania pracy naszego programu (asynchronicznie). Skrypt uruchamia się jako osobny proces poza naszym głównym skryptem. Spójrzmy na przykład:

$.ajax({
	method: 'POST',
	dataType: 'json',
	url: 'zapisz-do-bazy.php', 
	data: {
        'dane': 'ucze sie jQuery'
	}, 
	success: function(result) {
		// zapisałem
	},
	error: function(result) {
	    // nie udało się zapisać
	}	
});

W powyższym przykładzie:

  • wysyłamy dane do pliku zapisz-do-bazy.php metodą POST
  • oczekujemy na odpowiedź w formacie json (jQuery będzie próbował przekonwertować odpowiedź samodzielnie), a to co zwróci serwer będzie w zmiennej result
  • jeżeli plik wyświetli prawidłową odpowiedź (w formacie JSON) to wykonujemy kod znajdujący się w podpiętej funkcji obiektu success
  • jeżeli nie udało się wysłać danych lub odpowiedź była nieprawidłowa wykonujemy kod w error

Spójrzmy teraz na drugi plik zapisz-do-bazy.php

saveToDb ( $_POST['dane'] );
echo json_encode (true);

Skrypt wykonuje jakąś funkcję zapisującą do bazy (saveToDb) do której przekazuje to co wysłaliśmy metodą POST. Następnie wyświetla true (czyli 1) kodując je za pomocą JSON-a.

Dla porządku dodam, że tego typu zapytania nazywa się zapytaniami AJAX.

Manipulacja DOM

Strona HTML składa się z wielu elementów (tzw. tagów) zawierających wewnątrz inne elementy np. wewnątrz elementu <div> możemy umieścić <a> z atrybutem href. Nie będę tutaj omawiał HTML-a. Zakładam że znasz jego podstawy. W dużym uproszczeniu – struktura pliku HTML (formalnie XML) nosi nazwę DOM (Document Object Model) i z tym terminem spotkasz się nie raz (np. każdy <div> na tej stronie jest elementem DOM).

Manipulacja DOM oznacza po prostu zmianę zawartości lub wartości wspomnianych tagów / atrybutów. Spójrzym na przykład z wykorzystaniem jQuery.

<div id="tekst_pierwszy">Witaj, jestem jakiś tekst</div>
<div class="tekst_drugi">Cześć, jestem jakiś tekst</div>

<script>
    $("#tekst_pierwszy).html ('Witaj, jestem inny tekst');
    $(".tekst_drugi).html ('Cześć, jestem inny tekst');
</script>

Powyżej tworzymy elementy <div> w których umieszczamy jakiś tekst. Następnie w skrypcie podmieniamy zawartość tych elementów znajdując je po identyfikatorze i klasie. Wywołanie kodu wyświetli więc teksty, który podmieniliśmy (chyba, że mamy bardzo wolny komputer to domyślne migną zanim zmienią się na drugie). Mam nadzieję, że jest to w miarę zrozumiałe.

Biblioteka jQuery

Korzystanie z jQuery wymaga dołączenia biblioteki do naszego skryptu. Najnowszą znajdziemy w jQuery CDN, np.

<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>

Przy użyciu JavaScript i jQuery możemy praktycznie dowolnie manipulować elementami wewnątrz DOM – podmieniać style, modyfikować elementy, atrybuty.. dodawać, usuwać itd. Pełna dokumentacja jQuery znajduje się na oficjalnej stronie jQuery, a ja będę tylko omawiał to co przyda nam się w projekcie.

Testowanie zapytań Ajax

Wywołanie z wykorzystaniem JavaScript (te, które omawialiśmy) nazywa się zapytaniami AJAX (wspomniałem o tym wcześniej). Testowanie kodu pisanego w ten sposób jest niestety dosyć problematyczne, gdyż uruchamiamy inny kod, który nie jest częścią bieżącego skryptu.

Błędów zwracanych przez kod często nie widać. Na szczęście z pomocą przychodzą nam narzędzia dla webmasterów, które są dostępne praktycznie w każdej przeglądarce (dostęp w Edge uzyskamy z poziomu menu lub po naciśnięciu CTRL+SHIFT+I). W Edge, w zakładce Network widzimy wszystkie zapytania, które są wysyłane przez skrypt, wraz z odpowiedziami. Tutaj też będą zwracane wszelkiego błędy.

Narzędzia dla webmasterów w Edge

jQuery w WordPress

WordPress korzysta z jQuery. Nie musimy więc dodawać biblioteki (jest to prawda, ale tylko dla panelu administratora). Zamiast używać $, korzystamy z jQuery. Spójrzmy raz jeszcze na poprzedni przykład, ale zamieńmy znak dolara.

<div id="tekst_pierwszy">Witaj, jestem jakiś tekst</div>
<div class="tekst_drugi">Cześć, jestem jakiś tekst</div>

<script>
    jQuery ("#tekst_pierwszy).html ('Witaj, jestem inny tekst');
    jQuery (".tekst_drugi).html ('Cześć, jestem inny tekst');
</script>

Znak $ mogą używać również inne biblioteki i dlatego nie jest to rezerwowane typowo pod jQuery. Można to obejść, ale po co – nie jest to aż tak problematyczne.

Druga kwestia jest związana z bezpieczeństwem. Jakiekolwiek przesyłanie danych w WordPress powinno być zabezpieczone, aby utrudnić ewentualne próby wywoływania skryptów przez tzw. script kids. W tym celu do zapytania dodajemy pewną wartość tekstową (tzw. token), którą sprawdzamy zanim wywołamy skrypt.

WordPress udostępnia mechanizm, który pozwala tworzyć i odczytywać losowo wygenerowane tokeny. Jedyne co musimy zrobić to sprawdzić czy wartość się zgadza. Spójrzmy raz jeszcze na zapytanie jQuery i prawidłową weryfikację.

jQuery.ajax({
	method: 'POST',
	dataType: 'json',
	url: 'zapisz-do-bazy.php', 
	data: {
        'dane': 'ucze sie jQuery',
        'token': 'action': '<?php echo wp_create_nonce('savetodb'); ?>'
	}, 
	success: function(result) {
		// zapisałem
	},
	error: function(result) {
	    // nie udało się zapisać
	}	
});
if ( wp_verify_nonce($_POST['token '], 'savetodb') ) {

    saveToDb ( $_POST['dane'] );
    echo json_encode (true);
}

W powyższym kodzie dodaliśmy element token w którym przesyłamy wartość utworzoną przez funkcję WordPress-a wp_create_nonce. Następnie Sprawdzamy czy ta wartość się zgadza i wykonujemy stosowny kod.

Ostatnią ważną rzeczą związaną z używaniem wywołań Ajax we wtyczkach WordPress jest źródło gdzie wysyłamy dane. Aby wszystko działało, zapytania musimy kierować do pliku admin-ajax.php, który przetwarza żądanie i ładuje docelowo kod z naszej wtyczki. Oznacza to, że kodu (domyślnie) nie umieszczamy w zewnętrznym pliku, a właśnie w naszej wtyczce (oczywiście możemy go dołączyć z zewnętrznego źródła). Adres wywołania (adres do pliku admin-ajax.php) uzyskamy korzystając ze zmiennej globalnej ajaxurl.

Spójrzmy na cały przykład.

<script type="text/javascript">

jQuery.ajax({
	method: 'POST',
	dataType: 'json',
	url: ajaxurl, 
	data: {
        'dane': 'ucze sie jQuery',
        'token': '<?php echo wp_create_nonce('savetodb'); ?>'
	}, 
	success: function(result) {
		// zapisałem
	},
	error: function(result) {
	    // nie udało się zapisać
	}	
});

</script>

<?php

if ( wp_verify_nonce($_POST['token'], 'savetodb') ) {

    saveToDb ( $_POST['dane'] );
    echo json_encode (true);
    wp_die();
}

?>

Wysyłamy zapytanie do samego siebie i wywołujemy stosowny kod. Na końcu dodaliśmy jeszcze instrukcję wp_die(), która kończy wywoływanie skryptu. Jest to ważne w kontekście AJAX-a, gdyż standardowo WordPress może zwrócić jakąś wartość (np. zero) i kod nam nie będzie działać, bo skrypt oczekuje odpowiedzi zakodowanej w formacie JSON.

Wtyczka TO DO

Uzbrojeni w tą wiedzę możemy przystąpić do tworzenia wtyczki.

Spiszmy najpierw co wtyczka ma robić:

  1. Wtyczka doda menu TO DO do panelu administratora.
  2. Wtyczka ma zawierać:
    • pole tekstowe z przyciskiem do dodania nowego zadania
    • listę zadań z opcją odhaczenia wykonania
    • datę zadania
    • numer
  3. Dodanie nowego zadania powoduje umieszczenie zadania na końcu listy z dzisiejszą datą i wyczyszczenie pola z zadaniem.
  4. Odhaczenie zadania usuwa z listy (zadań nie usuwamy z bazy – jedynie ukrywamy).
docelowa wtyczka

Te informacje wystarczą nam na zrozumienie co wtyczka ma robić. Spróbujmy teraz zastanowić się nad technicznymi aspektami jej budowy:

  1. Instalacja wtyczki powoduje utworzenie dedykowanej tabeli w bazie danych
  2. Odinstalowanie wtyczki usuwa tabelę z bazy danych
  3. Wtyczka wyświetla swój element w menu administora
  4. Wpisanie tekstu w polu i naciśnięcie przycisku spowoduje:
    • sprawdzenie czy pole nie jest puste
    • dodanie wpisu do bazy danych
    • pobranie wpisu z bazy danych i dodanie go do listy
  5. Naciśnięcie „wykonania zadania” spowoduje:
    • ukrycie zadania w bazie danych
    • pobranie ukrycia z bazy danych
    • usunięcie / ukrycie zadania z listy

Mając wszystko w miarę rozpisane i przemyślane możemy przystąpić do pisania kodu.

Plik główny wtyczki

Tworzymy plik w katalogu takim samym jak wtyczka i dodajemy w komentarzu informacje, które zaczyta WordPress. Pominę szczegóły dotyczące tworzenia wtyczek, gdyż pisałem o tym nie raz np w kursie tworzenia wtyczek w WordPress

<?php
/**
 * Plugin Name:       TO DO by Eskim Example
 * Plugin URI:        https://eskim.pl
 * Description:       Prosta lista TO DO
 * Version:           1.0
 * Requires at least: 5.2
 * Requires PHP:      7.0
 * Author:            Maciej Włodarczak
 * Author URI:        https://eskim.pl
 * License:           GPL v3 or later
 * Text Domain:       eskim_pl_example_to_do
 * Domain Path:       /languages
 */
 
 /**
 * Sprawdzenie czy wtyczka została zainicjowana przez WordPressa. Jeżeli nie - zwróć błąd 404.
 */
 if ( !function_exists( 'add_action' ) ) {

	header("HTTP/1.0 404 Not Found");
	die();
}

 ?>

Do tego pliku będziemy dołączać kolejne elementy.

Baza danych

Do działania wtyczki będziemy potrzebowali bazy danych WordPress. Utwórzmy sobie osobny plik w katalogu src którym umieścimy klasę do obsługi bazy. Tym razem nie będę go omawiał szczegółowo. O korzystaniu z bazy danych WordPress pisałem już wcześniej.

<?php

/**
 * Sprawdzenie czy wtyczka została zaincjowana przez WordPressa. Jeżeli nie zwróci błąd 404.
 */
if ( !function_exists( 'add_action' ) ) {

    header("HTTP/1.0 404 Not Found");
    die();
}

/**
 * Daje możliwość korzystania z funkcji dbDelta()
 */
require_once (ABSPATH . 'wp-admin/includes/upgrade.php');

/**
 * Klasa do obsługi bazy danych
 */
class eskim_pl_example_to_do_DB {


    /**
     * Tworzy tabelę w bazie danych
     */
    static public function CREATE () : void {

        global $wpdb;
        $table = $wpdb->prefix . "eskim_pl_example_to_do";

        $sql = "CREATE TABLE IF NOT EXISTS $table (
                    id INT NOT NULL AUTO_INCREMENT,
					task VARCHAR (200),
					created TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
					closed INT NULL,
					PRIMARY KEY (id)
				);";

        dbDelta( $sql );
    }

    /**
     * Usuwa tabelę z bazy danych
     */
    static public function DROP () : void {

        global $wpdb;
        $table = $wpdb->prefix . "eskim_pl_example_to_do";

        $wpdb->query( "DROP TABLE IF EXISTS $table" );
    }
}

?>

Na uwagę zasługują dwie klasy statyczne, które służą do tworzenia i usuwania tabeli. Jest to wymóg funkcji rejestrujących WordPress-a.

Załadujmy plik w pliku głównym wtyczki

include_once plugin_dir_path( __FILE__ ).'/src/db.php';

i dodajmy tworzenie i usuwanie tabeli w momencie aktywacji i deinstalacji.

register_activation_hook (__FILE__, [ 'eskim_pl_example_to_do_DB', 'CREATE' ] );
register_uninstall_hook (__FILE__, [ 'eskim_pl_example_to_do_DB', 'DELETE' ] );

Wywołujemy kod wewnątrz statycznych metod CREATE i DELETE w klasie eskim_pl_example_to_do_DB.

Menu

Zanim dodamy pozycję w menu utwórzmy katalog views i umieśćmy tam plik, który zostanie załadowany po kliknięciu na przycisk w panelu.

<?php

/**
 * Sprawdzenie czy wtyczka została zainicjowana przez WordPressa. Jeżeli nie zwróci błąd 404.
 */
if ( !function_exists( 'add_action' ) ) {

    header("HTTP/1.0 404 Not Found");
    die();
}

?>

Dodajmy menu z napisem TO DO w którym po kliknięciu załaduje się powyższy plik.

add_action('admin_menu', function () {

    add_menu_page(
        'TO DO',
        'TO DO',
        'manage_options',
        'eskim_pl_example_to_do',
        function() { include_once plugin_dir_path( __FILE__ ).'/views/main.php'; },
        'dashicons-calendar',
        20);
});

Interfejs graficzny

Zbudujmy interfejs graficzny (UI). Chcemy umieścić pole input z przyciskiem obok, a pod spodem listę zadań z przyciskiem do ukrywania każdego. Aby wyglądało to ładnie możemy skorzystać ze wspomnianej wcześniej bibliotek bootstrap. Załadujmy stosowne CSS-y oraz JavaScript ze strony Bootstrap i dodajmy je do pliku z layoutem.

add_action('admin_enqueue_scripts', function ($hook) {

    if ( strpos ($hook, 'eskim_pl_example_to_do') !== 0) return;
    wp_enqueue_style('eskim_pl_example_to_do_bootstrap_css', 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css');
    wp_enqueue_style('eskim_pl_example_to_do_bootstrap_icons', 'https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.2/font/bootstrap-icons.min.css');
    wp_enqueue_script('eskim_pl_example_to_do_bootstrap_js', 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js');
});

W powyższym kodzie ładujemy wszystko w ten sposób, aby CSS i Java Script nie wpływały na działanie całego panelu administratora, a były ładowane tylko wtedy kiedy użytkownik korzysta z naszej wtyczki (sprawdzamy czy nazwa skrypty wywołującego wtyczkę zaczyna się od eskim_pl_example_to_do).

Teraz dodamy przykładowy kod z tabelką.

<div class="w-50">
    <div class="input-group mb-3">
        <input type="text" class="form-control" placeholder="Zadanie">
        <button class="btn btn-primary" type="button"><i class="bi-plus-square"></i></button>
    </div>
    <table class="table">
        <thead>
        <tr>
            <th scope="col">#</th>
            <th scope="col">Data</th>
            <th scope="col">Zadanie</th>
            <th scope="col"></th>
        </tr>
        </thead>
        <tbody>
        <tr>
            <th scope="row">1</th>
            <td class="text-nowrap">2023-12-06</td>
            <td>Napisz artykuł o tworzeniu w wtyczek z wykorzystaniem jQuery.</td>
            <td class="text-nowrap m-0 p-0 text-end"><button class="btn btn-success" type="button"><i class="bi bi-check2-square"></i></button></td>
        </tr>
        <tr>
            <th scope="row">2</th>
            <td class="text-nowrap">2023-12-06</td>
            <td>Napisz artykuł o pobieraniu danych z hurtowni zewnętrznej (dropshipping) do WooCommerce. Zamówienia są realizowane automatycznie.</td>
            <td class="text-nowrap m-0 p-0 text-end"><button class="btn btn-success" type="button"><i class="bi bi-check2-square"></i></button></td>
        </tr>
        <tr>
            <th scope="row">3</th>
            <td class="text-nowrap">2023-12-06</td>
            <td>Napisz artykuł o tensorflow</td>
            <td class="text-nowrap m-0 p-0 text-end"><button class="btn btn-success" type="button"><i class="bi bi-check2-square"></i></button></td>
        </tr>
        </tbody>
    </table>
</div>

Na górze umieściliśmy pole input z przyciskiem po prawej stronie, a poniżej jest tabelka z zadaniami. Całość prezentuje się tak.

Wygląda to moim zdaniem nieźle. Przy użyciu biblioteki bootstrap tworzenie takich interfejsów jest stosunkowo proste, a sama dokumentacja czytelna i można śmiało przeanalizować powyższy kod we własnym zakresie.

Dodawanie wiersza tabeli

Skoro już mamy przetestowany interfejs to oprogramujmy go. Zaczniemy od generowania wiersza tabeli, któremu dodatkowo nadamy identyfikator (id), aby móc później się nim posługiwać np. przy ukrywaniu zadania. Identyfikator będzie powiązany z zadaniem w bazie danych. Dodatkowo do elementu nadrzędnego też dodamy identyfikator (tasks), gdyż to wewnątrz niego będziemy dodawać kolejne zadania. Zadania są numerowane, a numeracja zmieni się jak ukryjemy zadanie.

<tbody id="tasks">

<tr id="task_1">
    <th scope="row">1</th>
    <td class="text-nowrap">2023-12-06</td>
    <td>Napisz artykuł o tworzeniu w wtyczek z wykorzystaniem jQuery.</td>
    <td class="text-nowrap m-0 p-0 text-end"><button class="btn btn-success" type="button"><i class="bi bi-check2-square"></i></button></td>
</tr>

Teraz napiszmy kod, którego zadanie będzie rysowanie kolejnego wiersza. W celach testowych podepnijmy go do przycisku z zadaniami. Tutaj ponownie dodamy stosowny identyfikator z którego będziemy zaczytywać wartość i przekazywać ją do funkcji korzystając ze zwykłego CSS-a.

<input type="text" id="new_task" class="form-control" placeholder="Zadanie">
<button class="btn btn-primary" type="button" onclick="drawTask(1, getElementById('new_task').value.trim())"><i class="bi-plus-square"></i></button>

<script type="text/javascript">

    var count = 0;
    var today = new Date().toISOString().slice(0, 10);

    function drawTask(id, task) {

        count++;
        jQuery("#tasks").append(
            '<tr id="task_'+id+'">'+
            '<th scope="row">'+count+'</th>'+
            '<td class="text-nowrap">'+today+'</td>'+
            '<td>'+task+'</td>'+
            '<td class="text-nowrap m-0 p-0 text-end"><button class="btn btn-success" type="button"><i class="bi bi-check2-square"></i></button></td>'+
            '</tr>'
        );
    }

</script>

Po wpisaniu kodu naciśnięcie przycisku powoduje dodanie zadania na koniec listy wraz z kolejnym numerem. O taki efekt nam chodziło.

Oczywiście na chwilę obecną zadania nie są zapisywane, a identyfikator jest zawsze taki sam (1). Rozbudujmy wtyczkę o zapisywanie zadań do bazy i zwracanie zapisanej wartości w bazie (w ten sposób mamy pewność, że zadanie się zapisało).

Zapisywanie zadania w bazie

Zapis będzie się odbywał po naciśnięciu przycisku – asynchronicznie. Po kliknięciu wywoływany jest osobny skrypt, który zajmie się obsługą naszego żądania.

W katalogu src dodajmy plik ajax.php oraz dołączmy go do naszego projektu.

<?php

/**
 * Sprawdzenie czy wtyczka została zainicjowana przez WordPressa. Jeżeli nie zwróci błąd 404.
 */
if ( !function_exists( 'add_action' ) ) {

    header("HTTP/1.0 404 Not Found");
    die();
}

?>
include_once plugin_dir_path( __FILE__ ).'/src/ajax.php';

W pliku z widokiem będziemy wysyłać dane do skryptu ajax.php. W pierwszej kolejności oprogramujemy dodanie zadania do bazy.

function saveTask(task) {

    if (task.length === 0) return;

    jQuery.ajax({
        method: 'POST',
        dataType: 'json',
        url: ajaxurl,
        data: {
            'task': task,
            'action': '<?php echo wp_create_nonce('saveTask'); ?>'
        },
        success: function (result) {
            drawTask (result['id'], result['task']);
            $(#new_task).val();
        },
        error: function (result) {
            alert ('Nie udało się zapisać');
        }
    });
}

W powyższym przykładzie wysyłamy dane do wtyczki (z tego powodu dołączaliśmy plik ajax.php do samej wtyczki), a następnie dodajemy zadanie do tabeli przy użyciu wcześniej napisanej funkcji drawTask(). Ponadto czyścimy pole tekstowe służące do dodawania zadań. Wcześniej jednak sprawdzamy czy nie został przekazany pusty ciągu znaków oraz umieszczamy token w celu zabezpieczenia skryptu jak i weryfikacji czy zapytanie pochodzi z naszego serwera.

Aby wysyłka działała musimy jeszcze podpiąć tą akcję pod przycisk. W tym celu poprawiamy zdarzenie onclick przy przycisku.

<button class="btn btn-primary" type="button" onclick="saveTask(getElementById('new_task').value.trim())"><i class="bi-plus-square"></i></button>

W pliku do obsługi bazy danych umieszczamy kod odpowiadający za dodawanie pojedynczego zadania do bazy danych.

/**
 * Dodaje zadanie
 *
 * @param string $task Zadanie do zapisania
 * @return int id zadania w bazie
 */
public function addTask (string $task) : int {


    global $wpdb;
    $table = $wpdb->prefix . "eskim_pl_example_to_do";

    $wpdb->insert( $table,
        [
            'task' => $task
]);
    return $wpdb->insert_id;
}

Teraz w pliku ajax.php dodajemy obsługę żądania zapisu (wtyczka wysyła żądanie AJAX, aby zapisać dane w bazie).

if ( wp_verify_nonce($_POST['action'], 'saveTask' ) ) {

    $db = new eskim_pl_example_to_do_DB();
    $id = $db->addTask($_POST['task']);
    $task = $db->getTask($id);

    echo json_encode([
        'id' => $id,
        'task' => $task
    ]);
    wp_die();
}

Po naciśnięciu przycisku zadanie dodaje nam się do listy co oznacza, że zapisało się również w bazie (zauważ, że mechanizm najpierw umieszcza w bazie, a potem pobiera raz jeszcze dane z bazy).

Wyświetlanie zapisanych zadań

Dodajemy zadania, ale jeszcze ich nie wyświetlamy po wejściu w TO DO. Stwórzmy we wtyczce kilka testowych zadań. Oprogramujemy ich pobieranie. Lista zadań ma nam się załadować od razu po wejściu na zakładkę. Nie musimy do tego celu wykorzystywać AJAX-a (chociaż możemy). Wystarczy pobrać dane z bazy i w pętli utworzyć poszczególne wiersze tabeli.

Dodajmy najpierw pobieranie danych z bazy

    /**
     * Pobiera aktywne zadania
     *
     * @return array|null
     */
    public function getActiveTasks () : ?array {

        global $wpdb;
        $table = $wpdb->prefix . "eskim_pl_example_to_do";

        $query = $wpdb->prepare ("
			SELECT id, task, DATE_FORMAT (created, '%%Y-%%m-%%d') AS created
			FROM $table 
			WHERE closed IS NULL
            ORDER BY created
        ");

        $result = $wpdb->get_results ($query);
        if ($result === null) return [];
        return $result;
    }

Zwróć uwagę, że aby użyć znaków % w funkcji SQL DATE_FORMAT musieliśmy je poprzedzić procentami. Dodatkowo sprawdzamy czy funkcja zwróciła jakąś wartość. Jeżeli nie – zwracamy pustą tablicę.

Wywołajmy tą metodę podczas ładowania listy zadań.

$db = new eskim_pl_example_to_do_DB();
$count = 0;

foreach ($db->getActiveTasks() as $entity) {

    $count++;
    echo "
    <tr id='task_$entity->id'>
        <th scope='row'>$count</th>
        <td class='text-nowrap'>$entity->created</td>
        <td>$entity->task</td>
        <td class='text-nowrap m-0 p-0 text-end'><button class='btn btn-success' type='button'><i class='bi bi-check2-square'></i></button></td>
    </tr>
    ";
}

Przy uruchomieniu listy, ładuje się z bazy to co dodaliśmy w trakcie testów.

Ukrywanie zadań

Pozostało już tylko ukrywanie aktywnych zadań. W tym celu musimy ustawić datę ukrycia w bazie (closed) i usunąć wpis z listy. Na koniec należy ponownie zapisać numery zadań dla każdego wiersza.

Podobnie jak poprzednio, dodajmy najpierw metodę ukrywającą dane w bazie.

/**
 * Ukrywa zadanie
 *
 * @param int $id Id zadania
 * @return void
 */
public function hideTask (int $id) : void {

    global $wpdb;
    $table = $wpdb->prefix . "eskim_pl_example_to_do";

    $wpdb->update( $table,

        ['closed' => time()],
        ['id' => $id],
        ['%d'],
        ['%d']
    );
}

oraz metodę, która sprawdza czy zadanie jest ukryte.

public function isHidden (int $id): bool {

    global $wpdb;
    $table = $wpdb->prefix . "eskim_pl_example_to_do";

    $query = $wpdb->prepare("
            SELECT closed
            FROM $table 
            WHERE id = %d",
        $id
    );

    $hidden = $wpdb->get_var ($query);
    return $hidden > 0;
}

Jeżeli zwrócona wartość jest większa od zera to znaczy, że prawidłowo zostało ukryte pole.

Oprogramujmy odpowiednie zapytanie w pliku wtyczki.

function hideTask(id) {
    
    jQuery.ajax({
        method: 'POST',
        dataType: 'json',
        url: ajaxurl,
        data: {
            'id': id,
            'action': '<?php echo wp_create_nonce('hideTask'); ?>'
        },
        success: function (result) {
            if (result == id) jQuery('#task_'+id).hide();
        },
        error: function (result) {
            alert ('Nie udało się ukryć');
        }
    });
}

Jeżeli uda się ukryć zadanie, wówczas ukrywamy odpowiedni wiersz.

Teraz powinniśmy dodać wywołanie funkcji hideTask(id) dla przycisków ukrywających. Musimy to zrobić zarówno podczas tworzenia tabeli jak i dodawania nowego zadania do listy.

<td class="text-nowrap m-0 p-0 text-end">
<button class="btn btn-success" type="button" onclick="hideTask('+id+')"><i class="bi bi-check2-square"></i>
</button>
</td>

W pliku ajax.php umieszczamy odpowiedź na powyższe zapytanie i zwracamy identyfikator ukrytego zadania.

elseif ( wp_verify_nonce($_POST['action'], 'hideTask' ) ) {

    $db = new eskim_pl_example_to_do_DB();
    $db->hideTask($_POST['id']);
    $hidden = $db->isHidden($_POST['id']);

    if ($hidden) {
        echo json_encode($_POST['id']);
        wp_die();
    }
}

Ponowna numeracja

Po ukryciu zadania ze środka zostaje zaburzona numeracja. Możemy to naprawić zmieniając wszystkie wartości w tabeli. Dopiszmy do odpowiedzi na ukrycie poniższy kod.

var count = 0;
let elements = jQuery('tr[id^="task_"]').children('th');
jQuery.each( elements, function( index, item ){

    if (!jQuery(this).is(":hidden")) {
        count++;
        jQuery(this).html(count);
    }
});

W powyższym skrypcie szukamy elementów tr (wiersze tabeli) o identyfikatorze zaczynającym się od task_ i pobieramy ich podrzędne elementy th (miejsce w którym znajduje się licznik). Następnie iterujemy po nich i jeżeli element nie jest ukryty to aktualizujemy licznik.

Zakończenie

Skrypt oczywiście można rozbudować, dodać kolorowanie po najechaniu na wiersz, obsługę użytkowników, lepszą obsługę błędów, czyszczenie bazy oraz datę deadline’u. Możliwości jest sporo, ale już znacznie wykraczają ponad prostą implementację listy TO DO.

Tradycyjnie kod na GitHub