Hacker News new | past | comments | ask | show | jobs | submit login
Backbone.js views done right (gaslightsoftware.com)
111 points by joshowens on June 14, 2012 | hide | past | favorite | 37 comments



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


> 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:

And if you want to delegate the handling of these DOM lines to sub-views (for event handling), you can instantiate them after rendering and call `View#setElement` with a row each.


I started with an approach like yours, but really wanted the ability to have a subview helper function that inserts the subview in the right place in the parent's template. In order for this to work, I need the helper function to output the subview's element but defer rendering the subview until the subview's element is in the DOM. One case I really feel I see benefit from this is in creating reusable subviews for form elements, but I wanted to choose a simpler example to show the approach. Could be the example I chose was too simple.


"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.


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


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...


I see what you did there


See: "rocket surgeon", or "enough rope to shoot yourself in the foot".

Also: jashkenas wrote Backbone.


Seriously?! My fellow nerds drive me insane sometimes with their ridiculous focus on details that mean absolutely nothing. Arrrrrgh.


I always assume people nitpicking pointless details have nothing better to say, but still want to say something. Plus, look how smart he/she looked.


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.


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


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.


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


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


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.


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.


Assuming people read the comments


FWIW, I read the comments first to make a decision whether to read or not the linked article.


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.


    > 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.)?
Funnily enough, it usually isn't. Especially in older browsers, where performance matters most -- setting a single innerHTML call with a bunch of HTML is far cheaper than doing the equivalent number of DOM-twiddling operations.

While it's entirely possible with Backbone views to listen to specific change events and only modify the smallest possible portion of the DOM, it's usually not worth the bother -- appropriately coarse-grained template renders are both more convenient and faster.


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.


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.


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.


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.


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.



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).


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


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?


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

Found this S.O thread http://stackoverflow.com/questions/10296791/backbone-js-perf... (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.


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.


Yes. This is the way to go. I posted pretty much the same thing, but of course it's half as much code in CoffeeScript... time to give that a serious look.


This is unrelated to the discussion at hand, but: I'd been writing JS for a while, but it wasn't until I picked up CoffeeScript that I really grasped JS.

I know that sounds strange, but CoffeeScript taught me important concepts in JS which I just couldn't grasp beforehand. Prototypal inheritance, scoping, object construction, and use, etc.

I'm a pretty big advocate at this point. Contrary to what a lot of its detractors say, CoffeeScript insinuated in my mind most of the JS best practices. Going back to JS from CoffeeScript, I found JS just as easy to correctly write and use as CoffeeScript had ever been.

How about that?


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.


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.


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.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: