backbone.js: trouble persisting simple Model to localStorage - backbone.js

I'm getting started with backbone, I used this localStorage adapter.
Here's my test code:
cartStatus = Backbone.Model.extend({
localStorage: new Store("cartStatus"),
currentClientId: ""
});
var myStatus = new cartStatus;
$(function () {
myStatus.fetch();
alert("loaded" + myStatus.get("currentClientId"));
myStatus.set({ "currentClientId": "abc" });
myStatus.save();
});
If I load the page multiple times it says loaded: undefined every time.
however, after my second load I would expect loaded: abc every time.
when I inspect my localstorage after 2 loads I see this:
cartStatus-d2a7b64d-2f15-a741-9a8c-e254b4a13682 {"0":{"currentClientId":"abc","id":"dd5e0e47-9356-ea30-2de3-75a041848b88"},"currentClientId":"abc","id":"d2a7b64d-2f15-a741-9a8c-e254b4a13682"}
cartStatus dd5e0e47-9356-ea30-2de3-75a041848b88,d2a7b64d-2f15-a741-9a8c-e254b4a13682
cartStatus-dd5e0e47-9356-ea30-2de3-75a041848b88 {"currentClientId":"abc","id":"dd5e0e47-9356-ea30-2de3-75a041848b88"}
Does anyone know what's going wrong?
Edit
I've created a jsfiddle demonstrating this (run it twice) http://jsfiddle.net/Myster/fE9eQ/3/

#tkone, you are suggesting the correct answer I think. The fetch is async, so unless you use jquery deferreds or do the model set/save in the success callback, then this will be the behavior.
cartStatus = Backbone.Model.extend({
localStorage: new Store("cartStatus"),
currentClientId: ""
});
var myStatus = new cartStatus();
$(function () {
debugger;
myStatus.fetch({
success: function(x) {
document.write('myStatus.fetch - success !<br />');
myStatus.set("currentClientId", "abc" );
myStatus.save();
}
});
});

Related

Connection between model and collection in backbone and parse.com

i'm trying to connect model and collection using parse.com but i'm confused. I'm tring to fetch by collection using backbone and javascript api parse.com but compare this error:POST https://api.parse.com/1/classes 404 (Not Found).
Model:
var Person = Backbone.Model.extend({
defaults:{
},
initialize:function(){
console.log("inperson");
this.validate();
this.send();
},
validate:function(){
console.log("validate");
},
send:function(){
var user = new Parse.User();
user.set("username", this.get("username"));
user.set("password", this.get("password"));
user.set("email", this.get("email"));
user.signUp(null, {
success: function(user) {
// Hooray! Let them use the app now.
},
error: function(user, error) {
// Show the error message somewhere and let the user try again.
alert("Error: " + error.code + " " + error.message);
}
});
}
});
return Person;
});
Collection:
var Usercollection = Parse.Collection.extend({
model:Person,
initialize:function(){
}
});
return Usercollection;
});
and finally the view that call the colletion and fetch:
var HomeView = Backbone.View.extend({
template: Handlebars.compile(template),
events: {
},
initialize: function() {
console.log("inhomeview");
var amici = new Usercollection();
amici.fetch({
success: function(collection) {
amici.each(function(object) {
console.warn(object);
});
},
error: function(amici, error) {
// The collection could not be retrieved.
}
});
},
render: function() {
}
});
return HomeView;
});
Cant you just swap the backbone collection and model to Parse's ones? (You only used the Parse type of the collection, not the model!)
Try switch that Backbone model to a Parse.Object .
Step by step below:
First of all Lets create a new app on Parse.com, mine is called FunkyAppartments.
Insert the script tag for loading Parse javascript lib into index.html or whathever:
<script src="http://www.parsecdn.com/js/parse-1.5.0.min.js"></script>
Switch the backbone model and collection to use parse types instead (and rename the fetch method if you have extended backbones, since we do not want to overide the one of parse):
//var Appartment = Backbone.Model.extend(); Backbone wo. Parse.com
var Appartment = Parse.Object.extend("Appartment");
//var Appartments = Backbone.Collection.extend({ Backbone wo. Parse.com
var Appartments = Parse.Collection.extend({
model: Appartment,
loadAppartments: function(callback){
debugger;
this.query = new Parse.Query(Appartment);
this.fetch();
}
});
I added a debugger tag in the load appartments so that developer tools breaks in the middle of the controller, here I have access to the Appartment private type of the controller, hence i can store some data on the parse server and verify by pasting the below in the developer tools console.
var testAppartment = new Appartment();
testAppartment.save({name: "foobars"}).then(function(object) {
alert("yay! it worked");
});
Yei, the data shows up in the parse.com UI for the app we just added there. And more importantly it shows up in our frontend. That was easy!
UPDATE: PROBLEMS W BACKBONE 1.2.1, MARIONETTE 2.4.2, UNDERSCORE 1.8.3
I noticed that I actually had been using old versions of marionette, backbone and underscore.js. An initial update appeared to break the application.
After some research i found that it was the parse part that did not return objects that would successfully render. Hence I changed the collection type back to an extension of: Backbone.collection instead of Parse.collection.
I also had to override the query method, since the objects would not save on the correct id, updating an object resulted in a new object being added instead of an old one being updated.
var Apartment = Parse.Object.extend('Appartment');
var Apartments = Backbone.Collection.extend({
model: Apartment,
query: new Parse.Query(Apartment),
initialize: function(){
MyApp.vent.on('search:param', function(param){self.search(param); });
var self = this;
this.query.find({
success: function(results){
self.reset();
results.forEach(function(result){
result.attributes.id__ = result.id
var ap = new Apartment(result.attributes);
self.add(ap);
});
}
});
}
});
I added an attribute: id__ to hold the parse id (naming it just id did not work since it backbone interfered with it, making it disappear).
Finally in saving the model to parse i utilized id__ as id in the save call:
var ApartmentEditView = Backbone.Marionette.ItemView.extend({
template: "#apartment-edit-template",
className: "apartmentDetail",
events: {
"click .store": "storeEdit",
"click .close": "closeEdit"
},
storeEdit: function(){
var priceNum = Number($('#price_field').val().replace(/\s/g, ''));
this.model.set({
id: this.model.attributes.id__,
name:$('#name_field').val(),
price:priceNum,
description:$('#desc_field').val(),
url:$('#url_field').val()
});
this.model.save();
this.closeEdit();
},
closeEdit: function(){
var detailView = new ApartmentDetailView({model: this.model});
MyApp.Subletting.layout.details.show(detailView);
}
});
Now the object is updated correctly in the database.

Can't access attributes model backbone js

I am creating my "Hello world" app in backbone js. I am stuck at the very basic.
var gs = {
documentRoot: ""
}; // create namespace for our app
gs.Test = Backbone.Model.extend({
url: gs.documentRoot+'/test.php',
initialize: function(){
this.fetch();
}
});
gs.TestView = Backbone.View.extend({
render: function(){
console.log(this.model);
console.log(this.model.get('testId'));
}
});
var testM = new gs.Test();
var test = new gs.TestView({model: testM});
test.render();
Here when I log model in the console, it shows fetched attributes from the server but I can't access those attributes from test.get('attribute'). I tried logging test.attributes, it gives empty object but when I log test, it shows those attributes in attributes object.
model#fetch method has a success and error callback options that can be passed to fetch. The success callback gets called when the response from the server has come.
Right way to test the fetched attributes of a model is
test.fetch({
success: function(model){
// model here and test are same
console.log(model);
console.log(test.toJSON());
// access your attribute with name `attributeName`
console.log(test.get('attributeName'));
}
});
fetch is async method, so you have to wait some time.
The best solution in this case is promises:
test.fetch().done(function() {
console.log(test);
});
Your updated model:
initialize: function() {
// save link to promise
this.deferred = this.fetch();
}
And your render function:
render: function() {
// use promise to render view after model will be fetched
// use `bind` to save context of this view
this.model.deferred.done(_.bind(function () {
// model is fetched
// all operations goes here
console.log(this.model.get('testId')); // <- proper value
}, this));
console.log(this.model.get('testId')); // <- undefined
}
More about ajax you can read here http://api.jquery.com/jQuery.ajax
var TestModel = Backbone.Model.extend({
url : '/test.php'
});
var test = new TestModel();
// `context` context to be passed to any callback function
test.fetch({context:test}).done(function () {
// `this` is equals to `test` (`context` option)
// In case if you want to get all model data:
// the best way to get model data for read-only mode.
// this metod return a copy of the model's attributes
console.log(this.toJSON());
// you can also use `this.attributes` but this is not recommended
console.log(this.attributes());
// In case if you want to get some model data:
console.log(this.get('some_attribute'));
// If you want to get `c` from this model ({a:{b:{c:1}}}):
console.log(this.get('a').b.c);
});
For those who are stuck with the same problem, here is the solution from the library itself.
Use model's in-built 'sync' event to get the model attributes after fetch()/save() calls.
testM.on('sync',function(){
test.render();
});

Backbone Collection is empty when testing with Jasmine

I'm just getting started with Jasmine and trying to set up some tests for the first time. I have a Backbone collection. I figured I would get my collection as part of the beforeEach() method, then perform tests against it.
I have a test json object that I used while I prototyped my app, so rather than mocking an call, I'd prefer to reuse that object for testing.
Here's my code so far (and it is failing).
describe("Vehicle collection", function() {
beforeEach(function() {
this.vehicleCollection = new Incentives.VehiclesCollection();
this.vehicleCollection.url = '../../json/20121029.json';
this.vehicleCollection.fetch();
console.log(this.vehicleCollection);
});
it("should contain models", function() {
expect(this.vehicleCollection.length).toEqual(36);
console.log(this.vehicleCollection.length); // returns 0
});
});
When I console.log in the beforeEach method -- the console look like this ...
d {length: 0, models: Array[0], _byId: Object, _byCid: Object, url: "../../json/20121029.json"}
Curiously when I expand the object (small triangle) in Chrome Developer Tools -- my collection is completely populated with an Array of vehicle models, etc. But still my test fails:
Error: Expected 0 to equal 36
I'm wondering if I need to leverage the "waitsFor()" method?
UPDATE (with working code)
Thanks for the help!
#deven98602 -- you got me on the right track. Ultimately, this "waitsFor()" implementation finally worked. I hope this code helps others! Leave comments if this is a poor technique. Thanks!
describe("A Vehicle collection", function() {
it("should contain models", function() {
var result;
var vehicleCollection = new Incentives.VehiclesCollection();
vehicleCollection.url = '/json/20121029.json';
getCollection();
waitsFor(function() {
return result === true;
}, "to retrive all vehicles from json", 3000);
runs(function() {
expect(vehicleCollection.length).toEqual(36);
});
function getCollection() {
vehicleCollection.fetch({
success : function(){
result = true;
},
error : function () {
result = false;
}
});
}
});
});
Just glancing at your code, it looks to me like fetch has not yet populated the collection when you run the expectation.
You can use the return value from fetch to defer the expectation until the response is received using waitsFor and runs:
beforeEach(function() {
this.vehicleCollection = new Incentives.VehiclesCollection();
this.vehicleCollection.url = '../../json/20121029.json';
var deferred = this.vehicleCollection.fetch();
waitsFor(function() { return deferred.done() && true });
});
it("should contain models", function() {
runs(function() {
expect(this.vehicleCollection.length).toEqual(36);
});
});
I haven't actually tried this can't guarantee that it will work as-is, but the solution will look something like this. See this article for more on asynchronous testing with Jasmine.
the collection.fetch() is asyn call that accepts success and error callbacks
var result;
this.collection.fetch({success : function(){
result = true;
}})
waitsFor(function() {
return response !== undefined;
}, 'must be set to true', 1000);
runs(function() {
expect(this.vehicleCollection.length).toEqual(36);
console.log(this.vehicleCollection.length); // returns 0
});

Fetch backbone collection fail from django-tastypie

I'm using backbone-tastypie from https://github.com/PaulUithol/backbone-tastypie, and I can't fetch a collection data.
Thats my code:
var User = Backbone.Model.extend({
url: '/api/v1/user'
});
var HoraExtra = Backbone.Model.extend({
url: '/api/v1/horasextra/'
});
var HorasExtra = Backbone.Collection.extend({
url: '/api/v1/horasextra/',
model: HoraExtra
});
var Horas = new HorasExtra();
var activeUser = new User();
var HorasExtraView = Backbone.View.extend({
initialize: function() {
_.bindAll(this, "render");
},
render: function() {
var plantilla = Handlebars.compile($("#horas_extra_template").html());
var html = plantilla(Horas);
this.$el.html(html);
console.log(JSON.stringify(Horas));
}
});
var HorasExtraWidget = new HorasExtraView({el: $('#base')});
Horas.fetch({
data: {
"usuario__id": 2,
"hor_com__month": 11
}
});
HorasExtraWidget.render();
And that's the result of console.log(JSON.stringify(Horas):
[]
Many thanks
I would suggest to you that you take tastypie, backbone and build your own simple project in 1-2 days.
Then you will get to know basic issues of coupling those 2 frameworks.
Without that knowledge it is pointless you try out other people's projects and then wonder "what isn't working".
And from my personal experience, both tastypie and javascript are pretty straightforward and are easy to couple.
And I am NOT an expert.
couple of points: try explicitly fetching models with fetch, manipulate fetched model from success callback, and watch your model url's, forward slashes in them etc.
Seems your collection is empty.
See in console if there is a GET call to server.
If not, you're not actually fetching anything from the server.
I used https://github.com/amccloud/backbone-tastypie and then your code should look like this, I guess. Not tested.
var HorasCollection = Backbone.Tastypie.Collection.extend({
url: '/api/v1/horasextra/'
});
var HorasExtraView = Backbone.View.extend({
el: $('#base'),
entries: new HorasCollection(),
render: function() {
var that = this;
this.entries.fetch({
success: function(entries){
console.log("Entries", entries.models);
// var t = _.template(template, {hello: 'world'});
// that.$el.html(t);
},
error: function(model, response){
console.log("Error", response);
}
});
}
});
If you're trying to create a table for your data, try my jquery plugin :)
https://github.com/sspross/tastybackbonepie

Failing to pass models correctly from collection view?

I've been staring at this for a while and trying various tweaks, to no avail.
Why am I getting a "this.model is undefined" error at
$(function(){
window.Sentence = Backbone.Model.extend({
initialize: function() {
console.log(this.toJSON())
}
});
window.Text = Backbone.Collection.extend({
model : Sentence,
initialize: function(models, options){
this.url = options.url;
}
});
window.SentenceView = Backbone.View.extend({
initialize : function(){
_.bindAll(this, 'render');
this.template = _.template($('#sentence_template').html());
},
render : function(){
var rendered = this.template(this.model.toJSON());
$(this.el).html(rendered);
return this;
}
})
window.TextView = Backbone.View.extend({
el : $('#notebook') ,
initialize : function(){
_.bindAll(this, 'render');
},
render : function(){
this.collection.each(function(sentence){
if (sentence === undefined){
console.log('sentence was undefined');
};
var view = new SentenceView({model: sentence});
this.$('ol#sentences').append(view.render().el);
});
return this;
}
});
function Notebook(params){
this.text = new Text(
// models
{},
// params
{
url: params.url
}
);
this.start = function(){
this.text.fetch();
this.textView = new TextView({
collection: this.text
});
$('body').append(this.textView.render().el);
};
}
window.notebook = new Notebook(
{ 'url': 'js/mandarin.js' }
);
window.notebook.start();
})
There's an online version wher eyou can see the error in a console at:
http://lotsofwords.org/languages/chinese/notebook/
The whole repo is at:
https://github.com/amundo/notebook/
The offending line appears to be at:
https://github.com/amundo/notebook/blob/master/js/notebook.js#L31
I find this perplexing because as far as I can tell the iteration in TextView.render has the right _.each syntax, I just can't figure out why the Sentence models aren't showing up as they should.
var view = new SentenceView({model: sentence});
I'm pretty sure when you pass data to a backbone view constructor, the data is added to the Backbone.View.options property.
Change this line
var rendered = this.template(this.model.toJSON());
to this
var rendered = this.template(this.options.model.toJSON());
and see if it works
UPDATE:
From the doco:
When creating a new View, the options you pass are attached to the view as this.options, for future reference. There are several special options that, if passed, will be attached directly to the view: model, collection, el, id, className, and tagName
So, disregard the above advice - the model should by default be attached directly to the object
Things to check next when debugging:
confirm from within the render() method that this is actually the SentenceView object
confirm that you are not passing in an undefined sentence here:
var view = new SentenceView({model: sentence});
UPDATE2:
It looks like the collection is borked then:
this.textView = new TextView({
collection: this.text
});
To debug it further you'll need to examine it and work out what's going on. When I looked in firebug, the collection property didn't look right to me.
You could have a timing issue too. I thought the fetch was asynchronous, so you probably don't want to assign the collection to the TextView until you are sure it has completed.
Backbone surfaces underscore.js collection methods for you so you can do this. See if this works for you:
this.collection.each(function(sentence) {
// YOUR CODE HERE
});
I think the problem is on line 48 of notebook.js shown below:
render : function(){
_(this.collection).each(function(sentence){
var view = new SentenceView({model: sentence});
this.$('ol#sentences').append(view.render().el);
});
The problem is you are wrapping the collection and you don't have to. Change it to
this.collection.each(function(sentence){ ...
hope that fixes it
EDIT:
OK i'm going to take another crack at it now that you mentioned timing in one of your comments
take a look at where you are fetching and change it to this:
this.start = function(){
this.text.fetch({
success: _.bind( function() {
this.textView = new TextView({
collection: this.text
});
$('body').append(this.textView.render().el);
}, this)
);
};
I typed this manually so there may be mismatching parentheses. The key is that fetch is async.
Hope this fixes it
try using _.each
_.each(this.collection, function(sentence){
if (sentence === undefined){
console.log('sentence was undefined');
};
var view = new SentenceView({model: sentence});
this.$('ol#sentences').append(view.render().el);
},this);

Resources