Backbone - loading JSON into collection won't render in the view - javascript

I can't seem to get JSON that is loading into my FriendsCollection to render into FriendListView. I can see that it is loading through the network panel, and I can log the data to the console, but for some reason the fetch command isn't passing the data to the view to be rendered.
I'm using Backbone 1.0.
The code i'm using is available on jsbin here: http://jsbin.com/OHePaki/1/edit?html,js,output
// MODELS
var ArtifactModel = Backbone.Model.extend({
initialize: function() {
this.on('reset', function(){ artifactView.render() })
},
defaults: {
"text": "Unknown Text",
"timestamp": "Unknown timestamp"
}
});
var artifactModel = new ArtifactModel();
// COLLECTIONS
var ArtifactCollection = Backbone.Collection.extend({
model: ArtifactModel,
url: '/getDigest.json',
// url: 'http://we365.local/Artifact/GetShareableArtifact?token=b88d2640826bb8593f6edb308ce604f28225f240&artifact_id=2&social_site=tw&log_inside=&go',
parse: function(data) {
console.log('running parse');
return _.map(data.response.content, _.identity);
},
initialize: function(){
this.on('reset', function(){ artifactListView.render(); }),
console.log('running init function for ArtifactCollection');
this.fetch();
//this.reset(artifactjson, { parse: true });
console.log(this.toJSON());
}
});
var artifactCollection = new ArtifactCollection();
// VIEWS
var ArtifactView = Backbone.View.extend({
tagName: 'li',
className: 'single-model',
render: function(){
var template = Handlebars.compile($('#stream_getDigest').html());
this.$el.html(template(this.model.toJSON()));
return this;
}
});
var ArtifactListView = Backbone.View.extend({
initalize: function(){
this.collection.on('add', this.addOne, this);
},
render: function(){
this.collection.forEach(this.addOne, this);
},
addOne: function(artifactModel){
var artifactView = new ArtifactView({model: artifactModel});
this.$el.append(artifactView.render().el);
}
});
// rendering
var artifactView = new ArtifactView({model: artifactModel});
var artifactListView = new ArtifactListView({collection: artifactCollection});
artifactView.render();
artifactListView.render();
$('#list').html(artifactListView.$el.html());

By default jQuery ajax call is asynchronous, the code will keep running without waiting for the .fetch() to be finished. In your code the view is rendered before the collection is ready so the data for the view is empty.
You can pass jQuery ajax option to fetch function so you can do the following (http://backbonejs.org/#Collection-fetch):
...
initialize: function(){
this.on('reset', function(){ artifactListView.render(); }),
console.log('running init function for ArtifactCollection');
this.fetch({async:false});
console.log(this.toJSON()); //This will log the loaded collection
}
...
Or you can change fetching strategy to take the advantage of asynchronous load:
this.fetch().done(function(){
//Things to do after collection is loaded
});
//this's not good to use in init function

You need to set handlers on the models. Something like this:
friendModel.on('change', function() { friendView.render(); });
friendCollection.on('change', function() { friendListView.render(); });
Or better yet, put these lines in the constructors for friendModel and friendCollection (see http://backbonejs.org/#View-constructor ).

Related

Wrong backbone collection length. Can't each this collection

Sorry for my bad English. Tell me why the following happens:
I have some backbone collection:
var Background = window.Models.Background = Backbone.Model.extend({});
var Backgrounds = window.Models.Backgrounds = Backbone.Collection.extend({
model: window.Models.Background,
url: '/backgrounds/',
initialize: function() {
this.fetch({
success: this.fetchSuccess(this),
error: this.fetchError
});
},
fetchSuccess: function( collect_model ) {
new BackgroundsView ({ collection : collect_model });
},
fetchError: function() {
throw new Error("Error fetching backgrounds");
}
});
And some view:
var BackgroundsView = window.Views.BackgroundsView = Backbone.View.extend({
tagName: 'div',
className: 'hor_slider',
initialize: function() {
this.render();
},
render: function() {
console.log(this.collection);
this.collection.each( function (background) {
console.log(background);
//var backgroundView = new BackgroundView ({ model: background });
//this.$el.append(backgroundView.render().el);
});
}
});
now i creating collection
var backgrounds = new Models.Backgrounds();
but when I must render this view, in the process of sorting the collection its length is 0, but should be two. This log I see at console. How is this possible? What am I doing wrong??
You are creating the view before the collection fetch is successfull. Your code should be:
initialize: function() {
this.fetch({
success: this.fetchSuccess,
//------------------------^ do not invoke manually
error: this.fetchError
});
},
fetchSuccess: function(collection, response) {
new BackgroundsView ({ collection : collection});
},
You should let backbone call fetchSuccess when the fetch succeeds. Right now you're invoking the funcion immediately and passing the return value undefined as success callback.
This looks like a wrong pattern. Your data models shouldn't be aware of/controlling the presentation logic.
You have a view floating around without any reference to it. You should be creating a view instance with reference(for example from a router, or whatever is kick starting your application) and passing the collection to it. Then fetch the collection from it's initialize method and render after the fetch succeeds. Collection can be referenced via this.collection inside view.
Alternatively you can fetch the collection from router itself and then create view instance. Either way collection/model shouldn't be controlling views.
If the code is structured in the following way, the problem is solved. It was necessary to add a parameter reset to fetch.
var Background = window.Models.Background = Backbone.Model.extend({});
var Backgrounds = window.Models.Backgrounds = Backbone.Collection.extend({
model: window.Models.Background,
url: '/backgrounds/',
initialize: function() {
this.fetch({
reset : true,
});
}
});
var BackgroundsView = window.Views.BackgroundsView = Backbone.View.extend({
tagName: 'div',
className: 'hor_slider',
initialize: function() {
this.listenTo(this.collection, 'reset', this.render);
},
render: function() {
this.collection.each( function (background) {
var backgroundView = new BackgroundView ({ model: background });
this.$el.append(backgroundView.render().el);
}, this);
$('#view_list').empty();
$('#view_list').append(this.$el);
return this;
}
});

Backbone refresh collection with event handler

I'm using backbone and I'm very new at it, I have a list of products sizes and a list of quantities / prices. When someone selects a different product size, I use backbone to do an ajax call to the server to get me an updated price list.
I'm struggling to get the save function to work so that I can return the updated collection. I will have to pass back a couple params, but for the time being, I'm just trying to get it to save to the backend. I've read save can be used to automatically setup the ajax request.
I'd also only like this to load the template when the li element is clicked, not on page load.
My code
var models = {};
models.PriceModel = Backbone.Model.extend({
})
models.PriceList = Backbone.Collection.extend({
initialize: function(options) {
this.productId = options.productId;
},
model: models.PriceModel,
url: function() {
return '../product/pricing/' + this.productId + '.json'
}
});
View
var PriceView = Backbone.View.extend({
el: '#product-module',
template: Handlebars.compile($("#priceTemplate").html()),
events: {
"click #product-dimensions li": "dimensionClicked",
},
initialize: function(){
this.listenTo(this.collection, 'add', this.render);
this.listenTo(this.collection, 'reset', this.render);
},
render: function() {
this.$el.find('#product-quantities').html( this.template(this.collection.toJSON()));
},
dimensionClicked: function(event, callback){
this.collection.save({},{
success: function(model, data){
console.log('success')
this.collection.fetch();
},
error: function(model, response) {
console.log('error! ' + response);
}
});
},
});
Page
<script>
var prices = new models.PriceList({productId:${productInstance.id}});
var priceView = new PriceView({collection: prices});
<%-- prices.fetch({reset: true});--%>
</script>
The error I'm getting.
TypeError: this.collection.save is not a function
this.collection.save({},{
How do I pass back a couple of params and then refresh the template?
The solution was to use
this.collection.fetch({data: {customParm : searchData}, reset: true});

Backbone collection change event fires, but nothing changes in the view

I am running the following view:
app.OrganisationTab = Backbone.View.extend({
el : "#organisations",
template : _.template( $("#tpl-groups-list").html() ),
events : {
"click .js-edit-group" : "editGroup"
},
initialize: function() {
this.listenTo(this.collection, 'change', this.change);
var that = this;
this.collection.fetch({
success: function() {
that.render();
}
})
},
change: function() {
//this.$el.empty();
console.log("collection has changed");
},
render:function() {
this.$el.empty();
this.addAll();
return this;
},
addAll: function() {
this.collection.each(this.addOne, this);
},
addOne: function(model) {
var view = new app.GroupEntry({
model: model
});
this.$el.append(view.render().el);
},
editGroup: function(e) {
e.preventDefault();
var elm = $(e.currentTarget),
that = this;
$('#myModal').on('hidden.bs.modal', function () {
$('.modal-body').remove();
});
var organisation = this.collection.findWhere({ id : String(elm.data('groupid')) });
var members = organisation.get('users');
organisation.set('members', new app.UserCollection(members));
var projects = organisation.get('projects');
organisation.set('projects', new ProjectCollection(projects));
var orgForm = new app.createOrganisationForm({
model : organisation,
});
$('#myModal').modal({
backdrop: 'static',
keyboard: false
});
}
});
This view triggers a new view, and in that I can change a model save it (sends a PUT) and I can get in my console, collection has changed. If I console.log this collection I can see that the collection has changed. If I try and re-render the page all I see are the models as they were without the edits.
Why would this be happening, when clearly the collection is getting changes as it fires the events and I can see it when I log the collection?
After reading your comment:
No sorry on collection change I try to run render() which should empty
the container, and add all the models...but it seems to render the old
collection again.
You're getting this problem because you are overriding the success handler for the fetch call. That success callback is triggered before the models are placed in the collection. You need to listen to the sync event if you want render after the collection has been synchronized with the server (models are updated after fetch).
Update initialize to:
initialize: function() {
this.listenTo(this.collection, 'change', this.change);
this.listenTo(this.collection, 'sync', this.render);
this.collection.fetch();
},

BackboneJS Uncaught Error: A "url" property or function must be specified

I am getting this error . I am able to preform read, and remove functions using BackboneJs , but i am having error when i execute the add method any help will be appreciated.
JSfiddel path is http://jsfiddle.net/2wjdcgky/
BackboneJS Uncaught Error: A "url" property or function must be specified
$(function() {
Model
var modelContact = Backbone.Model.extend({
defaults: function() {
return {
Id: 0,
Name: "",
Address: ""
};
},
idAttribute: "Id"
});
ModelCollection
var contactCollection = Backbone.Collection.extend({
model: modelContact,
url: function() {
return 'api/Contact';
},
add: function(model) {
this.sync("create", model); // Error On create
},
remove: function(model) {
this.sync("delete", model); //Runs Fine
}
});
var contacts = new contactCollection;
View
var contactView = Backbone.View.extend({
tagName: "tr",
events: {
"click a.destroy": "clear"
},
template: _.template($("#newContacttemplate").html()),
initialize: function() {
this.model.on("change", this.render, this);
this.model.on('destroy', this.remove, this);
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
},
clear: function(e) {
contacts.remove(this.model); // runs fine
}
});
Main View
var main = Backbone.View.extend({
el: $("#contactApp"),
events: {
"click #btnsave": "CreateNewContact"
},
initialize: function() {
this.Nameinput = this.$("#contactname");
this.Addressinput = this.$("#contactaddress");
contacts.on("add", this.AddContact, this);
contacts.on("reset", this.AddContacts, this);
contacts.fetch();
},
AddContact: function (contact) {
console.log("AddContact");
var view = new contactView({ model: contact });
this.$("#tblcontact tbody").append(view.render().el);
},
AddContacts: function () {
console.log("AddContacts");
contacts.each(this.AddContact);
},
CreateNewContact: function (e) {
console.log(e);
//Generate an error "BackboneJS Uncaught Error: A "url" property or function must be specified"
contacts.add({ Name: this.Nameinput.val(), Address: this.Addressinput.val() });
}
});
var m = new main;
});
Your JSFiddle was missing Backbone references and all.
Working update: http://jsfiddle.net/apt7hchL/2/
Much simpler code (no need to define those add and remove methods on the collection!). Also more common Javascript coding style conventions.
Please note I had to manually generate an "Id" attribute to allow creating more than one contact. As you are making Id = 0 by default, second model with same is not added, as Backbone sees a model with id=0 is already in the collection.
When you want to save, call the model.save() method. Don't call sync manually, you'll normally don't need to!
For the model to be saved to the database before being added to the collection, use:
createNewContact: function (e) {
e.preventDefault();
var self = this;
var newContact = new ContactModel({
Name: this.$("#name").val(),
Address: this.$("#address").val()
});
newContact.save({ success: function(model){
self.collection.add(model);
});
//clear form
this.$("#name").val("");
this.$("#address").val("");
}
Sync method tries to sync to a server setup to handle it, with CRUD abilities. If thats not what you're looking for, and you just want to display this information on the client side, instead of using sync, you should use Collection.add(model) and Collection.remove(model)

How do i stop a single model adding his id to url in Backbone?

i have a problem with backbone.js. I'm creating a frontend for an existing api, for me unaccessable. The problem is that when I try to add a new model to a collection, i can see in my firebug that every time backbone tries to create the model it appends the attribute name to the url.
Example:
default url = /api/database
when i perform a GET = /api/database
when i perform a GET/POST with object {"name": "test"} =
/api/database/test is the result
Anyone knows how to avoid that behaviour?
Greetings Kern
My View:
window.databaseView = Backbone.View.extend({
el: '#content',
template: new EJS({url: 'js/templates/databaseView.ejs'}),
initialize: function() {
var self = this;
this.collection.fetch({
success: function() {
console.log(self.collection);
var test = self.collection.get("_system");
console.log(test);
self.collection.get("_system").destroy();
self.collection.create({name: "test"});
}
});
},
render: function(){
$(this.el).html(this.template.render({}));
return this;
}
});
Model:
window.Database = Backbone.Model.extend({
initialize: function () {
'use strict';
},
idAttribute: "name",
defaults: {
}
});
Collection:
window.ArangoDatabase = Backbone.Collection.extend({
model: window.Database,
url: function() {
return '../../_api/database/';
},
parse: function(response) {
return _.map(response.result, function(v) {
return {name:v};
});
},
initialize: function() {
this.fetch();
},
getDatabases: function() {
this.fetch();
return this.models;
},
dropDatabase: function() {
},
createDatabse: function() {
}
});
By default, Backbone create models URLs this way: {collection url}/{model id}.
It consider the collection URL to be a base URL in a RESTful way.
Here you only want to set the Model url property to the URL you whish to call. That'll overwrite the default behavior. http://backbonejs.org/#Model-url

Categories

Resources