viewbridge
Version:
Shares views or templates between the server and client, making client side template pre-compilation and/or server side view pre-rendering easy.
172 lines (141 loc) • 4.45 kB
text/jade
extends ../layout
block content
h1= title
form#movie_form.pure-form(action='/movies', method='post')
fieldset
legend Add a Movie
input(type='text', name='title', placeholder='Title')
input(type='text', name='studio', placeholder='Studio')
label
input(type='checkbox', name='like')
| Liked?
button.pure-button.pure-button-primary(type='submit') Add
table#movie_table.pure-table
thead
tr
th Studio
th Title
th Liked?
tbody
each movie in movies
tr(id="movie_#{movie.id}")
// Yes, `with` is deprecated and frowned upon.
// Jade used to have partials and this works in lieu of them.
- with (movie) {
//- View/Template used on both server and client
include table_row
- }
block scripts
script(src='/javascripts/vendor/jquery.js')
script(src='/javascripts/vendor/lodash.underscore.js')
script(src='/javascripts/vendor/backbone.js')
// Templates exported and precompiled via Viewbridge.
script(src='/javascripts/templates.js')
script
var app = app || {};
void function() { 'use strict'
//
// Model - Movie
//
app.Movie = Backbone.Model.extend();
//
// Collection - Movies
//
app.movies = function() {
var Movies = Backbone.Collection.extend({
model: app.Movie
});
return new Movies();
}();
//
// View - Movie Row
//
app.MovieRow = Backbone.View.extend({
tagName: 'tr',
initialize: function() {
this.listenTo(this.model, 'change', this.render);
},
events: {
'click .like': 'toggleLike'
},
render: function() {
// Viewbridge!
// This is the very same template used on the server.
this.el.innerHTML =
viewbridge.movies.table_row(this.model.toJSON());
return this;
},
toggleLike: function() {
var like = this.model.get('like');
this.model.set('like', ! like);
}
});
//
// View - Movie Table
//
app.MovieTable = Backbone.View.extend({
el: '#movie_table',
initialize: function() {
this.tbody = this.el.querySelector('tbody');
this.listenTo(app.movies, 'add', this.add);
this.listenTo(app.movies, 'reset', this.reset);
},
add: function(model) {
var row = new app.MovieRow({model: model});
this.tbody.appendChild( row.render().el );
},
bootstrap: function() {
app.movies.each(function(model) {
var el = document.getElementById('movie_' + model.id);
if (el === null) return;
// Since the view was already rendered on the server, we just
// need to initialize the Backbone View with the model and the
// DOM element.
new app.MovieRow({model: model, el: el});
});
},
reset: function(collection, options) {
if (options.bootstrap) {
this.bootstrap();
} else {
this.tbody.innerHTML = '';
app.movies.each(this.add, this);
}
}
});
//
// View - Movie Form
//
app.MovieForm = Backbone.View.extend({
el: '#movie_form',
initialize: function() {
this.title = this.el.querySelector('[name=title]');
this.studio = this.el.querySelector('[name=studio]');
this.like = this.el.querySelector('[name=like]');
},
events: {
'click [type=submit]': 'add',
},
add: function(evt) {
evt.preventDefault();
app.movies.add({
title: this.title.value,
studio: this.studio.value,
like: this.like.checked
});
this.el.reset();
}
});
var bootstrapData = !{JSON.stringify(movies)};
//
// Initialize the app
//
$(function() {
new app.MovieTable();
new app.MovieForm();
// Bootstrap the collection with initial data. Pass a custom
// flag to indicate that we only want to initialize the relevant Views
// and not render them.
app.movies.reset(bootstrapData, {bootstrap: true});
});
}();