The situation I'm experiencing is that my fetch is working fine, I can see the data in my backbone collection, if I step into the method where I assign the item from the collection to the model var or pause for a second or two, all is well: the line of code this.member gets populated
this.member = this.members.get(1);
. If I just let the code run, I wind up passing in a null model to my view. I don't understand what I'm missing. Do I need to bind data in the collection before I access the collection? I'm trying to fall in love with Backbone, but so far, she's been a cruel mistress...
`
//create the namespace
var Endeavor = {
Models: {},
Collections: {},
Views: {},
Templates: {}
};
Endeavor.Models.Member = Backbone.Model.extend({
idAttribute: "Id"
});
Endeavor.Collections.Members = Backbone.Collection.extend({
model: Endeavor.Models.Member,
url: "Http://localhost:60000/api/members/" + "1", // $.cookie('UserId')
initialize: function () {
console.log("Members collections init");
}
});
Endeavor.Views.MemberView = Backbone.View.extend({
id: "memberForm",
template: "#memberTemplate",
initialize: function () {
console.log('init member view');
},
render: function () {
console.log('memberView render called');
console.log(this.model.toJSON());
var html = $(this.template).tmpl();
$(this.el).html(html);
$("#Name").text = this.model.Name;
}
});
jQuery(document).ready(function () {
// router
Endeavor.Router = Backbone.Router.extend({
routes: {
"": "lists",
},
lists: function () {
this.members = new Endeavor.Collections.Members();
this.members.fetch();
this.member = this.members.get(1);
var memberView = new Endeavor.Views.MemberView({ model: this.member });
memberView.render();
$("#content").html(memberView.el);
}
});
// populate local variables
var appRouter = new Endeavor.Router();
Backbone.history.start();
});
`.
Members.fetch is async call. Try to render view on members reset event, or pass success callback in fetch method, or call this.members.fetch({async: false}). There are many different options.
Related
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;
}
});
So my issue is that the collection in my function that is being fired from my router vent/event aggregator does not have access to my main collection's fetched models.
My guess is that it's an asynchronous call issue, but how can I make it so the vented function call WAITS until the collection/models are fetched before executing? Or is that even my issue?
Here's is my relevant code. I'm using require.js and backbone to create a modular AMD app. Thank you so much in advance:
main.js
require(['views/app'], function (AppView) {
window.App = {
Vent : _.extend({}, Backbone.Events)
};
new AppView();
router.js
define([
'backbone',
], function(Backbone){
var MainRouter = Backbone.Router.extend({
routes: {
'levelone/:id':'showWork'
},
showWork: function (index){
App.Vent.trigger('addressChange', {
index: index
});
}
});
return MainRouter;
});
App.js
define([
'backbone',
'views/levelone/LevelOneView',
'views/leveltwo/LevelTwoView',
'views/static/StaticView',
'router'
],
function(Backbone, LevelOneView, LevelTwoView, StaticView, MainRouter){
var AppView = Backbone.View.extend({
el: $("body"),
events: {
...
},
initialize: function(){
new LevelOneView();
App.router = new MainRouter();
Backbone.history.start();
},
.............
LevelOneView.js
initialize:function() {
this.getCollection();
this.domSetup();
App.Vent.on('addressChange', this.addressChange, this);
},
getCollection : function(){
var self = this;
onDataHandler = function(collection) {
self.LevelTwoCollectionGrab();
};
this.collection = new LevelOneCollection([]);
this.collection.fetch({ success : onDataHandler, dataType: "jsonp" });
},
// We grab a Level Two Collection here so we can take the ids from it and add them to our Level One collection.
// This is necessary so we can create links between the two levels.
LevelTwoCollectionGrab: function(){
var self = this;
this.leveltwocollection = new LevelTwoCollectionBase([]);
onDataHandler = function(collection){
self.render();
self.$el.animate({
'opacity': 1
}, 1200);
self.renderLevelTwoIds();
self.setLevelTwoids();
self.attachLevelTwoLink();
}
this.leveltwocollection.fetch({success : onDataHandler, dataType: "jsonp"});
},
renderLevelTwoIds: function(){
return this;
},
render: function(){
var pathname = window.location.hash;
this.setModelId(this.collection.models);
this.addPositionsToIndex();
this.determineModels();
this.attachLevelTwoLink();
.......
},
addressChange: function(opts){
console.log(this.collection.models)
//returns a big fat empty array. WHY?!
}
You could use the jQuery Promises returned by fetch to help you know when both collections are fetched.
initialize:function() {
this.getCollection();
this.domSetup();
App.Vent.on('addressChange', this.addressChange, this);
},
getCollection : function(){
var self = this;
console.log('should be first');
this.collection = new LevelOneCollection([]);
this.fetchingLevelOne = this.collection.fetch({ dataType: "jsonp" });
this.fetchingLevelTwo = this.leveltwocollection.fetch({ dataType: "jsonp"});
// wait for both collections to be done fetching.
// this one will always be called before the one in addressChange
$.when(this.fetchingCollectionOne, this.fetchingCollectionTwo).done(function(){
console.log('should be second');
self.render();
self.$el.animate({
'opacity': 1
}, 1200);
self.renderLevelTwoIds();
self.setLevelTwoids();
self.attachLevelTwoLink();
});
},
renderLevelTwoIds: function(){
return this;
},
render: function(){
var pathname = window.location.hash;
this.setModelId(this.collection.models);
this.addPositionsToIndex();
this.determineModels();
this.attachLevelTwoLink();
.......
},
addressChange: function(opts){
var self = this;
// wait for both collections to be done fetching.
// this one will always be called AFTER the one in getCollection
$.when(this.fetchingCollectionOne, this.fetchingCollectionTwo).done(function(){
console.log('should be third');
console.log(self.collection.models);
});
}
A nice thing about this, if the user is very very fast at typing in the address bar, and several addressChange calls are made, they will all wait until the collections are fetched and will execute in the proper order.
I think I solved it. Basically, I'm now calling the function inside of $.when function--
Like so:
$.when(this.collection.fetch(), this.leveltwocollection.fetch()).done(function(){
$.when(self.render()).done(function(){
_.each(self.collection.models, function(model){
var wpid = model.get('id'),
bbid = model.id;
if (wpid == index){
window.App.InfoPos.pos5 = bbid;
var modelinfo = model.toJSON();
$('.box5').empty();
$('.box5').html(tmplOne(modelinfo));
self.$el.animate({
'opacity': 1
}, 1200);
}
});
});
});
The function launches from inside the when call and then waits until completed before executing anything in the done function. Works now! Thanks for the help all, especially you Paul.
During the initialize function of my app I would like to default to my search page and pass my LeagueCollection as the model.
I am encountering an issue where I can add a watch to this.searchResults in my App initialize and see models: Array[3] as expected,
but when the this.model.toJSON() in the view is called I get the error object has no method toJSON.
This code was working fine with a in memory collection and then I switched to using backbone.localstorage.js to store the app data locally.
So my question is: why is the model not populated in the view?
In my main.js I have
var AppRouter = Backbone.Router.extend({
routes: {
"": "list",
...
},
initialize: function () {
this.searchResults = new LeagueCollection();
this.searchPage = new SearchPage({
model: this.searchResults.fetch()
});
this.searchPage.render();
},
...
});
In my Search Page view
window.SearchPage = Backbone.View.extend({
initialize:function () {
this.template = _.template(tpl.get('search-page'));
},
render:function (eventName) {
var self = this;
$(this.el).html(this.template(this.model.toJSON()));
this.listView = new LeagueListView({el: $('ul', this.el), model: this.model});
this.listView.render();
return this;
},
...
});
The method collection.fetch doesn't return the collection -- it's asynchronous. What you probably want is to use its success callback:
this.searchResults = new LeagueCollection();
var self = this;
this.searchResults.fetch({
success: function(collection, response) {
self.searchPage = new SearchPage( { model: collection } );
self.searchPage.render();
}
});
I've fetch() a model by a server and I want to render the results of fetch() with a view.
The results of fetch() is an array of objects (var risultati) and I want render this var risultati. I've tried but nothing works.
var AppRouter = Backbone.Router.extend({
routes: {
"": "list",
},
initialize: function () {},
list: function () {
var utente = new Person();
var risultati;
utente.fetch({
success: function (data) {
var ris = data.attributes;
var risultati = ris.results;
console.log(risultati); /* risultati contains array of object to render*/
}
});
this.page = new UserListView({
model: this.utente
});
$('body').append(this.page.$el);
}
});
You may be having problems because your call to render the view is occurring separate from your utente.fetch() call.
Since .fetch() is asynchronous, your view code will be executed before .fetch() has finished. You should add the view creation/rendering as part of the success function, or you should bind the change event that occurs when the model is updated to fire off a new function that contains your view creation.
You should separate your MVC logic... don't attach objects (collections I think) to a router route handler.
Assuming that you are trying to render a collection of person models, I suggest you use a model and view for the person and a collection and a view for handling the "array of objects" :
var Person = Backbone.Model.extend({
initialize : function(){
// initialize the view
this.view = new PersonView({model : this});
}
}),
PersonView = Backbonke.View.extend({
render : function(){
// render your person
}
}),
UserList = Backbone.Collection.extend({
model : Person,
initialize : function(){
this.view = new UserListView({
collection : this
});
},
update : function(){
var self = this;
this.fetch({
success: function (data) {
var ris = data.attributes;
var risultati = ris.results;
console.log(risultati); /* risultati contains array of object to render*/ self.view.render();
}
});
}
}),
UserListView = Backbone.View.extend({
render : function(){
this.collection.each(function(el,i){
el.view.render();
});
}
});
and then use it as :
var page = new UserList();
var AppRouter = Backbone.Router.extend({
routes: {
"": "list",
},
initialize: function () {},
list: function () {
page.update();
}
});
Hope this helps!
I am trying to write some backbone.js stuff to get a better understanding on where and if it fits in better for me on projects. Any way I have a site and I am loading a collection with page content.
Json data comes back with (pid,name,title,content) on my router the default is
defaultRoute: function (actions)
{
this.showInfo('food');
},
showInfo: function (id)
{
var view = new ContentView({ model: this._items.at(id) });
$(".active").removeClass("active");
$("#" + id).addClass("active");
view.render();
}
if I put a 0 in place of id in this "new ContentView({ model: this._items.at(0) })" I will get the first item in the collection and if I do this in the View:
var ContentView = Backbone.View.extend({
el: $('#content'),
render: function ()
{
this.el.empty();
$(this.el).append(this.model.attributes.content);
return this;
}
});
I get the content displayed perfectly but of course may not be the content I wanted
Is it possible to select from a collection based on name == "food"?? I dont want to have to map the content to id numbers defeats the purpose of storing in a db
Sorry if this seems like a foolish question but I have crawled all over looking and Im sure Im missing something simple
here is my full NavigationRouter code in case it helps
var NavigationRouter = Backbone.Router.extend({
_data: null,
_items: null,
_view: null,
routes: {
"p/:id": "showInfo",
"*actions": "defaultRoute"
},
initialize: function (options)
{
var _this = this;
$.ajax({
url: "page_data.php",
dataType: 'json',
data: {},
async: false,
success: function (data)
{
_this._data = data;
_this._items = new ItemCollection(data);
_this._view.render();
Backbone.history.loadUrl();
}
});
return this;
},
defaultRoute: function (actions)
{
this.showInfo('home');
},
showInfo: function (id)
{
var view = new ContentView({ model: this._items.at(id) });
$(".active").removeClass("active");
$("#l_" + id).parent().addClass("active");
view.render();
}
});
Backbone mixes in a bunch of Underscore's functions into its Collections.
So if you want to find the model in the collection where name === 'food', you can do:
var foodModel = this._items.find(function(model) {
return model.get('name') === 'food';
});
// this will set foodModel to the first model whose name is 'food'
As a side note, you don't need to call empty in your render function, which can just be:
render: function() {
$(this.el).html(this.model.get('content'));
return this;
}
jQuery's html function just replaces the content of an element with the html string you pass in.