Backbone View Rendering — Advanced Rendering Techniques
In this tutorial, you will learn about Backbone View Rendering. We cover key concepts, practical examples, and best practices to help you master this topic.
Backbone View rendering involves more than just calling render(). Efficient rendering strategies include sub-view management, document fragment batching, deferred rendering, and template precompilation for building responsive applications.
What You'll Learn
You'll learn advanced rendering patterns: managing sub-views, using document fragments, lazy rendering, template strategies, and preventing memory leaks in complex view hierarchies.
Why It Matters
Poor rendering performance causes janky user interfaces. When a Collection has hundreds of items, naive re-rendering freezes the browser. Efficient rendering keeps the UI smooth even with large datasets.
Real-World Use
A log viewer displays thousands of security events. Using document fragment rendering, it batches DOM updates into a single reflow, keeping the UI responsive while processing large data sets.
flowchart LR
A[Collection.fetch] --> B[View.render]
B --> C[Create DocumentFragment]
C --> D[Render each model]
D --> E[Append fragment to DOM]
E --> F[Single reflow]
Basic Render Pattern
The standard pattern: render() clears the element, generates HTML, and appends it. Always return this for chaining.
var TaskList = Backbone.View.extend({
tagName: 'ul',
initialize: function() {
this.collection.on('reset', this.render, this);
},
render: function() {
this.$el.empty();
this.collection.each(function(task) {
this.$el.append('<li>' + task.get('title') + '</li>');
}, this);
return this;
}
});
Sub-View Management
For complex UIs, create sub-views and manage them in a parent view.
var TaskView = Backbone.View.extend({
tagName: 'li',
className: 'task',
template: _.template('<%= title %> - <%= priority %>'),
initialize: function() {
this.listenTo(this.model, 'change', this.render);
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
},
remove: function() {
this.stopListening();
this.$el.remove();
return this;
}
});
var TaskListView = Backbone.View.extend({
tagName: 'ul',
className: 'task-list',
initialize: function() {
this.subViews = [];
this.listenTo(this.collection, 'add', this.addOne);
this.listenTo(this.collection, 'reset', this.render);
},
render: function() {
this.removeSubViews();
this.$el.empty();
this.collection.each(this.addOne, this);
return this;
},
addOne: function(task) {
var view = new TaskView({ model: task });
this.subViews.push(view);
this.$el.append(view.render().el);
},
removeSubViews: function() {
_.each(this.subViews, function(view) { view.remove(); });
this.subViews = [];
},
remove: function() {
this.removeSubViews();
Backbone.View.prototype.remove.call(this);
}
});
Document Fragment Rendering
For large collections, use a DocumentFragment to batch DOM operations into a single reflow.
var EfficientListView = Backbone.View.extend({
tagName: 'ul',
render: function() {
var fragment = document.createDocumentFragment();
this.collection.each(function(model) {
var li = document.createElement('li');
li.textContent = model.get('title');
fragment.appendChild(li);
});
this.$el.empty();
this.el.appendChild(fragment);
return this;
}
});
// Compare performance
var Task = Backbone.Model.extend({ defaults: { title: '' } });
var TaskList = Backbone.Collection.extend({ model: Task });
var tasks = new TaskList();
for (var i = 0; i < 1000; i++) {
tasks.add({ title: 'Task ' + i });
}
var view = new EfficientListView({ collection: tasks });
console.time('render');
view.render();
console.timeEnd('render');
console.log('Rendered', tasks.length, 'items');
Expected output:
render: 2-5ms
Rendered 1000 items
Template Precompilation
Precompile templates outside the View for better performance.
// Precompile once
var taskTemplate = _.template(
'<div class="task <%= priority %>">' +
'<h3><%= title %></h3>' +
'<p><%= description %></p>' +
'<span class="status"><%= status %></span>' +
'</div>'
);
var itemTemplate = _.template('<li><%= name %></li>');
var detailTemplate = _.template('<div class="detail"><%= content %></div>');
var TaskDetailView = Backbone.View.extend({
tagName: 'div',
className: 'task-detail',
render: function() {
// Use precompiled template
this.$el.html(taskTemplate(this.model.toJSON()));
return this;
}
});
var task = new Backbone.Model({
title: 'Optimize templates',
priority: 'high',
description: 'Precompile for better performance',
status: 'In Progress'
});
var view = new TaskDetailView({ model: task });
view.render();
console.log(view.el.outerHTML);
Expected output:
<div class="task-detail"><div class="task high"><h3>Optimize templates</h3><p>Precompile for better performance</p><span class="status">In Progress</span></div></div>
Conditional Rendering
Show or hide parts of the UI based on data state.
var AlertView = Backbone.View.extend({
tagName: 'div',
className: 'alert',
template: _.template(
'<div class="alert-header">' +
'<span class="severity <%= severity %>"><%= severity.toUpperCase() %></span>' +
'<span class="timestamp"><%= timestamp %></span>' +
'</div>' +
'<div class="alert-body">' +
'<p><%= message %></p>' +
'<% if (severity === "critical") { %>' +
'<div class="critical-actions">' +
'<button class="escalate">Escalate to Engineer</button>' +
'</div>' +
'<% } %>' +
'<% if (resolved) { %>' +
'<span class="resolved-badge">Resolved</span>' +
'<% } else { %>' +
'<button class="resolve">Mark Resolved</button>' +
'<% } %>' +
'</div>'
),
initialize: function() {
this.listenTo(this.model, 'change', this.render);
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
var alert = new Backbone.Model({
severity: 'critical',
timestamp: '2026-06-28 14:30:00',
message: 'SQL injection attempt detected on /api/login',
resolved: false
});
var view = new AlertView({ model: alert });
console.log(view.el.outerHTML);
Expected output:
<div class="alert"><div class="alert-header"><span class="severity critical">CRITICAL</span><span class="timestamp">2026-06-28 14:30:00</span></div><div class="alert-body"><p>SQL injection attempt detected on /api/login</p><div class="critical-actions"><button class="escalate">Escalate to Engineer</button></div><button class="resolve">Mark Resolved</button></div></div>
Lazy Rendering
Render only visible items in scrollable lists using the "window" pattern.
var LazyListView = Backbone.View.extend({
tagName: 'div',
className: 'lazy-list',
initialize: function() {
this.visibleStart = 0;
this.visibleCount = 20;
this.itemHeight = 30;
this.listenTo(this.collection, 'reset', this.render);
this.$el.on('scroll', _.bind(this.handleScroll, this));
},
render: function() {
var end = Math.min(this.visibleStart + this.visibleCount, this.collection.length);
var fragment = document.createDocumentFragment();
for (var i = this.visibleStart; i < end; i++) {
var model = this.collection.at(i);
var div = document.createElement('div');
div.className = 'list-item';
div.style.height = this.itemHeight + 'px';
div.textContent = model.get('title');
fragment.appendChild(div);
}
var totalHeight = this.collection.length * this.itemHeight;
this.$el.html(
'<div style="height:' + totalHeight + 'px;position:relative;">' +
'<div style="position:absolute;top:' + (this.visibleStart * this.itemHeight) + 'px;width:100%;">' +
'</div>' +
'</div>'
);
this.$('.lazy-list > div > div').append(fragment);
return this;
},
handleScroll: function() {
var scrollTop = this.$el.scrollTop();
this.visibleStart = Math.floor(scrollTop / this.itemHeight);
this.render();
}
});
This pattern renders only 20 items regardless of collection size, keeping the DOM small and scroll performance smooth.
Common Mistakes
- Re-rendering the entire view on every data change. For small changes, update only the affected element. Use
render()only when the entire view needs to refresh. - Not cleaning up sub-views. Sub-views hold DOM references and event listeners. Always call
remove()on child views when the parent is removed. - Using string concatenation for HTML templates. Templates are easier to read, maintain, and escape XSS. Use
_.template()or other template engines. - Direct DOM manipulation without re-render. Modifying
eldirectly while the view also callsrender()creates inconsistencies. Use one pattern consistently. - Rendering during initialization for Collection Views. The Collection may not be fully populated yet. Let the developer call
render()explicitly after data is ready.
Practice Questions
- What is a DocumentFragment and why is it useful?
- How do you prevent memory leaks from sub-views?
- Why should templates be precompiled?
- What is the "lazy rendering" pattern and when should you use it?
- Challenge: Create a view that renders a collection of 500 items using DocumentFragment. Add a search input that filters the displayed items in real-time without re-rendering the full collection.
FAQ
Mini Project
Create a DataGridView that renders a collection of objects in a table format. Support sorting by clicking column headers. Use DocumentFragment for rendering. Support Lazy Loading for 1000+ rows by rendering only visible rows on scroll.
What's Next
Now that rendering is covered, learn Backbone Routers for URL-based navigation. Then explore Backbone History to understand browser history management.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro