AngularJS in WordPress gebruiken

Gepost in: Algemeen Started by

AngularJS in WordPress gebruiken

Wanneer je een wordpress site aan het bouwen bent (en je bent grote fan van AngularJS), dan kan je gerust AngularJS implementeren in WordPress. Onderstaande blogpost is geen uitgebreide tutorial over hoe AngularJS en WordPress in elkaar zitten. Daarom is een (basis) kennis van AngularJS en WordPress aangewezen.

In deze post zal ik je wegwijs maken in hoe je AngularJS opzet in WordPress, als het nu op de frontend is, in het admin gedeelte, voor de hele site, of enkel voor een deel van uw website. Er zijn ook enkele addertjes onder het gras waar je rekening mee moet houden.

Install

We beginnen met de angular libraries in WordPress te plaatsen. Installeer angular via bower, npm of download het via de officiële website. Plaats de bestanden in uw thema of plugin folder. We hebben ook angular-route nodig.

Heb je kennis van Wordpress, maar heb je nog geen eigen thema of plugin gemaakt? Dan kan de documentatie respectievelijk hier en hier vinden.

Vervolgens laad je de scripts in op de standaard WordPress manier.

function load_scripts()
{
    // load angular
    wp_enqueue_script(
        'angular',
        get_template_directory_uri() . '/components/angular/angular.min.js'
    );

    // load angular-route
    wp_enqueue_script(
        'angular-route',
        get_template_directory_uri() . '/components/angular-route/angular-route.min.js',
        array('angular')
    );

    // load my angular application
    wp_enqueue_script(
        'studyx-app',
        get_template_directory_uri() . '/js/app.js',
        array('angular', 'angular-route')
    );

    // load my controllers, I like them split from my main file
    wp_enqueue_script(
        'studyx-controllers',
        get_template_directory_uri() . '/js/controllers.js',
        array('angular', 'angular-route')
    );

    // pass data to use in scripts
    wp_localize_script(
        'localized-data',
        'wpData',
        array(
            'themeUrl' => get_template_directory_uri()
        )
    );

}

add_action( 'wp_enqueue_scripts', 'load_scripts' );


Hint: 
wp_enqueue_scripts is een hook die enkel voor de frontend gebruikt wordt. Wanneer je angular ook op de admin pages wilt gebruiken, zal je die ook moeten opladen met de hook admin_enqueue_scripts. Later zullen we echter zien dat we deze bestanden ook in een andere callback kunnen inladen.

Frontend

Vanaf nu zijn de functionaliteiten van angular beschikbaar. Wanneer je kiest om de volledige website in angular te schrijven, is het gebruik van de index.php file in uw thema voldoende. Werk je echter op een specifieke pagina, dan zal je de ngApp directive in een page template moeten plaatsen, maar daar komen we nog op terug.

<!DOCTYPE html>
<html ng-app="studyx-app">
    <head>
        <title>Using AngularJS in WordPress</title>
        <?php wp_head(); ?>
        <base href="/" />
    </head>
    <body>

    <div ng-view></div>

    <?php wp_footer(); ?>
    </body>
</html>

Voor we starten met het bouwen van onze views moeten we data ophalen die we in het admin gedeelte van wordpress aanmaken. Wij zullen de WP REST API plugin gebruiken. Je kan de plugin hier downloaden en de documentatie vind je hier terug.

Waar het op neer komt, is dat we HTTP requests uitsturen om informatie op te halen over posts, taxonomies, pagina’s, of je kan uw eigen endpoints ontwikkelen. Een voorbeeld uit de documentatie:

GET /wp-json/wp/v2/posts to get a collection of Posts. This is roughly equivalent to using WP_Query.
GET /wp-json/wp/v2/posts/123 to get a single Post with ID 123. This is roughly equivalent to using get_post().
POST /wp-json/wp/v2/posts to create a new Post. This is roughly equivalent to using wp_insert_post().
DELETE /wp-json/wp/v2/posts/123 to delete Post with ID 123. This is roughly equivalent to wp_delete_post(). 

Als response krijg je een JSON object terug. Een klein voorbeeldje van wat je terug krijgt na het gebruik van GET /wp-json/wp/v2/posts:

Screen Shot 2015-11-04 at 23.07.34

Next: het bouwen van de angular module, routes opbouwen en views opmaken.

// app.js

angular.module('studyx-app', ['ngRoute'])
.config(function($routeProvider, $httpProvider, $locationProvider){

    $locationProvider.html5Mode(true);

    // all routes
    $routeProvider
        .when('/', {
            templateUrl: wpData.themeUrl + '/views/home.html',
            controller: 'HomeController'
        })
        .when('/:slug', {
            templateUrl: wpData.themeUrl + '/views/article.html',
            controller: 'PostController'
        });
})



// controllers.js

function HomeController($scope, $http, $window, $rootScope)
{
    $http.get('/wp-json/wp/v2/posts').success(function(data, status, headers, config){
        $scope.posts = data;
    });
}

function PostController($scope, $http, $window, $rootScope, $routeParams)
{
    $http.get('/wp-json/wp/v2/posts/?filter[name]=' + $routeParams.slug')
        .success(function(data, status, headers, config){
            $scope.post = data;
        });
}

angular.module('studyx-app')
.controller('HomeController', HomeController)
.controller('PostController', PostController);

In plaats van een ID te gebruiken bij het ophalen van een specifieke post (/wp-json/wp/v2/posts/ID), gebruik ik de filter parameter ( ?filter[name]= ) zodat we een post kunnen ophalen aan de hand van een slug. Deze slug is afkomstig uit onze route URL, op deze manier blijven onze urls zinnig (/product/cursus is zinniger dan /product/64). Met ?filter[name]= kan je iedere parameter gebruiken die beschikbaar is in WP_Query (zolang de parameter public staat).

// home.html

<main id="article-container">
    <article ng-repeat="post in posts">
        {{ post.excerpt }}

        <a ng-href="/{{ post.slug }}">Read more</a>
    </article>
</main>


// article.html

<article>
    {{ post.content }}
</article>

Om de frontend te eindigen, heb ik nog een voorbeeldje wanneer je angular maar op één pagina wilt gebruiken. Dit kan bijvoorbeeld handig zijn om een multistep form te maken. Maak een pagina aan genaamd “formulier”. Geef deze pagina ook de slug “formulier”. Maak daarna je pagina template aan met de naam “page-formulier.php”.

<?php

get_header();

?>

<div id="primary" class="content-area">
    <main id="main" class="site-main" role="main">
        <div ng-app="studyx-app">
            <div ng-view></div>
        </div>
    </main>
</div>

<?php get_footer();

Vergeet ook niet om de base url toe te voegen aan deze pagina. Dit kan via de hook wp_head, of je kan header.php aanpassen en hem daar toevoegen. (dit is trouwens niet nodig indien html5mode op false staat in de angular config functie).

function add_base_href_frontend()
{
    $output = "<base href='/' />";

    echo $output;
}

add_action('wp_head', 'add_base_href_frontend');

Nog enkele tips:

1. Vaak zal de default routes die WP REST API aanbiedt, niet voldoende zijn. Het is niet ongebruikelijk dat je een route wilt aanmaken die een callback functie heeft waarin je bewerkingen met data uitvoert. Er zijn 2 manieren om dit probleem op te lossen. De eerste manier, en ook de beste, is het aanmaken van custom endpoints. Documentatie over hoe je dit kan doen vind je hier. Ter vervolledigheid, wil ik ook meegeven dat je de wordpress functie add_feed kan gebruiken. Het is heel snel te coderen, maar add_feed werd hiervoor niet ontworpen, dus is het beter om WP REST API te gebruiken.

// WP REST API endpoints

function my_callback_function($data) {
    // do stuff
}

add_action('rest_api_init', function() {
    register_rest_route( 'studyx/v1', '/producten/(?P<id>\d+)', 
        array(
            'methods' => 'GET',
            'callback' => 'my_callback_function',
        )
    );
});

//---------------------------------------------------------------------
// add_feed

function add_feed_links()
{
    add_feed('add-project', 'my_callback_function');
}

function my_callback_function()
{
    $form_data = $_POST['stuff'];

    // do stuff with your form data
}

add_action('init', 'add_feed_links');

Vergeet niet dat je, bij het gebruik van add_feed, nog 2 extra zaken moet configureren opdat $_POST leesbaar zou zijn in je add_feed callback functie. Plaats daarom volgend snippet in uw angular applicatie, in de .config functie.

// send all requests payload as query string
$httpProvider.defaults.transformRequest = function(data){
    if (data === undefined) {
        return data;
    }
    return jQuery.param(data);
};

// set all post requests content type
$httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';


2. Mocht je je afvragen hoe het zit met vertalingen: ik raad aan om angular-gettext te gebruiken. Docs: https://angular-gettext.rocketeer.be/.

3. Mogelijks kan je het probleem tegenkomen waarbij een element (nog) niet beschikbaar is wanneer je het probeert aan te roepen via jQuery. Dit is omdat $.ready niet weet wanneer uw view klaar is met laden (jQuery weet niet dat <div ng-view></div> slechts een placeholder is voor de werkelijke view). Wanneer $.ready dus afgevuurd wordt, zal je nog geen events kunnen binden op elementen die in uw view staan. Je kan dit oplossen aan de hand van directives, of gemakkelijker: via delegated events.

// Let's say there are clickMe classes in your angular view

// don't do this: .clickMe doesn't exist yet
jQuery('.clickMe').on('click', function(e){
    // do stuff
});

// do this: when clicked on the document, do stuff on all .clickMe elements
jQuery(document).on('click', '.clickMe', function(e){
    // do stuff
});

Backend

Het eerste dat je je kan afvragen, is waarom je angular zou gebruiken in het admin gedeelte van wordpress. Dit kan handig zijn wanneer je bijvoorbeeld een settings pagina hebt voor uw plugin, maar je wilt dit verspreiden over meerdere pagina’s om het overzichtelijker te maken. Een ander voorbeeld is, wanneer je bijvoorbeeld data van een formulier overzichtelijk wilt tonen aan uw klant die de website kocht. Eerst toon je een lijst van alle inzendingen, en van iedere inzending kan je naar een detailpagina gaan.

Uiteraard kan je dit ook zonder angular, maar angular maakt het des te makkelijker. Je hoeft maar één admin pagina aan te maken, daarna kan je de gewoonlijke flow van angular toepassen.

Er zijn echter een paar addertjes onder het gras.

Laten we beginnen met het aanmaken van een admin pagina.

function initialize_admin()
{
    $buyers_page = add_submenu_page(
        'edit.php?post_type=courses', // adds a submenu page for post type courses
        'Courses',
        'Courses',
        'manage_options',
        'courses',
        'add_courses_page'
    );

    add_action('admin_print_styles-' . $courses_page, 'add_courses_styles');
    add_action('admin_print_scripts-' . $courses_page, 'add_courses_scripts');
}

function add_courses_page()
{
    include plugins_url() . '/views/courses.php';
}

function add_courses_styles()
{
    // add styles here
}

function add_courses_scripts()
{
    // add scripts here, the same as before
}

add_action('admin_menu', 'initialize_admin');

Vanaf nu begint het tricky te worden. De admin pages gebruiken querystrings om te weten op welke pagina we zitten. Angular routes houdt geen rekening met querystring.

Om de routing te fixen, starten we met de correcte base URL te outputten. Voor het admin gedeelte in WordPress is de base URL /wp-admin/. Dit voegen we toe aan de head op de pagina waar we het nodig hebben. Opgelet, het is belangrijk dat je dit enkel doet op de adminpagina waar we het nodig hebben, omdat het op andere adminpagina’s plugins zou kunnen breken.

function add_base_href_backend()
{
    if($_GET['page'] == 'studyx') {
        $output = "<base href='/wp-admin/' />";

        echo $output;
    }
}

add_action('admin_head', 'add_base_href_backend');

Zoals je kan zien, gebruiken we de querystring om te zien op welke pagina we zitten: $_GET['page'] == 'studyx'. De slug hebben we eerder ingesteld toen we onze submenu pagina aanmaakten, alsook dat de pagina bij het post type ‘courses’ hoort. De url in de backend om op onze pagina te komen is daardoor /wp-admin/edit.php?post_type=courses&page=studyx.

Zie je het probleem? Aangezien angular routes enkel met restful paths werkt, zal angular route enkel /wp-admin/ interpreteren. We fixen dit met een controller die ons in de juiste richting zal sturen.

// in app.js

$routeProvider
    .when('/wp-admin/', {
        controller: 'RouteDeciderController',
        template: '<div ng-include="getTemplateUrl()"></div>' // because our controller doesn't need a view
    });


// in controllers.js

function RouteDeciderController($location, $window)
{
    if ($window.location.search == "?post_type=courses&page=studyx"){
        $location.path('/edit.php/studyx');
    }
}

Door de base tag die ingesteld is vanaf /wp-admin/, beginnen we onze url met /edit.php/.

Voorbeeldje waarbij we meerdere routes toevoegen:

$routeProvider
    .when('/wp-admin/', {
        controller: 'RouteDeciderController',
        template: '<div ng-include="getTemplateUrl()"></div>'
    })
    .when('/edit.php/studyx', {
        templateUrl: wpData.pluginUrl + '/views/studyx.html',
        controller: 'CoursesController'
    })
    .when('/edit.php/studyx/show/:id/', {
        templateUrl: wpData.pluginUrl + '/views/show-studyx.html',
        controller: 'ShowCourseController'
    })
    .when('/edit.php/studyx/delete/:id/', {
        templateUrl: wpData.pluginUrl + '/views/delete-studyx.html',
        controller: 'DeleteCourseController'
    });

Ook al zijn er meerdere routes, volgens WordPress zitten we nog steeds op dezelfde admin pagina (/wp-admin/edit.php?post_type=car&page=courses).

Onthoud dat, betreffende wordpress, we nog ons nog altijd op één pagina bevinden (/wp-admin/edit.php?post_type=car&page=courses).  Dit betekent dat elke route die we voor courses hebben zich al op de zelfde pagina bevinden (maar met verschillende views). Dit betekent ook dat we geen extra omleidingen moeten coderen in onze RouteDeciderController. Eén is genoeg voor /courses.

Conclusie

Mits een aantal zaken waar je voor moet oppassen, is AngularJS zeker te gebruiken in WordPress.

Laat een commentaar achter

Contacteer ONS

Uw naam *
Bedrijf
Email *
Telefoon *
Uw uitdaging *

Gelieve dit veld leeg te laten.

Velden gemarkeerd met een asterisk * zijn verplicht