I am trying to develop refresh feature on social feed, Evry minute i am checking if there are some new tweets.
I am fetching a backbone collection (col1) and after 1 min i am fetching the same collection (col2) to compare and extract new tweets.
#tweets = App.request "tweets:entities"
that = #
setInterval (->
that.freshTweetsCol = App.request "tweets:entities"
App.execute "when:fetched", that.freshTweetsCol, =>
console.log(that.freshTweetsCol == that.tweets)
freshTweets = _(that.freshTweetsCol.models).without(that.tweets.models)
freshTweets2 = _.difference(that.freshTweetsCol.toJSON(),that.tweets.toJSON())
console.log(freshTweets)
console.log(freshTweets2)
return
), 10000
#tweets gives me collection with 20 models
that.freshTweetsCol gives me collection with the same 20 models as
in #tweets, if there is no new tweets.
console.log(that.freshTweetsCol == that.tweets) gives false, even if models are the same
console.log(freshTweets) gives me array of 20 models, each called 'Tweet'
console.log(freshTweets2) gives me array of 20 objects
how can i check only for new tweets? why I cannot use _.difference method to compare the collections?
console.log(that.freshTweetsCol == that.tweets) gives false, even if models are the same
yes this happens check this
Use underscore to compare to objects it does recursive comparison
_.isEqual(obj1, obj2);
difference returns array of objects not model's so you should instantiate new Backbone collection like
frshTwe = _.difference(that.freshTweetsCol.toJSON(),that.tweets.toJSON())
var freshTweets = new TweetsCollection(freshTweets2 );
or you can reset the collection (check this)
Related
Background
I have an angular-meteor app and a collection of business objects in mongodb, e.g.:
{ startDate: new Date(2015, 1, 1), duration: 65, value: 36 }
I want to render this data in different views. One of the views is a graph, another is a list of entries.
The list of entries is easy. Just bind the collection to the model:
vm.entries = $scope.meteorCollection(MyData, false);
In the view I would do:
<div ng-repeat="entry in vm.entries">{{entry.startDate}} - ...</div>
But now for the graph. I want to transform each element into a { x, y } object and give the view that, e.g.:
vm.graphPoints = transformData(getDataHere());
The problem is that the data is not fetched here, in angular-meteor is looks like it is fetched when calling the entry in vm.entries in the view. The kicker is that the transformData method, needs to loop through the data and for each item index into other items to calculate the resulting x, y. Hence I cannot use a simple forEach loop (without having access to the other items in some way).
Question
So how can i fetch the data in the controller - transform it - and still have one-way binding (observing) on new data added to the database?
Thanks
Update
The following code works, but I think there could be performance problems with fetching the whole collection each time there is a change.
$scope.$meteorSubscribe("myData").then(function() {
$scope.$meteorAutorun(function() {
var rawData = MyData.find().fetch();
vm.model.myData = transformData(rawData);
});
});
EDIT:
This is the current solution:
$scope.collectionData = $scope.meteorCollection(MyData, false);
$meteor.autorun($scope, function() {
vm.graphPoints = transformData($scope.collectionData.fetch());
});
There is some missing information. do you wan't to have some kind of model object in the client? if that is correct I think you have to do something like this:
$meteor.autorun($scope, function() {
vm.graphPoints = transformData($scope.meteorCollection(MyData, false));
});
How about using the Collection Helper Meteor package to add the function:
https://github.com/dburles/meteor-collection-helpers/
?
I want to grab the length of a collection to use in a random number generator. I want a view that shows one model of the collection, randomly generated. I'm using coffeescript, btw
So far I've tried stuff like
#collection.fetch
data:
id: Math.floor((Math.random()*#length)+1)
which won't work because the length isn't there until after it's fetched... I've tried a couple of other methods, such as grabbing after fetching, but length is always zero.
Anyone give me an idea of how to do this?
edit: javascript for those who can't read coffee
this.collection.fetch({
data: {
'id': Math.floor((Math.random() * length) + 1)
}
});
I had same task in the past. I used underscore _.sample method.
Please try _.sample(collection) it will return random model from collection or even better _.sample(collection, 4) for 4 random models.
According to the Backbone manual :
Backbone.Collection
Collections are ordered sets of models
So what you need in your application is actually a random model from your server database. According to your API, you need to get the count of your records in your server and then get a random model of one of the records. If you are the developer of your Serverside API there is a way to do that with one connection, otherwise you can do something like this :
class randomModel extends Backbone.Model
// Assuming 'GET' /api/model/100 will get record No. 100
urlRoot: '/api/model'
// ... in your document ready
$ () ->
model = null
// Assuming 'GET' /api/count, will return JSON string with your records count
$.getJSON '/api/count', (response) =>
model = new randomModel id: (Math.random()*response.count)+1
model.fetch()
console.log model
Pretty much that's what I would use in your case. Another method is to populate the whole collection and get the random model after it is populated ( you save one request ), by doing :
collection.fetch() // get's all models
collection.get (Math.random()*collection.length)+1
Using backbone.js and trying to get data from postsList array I got this in chrome console.log
d {length: 0, models: Array[0], _byId: Object, _byCid: Object, constructor: function…}
_byCid: Object
_byId: Object
length: 9
models: Array[9]
__proto__: f
When I'm trying to use console.log(postsList.length) I get 0, but there are 9 models inside. I don't know how to get number of them.
Yes, this is strange behaviour :)
Chrome displays object preview immediately after you have used console.log.
When you entered console.log(collection) it was empty (probably you have fetched model from the server). But at the moment when you expand the object in console Chrome displays actual object params at current moment.
Try this in console:
var object = {key1:{prop:true}};
console.log(object)
object.key2 = true;
console.log(object)
To get collection length use this way:
collection.fetch({context:collection}).done(function() {
console.log(this.length)
});
EDIT
No-no-no :)
Use this.length instead of this.lenght.
collection.fetch({context:collection}).done(function() {
// equals to this.length
console.log(this.size());
// get all models from collection (array of Backbone.Models)
console.log(this.models);
// get all models from collection (like simple array of objects)
console.log(this.toJSON());
// get model with index 1
console.log(this.at(1));
// get model data with index 1
console.log(this.at(1).toJSON());
// get model with id `some-id`
console.log(this.get('some-id'));
// get models data where property `id_str` equals to `292724698935070722`
console.log(this.where({id_str:'292724698935070722'}));
});
For more information look here:
http://backbonejs.org/#Collection
I think Vitaliys answer is a little bit dangerous, because the passed option {context: collection} is:
a) not mentioned in backbones documentation
b) handled deep down in jQuery when fetch triggers some Ajax-call.
Instead the length of the fetched collection can be easily checked in the success- and error- callback of fetch, e.g.:
someCollection.fetch({
success: function(collection) { // the fetched collection!
if (collection.length) {
// not empty
} else {
// empty
}
}
});
See http://backbonejs.org/#Collection-fetch
I'm writing a custom web app (an administrative utility) that queries a SQL Server database table, and I am giving users the ability to apply their own (limited) custom sorts and filters on the returned information. The sending page allows them to choose up to 3 sort criteria (e.g. Sort 1 then Sort 2 then Sort 3) using drop-down lists on an HTML form. They must also indicate a single letter of the alphabet (through the URL), and the application must return a list of data where field "Sort1" starts with the letter (the filtering is ALWAYS by the Sort1 field).
So for example, they could choose to return a list of all customers whose City starts with the letter "R", sorted by City then State then Name. Or, they could return all customers whose Name starts with "F", sorted by Name then Address then Customer ID.
I totally understand how to do this with fixed (known) fields/properties;
var _data = _data.Where(d => d.Name.StartsWith(letter)).OrderBy(p => p.Name).ThenBy(p => p.Address).ThenBy(p => p.CustomerID);
etc. But in my case, the table properties (fields) to be sorted/filtered are not explicitly known; they are only available to my app as strings. What I'd like to be able to do is...
var _data = _data.Where(d => d.["Sort1"].StartsWith(letter)).OrderBy(p => p.["Sort1"]).ThenBy(p => p.["Sort2"]).ThenBy(p => p.["Sort3"]);
where Sort1, Sort2 and Sort3 are posted form field values, but I know this doesn't work. How can I implement this? I'm using ASP.Net MVC 3 in C#, with LINQ using the Entity Framework (EDM).
What about this approach:
Func<Record, object> sort1 = r => GetProperty(r, "City");
Func<Record, object> sort2 = r => GetProperty(r, "State");
Func<Record, object> sort3 = r => GetProperty(r, "Address");
Func<Record, bool> filterPredicate = p => GetProperty(p, "City").ToString().StartsWith("A");
IEnumerable<Record> enumerable = list.Where(filterPredicate)
.OrderBy(sort1)
.ThenBy(sort2).
.ThenBy(sort3);
Where GetProperty is implemented as:
static object GetProperty(Record record, string paramName)
{
if (paramName == "City") return record.City;
if (paramName == "State") return record.State;
if (paramName == "Address") return record.Address;
if (paramName == "CustomerId") return record.CustomerId;
throw new InvalidEnumArgumentException();
}
You should make your query dynamic, and when make dynamic query you should use (Exec ) command on SQL to execute your dynamic query.
Thats it :D
But you should notice that execute command is not that good for performance issues.
TL;DR: If I'm polling the entire collection of models from the server, how can I merge the changed attributes into each model and add/remove added/removed models from the collection?
In my backbone app, I am polling for the entire collection of models. I have a Backbone.Collection that I am basically calling reset on each time I get the array of models, so:
myCollection.reset(server_response);
The only problem with this is that it gets rid of the old models, kind of eliminating the benefit of events on a model. This is reset's purpose of course, but what I want to do is only modify the changed attributes of the models, and remove models not in the response, and add models that were in the response but not the collection.
Essentially I want a sort of merge of the data.
For models that are in the response and in the collection already, I believe I can just do model.set(attributes) and it takes care of seting only the ones that actually changed, triggering the change events in the process. This is great.
But how do I handle the cases where the models were in the response but not in the collection already, and vice versa, not in the response but in the collection?
My Proposed Solution
I don't know if backbone already has a way of doing this, and I may be overcomplicating which is why I'm asking, but I was thinking then of creating a method on my collection which gets passed the server_response.
It would get all of the id attributes of the server_response, and all of the id attributes of the models already in the collection.
The difference of the id's in response - collection would = added models, and vice versa would be removed models. Add and remove those models respectively from the collection.
The intersection of both sets of id's would be the modified models, so iterate through these id's and simply do a collection.get(id).set(attributes).
In pseudocoffeescript:
merge: (server_response) =>
response_ids = _.pluck(server_response, 'id')
collection_ids = #pluck('id')
added = _.difference(response_ids, collection_ids)
for add in added
#add(_.find(server_response, (model) ->
return model.id == add
))
removed = _.difference(collection_ids, response_ids)
for remove in removed
#remove(#get(remove))
changed = _.intersection(response_ids, collection_ids)
for change in changed
#get(change).set(_.find(server_response, (model) ->
return model.id == change
))
This technique is useful sometimes. We extend Collection with the following method. This should do what you're looking for. It's not in coffee, but you could easily port it. Enjoy!
// Take an array of raw objects
// If the ID matches a model in the collection, set that model
// If the ID is not found in the collection, add it
// If a model in the collection is no longer available, remove it
freshen: function (objects) {
var model;
// Mark all for removal
this.each(function (m) {
m._remove = true;
});
// Apply each object
_(objects).each(function (attrs) {
model = this.get(attrs.id);
if (model) {
model.set(attrs); // existing model
delete model._remove
} else {
this.add(attrs); // new model
}
}, this);
// Now check for any that are still marked for removal
var toRemove = this.filter(function (m) {
return m._remove;
})
_(toRemove).each(function (m) {
this.remove(m);
}, this);
this.trigger('freshen', this);
}
This just went into Backbone master 3 days ago:
https://github.com/documentcloud/backbone/pull/1220