122
WORDPRESS REST API AND ANGULARJS FTW! Roy Sivan and Josh Pollock CalderaLearn.com

Caldera Learn - LoopConf WP API + Angular FTW Workshop

Embed Size (px)

Citation preview

WORDPRESS REST API AND ANGULARJS

FTW!

Roy Sivan and Josh Pollock CalderaLearn.com

CalderaLabs.org

Join our SlackLoopConf Slack: #workshop-wpapi

We will be posting links, taking questions, and communicating throughout the day via Slack

CalderaLabs.org

Hi I'm JoshLead Developer: Caldera Labs

I make WordPress pluginsI teach about WordPressI wrote a book about the WordPress REST APII am a core contributor to WordPressI am a member of The WP Crowd

CalderaLearn.com

Hi, I'm RoySenior Software Engineer: The Walt Disney Company

I am a member of The WP CrowdI blog on roysivan.comI teach on Lynda.com & CalderaLearnPeople say Hi to me a lot

What We're Covering Today

REST API 101Building Custom REST APIsUnit Testing Custom REST APIs

LUNCH BREAKAngularJS (1.x) BasicsBuilding Decoupled Front-endsBuilding Plugin Admin Screens

Educational Philosophy

All code is or is based on real world projectsWe will show different ways of doing the same thing.

Please ask why it's different◇ we may give you a good answer

Stop us and ask questions

Structure For Today

foreach ( $sections as $section ) :

Concepts / LectureExample Code Walkthrough (you will be cloning locally)

Hands-onDIY GroupWalk Through Code Group

endforeach;

WARNING

This workshop is to help you understand the basics and some advanced technologies. Nothing will be production ready code.

What you need for today

IDE or text editor (PHPStorm, Sublime, etc.)Local WP install (VVV, DesktopServer, etc.)npm PHPUnit (optional, included in VVV)Composer (optional)AngularJS Batarang Chrome Extension

https://chrome.google.com/webstore/detail/angularjs-batarang/ighdmehidhipcmcojjgiloacoafjmpfk?hl=en

CalderaLabs.org

0.WordPress REST API?

Do we even need it?

CalderaLabs.org

NOBut you can build cool stuff with it.

It’s all about the Data

Data! Data! Data!

CalderaLearn.com

The API allows you to take WP data and put it in a bucket. What you do with that bucket is up to you.

-- Morten Rand Hendriksen@mor10

CalderaLabs.org

What can we build with it?

Facebooks? Myspaces?

API Powered Stuff

Things you can build that are powered by the API

Phone appsCustom UI widgets on your site

Why use feed of another site, when you can APICustom Dashboards in the adminCustom WP Dashboard (YAS!)

User role based dashboard

CalderaLabs.org

1.Custom REST APIs

With The WordPress REST API

CalderaLabs.org

Modify Default Routes

CalderaLabs.org

WordPress Gives Us Routes

Which We Can Extend

CalderaLabs.org

Create Your Own Routes

CalderaLabs.org

Or We Can Make Our OwnWith The Same Tools

How The REST API Works

WP_REST_Server ??WP_REST_Request WP_REST_Response

How The REST API Works

WP_REST_ServerRoute

CallbackWP_REST_Request WP_REST_Response

ERROR!

ERROR!

CalderaLabs.org

Let's Talk About The Orange Box

The Route Callback

Customizing The Defaults

CalderaLabs.org

Extending DefaultsAdding Post Type

Support

add_action( 'init', 'my_book_cpt' ); function my_book_cpt() { $labels = array(...); $args = array( ... 'rest_controller_class' => 'WP_REST_Posts_Controller', 'show_in_rest' => true, 'rest_base' => 'books-api', ); register_post_type( 'book', $args );}

Add REST API Support To Post Type Registration

add_action( 'init', 'my_custom_post_type_rest_support', 25 ); function my_custom_post_type_rest_support() { global $wp_post_types; $post_type_name = 'book'; if( isset( $wp_post_types[ $post_type_name ] ) ) { $wp_post_types[$post_type_name]->show_in_rest = true; $wp_post_types[$post_type_name]->rest_base = $post_type_name; $wp_post_types[$post_type_name]->rest_controller_class = 'WP_REST_Posts_Controller'; } }

Add REST API Support To An Existing Post Type

CalderaLabs.org

Extending DefaultsAdding A Custom Field

CalderaLabs.org

Custom Field Is Any DataNot just meta fields

function slug_get_meta_field( $object, $field_name, $request ) {

return get_post_meta( $object[ 'id' ], $field_name );

}

function slug_update_meta( $value, $object, $field_name ) {

if ( ! $value || ! is_string( $value ) ) {

return;

}

return update_post_meta( $object->ID, $field_name, strip_tags( $value ) );

}

Adding Custom Fields To A Response: Callbacks

add_action( 'rest_api_init', 'slug_register_spaceship' );

function slug_register_spaceship() {

register_api_field( 'post',

'starship',

array(

'get_callback' => 'slug_get_meta_field',

'update_callback' => 'slug_update_meta_field',

)

);

}

Adding Custom Fields To A Response: Registration

CalderaLabs.org

Making Custom REST APIs

The BasicsHow It Works

add_action( 'rest_api_init', function () {

register_rest_route( 'myplugin/v1', '/author/(?P<id>\d+)', array(

'methods' => 'GET',

'callback' => 'route_callback',

) );

} );

Registering A Route

CalderaLabs.org

Creating Callback ClassRegister Route(s)

class my_simple_route {

public function register_routes(){

$namespace = 'my-api/v1';

register_rest_route( $namespace, '/items', [

'methods' => 'GET',

'callback' => [ $this, 'get_items' ],

'permissions_callback' => [ $this, 'get_items_permissions' ]

] );

...

}

}

Registering Route(s)

class my_simple_route {

public function register_routes(){

...

register_rest_route( $namespace, '/items/(?P<id>\d+)', [

'methods' => 'GET',

'callback' => [ $this, 'get_item' ],

'permissions_callback' => [ $this, 'get_item_permissions' ]

] );

}

}

Registering Route(s)

CalderaLabs.org

Creating Callback ClassDefining Route Fields

register_rest_route( $this->namespace, '/items/(?P<id>\d+)', [

...

'args' => [

'type' => [

'required' => true,

'validate_callback' => [ $this, 'validate_type' ]

],

'number' => [

'default' => 5,

'sanitize_callback' => 'absint'

]

]

] );

Registering Route(s) : Defining Fields

Registering Route(s) : Field Sanitization Callback

Use to ensure data is safe.Defined using a callable.

Change data to a safe value.Return prepared value

Registering Route(s) : Field Validation Callback

Use to ensure data is correct.Defined using a callable.

Used to reject invalid requestsReturn true or false

Registering Route(s) : Field Validation Callback

public function validate_type( $value ){

if( !in_array( $value, [ 'big', 'small', 'very-small' ] ) ){

return false;

}

return true;

}

CalderaLabs.org

Creating Callback ClassPermissions Callback

Registering Route(s) : Permissions Callback

Determine if user is authorized.Return true or false

Registering Route(s) : Permissions Callback

Example: Make require login.public function get_items_permissions(){

if( is_user_logged_in() ){

return true;

}

return false;

}

Registering Route(s) : Permissions Callback

Example: Limit To Adminspublic function get_items_permissions(){

if( current_user_can( 'manage_options' ) ){

return true;

}

return false;

}

Registering Route(s) : Permissions Callback

Example: Allow Alwayspublic function get_items_permissions(){

return true;

}

CalderaLabs.org

Creating Callback ClassRoute Callback Function

Registering Route(s) : Callback

Do something with the requestGets an object of WP_REST_RequestShould return WP_REST_Response or WP_Error

WP_Rest_Response

Represents current requestContains:◇ Parameters◇ HeadersNo need to access $_GET, $_POST, $_REQUEST

Implements Arrayaccess$request->param( 'field_name' );$request[ 'field_name' ];

Registering Route(s) : Callback

public function get_item( WP_REST_Request $request ){

$id = $request[ 'id' ];

$item = slug_crud_get( $id );

if( ! empty( $item ) && ! is_wp_error( $item ) ){

return rest_ensure_response( $item );

}elseif ( is_wp_error( $item ) ){

return $item;

}else{

$response = new WP_REST_Response( 'No items found', 404 );

return $response;

}

}

CalderaLabs.org

Initializing The ClassThe rest_api_init action

CalderaLabs.org

rest_api_initUse To Load Code Only

Needed In REST API Requests

Registering Route(s) : Initializing

add_action( 'rest_api_init', function(){

$route = new my_simple_route();

$route->register_routes();

});

CalderaLabs.org

2.Unit Testing Custom

REST APIsSubtitle

CalderaLearn.com

GoalOnly Test Our Endpoints

CalderaLabs.org

PHPUnit + WP_UnitTestCase

CalderaLabs.org

Setting UpPrepare To Test

Unit Testing 101

Make Sure Things Return What They Should Return

$this->assertEquals( 42, function_that_returns_42() );$this->assertSame( '42', function_that_returns_42() );$this->assertArrayHasKey( 'roy', array( 'roy' => 'hi' ) );

Google: "Pippin Williamson Unit Tests for WordPress Plugins"

Install PHPUnit

wget https://phar.phpunit.de/phpunit.pharchmod +x phpunit.pharmv phpunit.phar /usr/local/bin/phpunit

wget https://phar.phpunit.de/phpunit.pharphp phpunit.phar

Install WP CLI

curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar

php wp-cli.phar --info

chmod +x wp-cli.pharsudo mv wp-cli.phar /usr/local/bin/wp

Initialize Unit Tests

wp scaffold plugin-tests

CalderaLabs.org

Writing TestsTesting!

CalderaLearn.com

MockRequests

An Example

public function test_get_items_author_query() {

$this->factory->post->create( array( 'post_author' => 4 ) );

$this->factory->post->create( array( 'post_author' => 4 ) );

$this->factory->post->create( array( 'post_author' => 2 );

$request = new WP_REST_Request( 'GET', '/wp/v2/posts' );

$response = $this->server->dispatch( $request );

$this->assertEquals( 200, $response->get_status() );

$this->assertEquals( 3, count( $response->get_data() ) );

}

An Example

public function test_get_items_author_query() {

$this->factory->post->create( array( 'post_author' => 4 ) );

$this->factory->post->create( array( 'post_author' => 4 ) );

$this->factory->post->create( array( 'post_author' => 2 );

$request = new WP_REST_Request( 'GET', '/wp/v2/posts' );

$request->set_param( 'author', 4 );

$response = $this->server->dispatch( $request );

$this->assertEquals( 200, $response->get_status() );

$this->assertEquals( 2, count( $response->get_data() ) );

}

CalderaLearn.com

Test Case

Unit Test Case For REST API Endpoints

Create a reusable class that:

Create Instance of WP_REST_ServerBoot routesMake sure route is booted

Test Case Outline

class Test_API extends WP_UnitTestCase {

/** @var \WP_REST_Server*/

protected $server;

protected $namespaced_route = 'caldera-forms/v1';

public function setUp() {}

public function test_register_route() {}

public function test_endpoints() {}

}

Test Case: Set Up

protected $server;

public function setUp() {

parent::setUp();

/** @var \WP_REST_Server $wp_rest_server */

global $wp_rest_server;

$this->server = $wp_rest_server = new \WP_REST_Server;

do_action( 'rest_api_init' );

}

Test Case: Test Route Is Registered

public function test_register_route() {

$routes = $this->server->get_routes();

$this->assertArrayHasKey( $this->namespaced_route,

$routes );

}

Test Case: Test Endpoints Exist

public function test_endpoints() {

$the_route = $this->namespaced_route;

$routes = $this->server->get_routes();

foreach( $routes as $route => $route_config ) {

if( 0 === strpos( $the_route, $route ) ) {

$this->assertTrue( is_array( $route_config ) );

foreach( $route_config as $i => $endpoint ) {

$this->assertArrayHasKey( 'callback', $endpoint );

$this->assertArrayHasKey( 0, $endpoint[ 'callback' ], get_class( $this ) );

$this->assertArrayHasKey( 1, $endpoint[ 'callback' ], get_class( $this ) );

$this->assertTrue( is_callable( array( $endpoint[ 'callback' ][0], $endpoint[ 'callback' ][1] ) ) );

}

}

}

}

CalderaLearn.com

TestingCustom Routes

Test Your API Only!!!

Test internal logic elsewhereTest control of that logicTest response formatTrust core

Example Route Test

class Test_Hi extends Test_API {

protected $namespace = '/hi-api/v1/names';

public function test_list() {

$request = new WP_REST_Request( 'GET', $this->namespace );

$response = $this->server->dispatch( $request );

$this->assertEquals( 200, $response->get_status() );

$data = $response->get_data();

$this->assertArrayHasKey( 'name', $data[0] );

$this->assertEquals( 'shawn', $data[0][ 'name' ] );

$this->assertArrayHasKey( 'name', $data[1] );

$this->assertEquals( 'roy', $data[1][ 'name' ] );

}

}

Example Route Test

class Test_Hi extends Test_API {

protected $namespace = '/hi-api/v1/names';

public function test_single(){

$request = new WP_REST_Request( 'GET', $this->namespace . '/roy' );

$response = $this->server->dispatch( $request );

$this->assertEquals( 200, $response->get_status() );

$data = $response->get_data();

$this->assertArrayHasKey( 'name', $data );

$this->assertEquals( 'roy', $data[ 'name' ] );

}

}

CalderaLabs.org

3.Getting Started With

AngularJS 1.xControllers, Templates, Services & Factories

CalderaLabs.org

3 con’tWhy Angular?

You know it’s in the title of the workshop, right?

CalderaLearn.com

AngularJS is the best JavaScript Framework.-- Roy Sivan

Senior WordPress Engineer at Disney (so you know he is legit)

Not WordPress

To get you through the basics of AngularJS we will be using sample data, not WP

Our First Project

No npm or gulp

We aren’t using any build tools, this is a pure sample

https://github.com/caldera-learn/angularjs-intro

Quicker & Easier

Quicker to get going to show overall concepts.

Honest Truth

We already have a few projects built in it that are ready to go

Why 1.x? 2 is in RC!

Roy is lazy

I am not lazy, but didn’t have time to learn it deeply enough yet to give a full workshop on it.

Josh Switched Teams

He used to be team NG1.

Now he is team VueJS.

HTML powered JavaScript

With Angular 1.x you can use HTML to do most things reserved for PHP.

Get the data using JS, template with HTML. No PHP needed.

CalderaLearn.com

Step 1Setup the APP

Setting up the app

All functionality lives within 1 appWe use ng-app to encapsulate the app in the DOM, it can be used on any element including HTML.

Using it on the HTML encapsulates the whole DOM

Sample Data JSON

data.json

This file is going to be a sample of data, that we are going to use to build out a simple Angular App, we will then replace it with the WordPress REST API

Injectables

Injectables are objects which can be injected and used in

controllers, directives, etc.

We will be creating our own later...

AngularJS Controllers

Controller is all about $scopeA Controller is defined by a JavaScript constructor function that is used to augment the AngularJS Scope. When a Controller is attached to the DOM via the ng-controller directive, AngularJS will instantiate a new Controller object, using the specified Controller's constructor function

All new data to be used must be stored within $scope.your_key

ng-controller

All Together - JS

wpNG = {};

wpNG.app = ( function( app ){

console.log( 'initializing..' );

// define our app

app = angular.module('wpAngularApp', [])

.controller( 'listView', ['$scope', '$http', function( $scope, $http ) {

}]);

return app;

}(wpNG.app || {});

All Together - HTML

<div ng-app="wpAngularApp" id="app-container">

<div ng-controller="listView">

<!-- List View -->

</div>

</div>

CalderaLearn.com

Step 2HTML! Looping through the data

First we must get data

$http is a jQuery AJAX wrapper & returns a promise

$http({method: ‘’, url: ‘’})

$http.get(url)

$http.post(url)

Because it returns a promise we use .then()

Say hello to JSON

JSON is the JavaScript Object Notation.

We display JSON with curly brackets

We work with JSON similar to PHP arrays

{{Object.key}} displays that key value

In PHP that would be $object[‘key’];

The loop HTML

<div ng-controller="listView">

<h2>Posts</h2>

<!-- Loop through $scope.posts -->

<article ng-repeat="post in posts">

<h2>{{post.title}}</h2>

</article>

</div>

Add more data

<article ng-repeat="post in posts">

<img ng-src="{{post.image}}" />

<h2>{{post.title}}</h2>

<div

class="post-content"

ng-bind-html="post.content | to_trusted">

</div>

</article>

CalderaLabs.org

4.Decoupled Front-ends

With AngularJSA site has no WordPress

Decoupled Front End

Decouple front end is a front end app that doesn’t live within

WordPress at all.

Your website: myawesomesite.com

Decoupled: myawesomeapp.com - but running on the same

data!

How cool is that!

Decoupled Use Cases

Phone App

Piece of functionality that communicates with WP data

Advanced, Unique UI

Combine with other APIs

Different Stack (decoupled app doesn’t need to be PHP)

Our decoupled App

npm

gulp

REST url that is publicly accessible (we will give you one)

Tacos, you always need tacos.

What you will need:

https://github.com/caldera-learn/decoupled-app

CalderaLabs.org

What About CORS?Cross-Origin Resource Sharing

CORS

Allows browsers to make requests across domains.IS NOT SECURITY!!By default WordPress REST API is same-origin only.Set at rest_pre_serve_request hook

Allow All Domains For All Methods

add_action( 'rest_api_init', function() {

remove_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' );

add_filter( 'rest_pre_serve_request', function( $value ) {

header( 'Access-Control-Allow-Origin: *' );

header( 'Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT,

DELETE' );

header( 'Access-Control-Allow-Credentials: true' );

return $value;

});

}, 15 );

Only Allow GET Requests

add_action( 'rest_api_init', function() {

remove_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' );

add_filter( 'rest_pre_serve_request', function( $value ) {

$origin = get_http_origin();

if ( $origin ) {

header( 'Access-Control-Allow-Origin: ' . esc_url_raw( $origin ) );

}

header( 'Access-Control-Allow-Origin: ' . esc_url_raw( site_url() ) );

header( 'Access-Control-Allow-Methods: GET' );

return $value;

});

}, 15 );

Allows From Certain Origins

add_action( 'rest_api_init', function() {remove_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' );add_filter( 'rest_pre_serve_request', function( $value ) {

$origin = get_http_origin();if ( $origin && in_array( $origin, array(

'https://hiroy.club') ) ) {header( 'Access-Control-Allow-Origin: ' . esc_url_raw( $origin ) );header( 'Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT, DELETE' );header( 'Access-Control-Allow-Credentials: true' );

}return $value;

});}, 15 );

CalderaLabs.org

Factories, Storage, UI-Router, and

TemplatesAll the things!

AngularJS Factories

Factories allow you to create injectable objects. Inject $resource into the factory it to create an object that can automatically handle REST calls.

app.factory('Posts',function($resource){

return $resource( ngWP.config.api + ‘/:post_type/:ID?per_page=:per_page’, {

post_type: 'posts',

ID:'@id',

per_page: ngWP.config.posts_per_page,

});

})

AngularJS Factories cont’d

Posts.get()Posts.query() - like get, but expects arrayPosts.save()Posts.delete()Posts.remove()

Unlike $http these do not return promises

Local Storage

https://github.com/grevory/angular-local-storage

An AngularJS module that gives you access to the browser’s local storage with cookie fallback.

Local storage is a key/value store which we call on. In our example the regular posts (blog) will check for local storage first, before hitting the API.

UI-Router

https://github.com/angular-ui/ui-router

The Angular-UI project adds modules and functionality to Angular (think WP plugins for WP)

State Driven Routing& a lot of other functionality we won’t need.

Shows its true power when you have 1 view within another (post.detail within post.list)

Templates

Separating out templates into templates directory

Cleaner codeSeparation of views1 controller can have 1 template1 template can be powered by multiple controllersPure HTML (with JSON)

CalderaLabs.org

Now EnteringThe Danger Zone

First thing is first

Clone the repo

You will need to run npm install and gulp

The config file

Create a file called config.js in /assets/js

var ngWP = ngWP || {};

ngWP.config = {

api: 'https://calderaforms.com/wp-json/',

posts_per_page: 5

// Not needed for our menu: 'app'

};

Stepping through the code

Main APP file

UI Router Definitions

Small controllers live here

Templates

Blog/Author List

Blog Detail

Product List

CalderaLabs.org

5.Plugin Admin Screens

With AngularJSThe UI Router

Making Mini-Apps To Make WordPress Better

Very Similar ToRegular NG App

Basic

Use add_admin_menu() to print basic HTMLUse ui-router to switch routes inside admin page.Use wp_localize_script() for config

Questions?

Use PHP or HTML files for templates?How to handle translations?

wp_localize_script()PHP templates

Let's Look At Some Code!

What’s Next?

Angular 2 Theme currently in developmentgithub.com/royboy789/Angular-Wordpress-Theme/tree/v7

Vue.JS - The new simple JS frameworkReactJS - What everyone else wants you to learn

Admin Theme Boilerplategithub.com/WordPress-Admin-JavaScript-Boilerplate/ReactJS-Boilerplate

Want more Roy & Josh teachings?

Caldera Learn teaches through 4-week live classroom style webinars. Teachers are Josh and/or Roy with future guest teachers

CalderaLabs.org

Josh Pollock

JoshPress.net

CalderaForms.comCalderaLearn.com

CalderaLabs.org@Josh412

CalderaLabs.org

Roy Sivan

theWPCrowd.com

RoySivan.comLynda.com

CaldraLearn.com

@royboy789

CalderaLabs.org

Want To Learn More?

Coming Very Soon

CalderaLearn.com