20.5.1. Backbone.js Bibliothek

Die REST-API kommt mit einer Backbone.JS-Bibliothek namens wp-api daher. Sie kann mittels

<?php
wp_enqueue_script( 'wp-api' );
?>

eingebunden werden. Alternativ können Sie ein Abhängigkeit zu diesem Script schaffen. Siehe dazu auch das Kapitel „CSS und JavaScript in WordPress“ oder im Beispiel unten.

Die Bibliothek durchsucht das WordPress-eigene Schema der REST-API (alle Endpunkte unter wp/v2) und generiert daraus Backbone Models und Collections. Die zwei Root-Objekte findet man in der globalen Variable wp.api.models und wp.api.collections wieder.

Die Models beinhalten die einzelnen Objekttypen, die möglich sind. Unter anderem diese:

  • Category
  • Comment
  • Media
  • Page
  • PageMeta
  • PageRevision
  • Post
  • PostMeta
  • PostRevision
  • Schema
  • Status
  • Tag
  • Taxonomy
  • Type
  • User

Die Collections dienen zur Abfrage mehrerer einzelnen Objekte. In wp.api.collections findet man unter anderem diese hier:

  • Categories
  • Comments
  • Media
  • PageMeta
  • PageRevisions
  • Pages
  • Posts
  • Statuses
  • Tags
  • Taxonomies
  • Types
  • Users

Damit können Sie einzelne oder mehrere Objekte über die REST-API abrufen, verändern und löschen.

Die Standardwerte der Models oder Collections findet man in deren .prototype.args-Parameter. So hat das Objekt wp.api.models.Post.prototype.args zum Beispiel folgende Inhalte:

You’re not allowed to see this content. Please log in first.

Schauen wir uns das ganze anhand eines Beispiels an. Holen wir unser Track External Links-Plugin aus den vorherigen Kapitel und erweitern es wie folgt:

Schritt 1: JavaScript einbetten

In der Datei /inc/backend.php ergänzen wir die Klasse wie folgt:

<?php 
function __construct() {

	...

	add_action( 'admin_init', array( $this, 'register_scripts' ) );
	add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
}

public function register_scripts() {
	wp_register_script(
		'track-external-links',
		plugin_dir_url( TEL_PLUGIN_FILE ) . 'js/backend.js',
		array( 'wp-api' ),
		filemtime( plugin_dir_path( TEL_PLUGIN_FILE ) . 'js/backend.js' )
	);
}

public function enqueue_scripts( $hook_suffix ) {

	if ( 'toplevel_page_tel' !== $hook_suffix ) {
		return;
	}

	wp_enqueue_script( 'track-external-links' );
}
?>

Gesamtes Beispiel bei Github ansehen

Die erste Methode registriert ein neues JavaScript. Die Zweite fügt es auf der Seite ein, die alle Links auflistet. Das Script fügen wir unter /js/backend.js ein. Es hat noch keinen Inhalt.

Schritt 2: Screen Options richtig speichern

Dazu ergänzen wir folgenden Hook damit unsere Einstellungen unter „Ansicht anpassen“ auch korrekt gespeichert werden. Das fehlte im vorherigen Beispiel und rüsten wir jetzt nach.

You’re not allowed to see this content. Please log in first.

Schritt 3: JavaScript-Klasse erstellen

Wir bereiten das Script backend.js vor und erstellen ein eigene Klasse für unsere Zwecke:

You’re not allowed to see this content. Please log in first.

Schritt 4: Javascript-Events erstellen

Wir wollen zunächst die Vor- und zurück-Buttons ansprechen. Dazu müssen wir einige Event-Listeners erstellen.

You’re not allowed to see this content. Please log in first.

Wenn der HTML-Inhalt geladen ist, sind auch die Buttons verfügbar. Danach durchlaufen wir zwei Schleifen. Einmal die Button-Navigation (diese existiert zweimal, siehe Bild unten) und natürlich die jeweiligen Buttons selbst:

You’re not allowed to see this content. Please log in first.

Schritt 5: Seitennummern berechnen

Nun benötigen wir die on_click_pagination()-Funktion. Sie soll berechnen, welche Seite als nächstes geladen werden soll damit am Ende die Links nachgeladen werden können:

this.on_click_pagination = function ( el ) {
  var self = this,
      args = {};

  if ( el.classList.contains( 'first-page' ) ) {
    args.page = 1;
  }
  else if ( el.classList.contains( 'last-page' ) ) {
    /* Berechnen der letzten Seitennummer */
  }
  else if ( el.classList.contains( 'prev-page' ) ) {
    /* Berechnen der vorherigen Seitennummer */
  }
  else if ( el.classList.contains( 'next-page' ) ) {
    /* Berechnen der nächsten Seitennummer */
  }

  args.per_page = this.get_per_page();

  self.load_links( args );
};

Allerdings fehlen uns noch ein paar Werte. Zum einen, der per_page-Parameter. Zum anderen die Seitennummer.

Ersteres ergänzen wir wie folgt:

this.get_per_page = function () {
  return parseInt( document.getElementById( 'links_per_page' ).value );
};

Letzteres ist etwas aufwändiger. In der init-Methode müssen wir zuerst prüfen, auf welcher Seite wir uns befinden. Dazu lesen wir den URL-Parameter entsprechend aus:

this.init = function () {
  var self = this;

  ...
  
  this.find_current_page_from_url();

};

Dazu benötigen wir:

this.find_current_page_from_url = function () {

  var paged_r = window.location.search.match( new RegExp( 'paged=([0-9]+)' ) );

  if ( null === paged_r ) {
    return;
  }

  if ( paged_r[1] !== undefined ) {
    this.current_page = parseInt( paged_r[1] );
  }
};

Wir speichern die Variable dann in der Eigenschaft current_page um später drauf zurückgreifen zu können.

Was uns nun noch fehlt ist die Gesamtseitenzahl bzw. eigentlich die Nummer der letzten Seite. Um diese berechnen zu können übergeben wir den Wert, den die Methode record_count() der Klasse Link_Table ausgibt, an das Javascript.

Dazu erweitern wir die Datei backend.php wie folgt:

<?php
public function enqueue_scripts( $hook_suffix ) {

	if ( 'toplevel_page_tel' !== $hook_suffix ) {
		return;
	}

	wp_enqueue_script( 'track-external-links' );

	$args = array(
		'total_records' => Link_Table::record_count()
	);

	wp_add_inline_script(
		'track-external-links',
		sprintf( 'var tel_script_constants = %s;', json_encode( (object) $args ) ),
		'before'
	);
}
?>

In der Datei backend.js ergänzen wir:

this.init = function () {
  var self = this;

  ...

  this.constants = window.tel_script_constants;
};

Nun können wir über this.consants.total_records die Gesamtzahl der Links auslesen. Und damit können wir die Methode on_click_pagination() fast finalisieren:

this.on_click_pagination = function ( el ) {

  var args = {
    'per_page': this.get_per_page(),
  };

  var max_pages = Math.ceil( this.constants.total_records / Math.max( args.per_page, 1 ) );

  if ( el.classList.contains( 'first-page' ) ) {
    args.page = 1;
  }
  else if ( el.classList.contains( 'last-page' ) ) {
    args.page = max_pages;
  }
  else if ( el.classList.contains( 'prev-page' ) ) {
    args.page = Math.max( this.current_page - 1, 1 );
  }
  else if ( el.classList.contains( 'next-page' ) ) {
    args.page = Math.max( this.current_page + 1, max_pages );
  }
};

Sie können sich die gesamten Codeänderungen direkt bei Github ansehen. Hier zur Wiederholung nochmal die backend.js-Datei:

window.outgoing_links_class = function () {
  'use strict';

  this.current_page = 1;
  this.constants    = null;

  /**
   * Initializing.
   *
   * @since 0.3.0
   */
  this.init = function () {
    var self = this;

    /* Waiting for the DOM to be loaded */
    document.addEventListener( 'DOMContentLoaded', function () {
      self.add_pagination_event_handlers();
    } );

    this.find_current_page_from_url();

    this.constants = window.tel_script_constants;
  };


  /**
   * Reads the `per_page` Parameter from the current URL.
   */
  this.find_current_page_from_url = function () {

    var paged_r = window.location.search.match( new RegExp( 'paged=([0-9]+)' ) );

    if ( null === paged_r ) {
      return;
    }

    if ( paged_r[1] !== undefined ) {
      this.current_page = parseInt( paged_r[1] );
    }
  };


  /**
   * Add Event listeners to pagination links.
   * @since 0.3.0
   */
  this.add_pagination_event_handlers = function () {
    var self             = this,
        i,
        j,
        links,
        pagination_links = document.getElementsByClassName( 'pagination-links' );

    if ( pagination_links.length <= 0 ) {
      return;
    }

    for ( j = 0; j < pagination_links.length; j ++ ) {
      links = pagination_links[j].getElementsByTagName( 'a' );

      if ( links.length <= 0 ) {
        continue;
      }

      for ( i = 0; i < links.length; i ++ ) {
        links[i].addEventListener( 'click', function ( e ) {
          e.preventDefault();
          self.on_click_pagination( this );
        } );
      }
    }

  };


  /**
   * Handles onClick events for pagination links.
   *
   * @since 0.3.0
   */
  this.on_click_pagination = function ( el ) {

    var args = {
      'per_page': this.get_per_page(),
    };

    var max_pages = Math.ceil( this.constants.total_records / Math.max( args.per_page, 1 ) );

    if ( el.classList.contains( 'first-page' ) ) {
      args.page = 1;
    }
    else if ( el.classList.contains( 'last-page' ) ) {
      args.page = max_pages;
    }
    else if ( el.classList.contains( 'prev-page' ) ) {
      args.page = Math.max( this.current_page - 1, 1 );
    }
    else if ( el.classList.contains( 'next-page' ) ) {
      args.page = Math.min( this.current_page + 1, max_pages );
    }
  };


  /**
   * Gets the 'per_page' parameter from the screen options.
   *
   * @since 0.3.0
   */
  this.get_per_page = function () {
    return parseInt( document.getElementById( 'links_per_page' ).value );
  };

};

window.outgoing_links = new window.outgoing_links_class();
window.outgoing_links.init();

Schritt 6: Daten abrufen

Jetzt geht es an’s Eingemachte. Wir wollen die Daten abrufen und BackboneJS bzw. die WP-API dazu benutzen. Da die API – wie oben beschrieben – nur das Schema des Namespaces /wp/v2/ abruft, baut es unsere Models und Collections nicht mit. Deswegen müssen wir diese manuell ergänzen:

In der init-Methode ergänzen wir:

this.init = function () {
  ...

  wp.api.loadPromise.done( function () {
    self.init_backbone();
  } );
};

Aufpassen: wie oben erwähnt muss wp-api zuerst das Schema abrufen. Das bedeutet, dass wir warten müssen, bis sie vollständig initialisiert wurde.

Dann brauchen wir noch die Methode init_backbone(). Wir haben das Problem, dass wir keine OutgoingLinks Models oder Collections haben weil WordPress nur das Schema unter dem Namespace wp/v2 abruft. Deswegen müssen wir ihn noch ergänzen.

this.init_backbone = function () {

  var args           = wpApiSettings;
  args.versionString = 'tel/v1/';

  wp.api.init( args );
};

Hinweis
Beachten Sie den zwingend notwendigen Trailing-Slash am Ende des Version-Strings.

Und schon kann es losgehen. Wir ergänzen:

this.on_click_pagination = function ( el ) {
  ...

  this.load_links( args );
};

Sowie:

this.load_links = function ( args ) {
  var self = this;

  var links_collection = new wp.api.collections.OutgoingLinks();

  links_collection.fetch( {'data': args} ).done( function ( links ) {
    self.update_table( links );
    self.current_page = args.page;
    self.update_pagination_nav( args.page );
  } );
};

Wie Sie sehen, aktualisieren wir self.current_page um die aktuelle Seitenanzahl. Darüber hinaus brauchen wir eine Methode zur Aktualisierung der Tabelleninhalte sowie der Navigation. Auf letzteres gehen ich nicht näher ein. Sie können sie direkt bei Github ansehen.

Schritt 7: Daten darstellen

You’re not allowed to see this content. Please log in first.

Zusammenfassung

Sie haben nun gelernt, wie Sie die wp-api einbinden und zum Abruf von Daten nutzen können. Sehen Sie sich den gesamten Code bei Github an um nochmal genauer in den Code einzutauchen.

Natürlich lassen sich nicht nur Daten abrufen sondern auch verändern und löschen. Das geht wie folgt:

Einzelne Einträge verändern

Sie erstellen immer ein neues Objekt der Klasse, z.B.:

var params = { id: 18 };
var link = new wp.api.models.OutgoingLinks( params );

Durch die Übergabe eines oder mehrerer Parameter können Sie bestimmen, welcher Eintrag genau gemeint ist. Hier also der Eintrag mit der ID 18.

Nun rufen wir den Eintrag direkt von der Datenbank ab. BackboneJS macht für uns im Hintergrund einen AJAX-Aufruf an den entsprechenden REST-API Endpunkt:

link.fetch();

Nun lassen sich die Werte abfragen:

var id = links.get( 'id' );
var slug = inks.get( 'slug' );
var title = links.get( 'title' );

Oder natürlich verändern:

link.set( 'title', 'Neuer Titel' );

Das Speichern schiebt die Änderungen über die REST-API (Ajax-Aufruf) in die Datenbank:

link.save();