I'm learning backbone.js and have a problem following this tutorial. I have a Collection of Era
s called History
. When trying app.History.create({ from: 0, until: 1, stash: {from: null, until: null}, _enabled: true})
I get the error in the title.
Here's my code:
The Model
var app = app || {};
app.Era = Backbone.Model.extend({
defaults: {
from: Number.NEGATIVE_INFINITY,
until: Number.POSITIVE_INFINITY,
stash: {
from: null,
until: null
},
_enabled: true
},
toggle: function(){
if(this.get('_enabled')){
this.disable();
}else{
this.enable();
}
this.save();
},
enable: function(){
this.from = this.stash.from;
this.until = this.stash.until;
this.stash.from = null; // strictly speaking unnecssary
this.stash.until = null;
this._enabled = true;
},
disable: function(){
this.stash.from = this.from;
this.stash.until = this.until;
this.from = null;
this.until = null;
this._enabled = false;
},
enabled: function(){
return this._enabled;
},
});
var History = Backbone.Collection.extend({
model: app.Era,
localStorage: new Backbone.LocalStorage('karass-history'),
enabled: function(){
return this.filter(function(era){
return era.enabled();
});
},
nextOrder: function(){
if(!this.length){
return 1;
}
return this.last().get('order') + 1;
},
comparator: function( era ) {
return era.get('order');
},
comparatorFrom: function( era ){
return era.get('from');
},
comparatorUntil: function(era){
return era.get('until');
},
getFirstFrom: function(){
// todo
},
getLastUntil: function(){
// todo
},
getEraAt: function(time){
// todo
},
});
app.History = new History();
The Views
app.AppView = Backbone.View.extend({
// bind to the karassApp div we set up in the html file
el: "#karassApp",
// Our template for the line of statistics at the bottom of the app.
statsTemplate: _.template( $('#stats-template').html() ),
// delegated events for creating new items, and clearing completed ones
events: {
'keypress #new-era-start': 'focusOnEnd',
'keypress #new-era-end': 'createEraOnEnter',
'click #disable-history': 'toggleEnabledAllEras'
},
// At initialization we bind to the relevant events on the 'eras'
// collection, when items are added or changed. Kick things off by
// loading any preexisting todos that might be saved in *localStorage*
initialize: function(){
//_.bindAll(this);
this.era_start = this.$('#new-era-start');
this.era_end = this.$('#new-era-end');
this.disableHistory = this.$('#disable-history');
this.$footer = this.$('#footer');
this.$main = this.$('#main');
window.app.History.on('add', this.addOneEra, this);
window.app.History.on('reset', this.addAllEras, this);
// window.app.History.on('add:true', this.addAllEras, this);
window.app.History.on('change:_enabled', this.filterOneEra, this);
window.app.History.on('filter', this.filterAllEras, this);
window.app.History.on('all', this.renderHistory, this);
app.History.fetch();
},
// Re-rendering the App just means refreshing the statistics -- the rest
// of the app doesn't change.
renderHistory: function(){
var era_count = app.History.length();
if(app.History.length){
this.$main.show();
this.$footer.show();
this.$footer.html(this.statsTemplate({
era_count: era_count,
}));
this.$('#filters li a')
.removeClass('selected')
.filter('[href=#/' + (app.EraFilter || '' ) + '"]')
.addClass('selected');
}else{
this.$main.hide();
this.$footer.hide();
}
this.disableHistory = !app.History.enabled();
},
// Add a single era item to the list by creating a view for it, and
// appending its element to the `<ul>`.
addOneEra: function(era){
var view = new app.EraView({ model: era });
$('#history').append(view.render().el);
},
addAllEras: function(){
this.$('#history').html('');
app.History.each(this.addOneEra, this);
},
filterOneEra: function(era){
era.trigger('visible');
},
filterAllEras: function(){
app.History.each(this.filterOne, this);
},
// Generate the attributes for the new Era Item
newEraAttributes: function(){
return {
from: this.era_start.val().trim(), // validation logic should probably go here: make sure it's a date that can be saved
until: this.era_end.val().trim(), // ditto
stash: {
from: null,
until: null
},
order: app.History.nextOrder(),
_enabled: true,
}
},
focusOnEnd: function(e){
if(e.which !== ENTER_KEY || !this.era_start.val().trim()){
return;
}
this.era_end.focus();
},
createEraOnEnter: function(e){
if(e.which !== ENTER_KEY || !this.era_start.val().trim()){
return;
}
app.History.create(this.newEraAttributes());
this.era_start.val('');
this.era_end.val('');
},
toggleEnabledAllEras: function(){
var enabled = !this.disableHistory.checked;
app.History.each(function(era){
era.toggle();
era.save();
});
}
});
app.EraView = Backbone.View.extend({
tagName: 'li',
template: _.template( $('#era-template').html() ),
// The DOM events specified to an item
events: {
'dblclick label': 'edit',
'keypress .edit .start': 'focusOnEnd',
'keypress .edit .end': 'updateOnEnter',
'blur .edit': 'close',
},
// The EraView listens for changes to its model, re-rendering. Since there's
// a one-to-one correspondence between an era and a EraView in this app,
// we set a direct reference on the model for convenience.
initialize: function(){
//_.bindAll(this);
this.model.on('change', this.render, this);
},
// Re-renders the era item to the current state of the model and
// updates the reference to the era's edit input within the view
render: function(){
this.$el.html( this.template(this.model.toJSON()));
this.era_start = this.$('.era-start');
this.era_end = this.$('.era-end');
return this;
},
// Switch this view into editing mode, displaying the input field
edit: function(){
this.$el.addClass('editing');
this.era_start.focus();
},
// Close the editing mode, saving changes to the era
close: function(){
var start = this.era_start.val().trim();
var end = this.era_end.val().trim();
if(start && end){
this.model.save({from: start, until: end});
}
this.$el.removeClass('editing');
},
focusOnEnd: function(e){
if(e.which !== ENTER_KEY || !this.era_start.val().trim()){
return;
}
this.era_end.focus();
},
updateOnEnter: function(e){
if(e.which !== ENTER_KEY || !this.era_end.val().trim()){
return;
}
this.close();
}
});
And last but not least:
The HTML
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Backbone.js • HistoryMVC</title>
</head>
<body>
<section id="karassApp">
<header id="header">
<h1>Karass</h1>
<input id="new-era-start" placeholder="Since when?" autofocus>
<input id="new-era-end" placeholder="Until when?" >
</header>
<section id="main">
<input id="disable-history" type="checkbox">
<label for="disable-history">Disable History, work on Snapshot instead</label>
<ul id="history"></ul>
</section>
<footer id="footer"></footer>
</section>
<div id="info">
No info here, traveller!
</div>
<script type="text/template" id="era-template">
<li>
<input class="era-start" placeholder="Since when?" value="<%= from %>">
<input class="era-end" placeholder="Until when?" value="<%= until %>">
<label>Edit</label>
</li>
</script>
<script type="text/template" id="stats-template">
<% if (era_count) { %>
<p>There are <%= era_count %> eras to display</p>
<% }else{ %>
<p>There are no eras to display, yet.</p>
<% } %>
</script>
<script src="js/lib/jquery-1.9.0.min.js"></script>
<script src="js/lib/underscore-min.js"></script>
<script src="js/lib/backbone.js"></script>
<script src="js/lib/backbone.localStorage-min.js"></script>
<script src="js/models/era.js"></script>
<script src="js/models/history.js"></script>
<script src="js/views/app.js"></script>
<script src="js/views/era.js"></script>
</html>
Debug
I'm able to instantiate an Era
using era = new app.Era({ from: 0, until: 1, stash: {from: null, until: null}, _enabled: true});
, but calling app.History.create(...)
with the same arguments throws the error.
Question What is the error here? I tried to do everything anologous to the tutorial, and have been over all parts of the code three times now to see if I would spot an error.
Thank you!
Oh yeah, and credit goes to Nyxynyx, whose question I shamelessly ripped off after not finding an answer to mine.