Backbone: synchronizing Models and LocalStorage - javascript

I've extended a model A and a collection of As as follows:
define(['underscore', 'backbone', 'backbone.localStorage'], function(_, Backbone) {
var A = Backbone.Model.extend({
initialize: function() {
}
});
var A_Collection = Backbone.Collection.extend({
model: A,
localStorage: new Backbone.LocalStorage("as")
});
return {
Model: A,
Collection: A_Collection
};
});
Collections are stored in localStorage and all works fine in my application. Then I clear and replace the localStorage directly by code (using clear and setItem functions) and try to instantiate a new collection, but the changes are not detected:
var aux = new A.Collection();
aux.fetch();
// aux is empty
Otherwise if a try:
var aux = new A.Collection();
aux.localStorage = new Backbone.LocalStorage("as");
aux.fetch();
// aux contains new data
The latter is not valid for me because I'd have to modify all the creation of collections in my project.
What am I missing?

Instances of Backbone.LocalStorage are not designed to listen for LocalStorage changes that occur outside their own code. That's why you get the behavior you are getting. However, there is a workaround.
When you define a collection like this:
var A_Collection = Backbone.Collection.extend({
model: A,
localStorage: new Backbone.LocalStorage("as")
});
the localStorage value is shared by all the instances of A_Collection. You can automatically create a new instance of Backbone.LocalStorage, like this:
var A_Collection = Backbone.Collection.extend({
model: A,
initialize: function() {
A_Collection.__super__.initialize.apply(this, arguments);
A_Collection.prototype.localStorage = new Backbone.LocalStorage("as");
},
});
We have to set it on the prototype so that it is shared by all instance of A_Collection, which is the same behavior as your original code. With this in place, whenever you create a new instance of A_Collection, you will get a new instance of Backbone.LocalStorage, which will get information anew from LocalStorage.
Here is a plunker illustrating. Here is the relevant code, for reference:
var A = Backbone.Model.extend({
initialize: function() {}
});
var A_Collection = Backbone.Collection.extend({
model: A,
initialize: function() {
A_Collection.__super__.initialize.apply(this, arguments);
A_Collection.prototype.localStorage = new Backbone.LocalStorage("as");
},
});
// Setup a collection.
var collection = new A_Collection();
collection.fetch();
// Clean it out from previous runs... Note that we have to use destroy to destroy all items.
// Reset won't save to LocalStorage.
while (collection.length > 0) {
var model = collection.at(0);
model.destroy();
collection.remove(model);
}
// and set some elements.
collection.create({
name: "1"
});
collection.create({
name: "2"
});
console.log("collection length:", collection.length);
// Mess with it outside the Backbone code.
localStorage.clear();
// Manually create data that looks like what Backbone expects.
localStorage.setItem("as-1", JSON.stringify({
name: "foo",
id: "1"
}));
localStorage.setItem("as-2", JSON.stringify({
name: "bar",
id: "2"
}));
localStorage.setItem("as-3", JSON.stringify({
name: "baz",
id: "3"
}));
localStorage.setItem("as", "1,2,3");
// Create a new collection that loads from LocalStorage
var collection2 = new A_Collection();
collection2.fetch();
console.log("collection 2 length:", collection2.length);
console.log("first item", collection2.at(0).toJSON());
console.log("third item", collection2.at(2).toJSON());
console.log("instance is shared?", collection.localStorage === collection2.localStorage);
The code above generates this on the console:
collection length: 2
collection 2 length: 3
first item Object {name: "foo", id: "1"}
third item Object {name: "baz", id: "3"}
instance is shared? true

Related

Is Backbone.*.extend setting the protoypal value for the defined class?

When using extend with collections and models are attributes that are not backbone methods set as the prototypal value of the defined "class"? Meaning that if those attributes are modified in one instance of the class they are changed for all instances of the class?
I'm running into a problem similar to that of the following.
For example :
var MyCollection = Backbone.Collection.extend({
options:
{
someAttribute: null,
anotherAttribute : null
},
url : function()
{
return this.options.someAttribute + "/" + this.options.anotherAttribute
});
var myCollectionInstance1 = new MyCollection();
_.extend(myCollectionInstance1.options,{
someAttribute: "page1",
anotherAttribute : "location1"});
var myCollectionInstance2 = new MyCollection();
_.extend(myCollectionInstance2.options,{
someAttribute: "page1",
anotherAttribute : "location2"});
// I call fetch here which will eventually run into my redefined url
// and now my url for this function is going to be
// "page1/location2" instead of what I expect "page1/location1"
// I assume this is because the protoype is being changed in the above
// of myCollectionInstance2.options
myCollectionInstance1.fetch();
If this is the case then what is the best way to attach instance variables to collections?
Yes, everything in the first argument to extend ends up in the prototype and is thus shared by the instances. A common solution for mutable properties (such as objects and arrays) is to assign them inside initialize:
var MyCollection = Backbone.Collection.extend({
initialize: function(options) {
this.options = {
someAttribute: null,
anotherAttribute: null
};
// The rest of the initialization goes here...
}
});
If you want to keep them where they are for documentation purposes, then you can _.clone in initialize:
var MyCollection = Backbone.Collection.extend({
default_options: {
someAttribute: null,
anotherAttribute: null
},
initialize: function(options) {
this.options = _(this.default_options).clone();
// The rest of the initialization goes here...
}
});
Note that _.clone only does a shallow copy so you can still end up with accidental sharing if this.options contains embedded arrays or objects.
Your problem is when you do this:
var MyCollection = Backbone.Collection.extend({
options:
{
someAttribute: null,
anotherAttribute : null
}
you are creating an object { someAttribute: null, anotherAttribute : null } that's shared with all the instances, and when you do this:
_.extend(myCollectionInstance1.options,{ ...
you are updating this shared object.
The solution to your problem is when you want to set the collection.options create new objects:
myCollectionInstance1.options = {
someAttribute: "page1",
anotherAttribute : "location1"
};
...
myCollectionInstance2.options = {
someAttribute: "page1",
anotherAttribute : "location2"
};
this way each collection will hold its own object.

Objects and Arrays of one instance are getting referenced by new instance of Backbone model

I am using backbone model and quite surprised to see that my JSON objects and arrays which are set inside a particular instance of my Backbone model are getting accessible by other instances too.
var myModel = Backbone.Model.extend({
defaults: {
exp: [],
name: '',
json: { }
},
getExp: function() {
return this.get('exp');
},
getJSON: function() {
return this.get('json');
}
});
var m1 = new myModel();
var experiences = m1.getExp();
experiences.push('arrayitem1');
experiences.push('arrayitem2'); //Setting values for array of m1
m1.set('name', 'my name');
var json = m1.getJSON();
json.key = 'somevalue';
var m2 = new myModel();
console.log(m1.attributes);
console.log(m2.attributes);
Output:
{"exp": ["arrayitem1", "arrayitem2"], "json": {"key": "somevalue"}, "name": "my name"}
{"exp": ["arrayitem1", "arrayitem2"], "json": {"key": "somevalue"}, "name": ""}
Key-value pairs of m2
Name property inside m2 is default. (As expected)
exp : Same as in m1 (Not expected)
json: Same as in m1 (Not expected)
JSBIN DEMO
I am unable to reason for this behavior.
Update
I want to know how should I solve it. I have added an answer (Kinda hacky) as it is working for me but I do not know if it is right or not. Would love to know the reason for this behaviour too. What wrong am I doing or Is this some kind of bug in Backbone.
There is a hack I have used to solve this problem. Reset all the objects and arrays used as attributes in Backbone model in initialize method.
var myModel = Backbone.Model.extend({
defaults: {
exp: [],
name: '',
json: { }
},
getExp: function() {
return this.get('exp');
},
getJSON: function() {
return this.get('json');
},
initialize: function() {
this.set('exp', []);
this.set('json', {});
}
});
var m1 = new myModel();
var experiences = m1.getExp();
experiences.push('arrayitem1');
experiences.push('arrayitem2');
m1.set('name', 'my name');
var json = m1.getJSON();
json.key = 'somevalue';
var m2 = new myModel();
console.log(m1.attributes);
console.log(m2.attributes);
Working Demo at JSBIN

Backbone model .toJSON() doesn't render all attributes to JSON

I need to render a model's attributes to JSON so I can pass them into a template.
Here is what a render() function for a view looks like:
render: function() {
console.log(this.model);
console.log(this.model.toJSON());
$(this.el).html(this.template(this.model.toJSON()));
return this;
},
Here is the attributes output after doing console.log(this.model):
created_at: "2012-04-19"
id: "29"
name: "item"
resource_uri: "/api/v1/item/29/"
updated_at: "2012-04-21"
user: "/api/v1/user/9/"
Here is the model's JSON output after doing console.log(this.model.toJSON()):
id: "29"
__proto__: Object
What happened?
Edit:
Here is the instantiation:
var goal = new Goal({id: id});
goal.fetch();
var goalFullView = new GoalFullView({
model: goal,
});
Here are the contents of the new view:
console.log(this.model.attributes);
console.log(this.model.toJSON());
Here is what the console says:
Object
created_at: "2012-04-23"
id: "32"
name: "test"
resource_uri: "/api/v1/goal/32/"
updated_at: "2012-04-23"
user: "/api/v1/user/9/"
__proto__: Object
Object
id: "32"
name: "test"
__proto__: Object
If the toJSON is supposed to make a clone of the attributes, why doesn't it copy the correct name or why doesn't it copy the created_at, updated_at fields?
Edit 2:
Here is the model:
var Goal = Backbone.Model.extend({
// Default attributes for Goal
defaults: {
name: "empty goal",
},
// Check that the user entered a goal
validate: function(attrs) {
if (!attrs.name) {
return "name is blank";
}
},
// Do HTTP requests on this endpoint
url: function() {
if (this.isNew()) {
return API_URL + "goal/" + this.get("id") + FORMAT_JSON;
}
return API_URL + "goal/" + FORMAT_JSON;
//API_URL + "goal" + FORMAT_JSON,
},
});
Edit 3:
I figured out that I need to use the success callback from fetch to render a view that uses the model:
goal.fetch({success: function(model) {
var goalFullView = new GoalFullView({
model: goal,
});
}});
The toJSON() method just returns a shallow clone of the model's attributes property.
From the annotated Backbone.js source:
toJSON: function(options) {
return _.clone(this.attributes);
}
Without seeing more of your code, it looks like you directly set properties on the model object, rather than using the set function to set model attributes.
I.e. don't do this:
model.name = "item";
do this:
model.set("name", "item");
EDIT:
For your specific issue, it's possible that you called toJSON before the model had finished loading from the server.
E.g. This won't always work as expected:
var model = new Goal({id: 123});
model.fetch();
console.log(model.toJSON());
But this will:
var model = new Goal({id: 123});
model.fetch({
success: function() {
console.log(model.toJSON());
}
});

Nested Models in Backbone.js, how to approach

I've got the following JSON provided from a server. With this, I want to create a model with a nested model. I am unsure of which is the way to achieve this.
//json
[{
name : "example",
layout : {
x : 100,
y : 100,
}
}]
I want these to be converted to two nested backbone models with the following structure:
// structure
Image
Layout
...
So I define the Layout model like so:
var Layout = Backbone.Model.extend({});
But which of the two (if any) techniques below should I use to define the Image model? A or B below?
A
var Image = Backbone.Model.extend({
initialize: function() {
this.set({ 'layout' : new Layout(this.get('layout')) })
}
});
or, B
var Image = Backbone.Model.extend({
initialize: function() {
this.layout = new Layout( this.get('layout') );
}
});
I have the very same issue while I'm writing my Backbone application. Having to deal with embedded/nested models. I did some tweaks that I thought was a quite elegant solution.
Yes, you can modify the parse method to change a attributes around in the object, but all of that is actually pretty unmaintainable code IMO, and feels more of a hack than a solution.
Here's what I suggest for your example:
First define your Layout Model like so.
var layoutModel = Backbone.Model.extend({});
Then here's your image Model:
var imageModel = Backbone.Model.extend({
model: {
layout: layoutModel,
},
parse: function(response){
for(var key in this.model)
{
var embeddedClass = this.model[key];
var embeddedData = response[key];
response[key] = new embeddedClass(embeddedData, {parse:true});
}
return response;
}
});
Notice that I have not tampered with the model itself, but merely pass back the desired object from the parse method.
This should ensure the structure of the nested model when you're reading from the server. Now, you would notice that saving or setting is actually not handled here because I feel that it makes sense for you to set the nested model explicitly using the proper model.
Like so:
image.set({layout : new Layout({x: 100, y: 100})})
Also take note that you are actually invoking the parse method in your nested model by calling:
new embeddedClass(embeddedData, {parse:true});
You can define as many nested models in the model field as you need.
Of course, if you want to go as far as saving the nested model in its own table. This wouldn't be sufficient. But in the case of reading and saving the object as a whole, this solution should suffice.
I'm posting this code as an example of Peter Lyon's suggestion to redefine parse. I had the same question and this worked for me (with a Rails backend). This code is written in Coffeescript. I made a few things explicit for people unfamiliar with it.
class AppName.Collections.PostsCollection extends Backbone.Collection
model: AppName.Models.Post
url: '/posts'
...
# parse: redefined to allow for nested models
parse: (response) -> # function definition
# convert each comment attribute into a CommentsCollection
if _.isArray response
_.each response, (obj) ->
obj.comments = new AppName.Collections.CommentsCollection obj.comments
else
response.comments = new AppName.Collections.CommentsCollection response.comments
return response
or, in JS
parse: function(response) {
if (_.isArray(response)) {
return _.each(response, function(obj) {
return obj.comments = new AppName.Collections.CommentsCollection(obj.comments);
});
} else {
response.comments = new AppName.Collections.CommentsCollection(response.comments);
}
return response;
};
Use Backbone.AssociatedModel from Backbone-associations :
var Layout = Backbone.AssociatedModel.extend({
defaults : {
x : 0,
y : 0
}
});
var Image = Backbone.AssociatedModel.extend({
relations : [
type: Backbone.One,
key : 'layout',
relatedModel : Layout
],
defaults : {
name : '',
layout : null
}
});
I'm not sure Backbone itself has a recommended way to do this. Does the Layout object have its own ID and record in the back end database? If so you can make it its own Model as you have. If not, you can just leave it as a nested document, just make sure you convert it to and from JSON properly in the save and parse methods. If you do end up taking an approach like this, I think your A example is more consistent with backbone since set will properly update attributes, but again I'm not sure what Backbone does with nested models by default. It's likely you'll need some custom code to handle this.
I'd go with Option B if you want to keep things simple.
Another good option would be to use Backbone-Relational. You'd just define something like:
var Image = Backbone.Model.extend({
relations: [
{
type: Backbone.HasOne,
key: 'layout',
relatedModel: 'Layout'
}
]
});
I use Backbone DeepModel plugin for nested models and attributes.
https://github.com/powmedia/backbone-deep-model
You can bind to change events 'n levels deep. for example:
model.on('change:example.nestedmodel.attribute', this.myFunction);
CoffeeScript version of rycfung's beautiful answer:
class ImageModel extends Backbone.Model
model: {
layout: LayoutModel
}
parse: (response) =>
for propName,propModel of #model
response[propName] = new propModel( response[propName], {parse:true, parentModel:this} )
return response
Ain't that sweet? ;)
I had the same issue and I've been experimenting with the code in rycfung's answer, which is a great suggestion.
If, however, you do not want to set the nested models directly, or do not want to constantly
pass {parse: true} in the options, another approach would be to redefine set itself.
In Backbone 1.0.0, set is called in constructor, unset, clear, fetch and save.
Consider the following super model, for all models that need to nest models and/or collections.
/** Compound supermodel */
var CompoundModel = Backbone.Model.extend({
/** Override with: key = attribute, value = Model / Collection */
model: {},
/** Override default setter, to create nested models. */
set: function(key, val, options) {
var attrs, prev;
if (key == null) { return this; }
// Handle both `"key", value` and `{key: value}` -style arguments.
if (typeof key === 'object') {
attrs = key;
options = val;
} else {
(attrs = {})[key] = val;
}
// Run validation.
if (options) { options.validate = true; }
else { options = { validate: true }; }
// For each `set` attribute, apply the respective nested model.
if (!options.unset) {
for (key in attrs) {
if (key in this.model) {
if (!(attrs[key] instanceof this.model[key])) {
attrs[key] = new this.model[key](attrs[key]);
}
}
}
}
Backbone.Model.prototype.set.call(this, attrs, options);
if (!(attrs = this.changedAttributes())) { return this; }
// Bind new nested models and unbind previous nested models.
for (key in attrs) {
if (key in this.model) {
if (prev = this.previous(key)) {
this._unsetModel(key, prev);
}
if (!options.unset) {
this._setModel(key, attrs[key]);
}
}
}
return this;
},
/** Callback for `set` nested models.
* Receives:
* (String) key: the key on which the model is `set`.
* (Object) model: the `set` nested model.
*/
_setModel: function (key, model) {},
/** Callback for `unset` nested models.
* Receives:
* (String) key: the key on which the model is `unset`.
* (Object) model: the `unset` nested model.
*/
_unsetModel: function (key, model) {}
});
Notice that model, _setModel and _unsetModel are left blank on purpose. At this level of abstraction you probably can't define any reasonable actions for the callbacks. However, you may want to override them in the submodels that extend CompoundModel.
Those callbacks are useful, for instance, to bind listeners and propagate change events.
Example:
var Layout = Backbone.Model.extend({});
var Image = CompoundModel.extend({
defaults: function () {
return {
name: "example",
layout: { x: 0, y: 0 }
};
},
/** We need to override this, to define the nested model. */
model: { layout: Layout },
initialize: function () {
_.bindAll(this, "_propagateChange");
},
/** Callback to propagate "change" events. */
_propagateChange: function () {
this.trigger("change:layout", this, this.get("layout"), null);
this.trigger("change", this, null);
},
/** We override this callback to bind the listener.
* This is called when a Layout is set.
*/
_setModel: function (key, model) {
if (key !== "layout") { return false; }
this.listenTo(model, "change", this._propagateChange);
},
/** We override this callback to unbind the listener.
* This is called when a Layout is unset, or overwritten.
*/
_unsetModel: function (key, model) {
if (key !== "layout") { return false; }
this.stopListening();
}
});
With this, you have automatic nested model creation and event propagation. Sample usage is also provided and tested:
function logStringified (obj) {
console.log(JSON.stringify(obj));
}
// Create an image with the default attributes.
// Note that a Layout model is created too,
// since we have a default value for "layout".
var img = new Image();
logStringified(img);
// Log the image everytime a "change" is fired.
img.on("change", logStringified);
// Creates the nested model with the given attributes.
img.set("layout", { x: 100, y: 100 });
// Writing on the layout propagates "change" to the image.
// This makes the image also fire a "change", because of `_propagateChange`.
img.get("layout").set("x", 50);
// You may also set model instances yourself.
img.set("layout", new Layout({ x: 100, y: 100 }));
Output:
{"name":"example","layout":{"x":0,"y":0}}
{"name":"example","layout":{"x":100,"y":100}}
{"name":"example","layout":{"x":50,"y":100}}
{"name":"example","layout":{"x":100,"y":100}}
I realize I'm late to this party, but we recently released a plugin to deal with exactly this scenario. It's called backbone-nestify.
So your nested model remains unchanged:
var Layout = Backbone.Model.extend({...});
Then use the plugin when defining the containing model (using Underscore.extend):
var spec = {
layout: Layout
};
var Image = Backbone.Model.extend(_.extend({
// ...
}, nestify(spec));
After that, assuming you have a model m which is an instance of Image, and you've set the JSON from the question on m, you can do:
m.get("layout"); //returns the nested instance of Layout
m.get("layout|x"); //returns 100
m.set("layout|x", 50);
m.get("layout|x"); //returns 50
Use backbone-forms
It supports nested forms, models and toJSON. ALL NESTED
var Address = Backbone.Model.extend({
schema: {
street: 'Text'
},
defaults: {
street: "Arteaga"
}
});
var User = Backbone.Model.extend({
schema: {
title: { type: 'Select', options: ['Mr', 'Mrs', 'Ms'] },
name: 'Text',
email: { validators: ['required', 'email'] },
birthday: 'Date',
password: 'Password',
address: { type: 'NestedModel', model: Address },
notes: { type: 'List', itemType: 'Text' }
},
constructor: function(){
Backbone.Model.apply(this, arguments);
},
defaults: {
email: "x#x.com"
}
});
var user = new User();
user.set({address: {street: "my other street"}});
console.log(user.toJSON()["address"]["street"])
//=> my other street
var form = new Backbone.Form({
model: user
}).render();
$('body').append(form.el);
If you don't want to add yet another framework, you might consider creating a base class with overridden set and toJSON and use it like this:
// Declaration
window.app.viewer.Model.GallerySection = window.app.Model.BaseModel.extend({
nestedTypes: {
background: window.app.viewer.Model.Image,
images: window.app.viewer.Collection.MediaCollection
}
});
// Usage
var gallery = new window.app.viewer.Model.GallerySection({
background: { url: 'http://example.com/example.jpg' },
images: [
{ url: 'http://example.com/1.jpg' },
{ url: 'http://example.com/2.jpg' },
{ url: 'http://example.com/3.jpg' }
],
title: 'Wow'
}); // (fetch will work equally well)
console.log(gallery.get('background')); // window.app.viewer.Model.Image
console.log(gallery.get('images')); // window.app.viewer.Collection.MediaCollection
console.log(gallery.get('title')); // plain string
You'll need BaseModel from this answer (available, if you fancy, as a gist).
We have this problem too and a team worker has implemented a plugin named backbone-nested-attributes.
The usage is very simple. Example:
var Tree = Backbone.Model.extend({
relations: [
{
key: 'fruits',
relatedModel: function () { return Fruit }
}
]
})
var Fruit = Backbone.Model.extend({
})
With this, the Tree model can access then fruits:
tree.get('fruits')
You can see more informations here:
https://github.com/dtmtec/backbone-nested-attributes

Backbone collections representing tree data

I want to load json data in form of a tree into Backbone Collection. I can't do this. Can anyone explain what I'm doing wrong?
My very simple model:
CTreeDataItem = Backbone.Model.extend(
{
});
CTreeDataItems = Backbone.Collection.extend(
{
model: CTreeDataItem
});
And my load attepmt:
var treeJs =
[
{ id: "tvRoot", title: 'Root', cssClass: 'root',
items: [
{ id: "ti1", title: 'Item1', cssClass: 'item',
items: [
{ id: "ti11", title: 'SubItem11', cssClass: 'subitem'},
{ id: "ti12", title: 'SubItem12', cssClass: 'subitem'}]},
{ id: "ti2", title: 'Item2', cssClass: 'item',},
{ id: "ti3", title: 'Item3', cssClass: 'item',
items: [
{ id: "ti31", title: 'SubItem31', cssClass: 'subitem'},
{ id: "ti32", title: 'SubItem32', cssClass: 'subitem'},
{ id: "ti33", title: 'SubItem33', cssClass: 'subitem'}]}]
}];
this.TreeData = new CTreeDataItems();
this.TreeData.add(treeJs);
Try representing the tree with Model as the node and each node containing a Collection of its child nodes:
var CTreeDataItem = Backbone.Model.extend({
initialize: function() {
if (Array.isArray(this.get('items'))) {
this.set({items: new CTreeDataItemChildren(this.get('items'))});
}
}
});
// children collection
var CTreeDataItemChildren = Backbone.Collection.extend({
model: CTreeDataItem
});
// create
var treeData = new CTreeDataItemChildren(treeJs);
// access
treeData.at(0).get('items').at(1).get('title')
// returns "Item2"
EDIT 2011-05-18: If you want to flatten the tree and maintain a reference to the parent that each node had in the tree:
// flatten the tree outside of Backbone. Alternatively,
// you could override the constructor of the CTree collection
// to provide encapsulation
function flatten(parentID, arr) {
var out = [];
for (var i = 0; i < arr.length; i++) {
var node = arr[i];
node.parentID = parentID;
out.push(node);
if (Array.isArray(node.items))
Array.prototype.push.apply(out, flatten(node.id, node.items));
delete node.items;
}
return out;
}
// remove above code that would have provided nesting
var CTreeDataItem = Backbone.Model.extend({});
// children collection, as before
var CTreeDataItemCollection = Backbone.Collection.extend({
model: CTreeDataItem
});
// create
var treeData = new CTreeDataItemChildren(flatten('', treeJs));
// as before, but now there's the 'parentID' FK
treeData.at(3).get('parentID')
// returns "ti1"
Hope that's what you're after.
You need to use Backbone.Model parse method which is used to parse data before it gets passed to a model. You can use it to turn data on each level of your tree to Collections and Models representing items on these collections.
Then when saving you'd have to override the toJSON method on the Model to return the json representation of your data the same way you receive it. It is later on used by Backbone.sync to send the data back to the server. By default it returns only _.clone of your Model.attributes and you want all the Collections and CollectionModels in there as well.
All hail the backbone! :)

Categories