Ember.js adds elements to collection "magically" - javascript

Question related somewhat to: Ember.js: retrieve random element from a collection
I've two routes: randomThing route and things route.
The former displays a... random thing from an API (GET /things/random) (there is a button to "Get another random thing"), the latter: displays all things: (GET /things).
The problem is that EVERY TIME when I click on Get another random thing and new thing is displayed and I go to recipes route this newly displayed random thing is added to the collection...
Action to get random thing performs a find("random") as suggested in related question and sets this.content to this value.
What is wrong here?
EDIT:
I'm using ember-data and my route is like this:
App.ThingsRoute = Ember.Route.extend({
model: function() {
return App.Thing.find();
}
});

The problem is that EVERY TIME when I click on Get another random thing and new thing is displayed and I go to recipes route this newly displayed random thing is added to the collection...
This is expected behavior. App.Thing.find() does not simply query the api and return results. Instead find() returns an array containing of all Things ember knows about. It includes objects returned by past calls to find(), objects created client-side via App.Thing.createRecord(), and of course individual objects queried via App.Thing.find('random'). After returning this array, find() and kicks off another API call and if that returns additional records they are pushed onto the array.
What is wrong here?
It does not sound like anything is wrong per-se. If you want to prevent random things from showing up in the ThingsRoute, you'll need to change that route's model to be a filter instead of just returning every Thing. For example:
App.ThingsRoute = Ember.Route.extend({
model: function() {
//Kick off query to fetch records from the server (async)
App.Thing.find();
//Return only non-random posts by applying a client-side filter to the posts array
return App.Thing.filter(function(hash) {
if (!hash.get('name').match(/random/)) { return true; }
});
}
});
See this jsbin for a working example
To learn more about filters I recommend reading the ember-data store-model-filter integration test

Related

get more data after some given id in firebase

I'm new using firebase and I'd like to make an infinite scroll pagination to my posts.
Suppose I have this post IDS:
-M5HMGs3EnBv6O2NSxG
-M5HMGsA3YyW_NJ3fSV
-M5HMGsdfLOOEPPBR_s
-M5HMGsiH9HIZw9DNaz
-M5HMHFYoUX8kNolLrP
-M5HMHFZvDrV27S2hSt
-M5HMHFnrQ_l4mVQ0rX
-M5HMHFoaV8wbexwPCd
-M5HMJQaaFGxF450lJB
-M5HMJQe319R19Cvak6 // THE LAST ONE IN FIRST PAGINATION
-M5HMJQh2gWuah7GSht
-M5HMJQlrcfTTF3fbbI
-M5HMJQo6QT1HwWP2lz
-M5HMJQrIUiZyzhiqK3
-M5HMJQudSkFdvs42D1
-M5HMJRCmbFbxKp1NgA
-M5HMJRFwfo7yN8Is3-
-M5HMJRIsKT3YmoukQ0
-M5HMJRKDTq7XuqtshT
-M5HMJRNRabXbYQUMi6
-M5HMJRS1t9UBzH_3Jh
-M5HMJRXEv6BaPeQPMn
-M5HMJR_bRdxCBy-uma
-M5HMJRdOPBA-SMoMjB
So, first I get the first 10 posts till the id M5HMJQe319R19Cvak6 using this:
var starCountRef = firebase.database().ref('/posts/').limitToFirst(11);
it is ok. When I go to the last post my reactjs will call this function again sending the last loaded post ID (the M5HMJQe319R19Cvak6) in variable pageNumber.
What I'd like to do is get posts after this last id given to continue the pagination, like so:
-M5HMJQh2gWuah7GSht
-M5HMJQlrcfTTF3fbbI
-M5HMJQo6QT1HwWP2lz
-M5HMJQrIUiZyzhiqK3
-M5HMJQudSkFdvs42D1
...
I tried to use startAt(ID) but no result has shown:
var starCountRef = firebase.database().ref('/posts/').limitToFirst(11).startAt(pageNumber);
any ideas how can I get posts after an id in firebase?
Firebase Realtime Database queries consists of two steps:
You call one of the orderBy methods to order the child nodes on a specific value.
You then call one of the filtering methods (startAt, endAt, equalTo) to determine where to start and stop returning data.
Your code does that second step, but fails to do the first step. This means that you're using Firebase's default sort-order, which is by each node's priority. Priorities are a left-over from the days then this API didn't have any better sorting options. These days, you'll always want to call orderBy... before filtering.
With that knowledge, your query for getting the second page of results should look something like:
firebase.database()
.ref('/posts/')
.orderByKey()
.startAt("-M5HMJQe319R19Cvak6")
.limitToFirst(11)
I highly recommend calling the "-M5HMJQe319R19Cvak6" value something else than pagenumber as that variable name makes it seem as if Firebase queries are offset-based, which they aren't.

Ember filtered model entries count

I'm trying to get the number of results of the Ember Data Store filter. E.g
var users = this.store.filter('relevantUser', function(user)
{
return user.get('screenName') == screenName;
});
return user.get('length');
But this always seems to return 0. What am I doing wrong?
I think it should be users.get('length');.
Things to make sure when using filter method of the store.
First argument is the model type. Assuming you have a model named App.RelevantUser then your query is fine, else if the model is App.User then you should be using 'user'.
The var users is actually a DS.PromiseArray instance and not an array actually. Try doing this
this.store.filter('relevantUser',function(user){return user.get('screenName')==screenName}).then(function(relevantUsers){console.log(relevantUsers.get('length'))})
As store.filter queries the server too we need to wait for the promise to resolve before accessing the results. Otherwise they would be always 0.
Incase you are using Chrome. Open up Network Tab in Dev Tools and check the network request going to the server when you run the filter query.

Collection sorting with comparator only works after fetch is complete?

I have a simple collection of messages that I want to reverse sort on time (newest on top), using comparator:
...
this.comparator = function(message) {
var time = new Date(message.get("time")).getTime();
return time;
}
...
In my view, I use fetch and add event:
messages = new MessageCollection();
messages.fetch({update: true});
messages.on("add", this.appendMessage);
...
appendMessage: function(message) {
var messageView = new MessageView({
model: message
});
this.$el.prepend(messageView.render().el);
}
Sadly, the messages are not rendered in the order I am looking for, but in the original order they were in coming from the server.
Now, after some testing I found out that when I add all the messages at once (using reset), the order is as I expected.
messages.fetch();
messages.on("reset", this.appendCollection);
...
appendCollection: function(messages) {
messages.each(function(message) {
this.appendMessage(message);
}, this);
}
Even though I can understand this process since a collection probably can only figure out how it's supposed to be sorted after all models are added, this (the on("add") configuration) used to work in Backbone 0.9.2.
Am I missing something? Did the comparator method change, or the event model in regard to add? Or am I going at it the wrong way? Thanks!
You call appendMessage method when you add a model in collection. the appendMessage is being called in the order of adding models and not the actual order in the collection.
In the "add" case, the model is inserted in the right position in the collection, as it should be by "comparator" documentation). But then you are doing
this.$el.prepend(messageView.render().el);
which will put the html from the MessageView rendering at the top of the $el (which I assume is the CollectionView container).
The best way to also keep the Html respecting the sorted order would be to re-render the collection view, or scroll the collection view children and insert the added messageView at the right place (a bit more difficult to do).

Use $.get() or this.model.save() (Backbone.js)

I have a list of images each with a 'Like' button. When the 'Like' button is clicked, an AJAX request (containing the item_id and user_id) will be sent to the serverside to record the Like (by adding a new row in the table likes with values for item_id and user_id).
The model Photo is used for the images displayed on the page. If I understand correctly, this.model.save() is used if I want to update/add a new Photo, so it is not suitable for recording 'Likes'. Therefore, I have to use something like $.get() or $.post(). Is this the conventional way?
Or do I create a new model called Like as shown below, which seems to make it messier to have a View and template just for a Like button.
Like = Backbone.Model.extend({
url: 'likes'
});
LikeView = Backbone.View.extend({
template: _.template( $('#tpl-like').html() ),
events: {
'click .btn_like': 'like'
},
like: function() {
this.model.save({
user_id: 1234,
post_id: 10000
})
}
});
In similar cases to this I've used the $.get method rather than create a new model, obviously this will depend on your application, but here are my reasons.
This case appears to have the following characteristics:
Like is a relationship between a person and a photo,
you seem to have a server side resource that accepts the photo and user ids to create this relationship already,
you probably have no other information attached to that relationship, and
you probably don't have significant view logic to go with the like itself
This is better handled by adding another attribute to your Photo object, that contains the number of likes. Then use $.get to create the like, and a 200 response will simply update the photo object to up it's count (and hence the view). Then the server side just needs to include the like count as part of it when it returns.
I'm assuming here that once a like is made you won't be updating it. If you do need to update or delete it, I might still keep using the $.get. You can add a likes array to your photo object where each element is the id of the like resource. The view will display the length of the array as the count, and if you need to delete the like, you have access to the id and you can use $.post. Just make sure you don't use .push to add values to your array since that'll bypass backbone's set method and you won't get your event callbacks. You need to clone the array, then push, and then set it when you make changes.

backbone.js cache collections and refresh

I have a collection that can potentially contain thousands of models. I have a view that displays a table with 50 rows for each page.
Now I want to be able to cache my data so that when a user loads page 1 of the table and then clicks page 2, the data for page 1 (rows #01-50) will be cached so that when the user clicks page 1 again, backbone won't have to fetch it again.
Also, I want my collection to be able to refresh updated data from the server without performing a RESET, since RESET will delete all the models in a collection, including references of existing model that may exist in my app. Is it possible to fetch data from the server and only update or add new models if necessary by comparing the existing data and the new arriving data?
In my app, I addressed the reset question by adding a new method called fetchNew:
app.Collection = Backbone.Collection.extend({
// fetch list without overwriting existing objects (copied from fetch())
fetchNew: function(options) {
options = options || {};
var collection = this,
success = options.success;
options.success = function(resp, status, xhr) {
_(collection.parse(resp, xhr)).each(function(item) {
// added this conditional block
if (!collection.get(item.id)) {
collection.add(item, {silent:true});
}
});
if (!options.silent) {
collection.trigger('reset', collection, options);
}
if (success) success(collection, resp);
};
return (this.sync || Backbone.sync).call(this, 'read', this, options);
}
});
This is pretty much identical to the standard fetch() method, except for the conditional statement checking for item existence, and using add() by default, rather than reset. Unlike simply passing {add: true} in the options argument, it allows you to retrieve sets of models that may overlap with what you already have loaded - using {add: true} will throw an error if you try to add the same model twice.
This should solve your caching problem, assuming your collection is set up so that you can pass some kind of page parameter in options to tell the server what page of options to send back. You'll probably want to add some sort of data structure within your collection to track which pages you've loaded, to avoid doing unnecessary requests, e.g.:
app.BigCollection = app.Collection.extend({
initialize: function() {
this.loadedPages = {};
},
loadPage: function(pageNumber) {
if (!this.loadedPages[pageNumber]) {
this.fetchNew({
page: pageNumber,
success: function(collection) {
collection.loadedPages[pageNumber] = true;
}
})
}
}
});
Backbone.Collection.fetch has an option {add:true} which will add models into a collection instead of replacing the contents.
myCollection.fetch({add:true})
So, in your first scenario, the items from page2 will get added to the collection.
As far as your 2nd scenario, there's currently no built in way to do that.
According to Jeremy that's something you're supposed to do in your App, and not part of Backbone.
Backbone seems to have a number of issues when being used for collaborative apps where another user might be updating models which you have client side. I get the feeling that Jeremy seems to focus on single-user applications, and the above ticket discussion exemplifies that.
In your case, the simplest way to handle your second scenario is to iterate over your collection and call fetch() on each model. But, that's not very good for performance.
For a better way to do it, I think you're going to have to override collection._add, and go down the line dalyons did on this pull request.
I managed to get update in Backbone 0.9.9 core. Check it out as it's exactly what you need http://backbonejs.org/#Collection-update.

Categories