Presenting Data with Views

A Backbone view is a do-it-yourself kit for turning a model into HTML. Unlike other JavaScript frameworks, Backbone doesn’t perform rendering or respond to input events or interact with its model in any way unless you explicitly tell it to. So let’s roll up our sleeves and write some display logic.

We’ll start with a view class for our Card model. All it needs to do is render the model into markup and save changes in the description and due date fields to the model:

 class​ window.CardView ​extends​ Backbone.View
  render: ->
  html = JST[​'templates/card'​]
  description: @model.get(​'description'​)
  dueDate: @model.get(​'due-date'​)
 
  @$el.html html
 @
 
  events:
 'change [name=card-description]'​: ​'descriptionChangeHandler'
 'change [name=due-date]'​: ​'dueDateChangeHandler'
 
  descriptionChangeHandler: (e) ->
  @model.save ​'description'​, $(e.currentTarget).val()
 return
 
  dueDateChangeHandler: (e) ->
  @model.save ​'due-date'​, $(e.currentTarget).val()
 return

Let’s walk through the render method: when we instantiate the view, we pass in an options hash, and Backbone automatically sets model and $el as properties when passed. @model is a Card instance, and @$el is a jQuery object that’s wrapped around the HTML element that the view is in charge of. For CardView, that’s going to be a div with the card class. After putting the HTML from the template in the DOM, the method returns the view to allow chaining, for example cardView = new CardView(options).render().

render alone would be all we need if this view were unidirectional, simply allowing the data in the card model to be displayed. But we want it to be bidirectional, allowing the card model to be manipulated. So we define an events hash, which Backbone uses to listen for DOM events and send them to handlers. The hash keys are of the form "<selector> <event type>", and the values are the names of class methods. Using names rather than references to the methods themselves may seem strange, but Backbone has to call the methods in the context of the view object. A call to view[methodName](e) does that nicely, whereas method(e) would work only if method were bound to view.

The two handlers pull the value from the DOM element and pass it the model’s save method. The save method is very powerful: it not only changes the attribute on the model (as the set method would), it also syncs the model to the server (or, for now, localStorage).

Next up, ColumnView:

 class​ window.ColumnView ​extends​ Backbone.View
 
  initialize: (options) ->
  @cardViews = []
  @listenTo @model.get(​'cards'​), ​'add remove'​, =>
  @model.save()
  @render()
 super
 
  render: ->
  html = JST[​'templates/column'​]
  name: @model.get(​'name'​)
  cards: @model.get(​'cards'​).toJSON()
 
  @$el.html html
 
  @cardViews = @model.get(​'cards'​).map (card) =>
  cardView = ​new​ window.CardView(model: card)
  cardView.setElement @$(​"[data-card-id=​​#{​card.get(​'id'​)​}​​]"​)
  cardView.render()
  cardView
 @
 
  events:
 'change [name=column-name]'​: ​'nameChangeHandler'
 'click [name=add-card]'​: ​'addCardClickHandler'
 
  nameChangeHandler: (e) ->
  @model.save ​'name'​, $(e.currentTarget).val()
 return
 
  addCardClickHandler: (e) ->
  newCard = ​new​ window.Card({}, {parse: true})
  newCard.save()
  @model.get(​'cards'​).add(newCard)
 return

Backbone calls initialize whenever a class is instantiated. We’re using it here to declare an initially empty array of card views and to attach an event listener to our column’s collection of cards, so that we re-render the entire column every time a card is added or removed. This approach is a bit inefficient, since it re-renders all of the cards that were in the collection before the event, but we can optimize later if necessary (using those cardViews).

Note that the default implementation of initialize is a no-op and the return value is ignored, so ending our implementation with super is unnecessary. However, it gives us some leeway when refactoring. If we changed ColumnView in the future to extend a subclass of Backbone.View, we’d almost certainly want to call that subclass’s initialize from ColumnView’s.

The render method here has a couple of wrinkles that weren’t in CardView’s. First, we have to do some minor data transformation: we’re storing the column’s cards as a CardCollection in the Column model, and Eco doesn’t understand Backbone collections. Instead, we need to pass in an array containing our card data, which is exactly what a collection’s toJSON method gives us. Second, after rendering the column template we have to create a CardView for each card, give it the DOM element we’ve just created for it, and tell it to render. Otherwise, we’d just have a column full of card placeholders.

In addition to a name change handler that works just like the input handlers in CardView, the column view has a click handler for the New Card button. When clicked, it appends a new card with a unique ID to both allCards and the column’s collection. That will trigger an add event on the column’s collection, which will re-render the column, thanks to the listener we attached in initialize.

Finally, we have BoardView, which is very similar to ColumnView:

 class​ window.BoardView ​extends​ Backbone.View
  initialize: (options) ->
  @listenTo @model.get(​'columns'​), ​'add remove'​, =>
  @model.save()
  @render()
 super
 
  render: ->
  html = JST[​'templates/board'​]
  name: @model.get(​'name'​)
  columns: @model.get(​'columns'​).toJSON()
 
  @$el.html html
 
  @model.get(​'columns'​).forEach (column) =>
  columnView = ​new​ window.ColumnView(model: column)
  columnView.setElement @$(​"[data-column-id=​​#{​column.get(​'id'​)​}​​]"​)
  columnView.render()
  columnView
 @
 
  events:
 'change [name=board-name]'​: ​'nameChangeHandler'
 'click [name=add-column]'​: ​'addColumnClickHandler'
 
  nameChangeHandler: (e) ->
  @model.save ​'name'​, $(e.currentTarget).val()
 return
 
  addColumnClickHandler: (e) ->
  newColumn = ​new​ window.Column({}, {parse: true})
  newColumn.save()
  @model.get(​'columns'​).add(newColumn)
 return
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.67.16