

Backbone.js views done right - joshowens
http://blog.gaslightsoftware.com/post/24538291598/backbone-js-views-done-the-right-way?utm_medium=hackernews

======
jashkenas
Whoa there nellie. Complexity ahoy.

Why would you want to have a view that dumps out its HTML as a string? If a
string of HTML is all you want, don't use a view, just use a template.

But putting that aside for a second ... for this particular example, how about
this:

    
    
        render: ->
          this.$el.html JST["table_view_template"]()
          tbody = this.$el('tbody')
          for person in this.collection.models
            view = new TableRowView model: person
            tbody.append view.render().el
    

... or if you were in earnest about only needing the raw HTML from the sub-
view, how about just rendering the row templates within the table template,
making your render function as simple as this:

    
    
        render: ->
          this.$el.html this.template people: this.collection

~~~
ryanisinallofus
"Whoa there nellie. Complexity ahoy."

Mixing metaphors? Is it a horse or a ship you are riding?

It's really hard to read the rest of your technical reasoning (which makes
sense) with opening statements like that.

~~~
jashkenas
Aw, I'm just horsing around. You're quite right that it's dangerous to mix
metaphors while showing someone the ropes.

~~~
ryanisinallofus
Lol. This is the kind of shit I'm talking about. In my head jashkenas looks
just like this guy when he writes like this:
[http://upload.wikimedia.org/wikipedia/en/thumb/7/7c/Lulz_Sec...](http://upload.wikimedia.org/wikipedia/en/thumb/7/7c/Lulz_Security.svg/200px-
Lulz_Security.svg.png)

------
chime
How is the performance when each row gets its own object instantiated, its own
template loaded, and its own events attached? What happens when you have 1000+
rows in a scrollable/sortable/filterable table? I tried nested views approach
but the performance was lacking in slower PCs when the row count was high.

What I prefer to do is the following:

    
    
        class Example.Views.TableView extends Backbone.View
    
          events:
            'click td': 'clicked'
    
          rowHtml: ->
            x = []
            p = $('#person_row').html()
            for person in @collection.models
              x.push(_.template(p, person))
            x.join('')
    
          clicked: (e) ->
            id = $(e.target).parents('tr').attr('id')
            # handle action for row id
    
          render: ->
            # render self view including rowHTML
    
    

The template is loaded just once into a variable and filled in each iteration.
Only one set of events is attached to the DOM. Only one call is made to DOM to
create all the rows, one string merged from HTML array. I still get the
benefit of view and subview templates, without the constructor for subview
objects being called 1000 times when all I need is the <tr> HTML x 1000.

~~~
jashkenas
Yep -- when you're dealing with large numbers of DOM elements, you absolutely
don't want to have an individual View for each element. At the other extreme,
you don't want to have a single View for your entire application. Find a
balance that makes sense, and represents a logical chunk of UI.

For a bit more, see the FAQ, particularly the bottom of:
<http://backbonejs.org/#FAQ-tim-toady>

~~~
fooandbarify
Is there a good rule of thumb as to what constitutes a "large number" of
Views? I tend to assign Views to Models rather than Collections, partly
because I hate storing Model ids as DOM attributes and extracting them in
event handlers (as in the grandparent comment). I've gotten away with several
hundred Views on a page before, but that doesn't feel quite right either.

------
jcromartie
This is getting way too many votes for what amounts to bad advice. I think you
should keep subview rendering methods out of your templates (i.e. keep your
templates "dumb"). You can just create placeholder elements in the template,
and then pass the placeholder element to the subview constructor. Or you can
just append a list of collection-based subviews to a single container element.

You should be able to write Backbone apps without resorting to stuff like
$("#row_#{@model.id}")

And for an example, here's a gist <https://gist.github.com/2931491>

~~~
slewis
HN is about interesting discussion, and this article has generated that, so
maybe it has just the right amount of upvotes?

~~~
BHSPitMonkey
Part of the problem is that people will inevitably associate upvotes with a
consensus of agreement with the author, and thus trust advice they probably
shouldn't.

~~~
rudasn
Not necessarily. I often find my self upvoting articles that either seem
interesting or have interesting discussions in order to go back to them and
review them. Most often than not I read the comments first and then the
article.

If someone would implement some code pattern depending on the number of votes
that pattern has on HN they have a bigger problem than how HN voting works.

------
kpozin
Something I haven't been able to understand: why does almost every Backbone
project generate _and_ update views by applying templates to generate new HTML
strings, and then new DOM fragments?

Wouldn't it be much more efficient to create a view just _once_ by setting
innerHTML, and then update it using the DOM (setting attributes, classes,
innerText, etc.)? Surely this would reduce GC pressure, reflow events, and so
on.

I'm seeing "use templates for everything" in most Backbone tutorials and
projects, and I can't help but think that this is an anti-pattern.

~~~
ianstormtaylor
I think one of the biggest reasons this is hard to do is because you end up
having the same logic in two places. You have to keep the initial $.html() and
the incremental changes consistent.

Or you could have the incremental changes follow the first call t $.html(),
but then you always have to add those in each render and it gets ugly there
too.

If you have other ideas though I'd love to hear 'em.

~~~
kpozin
The second of those is exactly the approach I've taken in the couple client-
side web apps that I've built. I initially generate and insert generic DOM
fragments from templates, but only use the template to fill in values that I
know will not change throughout the lifetime of the view (the "templates"
often don't have parameters). After that, I use one code path both for the
initial value setting and for subsequent updates.

If I'm not mistaken, this is more or less the approach used in Knockout and
AngularJS.

------
rhysbb
A lot of people are finding out that views are the hardest thing to do -
especially with Backbone. The problem is that views are tightly coupled to the
model so it's hard to build them up. I just wrote
<http://modernjavascript.blogspot.com/2012/06/v-in-mvc.html> for another post
but believe that it could be helpful in this situation. You'd need to actually
write a Backbone.controller class and move the logic in to there but then you
could create a whole lot of Backbone.Views that were generic and could be
hooked up through the Control (I'd also like to see a Backbone.MultiView or
something that could take several views in and then re-emit the changes and
allow people to add in several small component views in to a large view either
for a collection or a model.

~~~
lucisferre
Sorry how they are tightly coupled in backbone? I have a number of beefs with
backbone.js but coupling between the model and views (or the views and
anything) is not one of them. In fact I mostly only use backbone for the views
and the convenient event binding.

------
koblas
Personally, I've been more inspired by EmberJS as a template approach. I've
gotten the basics working and up and available on github.
<https://github.com/koblas/distal>

While I don't yet have a fancy blog post about the system and why it's
"better" I would be interested if people think there is some core value in
this style of View abstraction for Backbone.

------
datapimp
Check out the Collection GridView in luca. <http://datapimp.com/luca/>.
Source: [https://github.com/datapimp/luca/blob/development-
tools/src/...](https://github.com/datapimp/luca/blob/development-
tools/src/components/grid_view.coffee)

------
esmevane
This is the pattern I go with: <https://gist.github.com/2931439>

It decouples a little more logic from the template, and I think it adheres
very closely with some things Backbone expects (like passing through the
entire el to the DOM).

~~~
haclifford
This is pretty much what we use, here it is with map and builds the element
prior to dom insertion (better performance): <https://gist.github.com/2932520>

~~~
esmevane
Well, I'm pretty much going to use this approach from now on. Much cleaner and
concise. If you look at the gist history you can see me updating my old code
to include fat arrow, and other intuitive-with-experience tricks that I didn't
know when I originally drafted the parent code in February.

I'm curious - where did you hear that it was better performance? Or is this
personal experience?

~~~
haclifford
Can't remember where I heard it, so decided to check.

Found this S.O thread [http://stackoverflow.com/questions/10296791/backbone-
js-perf...](http://stackoverflow.com/questions/10296791/backbone-js-
performance-on-ie6-ie7-and-ie8) (different code, same concept) and decided to
benchmark: Unqueued DOM insertion: <http://jsfiddle.net/arkxp/9/> Queued DOM
insertion: <http://jsfiddle.net/arkxp/8/>

The latter is consistently faster, and while the difference isn't a lot with
this example, given a more complex insertion it'll mount up.

~~~
esmevane
That is awesome. Thanks for digging it up

There's at least two projects I have going at the day job which have been
experiencing slowdown - and I had no idea that small-batch DOM interaction was
the culprit.

Looks like I've got some optimizin' to do.

------
voodoomagicman
Why not just append all of the views in TableView render, and then have
TableRowView set its own ID in its initialize or render method?

I think that would be more concise and straightforward.

~~~
superchris
Read to the end, that's pretty much what I end up doing in the final version.
I create the TableRowView in the helper and then listen to the parent views
"rendered" event so the row view knows when it's element is in the DOM and
it's safe to render itself.

~~~
voodoomagicman
I did read to the end. What you end up suggesting includes: \- rendering the
view's elements as html in the parent view \- creating all of the views and
binding callbacks for all of the views to 'render' on the parent view \-
triggering all of the callbacks to render the views \- each of which pull
their element from the first view using a jquery selector.

I guess the advantage is that re-rendering the parent will re-render the
subviews instead of re-creating them? Unless you need that, it seems to me
like this is a lot of mess compared to just adding @$el.attr 'id',
"row_#{@model.id}" to the child view.

