4
votes

I'm creating a search results page using Backbone in combination with CoffeeScript and Handlebars. I have two views: one for the list of results (ListView) and the second view is for a single result (ResultView). Simplified code:

ListView = Backbone.View.extend
  el: $("ul#results")
  ...

  addItem: (result) ->
    resultView = new ResultView({
      model: result
    })
    resultView.parentView = this
    this.el.append(resultView.render().el)

ResultView = Backbone.View.extend
  tagName: "li"
  className: "result"
  events:
    ...

Short explanation:

  • The ListView is assigned to ul#results
  • When a result is added to the listview, a ResultView is created, that has knowledge of its parent and renders itself
  • For the ResultView an element li.result is created (default Backbone behaviour)

This is (simplified) the template I'm using to render a search result:

<li class="result">
   <h1>
     <a href="{{link}}">{{title}}</a>
   </h1>
   <p>{{snippet}}</p>
</li>

Here's my conundrum, as you may have discovered yourself: I define a li.result in my Backbone ResultView, and in my template. What I can't do:

  • Bind the ResultView to the li.result in my template because it doesn't exist yet in the DOM
  • Remove the li.result from my template, because I still need it to render the page server side for those who don't have JavaScript enabled

Is there a way to (elegantly) re-assign a Backbone view to an element after its been instantiated? Or put simply, can I reference ResultView to a temporary element, render the template and then move it into ul#result? Or am I looking at it the wrong way?

Thanks!

2

2 Answers

2
votes

I would suggest to simply trigger a rendered event from your view and then perform your operations in an onRendered callback like so:

initialize: function() {
  this.bind('rendered', onRendered);
},

onRendered: function() {
   // do onRendered stuff
   // eg. remove an element from the template
},

render: function() {
  // your render stuff
  this.trigger('rendered');
}
1
votes

I've found that things are always going to get a bit more complicated when you try to support server side rendered views and client side views. The scenario you are describing here is a perfect example of that.

If at all possible I would move the rendering of the page to the client side which will greatly help to simplify your code.

That being said if you did want to keep the server rendered views I would probably perform a fetch() on your collection after the page has loaded to get all the objects from the server. You could then adjust your ResultView initialize function to do the following check.

ResultView = Backbone.View.extend
  initialize: (attributes) ->
    exisitingElement = $('result_' + attributes['id'])
    if exisitingElement?
      @el = exisitingElement 
      @delegateEvents()

Then you would change your template to include a unique id.

    <li id="result_{{id}}" class="result">
       <h1>
         <a href="{{link}}">{{title}}</a>
       </h1>
       <p>{{snippet}}</p>
    </li>

This way the ResultView will look for an existing element before rendering a new one to the page. Manually calling delegateEvents() after reassigning the the @el property ensures that any events you have defined will still work.