"Undefined" when using StackMob.Model instead of Backbone.Model - backbone.js

I'm using Backbone.js and StackMob for a mobile app, and have run into a strange problem that I can't get my head around. According to the StackMob docs, StackMob.Model extends Backbone.Model, and thus has access to the same methods, and should work the same way. As I understand it, it's a simple swap to make.
Here's the situation:
I have a collection that's being populated by an API call to FourSquare, which is being rendered out into the view as a list. Each model in the list is rendered with a data-id attribute:
$("ul#resultsList").append("<li class='result' data-id='" + place.get('id') + "'><span class='listingName'>" + place.get("name") + "</span><span class='listingDetail'>" + place.get("location")["address"] + "</span><span class='listingDetail'>" + place.category + "</span></li>");
(The above code is what's looped through to build the list).
The list shows up fine, and everything on that end works as it should. Then, I have the following attached to the click event on each list item:
saveItem: function(e) {
window.item = $(e.currentTarget).data("id");
window.newplace = this.collection.get(window.item);
console.log("New place: "+ window.newplace); //shows the model in Backbone, undefined in StackMob
App.navigate("#add", {
trigger: true
});
},
This code basically looks at the item clicked, goes and gets the ID, then looks up that model in the collection based on the ID and stores it into window.newplace.
Then, the app navigates over to #add, which instantiates the view with window.newplace as the model:
addItem: function() {
if (!window.newplace) {
window.newplace = new Place({});
}
this.changePage(new AddItemView({model:window.newplace}), "slide");
},
Here's the problem:
This works exactly as it should when the Place model is set up as a Backbone.Model. As soon as I switch it to StackMob.Model, it shows window.newplace as undefined.
I can't imagine why switching these should make any bit of difference, but it seemingly does.
Just for the sake of full clarification, here's the model when using Stackmob.Model:
window.Place = StackMob.Model.extend({
...
});
Any ideas? If it didn't work at all, I'd chalk it up to some really stupid thing, but since it works fine extending Backbone, but not StackMob, I'm really confused.
Thanks!

You need to call StackMob.init(..) first before using StackMob.Model, StackMob.Collection and other StackMob items.
Your external script is likely being executed before you call StackMob.init(..).
Hope that helps!
Erick

Ok, I figured out a workaround for this problem.
Here's the deal. When I was calling this.collection.get(window.item), I was looking for the ID property on the Place object being clicked (since I'm passing that ID into the URL, it's easy to retrieve on click).
With Backbone, that ID was showing up as a root property of the object, at place.id. With StackMob, for whatever reason, it wasn't. It was stored in the Attributes object inside Place (as if you'd called place.set("id")).
So, when I went looking to get it by ID, StackMob was looking for the root property "id" on the object, not finding it, and returning "undefined".
By putting this.id = this.get("id") in the initialization of the model, I was able to get it to the root and successfully retrieve the model.
I'm still unsure why that ID was being set at the root with Backbone, but not StackMob, but regardless, at least there's a solution.

Related

Angular CRUD, update view when backend/database changes ($resource and REST)

I am currently making an application in angular which does this:
(On page load) Make an api call in angular controller (to symfony2 end point) to get: items.
$scope.items = ItemsService.query(function(data){
$scope.loading = false;
}, function(err){
$scope.loading = false;
});
items is an array containing many item objects.
Each item contains parameters e.g. item.param1 item.param2.
I have built it in a similar way to this tutorial:
http://www.sitepoint.com/creating-crud-app-minutes-angulars-resource/
i.e. The angular controller calls a service which calls the (symfony2) backend api endpoint.
The endpoint passes back items which is gets from a database. Items are then put into the view using ng-repeat (item in items).
This all works fine.
Now, I have a button (in the ng-repeat) which effectively causes a PUT request to be made to (another symfony2 endpoint), thus updating item.param1in the database. This also happens in the tutorial I linked to.
The problem is that (in my application and in the tutorial) I have to again make an api call which updates ALL the items, in order to see the change.
I want to just update the data in the view (immediately) for one object without having to fetch them all again.
i.e. something like:
$scope.items[4] = Items.get({id: item.id}, function(){});
Except the application array key isn't known so I cant do that.
(so something like: $scope.items.findTheOriginalItem(item) = Items.get({id: item.id}, function(){});.
Another possible solution (which seems like it may be the best?). I have looked here:
http://teropa.info/blog/2014/01/26/the-three-watch-depths-of-angularjs.html
And tried doing the equality $watch: $scope.$watch(…, …, true);. Thus using watch to see when the item sub-array is updated. This doesn't seem to update the data in the view though (even though it is updating in the database).
Any advice on the best way of doing this (and how to do it) would be great! Thanks!
Essentially the button click should use ng-click to execute a function and pass the item to that function. Example:
...ng-repeat="item in items"...
<button type="button" ng-click="updateItem(item)">Update</button
...
Then in the function you have the exact item that you want to update. If you are using $resources, it would be something like:
$scope.updateItem = function(item) { item.$update(...); };
Unless I didn't understand you

Marionette 2.0 - Uncaught ViewDestroyedError: View has already been destroyed and cannot be used.

Struggling a bit to get my head round a problem in Marionette 2.
In various parts of my app I have the following trigger so when the currency is changed the views are rendered.
App.on("currency:change", function() {
cartView.render();
totalsView.render();
});
This works fine on the individual pages if loaded by refreshing the page, but on navigating between 2 pages with this I get the error.
Uncaught ViewDestroyedError: View has already been destroyed and cannot be used.
On investigation it looks it's trying to render a view from the previous page.
Each page is defined by a module which is started and stoped as loaded or closed, and the views seem to being destroyed, so a bit confused as to how this is happening. My first assumption is the views from the previous page haven't been fully removed, but I thought this was something Marionette did automatically.
I looks like you are using references to views that have been already removed. My first assumption is that you should recreate views:
App.on("currency:change", function() {
cartView = new CartView();
totalView = new TotalView();
cartRegion.show(cartView)
totalRegion.show(totalView);
});
I'm not sure how your application works but I can guess that you have something like currencyCollection or currencyModel. If yes your views should listen to changes on that model and refresh themselves and then "currency:change" event will be not needed anymore.

Binding to property of model attribute in Backbone.js

Disclaimer: I'm new to Backbone.js (coming from AngularJS), so I may have an inaccurate mental model of how this is supposed to work.
I have an object, characterNodes, which I'm making an attribute on my model. characterNodes looks something like this:
var characterNodes = {
character_1: {
stories: [// list of Stories]
},
character_2: {
stories: [// list of Stories]
}
...
}
My Backbone Model looks something like this:
var StoryGraph = joint.dia.Graph.extend({
initialize: function() {
// Call parent constructor
StoryGraph.__super__.initialize.apply(this, []);
this.set('characterNodes', characterNodes);
this.on('change:characterNodes', function() {
alert('test');
});
}
});
Each Story has a property "isUnlocked" which is changed elsewhere in the application. I want to fire an event (ie. that is, the alert 'test' should pop up) whenever this property is changed. With the code as it is above, the event never seems to fire.
I can't get a clear understanding from the Backbone docs whether this is supposed to work - does on('change:characterNodes') fire whenever any property (or sub-property, or sub-sub-property, etc) of characterNodes changes? Or only when the pointer to the object changes, that is, when it's replaced with another object? Or am I doing something else wrong? Thanks!
Backbone doesn't do any magic, basically, the change event is fired only if you set the "characterNodes" to a new object. If you're changing a nested property of that object, Backbone doesn't know it happened. You have three options: a) Change the whole object (e.g. by creating a copy), b) fire the change event manually (m.trigger("change:characterNodes")) whenever you change a nested property, c) Do not use nested objects for this. Have "character1_Stories" as a top level property.
Options c) is preferable. Try to keep properties in your models flat. Option a) is also fine but it has the disadvantage of having to copy the object. Option b) is not recommended. This is because Backbone keeps track of the previous value of the model properties (m.previous("characterNodes")). If you change a nested property, the previous value will have the same reference to the same object as the new value, therefore, it won't reflect its previous state.
Try to call a function instead define the function, and pass the third argument to keep the context call.
Something like this:
this.on('change:characterNodes', this.AlertSomething, this);
Hope it helps.

Render an object's children from Firebase in AngularJS

I'm following the "denormalized" data pattern that #Anant described nicely on the Firebase blog. To query and render child objects he suggests listening for child_added events on the parent to get the child ids, then querying those children individually with .once('value', fnc) to render them. From the blog post (he uses a posted Link as the parent and Comments as children -- think Reddit or Hacker News):
var commentsRef =
new Firebase("https://awesome.firebaseio-demo.com/comments");
var linkRef =
new Firebase("https://awesome.firebaseio-demo.com/links");
var linkCommentsRef = linkRef.child(LINK_ID);
linkCommentsRef.on("child_added", function(snap) {
commentsRef.child(snap.name()).once("value", function() {
// Render the comment on the link page.
));
});
[sic]
I'm trying to reconcile this with my AngularJS view, since adding the result of .once into a $scope array and using ngRepeat will leave you with a static list (the children won't update in realtime if they are changed or removed by another client).
Put another way, I'd like to have something like an angularFireCollection of child objects that will add, remove and update dynamically.
I think what you're looking for is a combination of AngularFire and FirebaseIndex. I haven't checked that combining these still works, but Kato reports it has in the past.
Ignoring that for a second, though, I don't see anything wrong with your proposed plan:
push the result of .once into an array, add it to the $scope and use ngRepeat
In this case, Kato's FirebaseIndex will probably still be useful, so definitely check that out.

How do I iterate a Backbone Firebase Collection?

I currently started using Firebase as my backend solution for persistance.
I found it easy to create new objects and persist it to Firebase with Backfire with a simple
collection.add(obj)
The issue comes when I try to get a Firebase collection from the server.
For example when i try
console.log(collection);
I get this output:
=> {length: 0, models: Array[0], _byId: Object, _events: Object, constructor: function…}
Which result in an empty models array
console.log(collection.models);
=> []
After some searching, I figured out that Backbone Collections aren't yet loaded at the time I try to log it to the console (see this previous question).
I also tried using
Backbone.Collection.extend({
model: Todo,
firebase: new Backbone.Firebase("https://<your-namespace>.firebaseio.com")
});
To explicitly call fetch from the server and use success callback with no success either.
My question is: How can I get the Firebase Collection and populate the DOM from it?
When you call Backbone.Firebase.Collection.add, it does not get added to the collection synchronously. Rather, it sends the request to Firebase and then waits for the return event. See the code here
Thus, if you immediately try to read the collection, you will see zero elements. However, if you try something like this:
collection.once('add', function() { console.log(collection.length); });
You'll see the element you have added.
Remember that we're dealing with real-time data here, so when you want to populate the DOM, you shouldn't think in terms of a single transaction, but instead rely on events and take everything as you get it (in real time).
So to populate the DOM, do something like this in your view:
Backbone.View.extend({
render: function() {
this.listenTo(this.collection, 'add', this.rowAdded);
},
rowAdded: function(m) {
/* use `m` here to create your new DOM element */
}
});
Additionally, you'll probably want to check out a nice binding library like ModelBinder to help you deal with the constantly changing DOM, so you don't have to re-invent any wheels.
It seems you have to use a Backbone.Firebase.Collection and not a Backbone.Collection which will tell you that your calls to fetch or sync are silently ignored.
Also, Backbone.Firebase's got a read and a readall methods that should get you started. It seems Backbone.Firebase.Collection doesn't inherit this method, but I'm not sure though.
Edit:
As Kato stated in his comment, it seems you don't have to do anything. Just use Backbone.Backfire.Collection and Backbone.Backfire.Model.

Resources