This question already has answers here:
How can I access a JavaScript object which has spaces in the object's key?
(4 answers)
Closed 1 year ago.
The example code is below:
exports.updateUser = functions.firestore
.document('users/{userId}')
.onUpdate((change, context) => {
// Get an object representing the document
// e.g. {'First Name': 'Marie', 'age': 66}
const newValue = change.after.data();
// ...or the previous value before this update
const previousValue = change.before.data();
// access a particular field as you would any JS property
//const name = newValue.name; //How does one accesses 'FIRST NAME'
// perform desired operations ...
});
If 'FIRST NAME' is replaced by 'name' it is easy, but my data comes from somewhere else where I have no control on the field names. So the field descriptions have spaces between. How do I read them please?
You need to use brackets notation of property accessor for that:
newValue["First Name"]
In the object[property_name] syntax, the property_name is just a string or Symbol. So, it can be any string, including '1foo', '!bar!', or even ' ' (a space).
Related
I am using a scheduled task in a Firebase Cloud Function to query an array which contains a number of objects that need to be updated if a matching condition exists. My current attempt is using the 'array-contains' method to get the objects, then loop over them to find a matching condition which will then batch update the items. This is my data structure:
I need to find an object that is <= the current time, and also if the 'active' value = false.
export const liveMeetingsTrigger = functions.runWith( { memory: '1GB' }).pubsub
.schedule('every 1 minutes').onRun(async context => {
const now = admin.firestore.Timestamp.now();
const liveMeetings = await admin.firestore().collection('fl_content').where('meeting', 'array-contains', 'liveMeetingDate').get();
const batch = admin.firestore().batch();
liveMeetings.forEach(doc => {
if(doc.data().liveMeetingDate <= now && doc.data().active == false){
batch.update(doc.ref,'active',true);
}
});
return await batch.commit();
});
I have also tried using an exact object in the query instead of just using 'liveMeetingDate', but still get no results back, any help would be great - thanks.
Debugging: As the array I am trying to reach is inside of the (map) object 'liveMeetings' i have tried the dot notation (liveMeetings.meeting) with no success. Also trying a new collection with the the 'meeting' array at top level has provided no success.
Simple logging in the console (liveMeetings.size) shows that nothing is being returned on the query, so therefore the logging does not even reach the loop in the code.
As explained in this anwser the following query will not work:
const liveMeetings = await admin.firestore().collection('fl_content').where('meeting', 'array-contains', 'liveMeetingDate').get();
because the meetings array contain some objects, instead of "simple" or primitive data (e.g. string, number...).
You could query it with the exact objects, like:
const obj = {active: false, liveMeetingDate: ..., meetingId: ..., ....};
const liveMeetings = await admin.firestore().collection('fl_content').where('meeting', 'array-contains', 'obj').get();
Another approach would be to create a new collection which contains the similar documents (same Document ID) but with a meeting Array that contains only the liveMeetingDate property.
Finally, note that since your Array is within a map, you need to do
await admin.firestore().collection('fl_content').where('liveMeetings.meeting', 'array-contains', ...).get();
(PS: I don't mark this question as duplicate since you expressly ask for more help in the comments of the duplicate question/answer)
Let's I had documents contain two fields 'likeCount' and 'viewCount'. Is it possible to order them by a sum of those two fields?
If not, how do I implement ordering by popularity? Ordering by popularity is a common feature in a variety of apps but I can't find any documentation or tutorials for this.
One solution is to have an extra field in each document which holds the value of the sum. Then you can easily sort based on this value.
To update this field, you can either do it when you update the document from your app, OR, if you don't have the info in the app when you update the doc (i.e. it would require fetching the document to get the value of the two fields) you can update the value in the backend, with a Cloud Function.
For example, a Cloud Function along the following lines would do the trick:
exports.updateGlobalCount = functions.firestore
.document('collection/{docId}')
.onUpdate((change, context) => {
const previousValue = change.before.data();
const newValue = change.after.data();
if (previousValue.likeCount !== newValue.likeCount || previousValue.viewCount !== newValue.viewCount) {
return change.after.ref.update({ globalCount: newValue.likeCount + newValue.viewCount })
} else {
return null;
}
});
In My Cloud Firestore database structure looks like this. Now, I'd like to delete index positions based on Index 0, Index 1 like this.
const arrayLikedImagesRef = {imageurl: image, isliked: true};
const db = firebase.firestore();
const deleteRef = db.collection('userdata').doc(`${phno}`);
deleteRef.update({
likedimages: firebase.firestore.FieldValue.arrayRemove(arrayLikedImagesRef)
});
});
As explained here, “bad things can happen when trying to update or delete array elements at specific indexes”. This is why the Firestore official documentation indicates that the arrayRemove() function will take elements (strings) as arguments, but not indexes.
As suggested in this answer, if you prefer using indexes then you should get the entire document, get the array, modify it and add it back to the database.
You can't use FieldValue to remove array items by index. Instead, you could use a transaction to remove the array items. Using a transaction ensures you are actually writing back the exact array you expect, and can deal with other writers.
For example (the reference I use here is arbitrary, of course, you would need to provide the correct reference):
db.runTransaction(t => {
const ref = db.collection('arrayremove').doc('targetdoc');
return t.get(ref).then(doc => {
const arraydata = doc.data().likedimages;
// It is at this point that you need to decide which index
// to remove -- to ensure you get the right item.
const removeThisIndex = 2;
arraydata.splice(removeThisIndex, 1);
t.update(ref, {likedimages: arraydata});
});
});
Of course, as noted in the above code, you can only be sure you are about to delete the correct index when you are actually inside the transaction itself -- otherwise the array you fetch might not line up with the array data that you originally selected the index at. So be careful!
That said, you might be asking what to do given that FieldValue.arrayRemove doesn't support nested arrays (so you can't pass it multiple maps to remove). In that case, you just want a variant of the above that actually checks values (this example only works with a single value and a fixed object type, but you could easily modify it to be more generic):
const db = firebase.firestore();
const imageToRemove = {isliked: true, imageurl: "url1"};
db.runTransaction(t => {
const ref = db.collection('arrayremove').doc('byvaluedoc');
return t.get(ref).then(doc => {
const arraydata = doc.data().likedimages;
const outputArray = []
arraydata.forEach(item => {
if (!(item.isliked == imageToRemove.isliked &&
item.imageurl == imageToRemove.imageurl)) {
outputArray.push(item);
}
});
t.update(ref, {likedimages: outputArray});
});
});
(I do note that in your code you are using a raw boolean, but the database has the isliked items as strings. I tested the above code and it appears to work despite that, but it'd be better to be consistent in your use of types).
Assuming a link is created and is pointing to a URL with the following pattern:
/00O70000001SOsa?pv0={!Account.Id}&pv1={!Case.IsClosed}
How can this be written in an Apex controller if I have to invoke the same thing using pagereference or any other similar API.
// You will have to apply your own logic on how you pick data for the link
Account acc = [SELECT Id FROM Account LIMIT 1];
Case c = [SELECT IsClosed FROM Case LIMIT 1];
Pagereference pr = new PageReference('/00O70000001SOsa');
pr.getParameters().putAll(new Map<String, String>{
'pv0' => acc.Id,
'pv1' => c.isClosed ? 'true' : 'false'
});
System.debug(pr.getUrl());
// outputs something like /00O70000001SOsa?pv0=0017000001TSmKmAAL&pv1=false
Parameters you pass to the URL have to be Strings (or something that easily casts to String). So it's your job to convert booleans, display dates in format that the other page will "like" (reports need dates in the same format the user would use so according to his/her locale, look at format method in Date and DateTime class).
But that's it. You don't have to worry about "?" sign or any fancy escaping of special characters, it'll be done for you:
Pagereference pr = new PageReference('/home/home.jsp');
pr.getParameters().putAll(new Map<String, String>{
'spaces' => 'spa ce',
'html-entities' => '& < >',
'percents' => '1 / 4 = 25%'
});
System.debug(pr.getUrl());
// /home/home.jsp?html-entities=%26+%3C+%3E&percents=1+%2F+4+%3D+25%25&spaces=spa+ce
I am trying to build an app that stores and shows book quotes by it's title and by it's author. I am using Firebase for the backend. My Firebase data structure looks like this.
When a book quote is added, I know the author. So to store the quote in author automatically, I am trying to use Firebase Functions.
I have tried two approaches,
Merge quotes from author with quotes from the book when book is updated.
exports.newQuotesTrigger = functions.database.ref('library/{bookAndAuthor}').onWrite((snap, context) => {
const message = snap;
console.log('Retrieved message content: ', message);
const newValue = message.after.val();
const oldValue = message.before.val();
const author = snakeCase(newValue.author);
admin.database().ref('authors/' + author).child('quotes').set(newValue.quotes);
console.log('Updated author quotes');
return message;
});
Just push the difference of new quotes and old quotes from the book
exports.newQuotesTrigger = functions.database.ref('library/{bookAndAuthor}').onWrite((snap, context) => {
const message = snap;
console.log('Retrieved message content: ', message);
const newValue = message.after.val();
const oldValue = message.before.val();
const newQuotes = newValue.quotes || [];
const oldQuotes = oldValue.quotes || [];
const diff = arrayDiff(newQuotes, oldQuotes);
if (diff) {
console.log('Quotes were updated for ', {title: newValue.title, author: newValue.author});
const author = snakeCase(newValue.author);
admin.database().ref('authors/' + author).child('quotes').push(diff);
console.log('Updated author quotes');
}
return message;
});
Both don't append/insert update quotes properly. I haven't found a way to append/insert to a Firebase db array.
You have to use update in order to "update specific children of a node without overwriting other child nodes", see:
https://firebase.google.com/docs/database/web/read-and-write#update_specific_fields
Your first piece of code should work with update if you slightly change your structure as follow with autogenerated Ids for the quotes
Database
author
- nassim
- quoteID1: "...." <- ID auto generated
- quoteID2: "...." <- ID auto generated
- quoteID3: "...." <- ID auto generated
Cloud Function
Replace, in your first version of the code, these lines
admin.database().ref('authors/' + author).child('quotes').set(newValue.quotes);
console.log('Updated author quotes');
return message;
by those ones
const quotesObject = newValue.quotes;
var updates = {};
Object.keys(quotesObject).forEach(function (key) {
let quote = quotesObject[key];
const newQuoteKey = firebase.database().ref('authors/' + author).child('quotes').push().key;
updates[newQuoteKey] = quote ;
});
return admin.database().ref('authors/' + author).child('quotes').update(updates);
Another important point is that you are not returning a promise in your Cloud Functions. You should return the promise from the update (or set) and not the message. See https://www.youtube.com/watch?v=652XeeKNHSk&t=26s
In case you really have to keep the quotes id generated by yourself (i.e. 0, 1, 2, etc) in a sequence you will have to manipulate arrays by getting the previous array of values, adding the new quote and overwriting the existing set of quotes with the new array.. a lot of efforts! especially that with auto-generated ids you will not loose the quotes order: they will still be saved in the order they were written.