I'm kind of new to backbone coming from knockout.js and I'm trying to get over this simple hump. I have this code:
$(function(){
window.Student = Backbone.Model;
window.Students = Backbone.Collection.extend({
model: Student,
url: 'test.php'
});
window.students = new Students();
window.AppView = Backbone.View.extend({
el: $('#container'),
initialize: function() {
Students.bind('reset', this.render);
},
render: function(){
console.log(Students.toJSON());
}
});
window.appview = new AppView();
$('#test').click(function(){
//var students = new Students();
students.fetch();
var q = students.toJSON();
console.log(q);
/*
students.create({
name: 'John',
grade: 'A'
}); */
})
});
My server sends the following JSON:
[{"id": "1233","name": "Karen","grade": "C"},{"id": "1234","name": "Susan", "grade": "F"}]
When I click the button and look at the console in Chrome, I see:
First Click:
[] - Corrected -just an empty array
Second Click:
[
Object
grade: "C"
id: "1233"
name: "Karen"
__proto__: Object
,
Object
grade: "F"
id: "1234"
name: "Susan"
__proto__: Object
]
First question is why does it take two clicks? Second: How can I simply assign/bind the grade(both as a collection and by id) to a textbox, <li> or other UI element(better yet make it observable when ajax pops in).
The console message you are seeing is from the click event handler. The console message from the render method never gets called.
You don't see anything in the first log message because fetch is an asynchronous method, so when you call toJSON right after fetch, the collection hasn't been populated from the fetch method yet.
There are a couple changes you need to make to your code to get it work as expected.
First, you need to pass in the collection when you instantiate the view
//original
window.appview = new AppView();
//new
window.appview = new AppView({ collection: window.students });
Then within the view you need to bind to the reset event on the collection that was passed in to the constructor. (You have to bind to an instantiated object, and not the definition of an object like you were originally doing)
window.AppView = Backbone.View.extend({
el: $('#container'),
initialize: function() {
_.bindAll(this, 'render');
this.collection.bind('reset', this.render);
},
render: function(){
console.log(this.collection.toJSON());
}
});
Now comment out the console log message in your click event, then you will only have to click once, and you will see a console log message from the render method.
$('#test').click(function(){
//var students = new Students();
students.fetch();
//var q = students.toJSON();
//console.log(q);
/*
students.create({
name: 'John',
grade: 'A'
}); */
})
Related
Below is the current code structure I have in place for a collection that I have manually constructed. I have a json file on my server which I am now trying to load in and basically remove the manual one and construct a collection based on that data. Was wondering what would I possibly need to change below to my code to help accommodate this.
var Game = Backbone.Model.extend({
defaults: {
name: 'John Doe',
age: 30,
occupation: 'worker'
}
});
var GameCollection = Backbone.Collection.extend({
model: Game,
url: 'path/to/json',
parse: function(response) {
return response;
}
});
var GamesView = Backbone.View.extend({
tagName: 'ul',
render: function() {
//filter through all items in a collection
this.collection.each(function(game){
var gameView = new GameView({model: game});
this.$el.append(gameView.render().el);
}, this)
return this;
}
});
var GameView = Backbone.View.extend({
tagName: 'li',
template: _.template($('#gameTemplate').html()),
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
var gameCollection = new GameCollection([
{
name: 'John Doe',
age: 30,
occupation: 'worker'
},
{
name: 'John Doe',
age: 30,
occupation: 'worker'
},
{
name: 'John Doe',
age: 30,
occupation: 'worker'
}
]);
var gamesView = new GamesView({collection: gameCollection});
$(document.body).append(gamesView.render().el);
This is one of the many things to love about Backbone. I don't know what you are using for your backend, but you state that you have a json file on your server, hopefully a json file full of the models that should be in your collection. And now here is the magic code (drumroll please..):
var GameCollection = Backbone.Collection.extend({
model: Game,
url: 'path/to/json/on/external/server',
});
var gameCollection = new GameCollection();
gameCollection.fetch();
Not much to it, right? Of course there are several options you can add or change to a fetch, so check out the docs here: http://backbonejs.org/#Collection-fetch. Backbone uses jQuery.ajax() be default, so check out the docs here to see all of the options: http://api.jquery.com/jQuery.ajax/
You shouldn't need the custom parse in your collection unless your models on the server don't match your backbone models.
Things to know:
fetch is asynchronous. It takes time to talk to the server, and the rest of your javascript will move on and complete. You will probably need to at least add a callback function to the success option, which will be called when fetch is finished, and it is good to add something to error as well, in case something goes wrong. You can add data as a query string so that your backend can use it using the data option, the data has to be an object. Here is an example:
gameCollection.fetch({
data: {collection_id: 25},
success: function(){
renderCollection(); // some callback to do stuff with the collection you made
},
error: function(){
alert("Oh noes! Something went wrong!")
}
});
fetch should receive data as JSON, so your url should either exclusive return JSON or be set up to detect an AJAX request and respond to it with JSON.
Firstly you need to fetch it from server as RustyToms said. And the other consideration is how to force the collection view to render itself again once data collected from server, as muistooshort commented.
If you manipulating fetch or sync you'll need to do it multiple times when there are more than one collection in app.
Doing such is native with Marionette, but in plain Backbone you can mimic the method of Marionette's CollectionView and do such:
//For the collection view
var GamesView = Backbone.View.extend({
initialize: function({
this.listenTo(this.collection, 'reset', this.render, this);
});
// Others
});
Then, when collection data fetched from server, the collection will trigger a reset event, the collection view noticed this event and render itself again.
For more than one collections, you can extract the code into a parent object in app and inherit from that.
var App.CollectionView = Backbone.View.extent({
initialize: //code as above
});
var GamesView = App.CollectionView.extend({
//Your code without initialize
});
I know this is a bit old at this point, but wanted to answer for anyone else stuck on this.
The code seems to come from the tutorial found here: http://codebeerstartups.com/2012/12/a-complete-guide-for-learning-backbone-js/
I too re-purposed the demo app found in that tutorial and had trouble rendering using external data.
The first thing is that the data itself needs to be converted to valid JSON or else you'll get a .parse() error.
SyntaxError: JSON.parse: expected property name or '}' at line 3 column 9 of the JSON data
or
error: SyntaxError: Unexpected token n
In your data source file, object properties need to be surrounded by quotes. It should look something like this:
[
{
"name": "John Doe",
"age": 30,
"occupation": "worker"
},
{
"name": "John Doe",
"age": 30,
"occupation": "worker"
},
{
"name": "John Doe",
"age": 30,
"occupation": "worker"
}
]
Secondly, once it's clear the external data is loading, we need to get it to render. I solved this (perhaps ungracefully) by moving the render() command into the success function of your gameCollection.fetch().
gameCollection.fetch({
success: function(collection, response, options) {
console.log('Success!! Yay!!');
$(document.body).append(gamesView.render().el);
},
error: function(collection, response, options) {
console.log('Oh, no!');
// Display some errors that might be useful
console.error('gameCollection.fetch error: ', options.errorThrown);
}
});
There are certainly better ways to accomplish this, but this method directly converts the code learned in the tutorial into something that works with external data.
Two common scenarios when I am using backbone backbone:
Attribute is listed as default value, then set
modelExample_A: Backbone.Model.extend({
defaults: {
whatever: 'foo'
something: 'blah'
}
});
viewExample_A: Backbone.View.extend({
//our view definition
});
var Example_A = new viewExample_A({
model: new modelExample_A()
})
Example_A.set({
'whatever': 'bar',
'something': 'weeeeeee',
});
Attribute is not listed as a default value, then set
modelExample_A: Backbone.Model.extend({
});
viewExample_A: Backbone.View.extend({
//our view definition
});
var Example_A = new viewExample_A({
model: new modelExample_A()
})
Example_A.set({
'whatever': 'bar',
'something': 'weeeeeee',
});
Attribute is not listed as a default value, set on creation
modelExample_A: Backbone.Model.extend({
});
viewExample_A: Backbone.View.extend({
//our view definition
});
var Example_A = new viewExample_A({
model: new modelExample_A({
'whatever': 'bar',
'something': 'weeeeeee',
})
})
But what about situations where I want to set a property of the model? I know this is generally discouraged, but sometimes in my code I like to make a not of a what model is the parent of the current model. This is something that almost certainly won't ever change, so there is no reason to put in the attribute for event listening/onChange purposes. Further, this is something without a default value (it can only get a value in context), so is it okay to just set it as a property of the model? Or will this cause problems down the line?
Setting a property instead of an attribute
modelExample_A: Backbone.Model.extend({
defaults: {
whatever: 'foo'
something: 'blah'
}
});
viewExample_A: Backbone.View.extend({
//our view definition
});
var Example_A = new viewExample_A({
model: new modelExample_A({
'whatever': 'bar',
'something': 'weeeeeee',
})
})
Example_A.parentModel = parentModelExample;
Used in moderation and with consideration, setting non-attribute properties on model instances is fine. Just be careful not to have this be data that can easily get into an inconsistent state, and if you are doing this a lot, that's a code smell. In that case, you may want to consider modeling some state as actual models with attributes, but just not persisting them (never call .save).
I want to pass an array to a view like this
this.account_nav = new AccountNav.View({
views: [
{ ref: new Member.Views.AccountNav({ model: this.model }), id: 'viewA' },
{ ref: new Member.Views.SettingsNav({ model: this.model}), id: 'viewB' }
]
});
However there is an error:
Uncaught Error: The argument associated with selector '' is defined
and a View. Set manage property to true for Backbone.View
instances. backbone.layoutmanager.js:208
pointing to
this.account_nav = new AccountNav.View({
Any ideas why I get this error?
If your View definition is created using Backbone.Layout.extend rather than Backbone.View.extend, this issue shouldn't arise.
To cite the Layout Manager documentation example:
var LoginView = Backbone.Layout.extend({
template: "#login-template"
});
versus
var LoginView = Backbone.View.extend({
// [...]
});
https://github.com/tbranyen/backbone.layoutmanager/wiki/Example-usage#structuring-a-view
backbone Model,board:
define([
'underscore',
'backbone',
'collections/lists',
'iobind',
'iosync'
], function( _, Backbone, Lists,ioBind,ioSync) {
var BoardModel = Backbone.Model.extend({
urlRoot: 'board',
noIoBind: false,
socket: io.connect(''),
idAttribute: '_id',
defaults: {
title: 'One Thousand and One Nights'
},
initialize: function() {
this.id = 1;
this.lists = new Lists;
this.socket.emit('joinBoard',this.id);
_.bindAll(this, 'getBoard');
this.ioBind('initBoard', this.getBoard, this);
},
getBoard: function(data){
this.set(data.data.board[0]);
}
});
return BoardModel;
});
backbone View: boardView:
var IndexView = Backbone.View.extend({
// Instead of generating a new element, bind to the existing elements in the HTML.
el: '#board',
// Board template html
template: Mustache.render(Template.board),
events: {
},
initialize: function() {
//Init Data
this.model = new Board();
// var lists = {
// lists: [
// {name: "To Do",
// cards:[
// {name: "Art work for A."},
// {name: "B Prototype."},
// {name: "C prototype."}
// ]
// },
// {name: "Doing",
// cards: [
// {name: "Art work for A."}
// ]
// },
// {name: "Done"}
// ]
// }
// var partial = {card: Template.card_in_list};
// var listHtml = Mustache.render(Template.list,lists,partial);
// template = $(this.template).find('.list-area').append(listHtml);
},
render: function() {
console.log(this.model);
console.log(this.model.toJSON());
var partial = {card: Template.card_in_list};
var listHtml = Mustache.render(Template.list,this.model,partial);
template = $(this.template).find('.list-area').append(listHtml);
this.$el.html(template);
}
});
in View function: render function, the console.log get different result.
console.log(this.model) can get correct object result:
child
_callbacks: Object
_changing: false
_escapedAttributes: Object
_ioEvents: Object
_pending: Object
_previousAttributes: Object
_silent: Object
attributes: Object
__v: 0
_id: "50b750a7795f285d4e000014"
created: "2012-11-29T12:10:15.269Z"
description: "simple is better, but not simpler"
dueDate: "2012-11-29T12:10:15.269Z"
lists: Array[6]
status: true
title: "test board unique"
__proto__: Object
changed: Object
cid: "c1"
getBoard: function () { [native code] }
id: "50b750a7795f285d4e000014"
lists: child
__proto__: ctor
but this.model.toJSON() only get model default values:
Object
title: "One Thousand and One Nights"
__proto__: Object
it confuse me. anyone know why reason the same model get different result.
In a Backbone Model, your business values (description, title ...) are store in the attributes attribute. When you call toJSON() on your model, what it does is it takes the attributes values, and remove the Backbone.Model object framework's functions and attributes.
When you manually want to set model attributes, you want to use set. I don't know what is in you data.data object, so you should check the doc : http://backbonejs.org/#Model-set
set model.set(attributes, [options])
Set a hash of attributes (one or
many) on the model. If any of the attributes change the models state,
a "change" event will be triggered, unless {silent: true} is passed as
an option. Change events for specific attributes are also triggered,
and you can bind to those as well, for example: change:title, and
change:content. You may also pass individual keys and values.
note.set({title: "March 20", content: "In his eyes she eclipses..."});
book.set("title", "A Scandal in Bohemia"); If the model has a validate
method, it will be validated before the attributes are set, no changes
will occur if the validation fails, and set will return false.
Otherwise, set returns a reference to the model. You may also pass an
error callback in the options, which will be invoked instead of
triggering an "error" event, should validation fail. If {silent: true}
is passed as an option, the validation is deferred until the next
change.
I found i trigger boardView.render twice. when i change code:
a = new boardView;
a.render();
to
a = new boardView;
i got the thing done.
by the way thanks Marcel Falliere's comments.
I am very new to Backbone and am doing a simple tutorial. I keep running into an error that I dont understand. Here is my code.
(function($) {
dataModel = new Backbone.Model.extend({
data: [
{text: "Google", href: "www.google.com"},
{text: "Yahoo", href: "www.yahoo.com"},
{text: "Youtube", href: "www.youtube.com"},
]
});
var View = Backbone.View.extend({
initialize: function(){
this.template = $('#list-template').children();
},
el: $('#container'),
events: {
"click button": "render"
},
render: function(){
var data = this.model.get('data');
for(var i = 0, l = data.length; i < l; i++){
var li = this.template.clone().find('a').attr('href', data[i].href).text(data[i].text).end();
this.el.find('ul').append(li);
}
}
});
var view = new View({ model: dataModel });
})(jQuery);
When I call this.model.get('data') I get the error TypeError: Object function (){return a.apply(this,arguments)} has no method 'get'. Please show me my error. Thanks.
All the properties and methods you pass when extending a model are set on its prototype not as its attributes, and dataModel here is not a Backbone model instance but a Backbone Model subclass. If done this way to access the data property you'd need to instantiate the model and do a modelInstance.data rather then modelInstance.get('data') as if it would be when data would be set as model attribute as shown in the example below.
What you wanted to do here was
var dataModel = new Backbone.Model({ // without the extend!
data: [
{text: "Google", href: "www.google.com"},
{text: "Yahoo", href: "www.yahoo.com"},
{text: "Youtube", href: "www.youtube.com"},
]
});
as you want to create an instance of a model rather then subclass Backbone.Model class. Extend method is used to subclass core Backbone classes – views, models, collections and routers.
Your 'dataModel' is just the definition of the Model. You need to create a model instance by calling new dataModel() when you pass it to the view.
a model define`s only the structure of one data element
Model
defaults:
text : null
href : null
what you need is a Collection
Collection
model : Model
you can then set data to the collection with .add
Collection.add([
{text: "Google", href: "www.google.com"},
{text: "Yahoo", href: "www.yahoo.com"},
{text: "Youtube", href: "www.youtube.com"},
])
to get the data from the collection look here
http://underscorejs.org/#collections
Hope it helps