Jennifer Dodd @jmdodd
Code Wrangler, Automattic
What is Backbone.js?
Why use Backbone.js?
Organization
var ToDo = ToDo || {};
ToDo.Models = ToDo.Models || {};
ToDo.Views = ToDo.Views || {};
ToDo.Collections = ToDo.Collections || {};
Trash Recycling with Disposal Containers by epSos.de, on Flickr
Models
ToDo task model:
ToDo.Models.ToDo = ( function( $, Backbone ) {
return Backbone.Model.extend( {
defaults: function() {
return {
id: 0,
title: '',
checked: '',
completedBy: false,
latestChange: 0,
source: 'local'
};
}
} );
} )( jQuery, Backbone );
On the server:
foreach ( (array) $posts as $post ) {
$todo = array(
'id' => $post->ID,
'title' => $post->post_title,
'checked' => $checked,
'completedBy' => $userdata,
'latestChange' => $at,
);
$todos[] = $todo;
}
wp_send_json_success( $todos );
Views
In the template:
{{ if ( completedBy ) { }}
{{ } }}
{{= title }}
{{ if ( completedBy ) { }}
{{= completedBy.username }}
{{ } }}
ToDo task view:
ToDo.Views.ToDo = ( function( $, Backbone ) {
return Backbone.View.extend( {
model: ToDo.Models.ToDo,
tagName: 'li',
template: _.template( $( '#todos-template' ).html() ),
initialize: function() {
// Start by rendering the View
this.render();
},
render: function() {
this.$el.html( this.template( this.model.toJSON() ) );
return this;
}
} );
} )( jQuery, Backbone );
Collections
star wars vintage figure collection (closer) by Simon Q, on Flickr
ToDo task collection:
ToDo.Collections.ToDos = ( function( $, Backbone ) {
return Backbone.Collection.extend( {
model: ToDo.Models.ToDo
} );
} )( jQuery, Backbone );
Events
Some common events are:
What does an event look like?
model.set, collection.reset) triggers an event{ silent: true } as an optionEvents are what make Backbone powerful
.bind() and .on(), we try to use .listenTo()Models and Views and Collections
Actions on a View can update a Model
ToDo.Views.ToDo = ( function( $, Backbone ) {
...
events: {
'click .on-completion': 'onCompletion'
},
onCompletion: function( e ) {
if ( e.currentTarget.checked ) {
this.model.set( {
checked: 'checked',
completedBy: ToDo.currentUser,
source: 'local'
} );
} else {
this.model.set( {
checked: '',
completedBy: false,
source: 'local'
} );
}
}
Updates to a Model can rerender a View
ToDo.Views.ToDo = ( function( $, Backbone ) {
...
model: ToDo.Models.ToDo,
initialize: function() {
// Update the View when the model changes
this.listenTo( this.model, 'change', this.render );
},
Updates to a Model send out the data
ToDo.Models.ToDo = ( function( $, Backbone ) {
...
initialize: function() {
this.listenTo( this, 'change', this.update );
},
update: function() {
if ( this.get( 'source' ) === 'local' ) {
var data = {
action: 'todos_check',
id: this.id,
checked: this.get( 'checked' )
};
and get a response from the server
var jqXHR = $.ajax( {
dataType: 'json',
url: ToDo.ajaxurl,
xhrFields: {
withCredentials: true
},
data: data
} )
.done( function( response, textStatus, jqXHR ) {
var todo = response.data[0];
todo.source = 'check';
ToDo.toDos.add( response.data[0], { merge: true } );
} );
Adding a Model to a Collection can add a View
ToDo.Views.Widget = ( function( $, Backbone ) {
...
initialize: function() {
this.listenTo( this.collection, 'add', this.addOne );
},
addOne: function( todo ) {
var view = new ToDo.Views.ToDo( {
model: todo
} );
this.$el.prepend( view.el );
return this;
},
What about other people's actions?
What about other people's actions?
Polling in the browser sends an AJAX request,
poll: function() {
var data = {
action: 'todos_poll',
since: ToDo.since
};
ToDo.current = Date.now();
var jqXHR = $.ajax( {
dataType: 'json',
url: ToDo.ajaxurl,
data: data
} )
receives back an encoded JSON response,
.done( function( response, textStatus, jqXHR ) {
if ( 'undefined' != typeof response.data ) {
ToDo.since = ToDo.current;
for ( m = 0, dl = response.data.length; m < dl; m++ ) {
var todo = response.data[m];
todo.source = 'poll';
ToDo.toDos.add( todo, { merge: true } );
}
}
} )
and then sets the next AJAX poll event
.always( function() {
ToDo.Polling.poller = setTimeout( ToDo.Polling.poll, ToDo.Polling.pollInterval );
} );
Polling on the server is a wp_ajax_ action
add_action( 'wp_ajax_todos_poll', array( 'ToDos_Widget', 'poll' ) );
public static function poll() {
$todos = array();
if ( isset( $_GET['since'] ) ) {
$since = absint( substr( $_GET['since'], 0, 10 ) ) - 3;
$min = time() - 24 * 60 * 60;
if ( $since < $min ) {
$since = $min;
}
$todos = self::get_todos_since( $since );
}
wp_send_json_success( $todos );
}
Getting a new Model from the server:
Getting an updated Model from the server
Custom Events within Backbone
ToDo.dispatcher= _.clone( Backbone.Events );
ToDo.dispatcher.trigger( 'toDo:server-update', { data: response.data } );
// object.trigger(event, [*args])
this.listenTo( ToDo.dispatcher, 'toDo:server-update', this.serverUpdated );
// object.listenTo(other, event, callback)
Why use custom events?
Talking to jQuery (and the DOM)
ToDo.appContainer = $( "#content" );
ToDo.appContainer.trigger( 'toDo:data-received', data );
ToDo.appContainer.on( 'toDo:data-received', function( data ) {
// Do something with data
} );
Why talk to jQuery?
Make it more familiar
ToDo.actions = [];
ToDo.currentActions = [];
ToDo.doneActions = [];
ToDo.doAction = function( hook ) {
if ( 'string' == typeof hook ) {
var args = Array.prototype.slice.call( arguments, 1 );
ToDo.currentActions.push( hook );
ToDo.appContainer.trigger( hook, args );
ToDo.doneActions.push( hook );
ToDo.currentActions.pop();
}
};
Add some helper functions
ToDo.currentAction = function() {
l = this.currentActions.length;
if ( l > 0 ) {
return this.currentActions[ l - 1 ];
}
return false;
}
ToDo.didAction = function( hook ) {
if ( -1 === $.inArray( hook, this.doneActions ) ) {
return false;
}
return true;
}
Then we can do this in JavaScript
ToDo.doAction( 'toDo:alertOn', data );
ToDo.appContainer.on( 'toDo:alertOnce', function() {
if ( ! ToDo.didAction( 'toDo:alertOnce' ) {
// Do something once
}
} );
Communication Issues
Communication Solutions
Slides: http://jmdodd.github.io/talks/2014/YUL/
ToDos: http://bit.ly/1l4JHsA