Post on 05-Jul-2015
description
INTRODUCTION TOBACKBONE.JS WITH
WORDPRESS / Brian Hogg @brianhogg
AGENDAWhy Backbone.jsBasics of Backbone.js / Underscore.jsEnd-to-end example plugin ( )Github
WHO ARE YOU?
WHY BACKBONE?Enforces some structure on your JavaScriptEvents system
WHY NOT JUST JQUERY?PerformanceLeveraging the communityRe-inventing the wheelCode structure (avoid 1000+ lines of jQuery that "just works")
WHAT IS BACKBONE.JS?STRUCTURE (MV*)
Uses jQuery, but only hard requirement is Underscore.js
WHAT ISUNDERSCORE.JS?
UTILITY FUNCTIONS WITH __.each_.templateLots more: http://documentcloud.github.io/underscore/
TEMPLATESvar template = _.template("hello <%= name %>");var html = template({ name: 'Brian' });console.log( html ); // "hello Brian"
var template = _.template("<strong><%- value %></strong>");var html = template({ value: '<script>' });console.log( html ); // "<strong><script></strong>"
ALTERNATIVESEMBER.JS, ANGULAR.JS, ...
Multiple ways of doing similar things. Even in Backbone.JS:
“It's common for folks just getting started to treatthe examples listed on this page as some sort ofgospel truth. In fact, Backbone.js is intended to
be fairly agnostic about many common patternsin client-side code.”
http://backbonejs.org/#FAQ-tim-toady
BACKBONE /UNDERSCORE
INCLUDED IN WORDPRESS SINCE 3.5The "backbone" of the media manager, revisions UI
MODELS“Models are the heart of any JavaScript
application, containing the interactive data aswell as a large part of the logic surrounding it:
conversions, validations, computed properties,and access control. You extend Backbone.Modelwith your domain-specific methods, and Model
provides a basic set of functionality formanaging changes.”
MODEL EXAMPLEvar Post = Backbone.Model.extend({ defaults: { title: "", post_status: "draft" }, initialize: function() { console.log("creating a post"); }});
var post = new Post({ title: "Hello, world", post_status: "draft" });
var title = post.get("title"); // Hello, worldvar post_status = post.get("post_status"); // draft
All models have an id attribute for syncing up with a server
LISTENING FOR CHANGESpost.on("change:title", function(model) { alert("Title changed to: " + model.get("title"));});
Or in the models initialize with:this.on("change:title", this.titleChanged);
VIEWSUsed to turn a model into something you can seeAlways contains a DOM element (the el property), whetherits been added to the viewable page or not
BARE MINIMUM TO USE BACKBONEvar PostView = Backbone.View.extend({ events: { "click .edit": "editPost", "change .post_status": "statusChanged" },
editPost: function(event) { // ... }, statusChanged: function(event) { // ... }});
var postView = new PostView({ el: '#my-form' });
VIEW EXAMPLEvar PostView = Backbone.View.extend({ tagName: "div", // div by default className: "bbpost", // for styling via CSS events: { "click .edit": "editPost", "change .post_status": "statusChanged" },
initialize: { this.listenTo(this.model, "change", this.render); },
render: { // ... }});
RENDERING THE VIEWvar template = _.template($("#tmpl-bbpost").html());var html = template(this.model.toJSON());this.$el.html(html);return this; // for chaining
This uses Underscore.js' _.template, but you can use another!
ACCESSING THE DOM ELEMENTthis.$el
this.$el.html()
this.el
// From within a parent viewvar view = new PostView({ model: post });this.$el.append(view.render().el);
this.$this.$('.title').val()
COLLECTIONSOrdered set of models
var Posts = Backbone.Collection.extend({ model: Post});
var post1 = new Post({ title: "Hello, world" });var post2 = new Post({ title: "Sample page" });
var myPosts = new Posts([ post1, post2 ]);
POPULATING COLLECTIONS FROM THE SERVEROut of the box, Backbone.js supports RESTful APIs throughBackbone.sync(method, model, [options]):
create → POST /collectionread → GET /collection[/id]update → PUT /collection/idpatch → PATCH /collection/iddelete → DELETE /collection/id
What Backbone expects when fetching/reading the collection:
[ { id: 1, title: "Hello, world" }, { ... }]
What this sends:wp_send_json_success( array( 'id': 1, 'title': 'Hello, world' ) );
{ success: true, data: [ { id: 1, title: "Hello, world" } ]}
So, just override .parse() to accommodate:var Posts = Backbone.Collection.extend({ model: Post, url: ajaxurl, // defined for us if we're in /wp-admin parse: function( response ) { return response.data; }});
// Kick things off$(document).ready(function() { posts = new Posts(); postsView = new PostsView({ collection: posts }); posts.fetch({ data: { action: 'bbpost_fetch_posts' } });});
Or can override .sync(), or even .fetch()
Note on calling .fetch() on page load:
“Note that fetch should not be used to populatecollections on page load — all models needed atload time should already be bootstrapped in to
place. fetch is intended for lazily-loading modelsfor interfaces that are not needed immediately:
for example, documents with collections of notesthat may be toggled open and closed.”
http://backbonejs.org/#Collection-fetch
Depends on the situation
ROUTERSUsed for routing your application's URLs when using hash tags
(#)
(CONTRIVED) EXAMPLEMANAGING WORDPRESS POST TITLES AND PUBLISH/DRAFT
STATUS IN AN ADMIN PANELDEMO
DIRECTORY STRUCTUREplugins/ backbone-js-wp-example/ backbone-js-wp-example.php css/ admin.css js/ collections/ posts.js models/ post.js views/ post.js posts.js
MODELS/POST.JSvar bbp = bbp || {};
(function($){ bbp.Post = Backbone.Model.extend({ });})(jQuery);
Could set defaults here, if creating new posts
BACKBONE-JS-WP-EXAMPLE.PHP/*Plugin Name: Backbone.js WP ExamplePlugin URI:Description: Basic Backbone.js Example in WordPress to edit basic Post propertiesVersion: 1.0Author: Brian HoggAuthor URI: http://brianhogg.comLicense: GPL2*/
define( 'BBPOST_VERSION', 1 );
BACKBONE-JS-WP-EXAMPLE.PHPSETTING UP ACTIONS
class BBPostAdmin { public function __construct() { if ( is_admin() ) { add_action( 'wp_ajax_bbpost_fetch_posts', array( &$this, 'ajax_fetch_posts' ) ); add_action( 'wp_ajax_bbpost_save_post', array( &$this, 'ajax_save_post' ) );
if ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) { add_action( 'admin_menu', array( &$this, 'admin_menu' ) ); if ( isset( $_GET['page'] ) and 'bbpostadmin' == $_GET['page'] ) { add_action( 'admin_enqueue_scripts', array( &$this, 'enqueue_scripts' ) ); } } } }
BACKBONE-JS-WP-EXAMPLE.PHPADDING THE MENU
add_menu_page( 'Backbone JS Post Admin Example', 'Backbone JS Post Admin Example', 'add_users'
admin_menu() function
BACKBONE-JS-WP-EXAMPLE.PHPADDING THE SCRIPTS
// Add backbone.js models first, then collections, followed by views$folders = array( 'models', 'collections', 'views' );foreach ( $folders as $folder ) { foreach ( glob( dirname( __FILE__ ) . "/js/$folder/*.js" ) as $filename ) { $basename = basename( $filename ); wp_register_script( "$folder/$basename", plugins_url( "js/$folder/$basename", __FILE__ ), wp_enqueue_script( "$folder/$basename" ); }}
wp_register_style( 'bbpost.admin.css', plugins_url( 'css/admin.css', __FILE__ ), false, ECN_VERSION );wp_enqueue_style( 'bbpost.admin.css' );
enqueue_scripts() function
wp-util gives us the wp.ajax helper function
ADMIN PAGE TEMPLATE<script id="tmpl-bbpost" type="text/html">
</script>
<h1>Backbone.js WordPress Post Admin Example</h1>
<div id="bbposts"></div>
<div class="bbpost"> <h2> </h2> Post title: <input type="text" class="title" value="<%- title %>" />, Status: <select class="post_status"> <option value=""></option> <option value="publish" <% if ( 'publish' == post_status ) { %>SELECTED <option value="draft" <% if ( 'draft' == post_status ) { %>SELECTED </select> <button>Update</button> </div>
<%- title %>
admin_page()
ADMIN PAGE TEMPLATEINDIVIDUAL POST
<div class="bbpost"> <!-- will update when the model updates, automatically --> <h2> </h2> Post title: <input type="text" class="title" value="<%- title %>" />, Status: <select class="post_status"> <option value=""></option> <option value="publish" <% if ( 'publish' == post_status ) { %>SELECTED >Published <option value="draft" <% if ( 'draft' == post_status ) { %>SELECTED >Draft</ </select> <button>Update</button></div>
<%- title %>
<% } %>
VIEWS/POSTS.JSvar bbp = bbp || {};
(function($){ bbp.PostsView = Backbone.View.extend({ el: '#bbposts', // Specifying an already existing element
initialize: function() { this.collection.bind('add', this.addOne, this); },
addOne: function(post) { var view = new bbp.PostView({ model: post }); this.$el.append(view.render().el); } });
$(document).ready(function() { bbp.posts = new bbp.PostsCollection(); bbp.postsView = new bbp.PostsView({ collection: bbp.posts }); bbp.posts.fetch({ data: { action: 'bbpost_fetch_posts' } }); });})(jQuery);
VIEWS/POST.JSvar bbp = bbp || {};
(function($){ bbp.PostView = Backbone.View.extend({ className: 'bbpost',
initialize: function() { this.model.on("change", this.render, this); },
render: function() { var template = _.template($('#tmpl-bbpost').html()); var html = template(this.model.toJSON()); this.$el.html(html); return this; },
events: { 'click button': 'updatePost' },
updatePost: function() { this.model.set('title', this.$('.title').val()); this.model.set('post_status', this.$('.post_status').val()); this.model.save(); } });})(jQuery);
BACKBONE-JS-WP-EXAMPLE.PHPFUNCTION TO SEND THE POST DATA
if ( ! current_user_can( 'edit_published_posts' ) ) wp_send_json_error();
$posts = get_posts( array( 'post_status' => 'any' ));$retval = array();foreach ( $posts as $post ) { $retval[] = array( 'id' => $post->ID, 'title' => $post->post_title, 'post_status' => $post->post_status, );}
wp_send_json_success( $retval );
ajax_fetch_posts()
COLLECTIONS/POSTS.JSvar bbp = bbp || {};
(function($){ bbp.PostsCollection = Backbone.Collection.extend({ model: bbp.Post, url: ajaxurl, parse: function ( response ) { // This will be undefined if success: false return response.data; } });})(jQuery);
SAVINGOVERRIDE SAVE() IN MODELS/POST.JS
var bbp = bbp || {};
(function($){ bbp.Post = Backbone.Model.extend({ save: function( attributes, options ) { options = options || {}; options.data = _.extend( options.data || {}, { action: 'bbpost_save_post', data: this.toJSON() }); var deferred = wp.ajax.send( options ); deferred.done( function() { alert('done'); }); deferred.fail( function() { alert('failed'); }); } });})(jQuery);
BACKBONE-JS-WP-EXAMPLE.PHPSAVING A POST TITLE/STATUS
if ( ! $post = get_post( (int) $_POST['data']['id'] ) ) wp_send_json_error();
if ( ! current_user_can( 'edit_post', $post->ID ) ) wp_send_json_error();
if ( wp_update_post( array( 'ID' => $post->ID, 'post_title' => $_POST['data']['title'], 'post_status' => $_POST['data']['post_status'], ) ) == $post->ID ) wp_send_json_success();else wp_send_json_error();
ajax_save_post() function
Extra work to set up initially, but worth it later on!
WP-BACKBONESpecial versions of Backbone.View (wp.Backbone.View)
revisions.view.Frame = wp.Backbone.View.extend({ className: 'revisions', template: wp.template('revisions-frame'), // ...});
Handling of SubViewstemplates use <# #> instead of <% %> (as PHP can see <% %>as code: see for details)See revisions.js for an example
trac
RESOURCEShttps://github.com/brianhogg/backbone-js-wp-example
http://backbonejs.org/
http://backbonetutorials.com/
https://github.com/addyosmani/backbone-fundamentals
http://kadamwhite.github.io/talks/2014/backbone-wordpress-wpsessions
WordPress revisions.js