edit - backbone.js beginner attempting to load data - backbone.js

Attempting to post a beginner question but not making it past the "quality standards" filter.
I read through the thread on the error message. Are beginner questions too basic? Understandable but it might be better to put that in the error message. I include my code and my English is correct outside of the code block. The question has not been addressed, or at least is not returned from various search patterns. Is there anyway to appeal the filter as multiple rewrites have not cleared the hurdle or solved my, admittedly beginner, problem? In a last ditch attempt to hack the filter I'm pasting my original question in bellow with a few English only edits. edit - this seems to have worked but leaving above paragraph in to not jinx it.
Trying to load data into backbone but the render function is not firing.
Firebug shows the GET is arriving with the correct data string.
I have attempted to assign "this" to a variable and fire as a function but still no luck.
(function($) {
var DObj = Backbone.Model.extend({
defaults: {
dstring: 'dstring again'
},
});
var MObs = Backbone.Collection.extend({
defaults: {
model: DObj
},
model: DObj,
url: 'scr/bbone.php'
});
var MView = Backbone.View.extend({
initialize: function() {
this.collection = new MObs();
//this.collection.bind("reset", this.render, this);
this.collection.on("sync", this.render, this);
//this.collection.bind("change", this.render, this);
this.collection.fetch();
},
render: function() {
alert("here : ");
}
});
var newMob = new MView();
})(jQuery);

I have rewritten the code with mockjax to mock response from the server, here it is:
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.4/underscore-min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.0.0/backbone-min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery-mockjax/1.5.1/jquery.mockjax.min.js"></script>
<script>
var DObj = Backbone.Model.extend({
});
var MObs = Backbone.Collection.extend({
model: DObj,
url: '/movies'
});
$.mockjax({
type: 'GET',
url: '/movies',
contentType: 'text/json',
status: 200,
response: function() {
this.responseText = JSON.stringify(new MObs([{ name: 'Lincoln' }, { name: 'Argo' }]));
}
});
var MView = Backbone.View.extend({
initialize: function() {
this.collection.on("sync", this.render, this);
},
render: function() {
alert("here : ");
}
});
(function($) {
var newCol = new MObs();
newMob = new MView({ collection: newCol });
newCol.fetch({
success: function () { console.log('Success'); },
error: function() { console.log('Error'); }
});
})(jQuery);
I think there are many issues with code, the issue you were specifically asking for has to do with the server response; you should see an alert with the above code. I recommend you read backbone.js documentation for others.

Related

Backbone.js Uncaught ReferenceError: x is not defined

I am getting Uncaught ReferenceError: _auditNumber is not defined error while trying to bind my model to the view using backbone.js and underscore.js
<script id="searchTemplate" type="text/template">
<div class="span4">
<p>"<%= _auditNumber %>"</p>
</div>
<div class="span4">
<p>"<%= _aic %>"</p>
</script>
Collection
//Collection
var AuditsCollection = Backbone.Collection.extend({
initialize: function() {
this.on('add', this.render);
},
render: function() {
_.each(this.models, function (item) {
var _auditView = new AuditView({
model: item
});
$("#audits").append(_auditView.render().el);
});
},
});
Model
var Audit = Backbone.Model.extend({
url: function () {
return myUrl;
},
defaults: {
_auditNumber: "",
_aic: "",
},
parse: function (data) {
data.forEach(function (auditItem) {
var auditsCollection = new AuditsCollection();
auditsCollection.add(JSON.stringify(auditItem));
});
}
});
// Sub View
var AuditView = Backbone.View.extend({
className: 'row-fluid',
template: $("#searchTemplate").html(),
render: function () {
var tmpl = _.template(this.template);
this.$el.html(tmpl(this.model.toJSON()));
return this;
}
});
I know I am missing something simple, any help is appreciated.
2 problems (at least - you're kind of off in the weeds given how many backbone tutorials there are).
Your model URL is returning a list of results. That's what collections are for. Your model should fetch a single record and the parse method has to return the model's attribute data. If you stick with the tutorials, you won't need a custom url function and you won't need a custom parse function at all.
var Audit = Backbone.Model.extend({
url: function () {
//This needs to be a url like /audits/42 for a single record
return myUrl;
},
defaults: {
_auditNumber: "",
_aic: "",
},
parse: function (data) {
//this needs to return an object
return data[0];
}
});
You aren't passing a valid data object to your template function.
// Sub View
var AuditView = Backbone.View.extend({
className: 'row-fluid',
//compile template string into function once
template: _.template($("#searchTemplate").html()),
render: function () {
//render template into unique HTML each time
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});

How to pass a template to a view in Backbone

I'm trying to pass a template to my view. I have several different templates I want to use and want to be able to switch them up in my router. I get no errors, but I get no results. It looks like the initialize method isn't being called in my second view. Here is my code:
(function() {
window.App = {
Models: {},
Collections: {},
Views: {},
Router: {}
};
window.template = function(id) {
return _.template( $('#' + id).html() );
};
var vent = _.extend({}, Backbone.Events);
_.templateSettings.interpolate = /\[\[(.+?)\]\]/g;
App.Router = Backbone.Router.extend({
routes: {
'' : 'index',
'send-message' : 'sendMessage',
'*other' : 'other'
},
index: function() {
t = new (App.Collections.Tables.extend({ url: 'main-contact'}))();
tables = new (App.Views.Tables.extend({
collection: t, template: template('mainContactTemplate')}))();
$('#web-leads').html(tables.el);
},
sendMessage: function() {
t = new (App.Collections.Tables.extend({ url: 'send-message'}))();
tables = new App.Views.Tables.extend({
collection: t, template: template('sendMessageTemplate')});
$('#web-leads').html(tables.el);
},
other: function() {
}
});
// Main Contact
App.Models.Table = Backbone.Model.extend({});
App.Collections.Tables = Backbone.Collection.extend({
model: App.Models.Table,
initialize: function(models, options) {
this.fetch({
success: function(data) {
//console.log(data.models);
}
});
if (options) {
this.url = this.url || options.url;
}
}
});
App.Views.Tables = Backbone.View.extend({
tagName: 'ul',
initialize: function() {
this.collection.on('reset', this.render, this);
},
render: function() {
return this.collection.each(this.addOne, this);
},
addOne: function(model) {
var t = new App.Views.Table({ model: model, template: template});
this.$el.append(t.render().el);
return this;
}
});
App.Views.Table = Backbone.View.extend({
tagName: 'li',
template: this.template,
initialize: function (attrs) {
this.options = attrs;
console.log(this.options);
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
new App.Router();
Backbone.history.start();
})();
EDIT: I was missing some parenthesis. But now I get an error of an unrecognized expression. Initialize is now being called.
The way you are doing it in App.Views.Table is (as far as I can tell) the "standard" way of using templates with Backbone. There are of course several alternatives though, and none of them are "wrong" per say.
That being said, you do have a couple problems in your code. Let's start with:
template: this.template,
At the time that code runs you're not in an instance of App.Views.Tables, you're in the global space declaring a class that (later) will be used to make instances. At that moment though, this just refers to window. What you really want to do is set the template in your initialize, which leads me to:
initialize: function(options) {
this.template = options.template;
},
But then there's one last problem:
var t = new App.Views.Table({ model: model, template: template});
there is no template variable in that function, so you're really doing template: undefined. That should use a real template.
All that being said, you might want to just consider putting the template on the view directly, the way you sort of tried to:
template: Handlebars.compile('<span>{{test}}</span>'),
After all, any given view should always use the same template, right? Also, you might want to consider moving the:
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
in to a parent class, so that you can share it between all of your templated views, instead of having to repeat it.

Backbone/Underscore uniqueId() Odd Numbers

I'm relatively new to Backbone and Underscore and have one of those questions that's not really an issue - just bugging me out of curiosity.
I built a very simple app that allows you to add and remove models within a collection and renders them in the browser. It also has the ability to console.log the collection (so I can see my collection).
Here's the weird thing: the ID's being generated are 1,3,5... and so on. Is there a reason specific to my code, or something to do with BB/US?
Here's a working Fiddle: http://jsfiddle.net/ptagp/
And the code:
App = (function(){
var AppModel = Backbone.Model.extend({
defaults: {
id: null,
item: null
}
});
var AppCollection = Backbone.Collection.extend({
model: AppModel
});
var AppView = Backbone.View.extend({
el: $('#app'),
newfield: $('#new-item'),
initialize: function(){
this.el = $(this.el);
},
events: {
'click #add-new': 'addItem',
'click .remove-item': 'removeItem',
'click #print-collection': 'printCollection'
},
template: $('#item-template').html(),
render: function(model){
var templ = _.template(this.template);
this.el.append(templ({
id: model.get('id'),
item: model.get('item')
}));
},
addItem: function(){
var NewModel = new AppModel({
id: _.uniqueId(),
item: this.newfield.val()
});
this.collection.add(NewModel);
this.render(NewModel);
},
removeItem: function(e){
var id = this.$(e.currentTarget).parent('div').data('id');
var model = this.collection.get(id);
this.collection.remove(model);
$(e.target).parent('div').remove();
},
printCollection: function(){
this.collection.each(function(model){
console.log(model.get('id')+': '+model.get('item'));
});
}
});
return {
start: function(){
new AppView({
collection: new AppCollection()
});
}
};
});
$(function(){ new App().start(); });
if you look in the backbone.js source code you'll notice that _.uniqueId is used to set a model's cid:
https://github.com/documentcloud/backbone/blob/master/backbone.js#L194
that means that every time you create a model instance, _.uniqueId() is invoked.
that's what causing it to increment twice.

Backbone.js - Correct way of filtering and displaying collection data in a view

I have got a huge list of tasks loaded on the start.
I want to show them depending on selected list / inbox, so that there won't be additional loadings for each list.
window.Task = Backbone.Model.extend({});
window.TasksCollection = Backbone.Collection.extend({
model: Task,
url: '/api/tasks',
inbox: function() {
return this.filter(function(task) {
return task.get('list') == null;
});
},
list: function(id) {
return this.filter(function(task) {
return task.get('list') == id;
});
}
});
window.tasks = new TasksCollection;
window.TaskView = Backbone.View.extend({
tagName: 'li',
template: _.template($('#item-template').html()),
initialize: function() {
_.bindAll(this, 'render', 'close');
this.model.bind('change', this.render);
this.model.view = this;
},
render: function() {
$(this.el).html(this.template(this.model.toJSON()));
this.setContent();
return this;
},
});
window.TasksView = Backbone.View.extend({
el: '#todo-list',
collection: tasks,
initialize: function() {
_.bindAll(this, 'render');
this.collection.bind('reset', this.render);
this.collection.fetch();
},
render: function() {
var t = this;
$(t.el).html('');
this.collection.each(function(task) {
var view = new TaskView({ model:task });
$(t.el).append( view.render().el );
});
return this;
},
});
window.Nicetask = Backbone.Router.extend({
routes: {
'': 'inbox',
'/inbox': 'inbox',
'/list/:id': 'list',
},
initialize: function() {
_.bindAll(this, 'inbox', 'list');
window.tasksView = new TasksView;
},
inbox: function() {
tasks.reset( tasks.inbox() );
},
list: function(id) {
tasks.reset( tasks.list(id) );
}
});
This code works, but the reset() function removes other tasks in actual list from tasks collection. And on another route, tasks collection is empty.
Is there any reasonable way to achieve this? thanks for any idea.
ps: backbone novice
UPDATE
Thx to #sled and #ibjhb for comments, here is snippet of working solution.
window.TasksView = Backbone.View.extend({
el: '#todo-list',
collection: Backbone.Collection.extend(),
initialize: function() {
_.bindAll(this, 'render', 'addOne', 'addAll');
this.collection.bind('add', this.addOne);
this.collection.bind('reset', this.render);
},
render: function(data) {
$(this.el).html('');
_.each(data, function(task) {
this.addOne(task);
}, this);
return this;
},
addOne: function(task) {
var view = new TaskView({ model:task });
$(this.el).append( view.render().el );
},
});
window.Nicetask = Backbone.Router.extend({
routes: {
'': 'inbox',
'/inbox': 'inbox',
'/today': 'today',
'/list/:id': 'list',
},
initialize: function() {
_.bindAll(this, 'inbox', 'today');
window.tasksView = new TasksView;
window.menuView = new MenuListView;
tasks.fetch();
},
inbox: function() {
tasksView.render( tasks.inbox() );
},
today: function() {
tasksView.render( tasks.today() );
},
list: function(id) {
tasksView.render( tasks.list(id) );
}
});
I think you need to use another collection. For example, in your inbox, do this:
inbox: function(){
currentCollection = new TasksCollection(tasks.inbox());
}
I haven't tested this but when you do a .reset(); you are removing all your models and loading the ones passed in.
#sled there's typos in the code you posted, see comments inline. Did you post this as a project somewhere?
// add models
add: function(models, options) {
// TYPO: next line was missing, so single models not handled.
models = _.isArray(models) ? models.slice() : [models];
var self = this;
models = _.filter(models, this.filter);
// return if no models exist
// TYPO: returned undefined, so was not chainable
if(models.length == 0) { return this; }
// actually add the models to the superset
this.superset.add(models, options);
return this;
},
// remove models
remove: function(models, options) {
// TYPO: next line was missing, so single models not handled.
models = _.isArray(models) ? models.slice() : [models];
// remove model from superset
this.superset.remove(_.filter(_.filter(models, function(cm) {
// TYPO: not 'm != null', causes error to be thrown
return cm != null;
}), this.filter), options);
// TYPO: missing return so not chainable
return this;
},
one quick amendment to you solution, you are using
$(this.el).html('');
My understanding is your the views and related event bindings will still exist in the browser memory, so you ideally need to use view.remove() on the TaskView to correctly clear the event bindings as well as the html.
This is a slightly different take on the answer as I have been looking for a solution to a similar problem, hope this may be of help to others.
My problem: - to filter a complete collection by attributes of the model. eg. a user clicks the models view, gets a list of (some of) the attributes, selecting an attribute filters the collection to only show ones with the same value.
The route I am taking is by calling a method on the collection from the view, in my case the view is specific to a model so:
this.model.collection.myFilter(attr,val);
where attr is an attribute of the model associated with the collection, then in the filter something like
myFilter: function(attr, val){
var groupByAttr = this.groupBy(function(article){
var res = (val === undefined)? true : (article.get(attr) == val);
article.set({selected:res});
return res;
});
return groupByAttr;
}
I have used ._groupBy as this returns 2 arrays (positive / negative) that may be of use. By setting the mode attribute "selected", and binding to this in the model view I can easily toggle a class which shows or hides the view.
if(val === undefined) is added as a simple way of clearing a filter by calling the same method without a value.

Backbone performing a GET immediately after a POST

I'm experimenting for the first time with backbone.js and I have a very simple Grails application with a single domain called Book. Things seem to be working well however, I've noticed that when I POST the data from the form to the server backbone then does a GET to the server with the ID of the new record. However, the POST returns the results as JSON and populates the table accordingly. I'm not sure I understand the need for the GET following the POST or how to stop this from happening.
$(function() {
// Model
window.Book = Backbone.Model.extend({
url: function() {
return this.id ? '/BackboneTest/books/' + this.id : '/BackboneTest/books.json';
},
defaults: { book: {
title: 'None entered',
description: 'None entered',
isbn: 'None entered'
}},
initialize: function() {
// can be used to initialize model attributes
}
});
// Collection
window.BookCollection = Backbone.Collection.extend({
model: Book,
url: '/BackboneTest/books.json'
});
window.Books = new BookCollection;
//View
window.BookView = Backbone.View.extend({
tagName: 'tr',
events: {
// can be used for handling events on the template
},
initialize: function() {
//this.render();
},
render: function() {
var book = this.model.toJSON();
//Template stuff
$(this.el).html(ich.book_template(book));
return this;
}
});
// Application View
window.AppView = Backbone.View.extend({
el: $('#book_app'),
events: {
"submit form":"createBook"
},
initialize: function() {
_.bindAll(this, 'addOne', 'addAll');
Books.bind('add', this.addOne);
Books.bind('refresh', this.addAll);
Books.bind('all', this.render);
Books.fetch();
},
addOne: function(book) {
var view = new BookView({model:book});
this.$('#book_table').append(view.render().el);
},
addAll: function() {
Books.each(this.addOne);
},
newAttributes: function(event) {
return { book: {
title: $('#title').val(),
description: $('#description').val(),
isbn: $('#isbn').val()
} }
},
createBook: function(e) {
e.preventDefault();
var params = this.newAttributes(e);
Books.create(params)
//TODO clear form fields
}
});
// Start the backbone app
window.App = new AppView;
});
I've determined that the cause of this was server side. Because of some scaffolded code that got generated for testing purposes, on the save, there was an additional redirect which resulted in a 302. This caused the GET after the POST. Once I cleaned up the server side code, I only get the POST, as expected.
Backbone usesPOST as a factory (getting the id from the server) with:
a payload request { title: 'None entered' }
a response { id: 12, title: 'None entered' }
It seems that your code trigger a GET action after the POST success. The code Books.bind('all', this.render) do not seems to be related to anything. It is not binded like add and there is no such method in the View.

Resources