In Meteor, how to remove items from a non-Mongo Collection? - meteor

In Meteor, I am publishing a collection from a non-Mongo source (IMAP specifically).
Meteor.publish("search_results", function(user, password, str) {
var self = this;
res_msg = [];
Imap.connect({... });
Imap.search(str, resultcb);
for (var i = 0; i < res_msg.length; i++) {
self.set("s_results", Meteor.uuid(), {uid: res_msg[i].uid, date: res_msg[i].date, headers:res_msg[i].headers});
}
self.flush();
self.complete();
self.flush();
console.log("total messages : ", res_msg.length);
});
This works fine. However, on the second pass though, new items are appended to the collection. There does not appear to be a way to remove records from a non-Mongo collection.
It seems from the documentation that if I use this.unset, it will change attributes, not remove the record(s).
I can't use collection.remove({}) either client or server side.

I found a really ugly way to do this, so I'm leaving the question open in the hopes that there is a better alternative.
Basically, if you unset all the attributes, the document goes away. The question is how to iterate over the collection within the publish method to find all documents so attributes can be unset. Creating a collection doesn't seem to work, let alone .find();
I stored the list of ids in a separate array. Ugly, I know. I hope you can do better.
for (i = 0; i < uuids.length; i++) {
self.unset("s_results", uuids[i], {});
}
uuids = [];
Imap.search(str, resultcb);
for (var i = 0; i < res_msg.length; i++) {
var u = Meteor.uuid();
self.set("s_results", u, {uid: res_msg[i].uid, date: res_msg[i].date, headers:res_msg[i].headers});
uuids.push(u);
}

Related

Update collection with an array in firebase

I need to update a collection in values like this :
{
"email" : "x#gmail.com",
"fullName" : "Mehr",
"locations" : ["sss","dsds","adsdsd"]
}
Locations needs to be an array. in firebase how can I do that ... and also it should check duplicated.
I did like this :
const locations=[]
locations.push(id)
firebase.database().ref(`/users/ + ${userId}`).push({ locations })
Since you need to check for duplicates, you'll need to first read the value of the array, and then update it. In the Firebase Realtime Database that combination can is done through a transaction. You can run the transaction on the locations node itself here:
var locationsRef = firebase.database().ref(`/users/${userId}/locations`);
var newLocation = "xyz";
locationsRef.transaction(function(locations) {
if (locations) {
if (locations.indexOf(newLocation) === -1) {
locations.push(newLocation);
}
}
return locations;
});
As you can see, this loads the locations, ensures the new location is present once, and then writes it back to the database.
Note that Firebase recommends using arrays for set-like data structures such as this. Consider using the more direct mapping of a mathematical set to JavaScript:
"locations" : {
"sss": true,
"dsds": true,
"adsdsd": true
}
One advantage of this structure is that adding a new value is an idempotent operation. Say that we have a location "sss". We add that to the location with:
locations["sss"] = true;
Now there are two options:
"sss" was not yet in the node, in which case this operation adds it.
"sss" was already in the node, in which case this operation does nothing.
For more on this, see best practices for arrays in Firebase.
you can simply push the items in a loop:
if(locations.length > 0) {
var ref = firebase.database().ref(`/users/ + ${userId}`).child('locations');
for(i=0; i < locations.length; i++) {
ref.push(locations[i]);
}
}
this also creates unique keys for the items, instead of a numerical index (which tends to change).
You can use update rather than push method. It would much easier for you. Try it like below
var locationsObj={};
if(locations.length > 0) {
for(i=0; i < locations.length; i++) {
var key= firebase.database().ref(`/users/ + ${userId}`).child('locations').push().key;
locationsObj[`/users/ + ${userId}` +'/locations/' + key] =locations[i];
}
firebase.database().ref().update(locationsObj).then(function(){// which return the promise.
console.log("successfully updated");
})
}
Note : update method is used to update multiple paths at a same time. which will be helpful in this case, but if you use push in the loop then you have to wait for the all the push to return the promises. In the update method it will take care of the all promises and returns at once. Either you get success or error.

How to delete all but most recent X children in a Firebase node?

Given a Firebase node lines filled with unique-ID children (from push() operations), such as this:
Firebase--
--lines
--K3qx02jslkdjNskjwLDK
--K3qx23jakjdz9Nlskjja
--K3qxRdXhUFmEJdifOdaj
--etc...
I want to be able to delete all children of lines except the most recently added 200 (or 100, or whatever). Basically this is a cleanup operation. Now I know I could do this by grabbing a snapshot of all children of lines on the client side, counting the entries, then using an endsAt(totalChildren-numToKeep) to grab the relevant data and run remove(). But I want to avoid grabbing all that data to the client.
Is there an alternative to my idea above?
Keep the most recent N items, is one of the trickier use-cases to implement. If you have any option to change it into "keep items from the past N hours", I recommend going that route.
The reason the use-case is tricky, is that you're counting items and Firebase does (intentionally) not have any count-based operations. Because of this, you will need to retrieve the first N items to know which item is N+1.
ref.child('lines').once('value', function(snapshot) {
if (snapshot.numChildren() > MAX_COUNT) {
var childCount = 0;
var updates = {};
snapshot.forEach(function (child) {
if (++childCount < snapshot.numChildren() - MAX_COUNT) {
updates[child.key()] = null;
}
});
ref.child('lines').update(updates);
}
});
A few things to note here:
this will download all lines
it performs a single update() call to remove the extraneous lines
One way to optimize this (aside from picking a different/time-based truncating strategy) is to keep a separate list of the "line ids".
lineids
--K3qx02jslkdjNskjwLDK
--K3qx23jakjdz9Nlskjja
--K3qxRdXhUFmEJdifOdaj
So you'll still keep the data for each line in lines, but also keep a list of just the ids. The code to then delete the extra ones then becomes:
ref.child('lineids').once('value', function(snapshot) {
if (snapshot.numChildren() > MAX_COUNT) {
var childCount = 0;
var updates = {};
snapshot.forEach(function (child) {
if (++childCount < snapshot.numChildren() - MAX_COUNT) {
updates['lineids/'+child.key()] = null;
updates['lines/'+child.key()] = null;
}
});
ref.update(updates);
}
});
This last snippet is slightly more involved, but prevents from having to download all lines data by just downloading the line ids.
There are many variations you can choose, but I hope this serves as enough inspiration to get started.

Why is IListDataAdapter.getCount asynchronous

In WinJS the only way to get the count of items in a ListView object is with the method getCount().
But this method is asynchronous.
This make it very difficult to be used in a for loop for example when there is a need to loop through the items of the list.
var listView = document.getElementById("listView").winControl;
listView.itemDataSource.getCount().done(
function (numItems) {
for (var i = 0; i < numItems; i++) {
//do your stuff here
}
});
If I put this in any part of my code I can't return the value I read in the loop from any function because the getCount() return a promise, making my function also return a promise and so on...
So my question is why? Isn't the number of items in a list already known when the method is called?
Have you tried joining promises? If your concern is to iterate all of the items in a ListView by selecting each item by index and then performing some work on them, you can use WinJS.Promise.join to create a single promise that contains the results of all the operations.
For example:
var listView = document.getElementById("listView").winControl;
listView.itemDataSource.getCount().then(
function (numItems) {
var joinedPromises = [];
for (var i = 0; i < numItems; i++) {
joinedPromises.push(listView.itemDataSource.itemsFromIndex(i, 0, 0));
}
return WinJS.Promises.join(joinedPromises);
}).done(
function (results) {
// Operate on each item in the ListView's data source.
},
function (err) {
// Handle any errors from the joined promises.
});
The ListView's data contract allows for asynchronous data sources, and we include a base class VirtualizedDataSource that you can use for fancy scenarios like that. If you are using a WinJS.Binding.List as your data source that API is in fact synchronous and you should be able to say:
listView.itemDataSource.list.length
However, if you're writing generic code that deals with ListView's and doesn't know what kind of data source it will

Meteor minimongo dynamic cursor

In my client UI I have a form with differents search criterias, and I'd like to reactively update the results list. The search query is transformed into a classical minimongo selector, saved in a Session variable, and then I have observers to do things with the results:
// Think of a AirBnb-like application
// The session variable `search-query` is updated via a form
// example: Session.set('search-query', {price: {$lt: 100}});
Offers = new Meteor.Collection('offers');
Session.setDefault('search-query', {});
resultsCursor = Offers.find(Session.get('search-query'));
// I want to add and remove pins on a map
resultCursor.observe({
added: Map.addPin,
removed: Map.removePin
});
Deps.autorun(function() {
// I want to modify the cursor selector and keep the observers
// so that I would only have the diff between the old search and
// the new one
// This `modifySelector` method doesn't exist
resultsCursor.modifySelector(Session.get('search-query'));
});
How could I implement this modifySelector method on the cursor object?
Basically I think this method needs to update the compiled version of the cursor, ie the selector_f attribute, and then rerun observers (without losing the cache of the previous results). Or is there any better solution?
Edit: Some of you have misunderstood what I'm trying to do. Let me provide a complete example:
Offers = new Meteor.Collection('offers');
if (Meteor.isServer && Offers.find().count() === 0) {
for (var i = 1; i < 4; i++) {
// Inserting documents {price: 1}, {price: 2} and {price: 3}
Offers.insert({price:i})
}
}
if (Meteor.isClient) {
Session.setDefault('search-query', {price:1});
resultsCursor = Offers.find(Session.get('search-query'));
resultsCursor.observe({
added: function (doc) {
// First, this added observer is fired once with the document
// matching the default query {price: 1}
console.log('added:', doc);
}
});
setTimeout(function() {
console.log('new search query');
// Then one second later, I'd like to have my "added observer" fired
// twice with docs {price: 2} and {price: 3}.
Session.set('search-query', {});
}, 1000);
}
This doesn't solve the problem in the way you seem to be wanting to, but I think the result is still the same. If this is a solution you explicitly don't want, let me know and I can remove the answer. I just didn't want to put code in a comment.
Offers = new Meteor.Collection('offers');
Session.setDefault('search-query', {});
Template.map.pins = function() {
return Offers.find(Session.get('search-query'));
}
Template.map.placepins = function(pins) {
// use d3 or whatever to clear the map and then place all pins on the map
}
Assuming your template is something like this:
<template name="map">
{{placepins pins}}
</template>
One solution is to manually diff the old and the new cursors:
# Every time the query change, do a diff to add, move and remove pins on the screen
# Assuming that the pins order are always the same, this use a single loop of complexity
# o(n) rather than the naive loop in loop of complexity o(n^2)
Deps.autorun =>
old_pins = #pins
new_pins = []
position = 0
old_pin = undefined # This variable needs to be in the Deps.autorun scope
# This is a simple algo to implement a kind of "reactive cursor"
# Sorting is done on the server, it's important to keep the order
collection.find(Session.get('search-query'), sort: [['mark', 'desc']]).forEach (product) =>
if not old_pin?
old_pin = old_pins.shift()
while old_pin?.mark > product.mark
#removePin(old_pin)
old_pin = old_pins.shift()
if old_pin?._id == product._id
#movePin(old_pin, position++)
new_pins.push(old_pin)
old_pin = old_pins.shift()
else
newPin = #render(product, position++)
new_pins.push(newPin)
# Finish the job
if old_pin?
#removePin(old_pin)
for old_pin in old_pins
#removePin(old_pin)
#pins = new_pins
But it's a bit hacky and not so efficient. Moreover the diff logic is already implemented in minimongo so it's better to reuse it.
Perhaps an acceptable solution would be to keep track of old pins in a local collection? Something like this:
Session.setDefault('search-query', {});
var Offers = new Meteor.Collection('offers');
var OldOffers = new Meteor.Collection(null);
var addNewPin = function(offer) {
// Add a pin only if it's a new offer, and then mark it as an old offer
if (!OldOffers.findOne({_id: offer._id})) {
Map.addPin(offer);
OldOffers.insert(offer);
}
};
var removePinsExcept = function(ids) {
// Clean out the pins that no longer exist in the updated query,
// and remove them from the OldOffers collection
OldOffers.find({_id: {$nin: ids}}).forEach(function(offer) {
Map.removePin(offer);
OldOffers.remove({_id: offer._id});
});
};
Deps.autorun(function() {
var offers = Offers.find(Session.get('search-query'));
removePinsExcept(offers.map(function(offer) {
return offer._id;
}));
offers.observe({
added: addNewPin,
removed: Map.removePin
});
});
I'm not sure how much faster this is than your array answer, though I think it's much more readable. The thing you need to consider is whether diffing the results as the query changes is really much faster than removing all the pins and redrawing them each time. I would suspect that this might be a case of premature optimization. How often do you expect a user to change the search query, such that there will be a significant amount of overlap between the results of the old and new queries?
I have the same problem in my own hobby Meteor project.
There is filter session var where selector is storing. Triggering any checkbox or button changes filter and all UI rerender.
That solution have some cons and the main - you can't share app state with other users.
So i realized that better way is storing app state in URL.
May be it is also better in your case?
Clicking button now change URL and UI rendering based on it. I realize it with FlowRouter.
Helpful reading: Keeping App State on the URL

How to delete an infowindow bound with a marker?

I wrote a chunk of Google Maps API code follow this idea.
But, when I delete all markers, the infowindows bound to these markers are not deleted.
Can anyone show me the solution?
Thanks.
This is how I delete the markers:
// Deletes all markers in the array by removing references to them
function deleteOverlays() {
if (markersArray) {
for (i in markersArray) {
markersArray[i].setMap(null);
}
markersArray.length = 0;
}
}
markersArray is a global var which stores all the markers.
I declared the infowindows like this:
marker.infowindow = new google.maps.InfoWindow(
{
content: '<div>something here</div>'
});
can't you just delete them along with the markers?
// Deletes all markers in the array by removing references to them
function deleteOverlays() {
if (markersArray) {
for (i in markersArray) {
markersArray[i].infoWindow.setMap(null);
markersArray[i].infoWindow = null; //this one is not necessary I think nut won't hurt
markersArray[i].setMap(null);
}
markersArray.length = 0;
}
}
Nothing's really being deleted, just the map property on the marker is being set to null. If you really want to delete the markers, you could use the delete operator.
// Deletes all markers in the array by removing them from the array
function deleteOverlays() {
if (markersArray) {
var arrayLength = markersArray.length;
for (var i = 0; i < arrayLength; i++) {
delete markersArray[i];
}
markersArray.length = 0;
}
}
According to the developers at Mozilla on the delete operator will do something similar to setting each marker instance to null. The article duncan cited also refers to this.
Therefore having an infoWindow array and setting them to null should do the trick. Another interesting thing which might affect you that has to do with memory management is that
JavaScript values are allocated when things (objects, strings, etc.) are created and "automatically" free'd when they are not used anymore. The latter process is called garbage collection. This "automatically" is a source of confusion and gives JavaScript (and high-level languages) developers the impression they can decide not to care about memory management. This is a mistake.
-Developers at Mozilla
So if you're looking for memory management of infoWindows you can take advantage of the "garbage collection" javascript does when it delete variables at the end of functions.

Resources