Firebase functions - adding new value to the oldvalue - javascript

I'm creating an android app that logs how long a person spends on certain things. I want to add the time spent to the total time spend, so I know how long a user has spent on an exercise type
I want to do it in a function, since I think it's easier than transactions.
exports.addExerciseTime = functions.database.ref('users/{userid}/stats/exerciseTime/{type}').onWrite( event =>{
console.log("Exercise time updates...");
var newdata = event.data.val();
var oldData = event.data.previous.val();
return event.data.ref.update(oldData+ newdata);
});
Now, I know that this function will loop until firebase shuts it down.
But how would I do this? Is there an easier way to do this?

you have an easy option of adding a flag indicating that you updated the data. next time you will get into the function, just start by checking if the flag exists in if so, exit the function. the con of this one is that you will run the function at least n+1
another option, according to their latest post, you know have a "onUpdate" and "onCreate" triggers as well. you might be able to use them smartly to optimize this even more (for example: only on first creation do XYZ, so it won't run on each update).
https://firebase.googleblog.com/2017/07/cloud-functions-realtime-database.html

Like you are saying, onWrite will capture every writing event. My solution would be replacing onWrite with onCreate, however let the user write to another path because Firebase will keep triggering the function. Besides that, your approach this is not the best solution since the updates can conflict. The use of transactions is better. That would look like this:
exports.addExerciseTime = functions.database.ref('users/{userid}/stats/exerciseTime/{type}').onCreate( event =>{
console.log("Exercise time updates...");
var newdata = event.data.val();
const pathToValue = //create the path here to exercisetime
return pathToValue.transaction(function(exercisetime) {
return (exercisetime || 0) + newdata;
});
});
*Notice the onCreate event instead of onWrite. Again: You will need to write it to a other path.

Related

How to store reference to function?

Given a list of functions, I wish the user to be able to select any of the functions to run at startup. How can this be done so that the user can "save" their choice of function to run the next time the code is run ie what would function runSelectedFunction (below) look like since you can't "save" a javascript function to file? Also, assume the list of potential functions is extensible.
const first = ()=>{
console.log('first');
}
const second = ()=>{
console.log('second');
}
const third = ()=>{
console.log('third');
}
loadUserSelectedFunctionFromDB()
.then(runSelectedFunction)
To be clear, the goal is to persist the user choice even if the code stops executing and is restarted. Normally, this would be done by storing a value in a database but the question is how to store a reference to a function in a database given an extensible set of functions?
Use a map like this:
const m = {
first, second, third
};
let selectFuncName = "first"; // from user selection, maybe click a button
let selectFunc = m[selectFuncName];
loadUserSelectedFunctionFromDB()
.then(runSelectedFunction)

Updating global variables with NodeJS

I'm beginner to NodeJS, so I'm not entirely sure what the best method to achieve this would be. Basically I want to create a global variable with a string, for instance 'USD', that would get updated whenever my 'set currency' event is fired. I want it to remain that way until the event is called again.
I am using EventEmitter to fire off some events, in one of my files I have the following.
var event = require('./events');
if (msg.content.includes('!currency set currency')) {
split = msg.content.split(' ');
event.emit('setCurrency', split[3])
}
And then inside the events file I'm doing something like the following.
var exchangePref;
var event = new events.EventEmitter();
event.on('setExchange', (exchange) => {
exchangePref = exchange;
return exchangePref;
});
modules.exports = event;
I understand that re-writing the variable inside a callback isn't going to do what I need it to do, but I'm quite lost with how to achieve what I need it to do due to the modules.exports = event part at the bottom, the calling function simply never gets the data. I've played around with creating a constructor, but even still I couldn't get it to work.
Any suggestions/ideas would be greatly appreciated.
I wouldn't use event emitter for this. Instead create a module along the lines of:
var exchangePrefs = { currency: "JPY" };
module.exports = {
setCurrency : function(newVal){ exchangePrefs.currency = newVal; },
getCurrency : function(){ return exchangePrefs.currency; }
};
Then in your various other modules you just:
require('./mymodule').setCurrency('USD');
and somewhere else
var currency = require('./mymodule').getCurrency();
I'm sure it can be made prettier, but I think you get the point. For almost all intents and purposes modules work like singletons. There are some gotchas, but nothing you'll run into too often. (Singleton pattern in nodejs - is it needed?)
Personally I'd use some sort of data persistence in the exchangePref-module just for peace of mind. Like redis, or saving to a json-file.

rxjs using promise only once on subscribe

I wanted to use rxjs for the first time but am a bit stucked 'cause it doesn't behave exactly like I want it to: In my scenario I want to create an observable from a promise. But I want the promise only being called once (not on every subscription) and I want it not being called on creation time (defer the call to the first subscription).
First I tried this:
var source = Rx.Observable.fromPromise(_this.getMyPromise())
which causes a call to the getMyPromise function right on creation time. This is not satisfying because at that time I don't know if the source really will be used.
Then I tried:
var source = Rx.Observable.defer(function() { return _this.getMyPromise() })
which causes a call to the getMyPromise function each time a new subscription is being made to source. This makes way too many unnecessary calls to the web server. The Rx.Observable.create function seems to have the same issue.
So what is left or what am I missing?
.shareReplay() does this, e.g.:
var source = Rx.Observable.defer(function() { return _this.getMyPromise() }).shareReplay();
If you're using rxjs5, you'll want to read: Pattern for shareReplay(1) in RxJS5
In answer to your comment below, I can think of a fairly straightforward extension to the above logic that will do what you want, but it has a caveat. Let's say the events you want to use to trigger a "refresh" are represented in a stream, s$, then you could do something like:
var source = Rx.Observable.of({}).concat(s$)
.flatMapLatest(function() {
return Rx.Observable.defer(function() {
return _this.getMyPromise()
})
})
.shareReplay(1)
What we have here is a stream starting with a dummy object to get things rolling, followed by a stream consisting of your refresh events. Each of these is projected into a new observable created from a fresh invocation of your getMyPromise method, and the whole thing is flattened into a single stream. Finally, we keep the shareReplay logic so we only actually make calls when we should.
The caveat is that this will only work properly if there's always at least one subscriber to the source (the first subscription after all others are disposed will run the promise again, and will receive both the previously-cached value and the result of the promise it caused to run).
Here is an answer that does not require at least one subscriber at the source at all times using a simple helper:
var _p = null;
var once = function() { return _p || (_p = _this.getMyPromise());
var source = Rx.Observable.defer(once);
Or if you're using lodash, you can _.memoize your getMyPromise and get this automatically.

Can I put a CollaborativeString inside a custom type?

I'm reading the Google Drive Realtime API documentation on Building a Collaborative Data Model.
I really like the way gapi.drive.realtime.databinding.bindString behaves. It doesn't mess up your cursor placement when multiple people are typing in the same text box. But it requires that you pass it a CollaborativeString.
But if you register a custom type, you have to use gapi.drive.realtime.custom.collaborativeField no matter what type of field you are defining, and you can't pass one of these to bindString. In fact, the collaborativeField type does not appear to be documented anywhere, and inspecting it in the console shows that it has no methods. That means there's no registerReference method, which CollaborativeString uses to keep track of cursor positions.
How frustrating. So I guess I have to work around it. I see a few options:
Ignore the fact that the cursor gets messed up during collaboration
Use a CollaborativeMap instead of a custom type, and wrap it with my custom type at runtime
Probably going to do option 2.
I think you misunderstand how this site works, the onus is not on other people to show you how to do something - you're asking other people to take time from their day and help you.
That being said, taking a quick look at the page that you linked shows that what you want to do is not only possible but quite straightforward and compatible with bindString. Stealing from the example code from that page:
// Call this function before calling gapi.drive.realtime.load
function registerCustomTypes()
{
var Book = function () { };
function initializeBook()
{
var model = gapi.drive.realtime.custom.getModel(this);
this.reviews = model.createList();
this.content = model.createString();
}
gapi.drive.realtime.custom.registerType(Book, 'Book');
Book.prototype.title = gapi.drive.realtime.custom.collaborativeField('title');
Book.prototype.author = gapi.drive.realtime.custom.collaborativeField('author');
Book.prototype.isbn = gapi.drive.realtime.custom.collaborativeField('isbn');
Book.prototype.isCheckedOut = gapi.drive.realtime.custom.collaborativeField('isCheckedOut');
Book.prototype.reviews = gapi.drive.realtime.custom.collaborativeField('reviews');
Book.prototype.content = gapi.drive.realtime.custom.collaborativeField('content');
gapi.drive.realtime.custom.setInitializer(Book, initializeBook);
}
and
// Pass this as the 2nd param to your gapi.drive.realtime.load call
function onDocLoaded(doc)
{
var docModel = doc.getModel();
var docRoot = docModel.getRoot();
setTimeout(function ()
{
var book = docModel.create('Book');
book.title = 'Moby Dick';
book.author = 'Melville, Herman';
book.isbn = '978-1470178192';
book.isCheckedOut = false;
book.content.setText("Call me Ishmael. Some years ago - never mind how long precisely - having little or no money in my purse, and nothing particular to interest me on shore, I thought I would sail about a little and see the watery part of the world.");
docRoot.set('tbook', book);
debugger;
}, 0);
}
Good luck and have fun with the Realtime API - it's a lot of fun to play with.
I know this question and answer are getting old, but for reference's sake, just the last part of Grant Watters' very good answer, the onDocLoaded routine, is rather misleading. That function as written, is more suited for the 3rd parameter to the gapi.drive.realtime.load call, the onInitializeModel callback.
The 2nd parameter is called every time the Doc is loaded. You wouldn't normally add the same object over and over as the above routine would... Instead, you would normally set up your event handling, your dataBinds etc. This version might clarify somewhat:
// Pass this as the 2nd param to your gapi.drive.realtime.load call
function onDocLoaded(doc)
{
var docModel = doc.getModel();
var docRoot = docModel.getRoot();
var text = doc.getModel().getRoot().get("text");
// Add an event listener...
text.addEventListener(gapi.drive.realtime.EventType.TEXT_INSERTED, onStringChanged);
// ...and/or bind to collaborative objects:
var textArea = document.getElementById('textArea1')
textBinding = gapi.drive.realtime.databinding.bindString(text, textArea);
etc...
}
Not incidentally, bindString returns the binding object, which is needed to "unbind" later, preventing an AlreadyBound error or other unexpected behavior when the next Doc is loaded. Do something like this:
function onDocLoaded(doc)
{
// Clear any previous bindings etc:
if (textBinding) { textBinding.unbind() };
textBinding = null;
etc...

How can I get/apply change delta in Ace editor?

I am trying too keep two instances of an Ace editor in sync. So when the user types in one, the other is updated.
Looking at their docs I see that the EditSession change event says that it returns a delta of the change, and the Document has an applyDeltas method.
So I have hooked into that change event, and when it is fired I call the other document.applyDeltas and pass it over, but it doesn't work.
I have been poking around their docs (and Google for an hour), but I am not seeing how to keep them in sync. Does anyone know how I can do that?
Ok, I figured it out. Nothing beats looking at src :)
The applyDeltas method on the document wants an array, AND you need to grab the data from the change event.
//on editor1.change
this.handleEditor1Changed = function (e) {
var deltas = new Array();
deltas[0] = e.data;
this.editor2.getSession().getDocument().applyDeltas(deltas);
};
If you looked at the source, you should have seen that applyDeltas just calls applyDelta in a loop for each element in the array. Thus, you could simply do this:
editor1.on('change', function(delta) {
editor2.session.doc.applyDelta(delta);
})
or, in more modern JavaScript
editor1.on('change', delta => editor2.session.doc.applyDelta(delta))

Categories

Resources