TEAMING UP WP API & BACKBONE.JS IN TITANIUM
WHO KNOWS… RFC 7230 - 7235
MAYBE THIS ONE… RFC 2616
FAMILIAR WITH THIS? HTTP
… AND THIS GUY?
- RFC 7230 - 7235 = RFC 2616 HTTPBIS
+ HTTPBIS = ?
WHO THINKS HE/SHE KNOWS…? REST
HTTP VERBS 101 CREATE POST /POSTS RETRIEVE GET /POSTS[/X] UPDATE PUT /POSTS/X DELETE DELETE /POSTS/X
HTTP STATUSES 101 2XX SUCCESS ALL IS COOL! 3XX REDIRECTION GO OVER THERE! 4XX CLIENT ERROR YOU SCREWED UP! 5XX SERVER ERROR WE SCREWED UP!
WORDPRESS.ORG/PLUGINS/REST-API/ WP API WP-API.ORG COMPOSER REQUIRE WPACKAGIST/REST-API
/WP-JSON { "name": “A WP Site", "URL": “https://www.a-wp-site.nl”, "namespaces": [ ... ], "authentication": [] "routes": { "/": { ... }, “/wp/v2/posts”: { ... }, “/wp/v2/posts/<id>”: { ... }, ... “/wp/v2/users”: { ... }, “/wp/v2/users/<id>”: { ... }, “/wp/v2/users/me”: { ... }, ... “/wp/v2/media”: { ... } } }
/WP-JSON (DETAIL) { ... “/wp/v2/posts/<id>”: { "supports": [ "HEAD", "GET", "POST", "PUT", "PATCH", "DELETE" ], "accepts_json": true }, ... }
/WP-JSON/WP/V2/POSTS POST /wp-json/wp/v2/posts HTTP/1.1 Host: www.a-wp-site.nl Authorization: Bearer anZitsafake6NjU0MzIx Content-Type: application/x-www-form-urlencoded title=test&content_raw=test
/WP-JSON/WP/V2/POSTS
§ POSTS § PAGES § POST / PAGE META § POST / PAGE REVISIONS § POST TYPES § USERS § STATUSES § MEDIA § TAXONOMIES § TERMS CATEGORIES / TERMS TAGS § COMMENTS OUT OF THE BOX SUPPORT
QUESTIONS? THAT’S IT!
SIDE PROJECT : LOKALER
YOU KNOW HOW TO CREATE A PLUGIN? EXTENDING THE API
MY PLUGIN.PHP SETUP <?php /* Plugin Name: MY REST API Plugin URI: https://www.a-wp-site.nl/ Description: WP REST API EXTENSION Version: 1.0 Author: JRDK Author URI: http://jrdk.nl */ require_once('src/Bootstrap.php'); new Bootstrap();
WHEN TO TRIGGER YOUR CODE public function __construct() { add_action('plugins_loaded', [$this, 'initServer'], 100); } public function initServer() { if (!defined('REST_API_VERSION')) { // return early if WP API does not exist add_action('all_admin_notices', [$this, 'showError']); return; } }
EXPOSE YOUR OWN CUSTOM POST TYPE public function initServer() { //... add_action('rest_api_init', [$this, 'updateExposure'], 12); } public function updateExposure() { global $wp_post_types; $wp_post_types['advertisement']->show_in_rest = true; $wp_post_types['advertisement']->rest_base = 'advertisement'; }
ATTENTION PLEASE!
WP API TEAM HAS DONE A HUGE JOB
VERSION 1.X JSON_
JSON != REST JSON_REST_API VS REST_API
V2 BETA REST_
IN CORE FOR WP 4.4 SINCE OCTOBER 8TH
CHANGE WP_JSON INTO … public function initServer() { if (!defined('REST_API_VERSION')) { // return early if WP API versions do not exist add_action('all_admin_notices', [$this, 'showError']); return; } add_filter('rest_url_prefix', [$this, 'changeApiBase']); } public function changeApiBase() { return 'api'; }
NAMESPACES { "name": "A WP Site", "description": "", "URL": "https://www.a-wp-site.nl", "namespaces": [ "wp/v2", "your_namespace" ], "authentication": [] "routes": { "/": { ... }, "/wp/v2/posts": { ... }, ... "/your_namespace/path": { ... } } }
EXTEND EXPOSURE OF CUSTOM POST TYPE public function initServer() { add_action('rest_api_init', [$this, 'updateExposure'], 12); } public function updateExposure() { global $wp_post_types; $wp_post_types['advertisement']->show_in_rest = true; $wp_post_types['advertisement']->rest_base = 'advertisement'; $wp_post_types['advertisement']->rest_controller_class = 'LokalerApiEndpointAdvertisement'; } class Advertisement extends WP_REST_Posts_Controller {}
EXTEND THE INPUT / OUTPUT register_api_field('advertisement', 'price', [ 'schema' => [ 'type' => 'integer', 'context' => ['view', 'edit'], ], 'get_callback' => [$this, 'getMetaField'], 'update_callback' => [$this, 'saveMetaField'] ]); public function getMetaField($post, $key) { return get_post_meta($post['id'], $key, true); } public function saveMetaField($value, $post, $key) { return update_post_meta($post->ID, $key, $value); }
ADD YOUR OWN ROUTE register_rest_route('khl', '/version/(?P<os>[a-z]{3,7})', [ [ 'methods' => WP_REST_Server::READABLE, 'callback' => [$this, 'getVersion'], 'permission_callback' => [$this, 'checkAccess'], 'args' => [ 'os' => [ 'validate_callback' => [$this, 'isOS'], 'sanitize_callback' => [$this, 'filterOS'], 'required' => true, ], ], ], ]);
WORDPRESS.ORG/PLUGINS/OAUTH2-PROVIDER/ AUTHORISATION WP-OAUTH.COM COMPOSER REQUIRE WPACKAGIST/OAUTH2-PROVIDER
APPCELERATOR TITANIUM
VERY GLOBAL OVERVIEW xml + tss + js ALLOY JAVASCRIPT proxy modules TITANIUM SDK hyperloop ANDROID IOS WINDOWS
BACKBONE.JS
BACKBONE DATA METHODS SAVE POST /POSTS FETCH GET /POSTS[/X] SAVE PUT /POSTS/X DESTROY DELETE /POSTS/X
THAT’S IT… AGAIN var post = Backbone.Model.extend({ urlRoot: '/api/wp/v2/posts' }); var posts = Backbone.Collection.extend({ model: post, url: '/api/wp/v2/posts' }); var newPost = new post({ title: ‘test', content_raw: ‘test' }); newPost.save();
§ ‘FETCH’ a MODEL or COLLECTION § ‘DESTROY’ a MODEL § ‘CHANGE’ an ATTRIBUTE of a MODEL § ‘ADD’ a MODEL to a COLLECTION § ‘REMOVE’ a MODEL from a COLLECTION § ‘RESET’ a COLLECTION § ‘ERROR’ on MODEL or COLLECTION request at server § ‘INVALID’ MODEL provided BACKBONE EVENTS IN TITANIUM
QUERY DATA FROM WORDPRESS var advertisements = Alloy.Collections.instance(‘advertisement'); advertisements.fetch({ urlparams: { // https://codex.wordpress.org/Class_Reference/WP_Query filter: { posts_per_page: 3, orderby: {'date': 'DESC', 'ID': 'DESC'} }, page: 1 }, sql: { orderBy: 'timestamp DESC, id DESC' }, success: function (col) { } });
EXTEND BACKBONE WITH REST BASED SQLITE STORAGE exports.definition = { config: { columns: { id: 'INTEGER PRIMARY KEY', title: 'VARCHAR(50)', image: 'TEXT', lastmodified: 'TEXT' }, URL: 'https://www.a-wp-site.nl/api/khl/advertisements', adapter: { type: 'sqlrest', collection_name: 'advertisements', idAttribute: 'id' } } //... };
PARSE WORDPRESS API DATA exports.definition = { config: { //... parentNode: function(data) { if (_.isArray(data)) { var entries = []; _.each(data, function (_entry, index) { entries.push({ 'id': _entry.ID, 'title': _entry.title, 'image': _entry.featured_image }); }); return entries; } } } //... };
IF MODIFIED SINCE GET_LASTPOSTMODIFIED
UPLOADING A FILE var bgImage = Ti.Filesystem.getFile('upload.jpg'); var xhr = Ti.Network.createHTTPClient(); xhr.timeout = 60000; xhr.open('POST', '/api/wp/v2/media'); xhr.setRequestHeader('Authorization', '...'); xhr.setRequestHeader('Content-Type', 'image/jpeg'); xhr.setRequestHeader( 'Content-Disposition', 'attachment; filename=upload.jpg' ); xhr.send(bgImage.read());
CREATING AN ADVERTISEMENT var ad = Alloy.createModel('advertisement'); ad.set('title', 'This is the title'); ad.set('content', 'This is the description'); ad.save( {}, { success: function(response) {}, error: function(error, response) {} } );
MAGIC MINUS NEGATIVE INTEGERS
THE END RESULT
QUESTIONS? JEROEN VAN DIJK @JRVANDIJK JEROEN@ENRISE.COM
Teaming up WordPress API with Backbone.js in Titanium

Teaming up WordPress API with Backbone.js in Titanium

  • 1.
    TEAMING UP WPAPI & BACKBONE.JS IN TITANIUM
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
    - RFC 7230 -7235 = RFC 2616 HTTPBIS
  • 7.
  • 8.
    WHO THINKS HE/SHEKNOWS…? REST
  • 9.
    HTTP VERBS 101 CREATEPOST /POSTS RETRIEVE GET /POSTS[/X] UPDATE PUT /POSTS/X DELETE DELETE /POSTS/X
  • 10.
    HTTP STATUSES 101 2XXSUCCESS ALL IS COOL! 3XX REDIRECTION GO OVER THERE! 4XX CLIENT ERROR YOU SCREWED UP! 5XX SERVER ERROR WE SCREWED UP!
  • 12.
  • 13.
    /WP-JSON { "name": “A WPSite", "URL": “https://www.a-wp-site.nl”, "namespaces": [ ... ], "authentication": [] "routes": { "/": { ... }, “/wp/v2/posts”: { ... }, “/wp/v2/posts/<id>”: { ... }, ... “/wp/v2/users”: { ... }, “/wp/v2/users/<id>”: { ... }, “/wp/v2/users/me”: { ... }, ... “/wp/v2/media”: { ... } } }
  • 14.
    /WP-JSON (DETAIL) { ... “/wp/v2/posts/<id>”: { "supports":[ "HEAD", "GET", "POST", "PUT", "PATCH", "DELETE" ], "accepts_json": true }, ... }
  • 15.
    /WP-JSON/WP/V2/POSTS POST /wp-json/wp/v2/posts HTTP/1.1 Host:www.a-wp-site.nl Authorization: Bearer anZitsafake6NjU0MzIx Content-Type: application/x-www-form-urlencoded title=test&content_raw=test
  • 16.
  • 17.
    § POSTS § PAGES §POST / PAGE META § POST / PAGE REVISIONS § POST TYPES § USERS § STATUSES § MEDIA § TAXONOMIES § TERMS CATEGORIES / TERMS TAGS § COMMENTS OUT OF THE BOX SUPPORT
  • 18.
  • 20.
  • 21.
    YOU KNOW HOWTO CREATE A PLUGIN? EXTENDING THE API
  • 22.
    MY PLUGIN.PHP SETUP <?php /* PluginName: MY REST API Plugin URI: https://www.a-wp-site.nl/ Description: WP REST API EXTENSION Version: 1.0 Author: JRDK Author URI: http://jrdk.nl */ require_once('src/Bootstrap.php'); new Bootstrap();
  • 23.
    WHEN TO TRIGGERYOUR CODE public function __construct() { add_action('plugins_loaded', [$this, 'initServer'], 100); } public function initServer() { if (!defined('REST_API_VERSION')) { // return early if WP API does not exist add_action('all_admin_notices', [$this, 'showError']); return; } }
  • 24.
    EXPOSE YOUR OWNCUSTOM POST TYPE public function initServer() { //... add_action('rest_api_init', [$this, 'updateExposure'], 12); } public function updateExposure() { global $wp_post_types; $wp_post_types['advertisement']->show_in_rest = true; $wp_post_types['advertisement']->rest_base = 'advertisement'; }
  • 25.
  • 26.
    WP API TEAMHAS DONE A HUGE JOB
  • 27.
  • 28.
  • 29.
  • 30.
    IN CORE FORWP 4.4 SINCE OCTOBER 8TH
  • 31.
    CHANGE WP_JSON INTO… public function initServer() { if (!defined('REST_API_VERSION')) { // return early if WP API versions do not exist add_action('all_admin_notices', [$this, 'showError']); return; } add_filter('rest_url_prefix', [$this, 'changeApiBase']); } public function changeApiBase() { return 'api'; }
  • 32.
    NAMESPACES { "name": "A WPSite", "description": "", "URL": "https://www.a-wp-site.nl", "namespaces": [ "wp/v2", "your_namespace" ], "authentication": [] "routes": { "/": { ... }, "/wp/v2/posts": { ... }, ... "/your_namespace/path": { ... } } }
  • 33.
    EXTEND EXPOSURE OFCUSTOM POST TYPE public function initServer() { add_action('rest_api_init', [$this, 'updateExposure'], 12); } public function updateExposure() { global $wp_post_types; $wp_post_types['advertisement']->show_in_rest = true; $wp_post_types['advertisement']->rest_base = 'advertisement'; $wp_post_types['advertisement']->rest_controller_class = 'LokalerApiEndpointAdvertisement'; } class Advertisement extends WP_REST_Posts_Controller {}
  • 34.
    EXTEND THE INPUT/ OUTPUT register_api_field('advertisement', 'price', [ 'schema' => [ 'type' => 'integer', 'context' => ['view', 'edit'], ], 'get_callback' => [$this, 'getMetaField'], 'update_callback' => [$this, 'saveMetaField'] ]); public function getMetaField($post, $key) { return get_post_meta($post['id'], $key, true); } public function saveMetaField($value, $post, $key) { return update_post_meta($post->ID, $key, $value); }
  • 35.
    ADD YOUR OWNROUTE register_rest_route('khl', '/version/(?P<os>[a-z]{3,7})', [ [ 'methods' => WP_REST_Server::READABLE, 'callback' => [$this, 'getVersion'], 'permission_callback' => [$this, 'checkAccess'], 'args' => [ 'os' => [ 'validate_callback' => [$this, 'isOS'], 'sanitize_callback' => [$this, 'filterOS'], 'required' => true, ], ], ], ]);
  • 36.
  • 38.
  • 39.
    VERY GLOBAL OVERVIEW xml+ tss + js ALLOY JAVASCRIPT proxy modules TITANIUM SDK hyperloop ANDROID IOS WINDOWS
  • 40.
  • 41.
    BACKBONE DATA METHODS SAVEPOST /POSTS FETCH GET /POSTS[/X] SAVE PUT /POSTS/X DESTROY DELETE /POSTS/X
  • 42.
    THAT’S IT… AGAIN varpost = Backbone.Model.extend({ urlRoot: '/api/wp/v2/posts' }); var posts = Backbone.Collection.extend({ model: post, url: '/api/wp/v2/posts' }); var newPost = new post({ title: ‘test', content_raw: ‘test' }); newPost.save();
  • 43.
    § ‘FETCH’ aMODEL or COLLECTION § ‘DESTROY’ a MODEL § ‘CHANGE’ an ATTRIBUTE of a MODEL § ‘ADD’ a MODEL to a COLLECTION § ‘REMOVE’ a MODEL from a COLLECTION § ‘RESET’ a COLLECTION § ‘ERROR’ on MODEL or COLLECTION request at server § ‘INVALID’ MODEL provided BACKBONE EVENTS IN TITANIUM
  • 44.
    QUERY DATA FROMWORDPRESS var advertisements = Alloy.Collections.instance(‘advertisement'); advertisements.fetch({ urlparams: { // https://codex.wordpress.org/Class_Reference/WP_Query filter: { posts_per_page: 3, orderby: {'date': 'DESC', 'ID': 'DESC'} }, page: 1 }, sql: { orderBy: 'timestamp DESC, id DESC' }, success: function (col) { } });
  • 45.
    EXTEND BACKBONE WITHREST BASED SQLITE STORAGE exports.definition = { config: { columns: { id: 'INTEGER PRIMARY KEY', title: 'VARCHAR(50)', image: 'TEXT', lastmodified: 'TEXT' }, URL: 'https://www.a-wp-site.nl/api/khl/advertisements', adapter: { type: 'sqlrest', collection_name: 'advertisements', idAttribute: 'id' } } //... };
  • 46.
    PARSE WORDPRESS APIDATA exports.definition = { config: { //... parentNode: function(data) { if (_.isArray(data)) { var entries = []; _.each(data, function (_entry, index) { entries.push({ 'id': _entry.ID, 'title': _entry.title, 'image': _entry.featured_image }); }); return entries; } } } //... };
  • 47.
  • 48.
    UPLOADING A FILE varbgImage = Ti.Filesystem.getFile('upload.jpg'); var xhr = Ti.Network.createHTTPClient(); xhr.timeout = 60000; xhr.open('POST', '/api/wp/v2/media'); xhr.setRequestHeader('Authorization', '...'); xhr.setRequestHeader('Content-Type', 'image/jpeg'); xhr.setRequestHeader( 'Content-Disposition', 'attachment; filename=upload.jpg' ); xhr.send(bgImage.read());
  • 49.
    CREATING AN ADVERTISEMENT varad = Alloy.createModel('advertisement'); ad.set('title', 'This is the title'); ad.set('content', 'This is the description'); ad.save( {}, { success: function(response) {}, error: function(error, response) {} } );
  • 50.
  • 51.
  • 52.