My publications:
Meteor.publish('items', function() {
return Items.find({}, {skip: randomNumber, limit: 100});
});
My subscription code happening when a button is clicked (in templates.event)
Meteor.subscribe('items');
Items.find().fetch();
However, the problem is that items isn't refreshed with the new data, but new data is appended to the old list instead. What can I do to unsubscribe the old data?
When you call Meteor.subscribe it will return a subscription handle.
You call stop on the handle to cancel it.
eg, in your event helper
if (SomeGlobalVar){
SomeGlobalVar.stop();
}
SomeGlobalVar = Meteor.subscribe('items');
The other way is to run the subscription inside Deps.autorun; it will automatically clean up old subscriptions.
eg, in you event handler
Session.set('subscribe', true);
Elsewhere in your code:
Deps.autorun(function(){
if (Session.get('subscribe')){
Meteor.subscribe('items');
}
});
If you call Session.set('subsribe', false); Meteor will automatically cancel/clean-up that subscription to items.
Seems to me like there is a bit of a confusion here, when you are connecting to a reactive data source you are not subscribing to specific data, so when new records are added ofcourse they are appended to your data through that connection.
If I understood your question and you want the new data to completely replace the old data I advise you not to build your template around the Collection but rather build it around a cached version of the data (which will kept in an object and made reactive using Deps), that way you will have total control over the data displayed.
Related
I have a database collection with readings, each new reading needs to be checked if it's out of the ordinary, if it is, there needs to be an alert sent.
So i'm using db.ref('collection').on('child_added', (child => { check(child); });
The problem with the .on function is that when the listener is added, all previous data is also read.
So how do i read a collection that only reads the changes in the database, also when the listener is first added? Or if that doesn't work, how do I differentiate the already added data with the new data?
The Firebase database synchronizes the state of whatever query or reference you attach your listener to. There is no option to only get new nodes built into the API.
If you want only new nodes, you will have to:
Ensure each node has an associated timestamp or order. If you're using Firebase's built-in push() keys, those might already serve that function.
Know what "new" means to the client, for example by either keeping the last timestamp or push key that it saw.
And then use a query to only request nodes after the stores timestamp/key.
So for example, if you only want to read nodes that are created after the moment you attach the listener, you could do something like this:
let now = db.ref('collection').push().key; // determine current key
db.ref('collection').orderByKey().startAt(now).on('child_added', ...)
Every time I do an update with the same object(s3) with same values and properties, Firebase trigger the event 'child_added' even if there's nothing to add or update.
I made some test by modifying on the firebase console some values in subcollection of the main object and noticed that it returns a snapshot with the first element correct and then all the other elements of the collections as 'ADDED' elements. This is not true because the collections didn't change except the one on which I performed an action.
I just need that when I send the same identical object that is stored on the db, firebase will recognize smartly that no action is requested and no trigger need to be activated.
var studentiRef = ref.child('studenti/' + s3.matricola);
studentiRef.update(JSON.parse(JSON.stringify(s3)));
studentiRef.on("child_changed", function(userSnapshot) {
var tasseRef = userSnapshot.ref.child('tasse');
tasseRef.on('child_added', function(itemSnapshot, prevKey){
console.log('ADDED ON');
console.log(itemSnapshot.key)
})
});
studentiRef.on("child_changed", function(userSnapshot) {
userSnapshot.ref.child('tasse').on('child_removed', function(itemSnapshot, prevKey){
console.log('REMOVED ON');
console.log(itemSnapshot.key)
})
});
studentiRef.on("child_changed", function(userSnapshot) {
userSnapshot.ref.child('tasse').on('child_changed', function(itemSnapshot, prevKey){
console.log('CHANGED ON');
console.log(itemSnapshot.key)
})
});
UPDATE:
Before posting update I made some experiments with no successful results.
Here the pics of the console, the database and the code.
Going nuts on this.
Here three screenshot: 1 firebase data 2 snippet 3 console log
UPDATE II:
scenario
behaviours on modifying value in firebase
SOLVED:
By getting inspired from the github firebase examples, I found out a common mistake in using firebase: i was not flatting the data.
To continue using my data structure (a root object within a list of objects), the solution was to trigger an update of every single object (pseudocode: ref.update(root/childobject) n-times instead of ref.update(root).
If someone else ran into this problem, I will explain better.
Always, FLAT YOUR DATA! (using firebase)
Most likely these events come directly from the client SDK, which doesn't detect if there was an actual change. The database server does perform such a check, and will only send out changes to other clients if there was an actual change.
Update:
The Firebase client + server behave in the following way when you're calling telling it to update a node to its current value.
The client fires the local event(s) to reflect the update. So child_changed will fire.
The client send the update to the server. This is needed since the client and server may be (slightly) out of sync, and the server is the single-source-of-truth.
The server compares the update with the current value of the node. If it is the same, the process stops here.
If the updated value is different from the current value and passes validation/permission checks, the data is committed to disk and broadcast to any active listeners.
If the updates value is different, but rejected by the validation/permission checks, the servers sends a rejection message to the original client, which then fires another child_changed event to revert the local change.
I am using Meteor with React JS.
I get the collection of "list" by this code,
Meteor.subscribe('getList', {status:'active'},function(){
self.setState({lists:Lists.find().fetch()});
});
Here is the code for publish,
Meteor.publish('getList', function(data){
data.user = this.userId;
return Lists.find(data);
});
So it is working. The problem is that I have two components that calling Meteor.subscribe('getList'). But the status is not the same.
So in other component, I have this code,
Meteor.subscribe('getList', {status:'archived'},function(){
self.setState({lists:Lists.find().fetch()});
});
So what happens here is, if the user go to FirstComponent, this.state.lists is empty (which is correct). Then when the user navigate to SecondComponent, this.state.lists is populated with data (which is correct). But when the user go back to FirstComponent, this.state.lists still populated with data (which is wrong).
It is like that the first collection (is empty) in client is still there. Then the second collection (not empty) is added. I want to clear the collection in client before subscribing again.
By the way I am using flow-router.
Since subscriptions are cumulative you should repeat the query condition in your setState functions:
let query = {status:'active'};
Meteor.subscribe('getList',query,function(){
self.setState({lists:Lists.find(query).fetch()});
});
query = {status:'archived'};
Meteor.subscribe('getList',query,function(){
self.setState({lists:Lists.find(query).fetch()});
});
Please note however that from a security perspective you really don't want to pass a query object from the (untrusted) client as a parameter to the subscription! From the console someone can just do:
Meteor.subscribe('getList',{});
It looks like you are setting the React state in the Meteor.subscribe onReady callback. Does each component have it's own state?
Where are you calling Meteor.subscribe from? Remember that Meteor puts subscription data in local mini-mongo and you are reading from there when you call Lists.find().
The way you have this set up, the data will not be reactive. You need to store handle to the Meteor.subscribe cursor and manage the subscription lifecycle with that.
If you could share more code, I could give a more concise answer.
I'm new to meteor.js. Still getting used to it.
I get how templates update reactively according to the cursor updates on the server, like this:
{{#if waitingforsomething.length}} Something Happened! {{/if}}
This is good to display elements on the page, updating lists and content. Now, my question is: what if I want to call some javascript or fire some event when something gets updated reactively? What would be the right way to do it with meteor.js?
Anything inside Tracker.autorun or template instance this.autorun runs with changes in reactive data sources inside these autoruns.
Reactive data sources are ReactiveVar instances, db queries, Session variables, etc.
Template.myTemplate.onCreated(function() {
// Let's define some reactive data source
this.reactive = new ReactiveVar(0);
// And put it inside this.autorun
this.autorun(() => console.log(this.reactive.get()));
});
Template.myTemplate.events({
// Now whenever you click we assign new value
// to our reactive var and this fires
// our console.log
'click'(event, template) {
let inc = template.reactive.get() + 1;
template.reactive.set(inc);
}
});
It is a little bit outdated, but Sacha Greif's Reactivity Basics is a very quick and concise introduction to meteor's reactivity model.
Basically, you have what's called reactive computations, code that observes special data objects (sessions, subscriptions, cursors, etc.) and gets executed whenever any of these reactive sources changes.
This is exposed via the Tracker API
Computation works pretty well for me:
Template.myTemplate.onRendered(function() {
this.computation = Deps.autorun(function () {
if (something) {
$(".reactive").html("Something Happened!");
}
});
});
Template.myTemplate.destroyed = function(){
if (this.computation){
this.computation.stop()
}
};
I Hope this helps.
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.