Extending Backbone Models, Views and Collections
You can extend ProjectHuddle's models, views and collections via by extending ProjectHuddle's objects. For instance, if you want to add new functions, add additional events, or even add your own UI you can hook into the initialize function on any view or model of your choice.
Why would you want to extend models, views or collections?
There are many reasons to extend models, views and collections. Here are some of the most common use-cases:
- You want to add or change functionality.
- You want to add new UI components.
- You need to reference model data.
- You want to listen for changes in model data and run your own functions.
ph.api.hooks
ph.api.hooks is an object that contains all the actions and filters used in the javascript on the page. For developers familiar with WordPress, they work very similar to WordPress' PHP hooks, and for developers familiar with Gutenberg, they work almost identically to Gutenberg's hooks.
You can add a new action using the following:
ph.api.hooks.addAction( tag, namespace, callback, priority )
ph.api.hooks.addFilter( tag, namespace, callback, priority );
argument | Description |
---|---|
tag string | The name of the action or filter |
namespace string | A namespace for your hook. This is so it can be referenced globally. |
callback function | The function to call when the hook occurs |
priority int | (optional) A priority for your hook. Defaults to 10 |
Using Hooks
Here's an example of how to use an addAction function. The initialize action is run when each model is initialized and populated with data - this is a very common way to add and run your own functions. Here's an example of how we would use the initialize function on a comment model:
/**
* Hook into website thread initialize
*/
ph.api.hooks.addAction(
// this is the comment action
"ph.api.models.Comment.initialize",
// this is the namespace
"myCustomization",
// the function we're going to run
'myFunction',
);
/**
* Hook into comment initialize
*/
function myFunction(model) {
// this will log the model
console.log(model);
// this will run a model's method
if ( typeof model.setModerate === "function" ) {
model.setModerate();
}
// this will log the comment content
console.log(model.get('content').rendered);
}
This example doesn't do much, but you can see we can access a model's data, methods, and more.
Extending Models
Now let's take things a step further. We can add our own methods, listen to model data changes and more. This will help us run functions within the scope of the model.
Types of Models
Models are stored in the ph.api.models object. Here's some of the models that may be included on a page:
ph.api.models.Comment
ph.api.models.MockupThread
ph.api.models.MockupImage
ph.api.models.Mockup
ph.api.models.WebsiteThread
ph.api.models.WebsitePage
ph.api.models.Website
ph.api.models.User
ph.api.models.UserMe
Adding New Functions
Let's begin by adding a new method to a model and calling it on initialize.
Prefix Recommended
Whenever you're adding new methods to a model, view or collection, it's always recommended you add a unique prefix to your function. This will prevent conflicts with existing functions within a model.
_.extend
Underscore provides and _.extend method for this reason! This lets us extend an existing model's p prototype with our own methods. Let's start by extending the comment model from the example above and add our own method.
We can tell from the source files that the comment model is stored in ph.api.models.Comment. Here we'll use the extend method and use a hook to call the new method:
/**
* Hook into comment initialize
*/
ph.api.hooks.addAction(
// this is the comment action
"ph.api.models.Comment.initialize",
// this is the namespace
"myCustomization",
// run our function
function(model) {
// run the new method we added
model.prefixMyFunction();
}
);
/**
* Extend model prototype functions with our own
*/
_.extend(ph.api.models.Comment.prototype, {
prefixMyFunction: function() {
console.log(this); // log this
// this will log the comment content
console.log(this.get('content').rendered);
}
});
You can see that by extending a model, we can add new methods and run functions within the context of a model.
Listening for Model Changes
We can also listen for model changes and act accordingly. For example, perhaps we want to perform another action when a website comment thread is resolved.
listenTo
You can do this with backbones listenTo
function. The advantage of using this form, instead of other.on(event, callback, object)
, is that
listenTo
allows the backbone object to keep track of the events, and they can be removed all at once later on.
This prevents listening to items if models are removed, for instance.
/**
* Hook into comment thread initialize
*/
ph.api.hooks.addAction(
// this is the comment action
"ph.api.models.WebsiteThread.initialize",
// this is the namespace
"myCustomization",
// run our function
function(model) {
// run the new method we added
model.prefixMyInitFunction();
}
);
/**
* Extend model prototype functions with our own
*/
_.extend(ph.api.models.WebsiteThread.prototype, {
// our init function
prefixMyInitFunction: function() {
// listen to when the resolved attribute changes
this.listenTo(this, 'change:resolved', this.prefixShowAlert);
},
// this function runs when the assigned
prefixShowAlert: function() {
// show alert on page
if ( this.get('resolved') ) {
alert('The comment was resolved');
} else {
alert('The comment was unresolved');
}
}
});
In the above example we are listening to the models "resolved" attribute and running our own function when the value changes.
Extending Views
Extending models is awesome for adding new data and functionality, but extending views will let you add your own subviews or events to a specific view. A view can be anything from a single button to a comment container.
Views work a little differently from models in that you don't need to use a hook or filter to extend them necessarily. Views can utilize the super method to extend the initialize function, instead of using a hook:
ph.api.views.Comment = ph.api.views.Comment.extend({
initialize: function() {
this.constructor.__super__.initialize.apply(this, arguments);
console.log(this); // log to check if initialize is called
}
});
Adding New Functions
You can add new functions to your views using underscore's extend
function
ph.api.views.Comment = ph.api.views.Comment.extend({
initialize: function() {
this.constructor.__super__.initialize.apply(this, arguments);
this.prefixMyFunction();
},
prefixMyFunction() {
console.log(this.model);
}
});
Adding New UI Event Listeners
You can also use Backbone's "events" object to add your own even listeners scoped to the model and it's child views. The object property in the view looks like this:
var ParentView = Backbone.View.extend({
events: {
'click': 'onclick',
'click .something': 'somethingClicked'
},
onClick: function(e) {
console.log('This view is clicked!');
},
somethingClicked: function(e) {
console.log('The .something element in this view was clicked!');
}
});
It follows this notation:
'{action} {selector (optional)}': '{function}'
You can extend this events object when you extend the view like this:
// store original events
var originalEvents = ph.api.views.Comment.prototype.events;
// extend our view
ph.api.views.Comment = ph.api.views.Comment.extend({
events: function(){
return _.extend({}, originalEvents,{
'click' : 'prefixOnClick'
});
}
});
Since you're overwriting the view, you'll want to store the original events before you extend them Otherwise you'll remove them entirely.
Creating New Views
To create a new view, we recommend that you extend ProjectHuddle's ph.api.View
base view. The base view
provides some additional functionality to ensure you can use subviews and more.
You can set your views template classnames and tag when you create your view. Use the className
attribute
in your view to set the classname. Use the tagName
attribute to set what tag to use.
Here's an example:
var myView = ph.api.View.extend({
events: {
'click': 'alert'
},
alert: function() {
alert('clicked');
}
});
Creating A New Underscore Template
You can set a custom underscore template to use for your view. First,
create the underscore template in php. Here I'll create a new file and call it
my-custom-view.tmpl.php
, then add this inside:
1. Create a new underscore template
<script type="text/template" id="tmpl-my-underscore-template">
<div class="inside-class">
{{ data.content.rendered}}
</div>
</script>
You must include the template in a script
tag with type="text/template"
. The most
important part is the id, which must use the format tmpl-{template-name}
. You will
reference the template name later in the view.
2. Output Your Underscore Template
Once you've created this file, and added it to your plugin, theme or child theme, you'll want to output it on the page for your javascript to use. You can do that with a couple actions.
ph_underscore_templates
Outputs underscore templates for the Mockup Interface.ph_website_header
Outputs underscore templates for the Website Interfaceadmin_footer
Outputs underscore templates on the admin pages.
<?php
/**
* Output backbone templates.
*
* @return void
*/
function my_underscore_templates() {
// if we've placed in the project-huddle directory
ph_get_template( 'my-custom-view.tmpl.php' );
// or we could load from a custom folder
ph_get_template( 'custom-views/my-custom-view.tmpl.php', '', '', get_theme_file_uri('custom-folder') );
}
// output on website interface
add_action( 'ph_website_header', 'my_underscore_templates', 20 );
// output on mockup interface
add_action( 'ph_underscore_templates', 'my_underscore_templates', 20 );
3. Use the template for your view
To use the template for your view, you can use the template
attribute and the wp.template()
method. Here's an example:
// A custom view you create
var myView = ph.api.View.extend({
// the ID of our template is tmpl-my-underscore-template
// notice how we don't use the tmpl part here
template: wp.template('my-underscore-template'),
});
Adding Child Views
ProjectHuddle makes use of WordPress' backbone subview manager, which ships with all WordPress installations.
You can add new subviews to a view using the this.views.add
or this.views.set
notation.
set
with replace all subviews for a given view.add
adds additional subviews for a given view.
this.views.add(selector, views, options)
this.views.set(selector, views, options)
Parameters
Parameter | Description |
---|---|
selector | (string) (optional) A jQuery selector. |
view | (object) The subviews for the main view. |
options | (object) Options for call. To insert views at a specific index, use options.at . If options.silent is true, no DOM modifications will be made. |
Example
// A custom view you create
var myView = ph.api.View.extend({
template: wp.template('my-underscore-template'),
className: 'my-view-classname',
tagName: 'span',
});
ph.api.views.Comment = ph.api.views.Comment.extend({
initialize: function() {
this.constructor.__super__.initialize.apply(this, arguments);
// add a subview
this.views.add(".ph-item-controls", new myView({
model: this.model
}));
},
});
In the above example we are adding our view to the '.ph-item-controls' selector, and using the comment model also for the subview. The benefit of this is we can listen to model events from within our subview to keep track of everything that's going on in the model.