Wednesday, March 6, 2013

What I don't like about Backbone

Yes, this will probably start a flame war of sorts, and no we don't all have to agree on this subject.

I've recently moved cities and jobs (which is why I haven't had any updates for a while) and I'm now at a place called Dataminr. Dataminr decided to go with Backbone and use Backbone.LayoutManager to control their views. So now I'm working on Backbone until the next big release when we're going to talk about redoing the architecture.

So what have I been up to? Well for the first few days I was banging my head just wishing that Backbone or Backbone.LayoutManager had certain functionality that I was used to. Then I decided to add the functionality myself, and as I did so I ran in to more problems, problems that are baked in to the core of the two libraries. So my gripes come in two different sets:

1. Things I wish they did.
2. Things I wish they didn't do.

so I'll go through them and then some other things I was thinking when starting or discovered, and finally what I did.

Things I wish They Did

I don't like the separation of model and collection. What if I want a collection of collections? I wish Collections was extended from model.

Why isn't Sync an object? It's just a function. Why is everything else an object but only Sync is a function? It'd be great to have it as an object with it's own events and methods so you can just override parts of it instead of having to write the entire thing from scratch if there is just one line you don't want there. We could extend it with something like this:

Backbone.SuperSync = {
getSync: function(options) {
return _.bind(this.sync, this);
},
sync: function(method, model, options) {
this[method.toLowerCase().substring(0,4)](model, options);
},
post: function(model, options) {},
put: function(model, options) {},
get: function(model, options) {},
dele: function(model, options) {}
}

Some way to have a single model across multiple collections would be nice.

This is mostly pointed at LayoutManager as Backbone takes no responsibility for how a view should function:

A listview that can handle itself. it's that simple. It's 2013 already, I just want to have a listview with a collection and tell it what the child views should be. I don't want to listen to events and update it, I want that done for me. Showing a list should be bread and butter.

Pass along events from the collection on to the view. This kind of goes with the previous point, when a child is added to the collection, a view should be added (and in the proper order) and then an add event should fire.

Why are views not kept in the same order as the collection? What happens if I want to change the sort order? Do I really have to throw out all my perfectly good views and start again?

Also I had a bit of trouble with the events passing up from a subview, it seemed that to pass them along I needed to trigger it on the element instead of the object - maybe it's just me.

Things I wish they didn't do

Models should not have a reference to their collection. They just shouldn't. If I want a model in more than one collection, which collection is going to be put on the model and why? Why do I need a reference? I'd argue it just promotes bad coding practices.

The render function, as the be all and end all of the view. I really wish there was a bit more flesh to the Views rather than just run a single function as the end result. It promotes the render function as a dumping ground for all the logic of the view.

That extend method. The code I'm working on has the extend method used to setup inheritance chains throughout the code so I have to go hunting to see how things are implemented.

why does LayoutManager use an append method on the child and not one from the list? How is the child supposed to know how to place itself in the list?

Other things:

Turns out I didn't miss binding data to elements too much, but this is mostly because the data comes from the server and acts as alerts rather than being data models that the user can change. Once a model comes in it's probably not going to change.

There are some things I liked about Backbone though. I do like how the add model will try and createModels using the _prepareModel method which is great to override and put in some custom logic.

What I did about it

okay, so the first thing I didn't like was that extend method. If it's going to be that flexible then may as well make it REALLY flexible. I wrote a method that could use functional mixins to mixin functionality on to Backbone constructors. Now with that I could mixin any type of functionality I needed. The first thing I wrote mixins that made views selectable, expandable, focusable etc. Then came the autolist functionality and a model registry.  Now I'm replacing LayoutManager with a new manager that is based off of goog.ui.component. It's a little different to how you're taught to use Backbone - you only render a view ONCE, then you just update it on events, though you can always force it to recreate the DOM if you're feeling lazy. This also solves a lot of complexity in LayoutManager that has to handle things being asynchronous, to give you an idea it has the same functionality and is about half the size. I'm hoping that I can release the code soon as I'm expecting that the mixins system will be the extremely handy for those creating views, to give you an example this is what it looks like:

var EventList = Backbone.View.extend({
  template: listTemplate,
  itemView: EventItem
}).mixin([
  Mixin.view.autolist,
  Mixin.view.keyboardNavigatableList,
  Mixin.view.keepScroll
], {
  scrollEl: '.scroll-container'
});

for a look at what the mixins look like, it's something like this:

Mixin.view.keyboardNavigatableList = function(options) {

        this.mixin([
            Mixin.view.handleKeyboard,
            Mixin.view.singleSelectChild
        ], options);


        this.after('onKeyup', function(event) {
            if(event.keyCode == $.ui.keyCode.DOWN) {
                this.selectPreviousChild();
            } else if(event.keyCode == $.ui.keyCode.UP) {
                this.selectNextChild();
            }
        });

    };

and that's it! give it a collection and it'll keep itself updated, you can navigate the children with your keyboard (up and down) and it will keep the scroll on the child when new elements are added (we add them at the top).

I'm sure there are more points I'm forgetting and some of the points I made are fixed with other plugins and some of these are more specific to using LayoutManager. For now I've got to get back to working on the code so I can share it with all of you sooner

2 comments: