Your approach does not really follow the Backbone architecture. In Backbone you
model your business domain as Models and Collections of Models. If you follow
this rule everything falls into place quite nicely.
The decision to name your model Table
is wrong in this context as a table is
a representational object. The data itself can be displayed in many ways.
What follows is an example how the application could (or should) be designed in
a way that does not work against Backbone.js. The data is modeled as a Collection
of Elements, each Element will be represented in a row of the table.
Please follow the comments in the source code for further explanation.
File: index.html (I build the application using npm and browserify, which is not
shown here. Be sure to provide the necessary dependencies).
<!doctype html>
<html>
<head>
<title>Coffeescript And Backbone</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<h2>Elements Table</h2>
<div class="table"></div>
</div>
<script id="tablerow" type="text/x-handlebars-template">
<td>{{id}}</td>
<td>{{name}}</td>
<td>{{value}}</td>
<td><button class="button">HaveDataBytes</button></td>
</script>
<script src="app.js"></script>
</body>
</html>
File: app.coffee
# The template is integrated in index.html (script#tablerow).
# Here I read and compile the template function for the table row.
#
# This must be called after the template script is in the DOM.
#
# Using a packaging tool like browserify or webpack would allow you
# to precompile the templates during the packaging process, but this
# approach is good for small applications.
compileTemplate = (name) ->
source = document.getElementById(name).innerHTML
Handlebars.compile(source)
rowTemplate = compileTemplate('tablerow')
# Define the applications backbone
App =
View: {}
Model: {}
Collection: {}
# This is a dummy dataset of elements that would be fetched from the server
# using the Collection abstraction. Each entry in data will be represented
# in a table view further down.
data = [
{ id: 1, name: "element1", value: "value1", haveDataBytes: true },
{ id: 2, name: "element2", value: "value2", haveDataBytes: false },
{ id: 3, name: "element3", value: "value3", haveDataBytes: true },
{ id: 4, name: "element4", value: "value4", haveDataBytes: true },
{ id: 5, name: "element5", value: "value5", haveDataBytes: false }
]
# The model element takes up each entry from data ...
class App.Model.Element extends Backbone.Model
# and is stored in a collection of Element models
class App.Collection.Elements extends Backbone.Collection
model: App.Model.Element
Compared to your approach with simple lists for headers and columns in the model
the major benefit is that all the data is kept and controlled by Backbone.
You could simply update the whole collection, listen to changes and send events,
serialize single entries to the server, respond to changes of single entries -
full control ...
# The table view
class App.View.ElementsTable extends Backbone.View
# is a table by itself
tagName: 'table'
# it receives the collection of elements
initialize: (options) ->
@collection = options.collection
# and renders each row ...
render: ->
@collection.each(@renderRow, @)
@
# ... in a element table row view
renderRow: (row) ->
rowView = new App.View.ElementTableRow(model: row)
@$el.append(rowView.render().el)
# The element table row ...
class App.View.ElementTableRow extends Backbone.View
# ... is itself a row
tagName: 'tr'
# and takes an element model
initialize: (options) ->
@model = options.model
# it handles the click event ...
events: {
'click button': 'click'
}
# ... with full access to the model and the collection, which is a member
# of the element model
click: (evt) ->
console.log(evt.target.name, @model, @model.collection)
@$('td').toggleClass('red')
# render uses the precompiled handlebars template to render the row,
# no HTML in the view or the model necessary
render: () ->
@$el.html(rowTemplate(this.model.toJSON()))
# Here I show/hide the buttons based on the element models data.
# It would definitely be better to not render the buttons in first place
if not @model.get('haveDataBytes')
@$('button').hide()
@
# simple start script
$(() ->
# 'load' the data as a collection
collection = new App.Collection.Elements(data)
# check the elements
console.log(collection.models)
# create and render the table view
view = new App.View.ElementsTable(collection: collection)
$('.table').append(view.render().el)
)
Last but not least, doFunction
should live in the view (see click
in
App.View.ElementTableRow
) or be called by the click handler of App.View.ElementTableRow
.