Introduction to backbone presentation

Post on 05-Jul-2015

210 views 0 download

description

Introduction to Backbone.js With WordPress

Transcript of Introduction to backbone presentation

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>&lt;script&gt;</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

ENJOY! | brianhogg.com @brianhogg