Skip to content

Latest commit

 

History

History
219 lines (168 loc) · 8.86 KB

diorama_nesting_view.md

File metadata and controls

219 lines (168 loc) · 8.86 KB

Backbone.Diorama.NestingView

Backbone.Diorama.NestingView allows you to nest views inside each other, for example a collection index view where each model in the collection gets a sub view. This approach has a number of advantages:

  • You can build smaller views and templates with clear responsibilities. (SRP).
  • Smaller views listen and respond to events on a smaller number of objects, meaning smaller parts of the DOM can be re-rendered when changes occur.
  • Sub views create opportunities for code re-use (DRY)
  • Smaller views are easier to test in isolation.

A quick example tutorial

In this example we will make an index list of blog posts, where each blog post has it's own view. Note that this code is very close to what is generated by diorama generate nestingView

PostIndexView

First, we create a new View extending from the Backbone.Diorama.NestingView class, which itself extends from Backbone.View:

class Backbone.Views.PostIndexView extends Backbone.Diorama.NestingView
  template: Handlebars.templates['post_index.hbs']

  initialize: (options) ->
    # Our Backone.Collection of posts
    @postCollection = options.postCollection 
    @render()

  render: =>
    # Render template, which will create subviews with addSubViewTo helper (see template below)
    # Note that a reference to @ is passed in as 'thisView'
    @$el.html(@template(thisView: @, posts: @postCollection.models))

    # Attach the subviews to the elements created by addSubViewTo helper
    @attachSubViews()

    return @

  onClose: ->
    # When we close this view, we also want to close all its child views
    @closeSubViews()

PostIndex Template

Next we need to create a template which uses the addSubViewTo handlebars helper to create the sub views and render placeholder elements (complete with data-sub-view-key attributes)

### post_index_view template ###
<h1>My Blog posts</h1>
{{#each posts}}
  <!-- Use addSubViewTo to create a PostRowView for each post -->
  <!-- Note that the first argument is the nesting view we're attaching to -->
  {{addSubViewTo ../thisView "PostRowView" post=this}}
{{/each}}

PostRowView

Finally, we need to create the child view 'PostRowView'. This is just a typical Backbone.View, which takes in a post model and calls render() in its initialize function.

class Backbone.Views.PostRowView extends Backbone.View
  template: Handlebars.templates['post_row.hbs']

  initialize: (options) ->
    # Read in the post model sent from the addSubViewTo handlebars helper
    @post = options.post
    # Call render as soon as the view is created
    @render()

  render: =>
    @$el.html(@template(post: @post.toJSON()))
PostRow Template

...and the post row template, which shows the title of the post

<h2>{{post.title}}</h2>

Now we have a PostRow view for each of our blog posts, which can setup its own event bindings for each model.

Advanced: Utilising view caching and reuse

In the code we've built so far, each time PostIndexView::render is called, all of the sub views are closed and replaced with new instances. While sometimes this is appropriate, often you may want to reuse subviews between render calls.

Diorama.NestingView allows you to do this by specifying a cache key as a third argument to addSubViewTo. Cache keys mean you can re-render parent views without destroying or re-rendering their children, making state easier to handle and improving app performance.

Let's modify our PostIndex template to use a sub view cache key:

<!-- addSubViewTo without a cache key -->
  {{addSubViewTo ../thisView "PostRowView" post=this}}
<!-- addSubViewTo modified with a cache key -->
  {{addSubViewTo ../thisView "PostRowView" "post-row-{{post.cid}}" post=this}}

The cache key is a handlebars template, which uniquely identifies a sub view. The handlebars template will interpolate the variables in the next argument (which are passed to the sub view initialize function).

Therefore, in the above example, with a post model with cid 'c1', the view with have a cache key 'post-row-c1'. This key is used to store the view in postIndexView.subViews['post-row-c1'].

If we call render again with post model 'c1' again, the view will reuse the sub view in postIndexView.subViews['post-row-c1']. If we remove post model 'c1' and call render again, the view in 'post-row-c1' will be closed and deleted.

For example:

posts = new Backbone.Collections.PostCollection()
# Add a post with cid = c1
posts.push(new Backbone.Models.Post())

# Create and render a postIndexView
postIndexView = new Backbone.Views.PostIndexView(postCollection: posts)

# A PostRowView will have been created and stored here using the cache-key:
postIndexView.subViews['post-row-c1'] 

# Calling render() again will reuse the subView at 'post-row-c1'
postIndexView.render()
postIndexView.subViews['post-row-c1'] # Same view as above

# remove post c1, add post c2 and re-render
posts.pop()
posts.push(new Backbone.Models.Post())
postIndexView.render()

# The view for post row c1 will be closed and deleted
postIndexView.subViews['post-row-c1'] # undefined
# A new view has been created for post c2
postIndexView.subViews['post-row-c2'] # the new view

The API

Handlebars helper

{{ addSubViewTo nestingView childViewName (cache-key-template) (optionsHash) }}

Call this helper to insert a sub view into a template at this point. Creates an instance of the given childViewName, adds it to nestingView.subViews and inserts a placeholder DOM element.

  • nestingView - The parent nesting view object, this should always be the view whose template is being rendered. This variable is required because handlebars helpers are executed in the global context. If you used a generator to create your nesting view, this will be passed into the template as thisView
  • childViewName - The class name of the child view you want to insert. This will expect the view to be namespaced in Backbone.Views, e.g. given 'PostRowView', it creates an instance of Backbone.View.PostRowView
  • cache-key-template - (optional) A optional handlebars template you can use to uniquely indentify your sub view, which allows view reuse and the ability to render a parent view without re-rendering its children. See Utilising view caching and reuse
  • optionsHash - (optional) A hash which will be passed into the initialize function of the view named by childViewName. If only 3 arguments are given, this is assumed to be the 3rd argument (slightly odd behavior forced by handlebars :-| ).

Examples:

  <!-- Add a new Backbone.Views.PostRowView for the post model into the current NestingView -->
  {{addSubViewTo thisView "PostRowView" model=post}} 
  <!-- Add a new Backbone.Views.TodoItem for the task model with a cache key template -->
  {{addSubViewTo thisView "TodoItem" "todo-item-{{task.cid}}" task=task}} 

Backbone.Diorama.NestingView methods

attachSubViews

For each view in @subViews, it sets the DOM element of the view to the placeholder rendered by the addSubViewTo handlebars helper. Call this after you've rendered the template for your NestingView (NestingViews generated by diorama generate nestingView do this by default).

closeSubViews

Calls close on each sub view. Call this inside your NestingView's onClose method (NestingViews generated by diorama generate nestingView do this by default).

renderSubViews

Calls the render function on each sub view. Sub-views generated by diorama generate nestingView call render inside their initialize functions, meaning there is no need to call this, unless you want to force the children to re-render for some reason

Upgrading from diorama <0.2.0

The syntax of NestingView was simplified in the 0.2.0 release, existing apps will require some small changes.

Nesting view <0.2.0

Render functions were previously required to call closeSubViews, then renderSubViews

  render: =>
    @closeSubViews() # No longer required to be called 
    @$el.html(@template(thisView: @))
    @renderSubViews() # Replaced with attachSubViews()

NestingView >= 0.2.0

closeSubView is no longer required (attachSubViews cleans up automatically), and attachSubViews replaces the call to renderSubViews. The preferred pattern is to have sub views call render themselves, for example in their initialize function, however, you can manually re-render (like diorama 0.1.1) by calling renderSubViews after attachSubViews.

  render: =>
    @$el.html(@template(thisView: @))
    @attachSubViews() # Attaches sub views (but does not call render)
    @renderSubViews() # (Optional) If you wish to manually render sub views, call this