I am trying to get a trigger for newly added child node on reference path and get value for just the new data. But event.data.val returns all the value in that reference path.
Following is the cloud function code I am using:
exports.sendOfferNotification = functions.database.ref('/Users/{uid}/offers/New').onWrite(event => {
// Only edit data when it is first created.
// if (event.data.previous.exists()) {
// return;
// }
// Exit when the data is deleted.
if (!event.data.exists()) {
return;
}
// Grab the current value of what was written to the Realtime Database.
const original = event.data.val();
console.log('New offer', event.params.uid, original);
});
I tried using onUpdate instead of onWrite but still same result.
you could try this:
exports.sendOfferNotification =
functions.database.ref('/Users/{uid}/offers/New/{values}') //notice this change
.onWrite(event => {
if (!event.data.exists()){return};
const original = event.params.values; //notice this change
console.log('New offer', event.params.uid, original);
});
Solved it using onCreate() on the nested level of New/
exports.sendNotificationForImportantOffers = functions.database.ref('/Users/{uid}/offers/New/Important/{values}').onCreate(event => {
// Only edit data when it is first created.
// if (event.data.previous.exists()) {
// return;
// }
// Exit when the data is deleted.
if (!event.data.exists()) {
return;
}
// Grab the current value of what was written to the Realtime Database.
const original = event.data.val();
console.log('New offer', event.params.uid, original);
});
Initially, I was trying to have one single function to listen to changes on all the nested level under New/ path but now I will have to monitor trigger for each sub-level separately with onCreate() which returns me only the newly added child.
Related
I have a parent component which is a list of records and in one of the child component I have a form that submits data and if successful is added to that list in the parent. Everytime the data is submitted, I need to check if there is an identical record with the same title. This child form component is used to add and edit records, so if the record is edited then I also have to check that it can be submitted with the same name of ofcourse. Below is the code I have and it is working fine but I have been thinking if there is a better way of writing this. Can it be done while going thru the list array the first time instead of going through it once and then going thru it again to check for unique items.
When the data is submitted in the child component (form) I am executing the following functions to see if title field is unique.
const isUniqueTitle = (title) => {
if(activities.find(activity => activity.title.toLowerCase() === title)){
// shows alert
return false;
}
return true;
}
// Child component/form calls this function with the form data
const validateData = data = {
let isUnique = true;
//activities below is available here in the parent
activities.forEach(activity => {
// check for id below tells me that its a record being edited so only do a check if the title
// has been changed else if there is no id then it means its a new record so continue with the
// check
if (activity.id && activity.title != activity.title) {
isUnique = isUniqueTitle(data.title);
} else if (!activity.id) {
isUnique = isUniqueTitle(data.title);
}
return isUnique;
})
}
Please advise, thank you!
You could use a Set to store titles and use its has method to check for the uniqueness of any given title
The Set object lets you store unique values of any type
const titleSet = new Set(activities.map(activity => activity.title.toLowerCase()))
const isUniqueTitle = (title) => {
return titleSet.has(title);
}
// Child component/form calls this function with the form data
const validateData = data = {
//activities below is available here in the parent
activities.forEach(activity => {
// check for id below tells me that its a record being edited so only do a check if the title
// has been changed else if there is no id then it means its a new record so continue with the
// check
if (activity.id && activity.title != activity.title) {
isUniqueTitle(data.title);
} else if (!activity.id) {
isUniqueTitle(data.title);
}
})
}
Based on the result of data.key === "high_temp_coil", I am printing the data into my webpage with data.val() as shown below:
var deviceRef = app.database().ref('/'+localStorage.getItem('machineid'));
deviceRef.on('child_added', function(data) {
if (data.key === 'high_temp_coil') {
$('#high_temp_coil .value').html(data.val())
}
if (data.key === 'low_temp_coil') {
$('#low_temp_coil .value').html(data.val());
}
}
In my code high_temp_coil represents the high temperature of the coil and low_temp_coil represents the low temperature of the coil, each with their own fields in my database. However, due to a manufacturing issue, sometimes the high temp and low temps are backwards and I need to figure this out before printing the data. This is how I was trying to do that but it doesn't work:
if (data.key === "high_temp_coil"){
let valueh= data.val();
if (data.key === "low_temp_coil"){
let valuel= data.val()
console.log(valueh);
console.log(valuel);
}
}
This is what the data looks like in my database:
{
"MachineNo": {
"water": "value"
"high_temp_coil": "value"
"low_temp_coil": "value"
}
}
When you use a child_added event listener, your callback will be invoked whenever one of the children under that database location changes. Using this, you would need to store high_temp_coil and low_temp_coil in variables outside of the function so that you can compare them properly. Because you store the result in an element, you could pull the current values from there.
Note: In the below snippets I follow the convention of naming the DataSnapshot object as snapshot, reserving data for the plain JavaScript object returned by snapshot.val(). This aids in preventing confusion later on, especially when not using TypeScript.
var deviceRef = app.database().ref('/'+localStorage.getItem('machineid'));
deviceRef.on('child_added', function(snapshot) {
if (snapshot.key === 'high_temp_coil') {
const newHighTempValue = snapshot.val();
const lowTempValue = $('#low_temp_coil .value').html();
if (Number(newHighTempValue) >= Number(lowTempValue)) { // <- assuming the values are numeric and not written as "52.1°C"
// new value is higher than current value
$('#high_temp_coil .value').html(newHighTempValue)
} else {
// new value is lower than current value, swap places
$('#high_temp_coil .value').html(lowTempValue)
$('#low_temp_coil .value').html(newHighTempValue)
}
}
if (snapshot.key === 'low_temp_coil') {
const newLowTempValue = snapshot.val();
const highTempValue = $('#high_temp_coil .value').html();
if (Number(newLowTempValue) < Number(highTempValue)) { // <- assuming the values are numeric and not written as "52.1°C"
// new value is lower than current value
$('#low_temp_coil .value').html(newLowTempValue)
} else {
// new value is higher than current value, swap places
$('#low_temp_coil .value').html(highTempValue)
$('#high_temp_coil .value').html(newLowTempValue)
}
}
}
The main issue with the above code is that the child_added events would fire once on page load, and not get live updates from any sensors updating the data because these changes would fire child_changed events.
However, for your data structure, you can greatly simplify your code by listening to value events instead. These listeners will be fired each time any of the data under that location is updated - including when a machine is created and any changes to the temperatures. Also, because the data is one level higher in the tree, you have access to the latest high_temp_coil and low_temp_coil values right in the snapshot. The trade-off for this listener is that you need to make sure to handle when the data does not exist (snapshot.exists()===false) because child_added listeners would only be invoked when data is created where it is guaranteed to exist.
var deviceRef = app.database().ref('/'+localStorage.getItem('machineid'));
deviceRef.on(
'value',
function(snapshot) {
if (!snapshot.exists()) {
// TODO: handle machine does not exist
console.error(`Machine ${localStorage.getItem('machineid')} does not exist in database`);
return;
}
const data = snapshot.val();
const highTempValue = data.high_temp_coil;
const lowTempValue = data.low_temp_coil;
if (Number(highTempValue) >= Number(lowTempValue)) { // <- assuming the values are numeric and not written as "52.1°C"
$('#high_temp_coil .value').html(highTempValue)
$('#low_temp_coil .value').html(lowTempValue)
} else {
$('#high_temp_coil .value').html(lowTempValue)
$('#low_temp_coil .value').html(highTempValue)
}
},
(error) => {
// TODO: implement better error handling
console.error("Listener was cancelled due to an error:", error);
}
);
Not sure where to start with this one... I'm creating a basic todo app, using localStorage. (I specially, am not using a backend database).
So far, I can update and display, the objects I have stored locally. And I am displaying them on my page.
form.addEventListener('submit', function(e){
e.preventDefault();
// Set object
let data = {
name: nameInput.value,
url: urlInput.value
};
bookMarksArray.push(data);
console.log("Added bookmark #" + data);
// Saving
localStorage.setItem("bookMarksArray", JSON.stringify(bookMarksArray));
});
However, I also want to able to edit, each item in my DOM. Once edited, I want that specific object, which correlates to this, to be updated in localStorage.
How do I do this?
I'm not sure where to start. Here's a codepen, of my code so far:
https://codepen.io/ReenaVerma1981/pen/LYEPbjL
EG
- if I want to update a URL value to www.google.co.uk
- And this is updated, in the correct object, in localStorage
Here's some psuedo code, is this a good approach?
// List each object as an individual form in DOM
// So I can easily update the input.value, (with a new value)
// The **edit** button, would be a submit button
// Or there's an eventHandler on this button
// Which onClick, takes the input.value, (either name.value or url.value)
// Identifies whether these values, match values in the localStorage object
// And if so, get the object index
// Then update these object values, based on the object index?
// Update localStorage via localStorage.setItem
Here's some example code, I'm writing, to try and do this:
// UPDATE/EDIT EXISTING
const list = document.querySelector('.url-list');
list.addEventListener('click', event => {
if (event.target.classList.contains('js-edit-url')) {
console.log('edit');
const editName = event.target.parentElement.name.value;
const editURL = event.target.parentElement.url.value;
let data = {
name: editName,
url: editURL
};
Object.keys(bookMarksArray).map(function (old_key, index) {
// console.log('old_key',old_key);
let new_key = data;
console.log('data', data);
if (old_key !== new_key) {
Object.defineProperty(bookMarksArray, new_key,
Object.getOwnPropertyDescriptor(bookMarksArray, old_key));
// console.log('bookMarksArray',bookMarksArray);
// localStorage.setItem("bookMarksArray", JSON.stringify(bookMarksArray));
delete bookMarksArray[old_key];
}
});
}
});
I figured it out!
I found a really good example here, using the ES6 way, without mutating original data:
// UPDATE/EDIT EXISTING
const list = document.querySelector('.url-list');
list.addEventListener('click', event => {
if (event.target.classList.contains('js-edit-url')) {
console.log('edit');
const editName = event.target.parentElement.name.value;
const editURL = event.target.parentElement.url.value;
// Find the object index, by looping through and matching name.value
const objIndex = bookMarksArray.findIndex(obj => obj.name === editName);
// make new object of updated object.
const updatedObj = { ...bookMarksArray[objIndex], url: editURL};
// make final new array of objects by combining updated object.
const updatedProjects = [
...bookMarksArray.slice(0, objIndex),
updatedObj,
...bookMarksArray.slice(objIndex + 1),
];
localStorage.setItem("bookMarksArray", JSON.stringify(updatedProjects));
}
});
I am trying to set some variables to send to pass into a function if a Firebase node is set to true. I am trying to use the .parent and .val() function to set a customer_id, based on the documentation here: https://firebase.google.com/docs/functions/database-events
exports.newCloudFunction = functions.database.ref('/user/{userId}/sources/saveSource').onWrite(event => {
// Retrieve true/false value to verify whether card should be kept on file
const saveSource = event.data.val();
if (saveSource) {
let snap = event.data;
let customer_id = snap.ref.parent.child('customer_id').val();
console.log(customer_id);
// pass customer_id into function
}
I was expecting snap.ref.parent to reference /sources and .child('customer_id').val() to access the value from the customer_id key.
However, when I try to run this function I get the following error:
TypeError: snap.ref.parent.child(...).val is not a function
at exports.linkCardToSquareAccount.functions.database.ref.onWrite.event (/user_code/index.js:79:56)
at /user_code/node_modules/firebase-functions/lib/cloud-functions.js:35:20
at process._tickDomainCallback (internal/process/next_tick.js:129:7)
How can I reference a node outside the scope of the original onWrite location?
You can't just call .val() on a database reference and expect to get the data at that location. You need to add a value listener in order to get new data.
Fortunately, this is fully supported inside of Cloud Functions:
exports.newCloudFunction = functions.database.ref('/user/{userId}/sources/saveSource').onWrite(event => {
// Retrieve true/false value to verify whether card should be kept on file
const saveSource = event.data.val();
if (saveSource) {
const customerIdRef = event.data.adminRef.parent.child('customer_id')
// attach a 'once' value listener to get the data at this location only once
// this returns a promise, so we know the function won't terminate before we have retrieved the customer_id
return customerIdRef.once('value').then(snap => {
const customer_id = snap.val();
console.log(customer_id);
// use customer_id here
});
}
});
You can learn more here.
I have a complicated data structure being built by queries on multiple collections and published.
It is working great for the initial creation, and on my local machine all the changes observed are reflected in the client as expected. However, in my staging environment I get the following error from mini-mongo when a change is observed
Uncaught Error: When replacing document, field name may not contain '.'(…)
The publishing code looks like this, where pub is the this from a Meteor.publish and rootObj is a reference to an Object in memory which gets properties modified but never has it's reference destoryed.
function _republish(pub, rootId, rootObj, handles, startup) {
// cleanup handles
if (handles.foo) {
handles.foo.stop();
}
// some query which could depend on rootObj/other calculated values
let cursor = SubColl.find({_id: {$in: bar}});
handles.foo = cursor.observeChanges({
removed(_id) {
rootObj.bar = rootObj.bar.filter(o => o._id !== _id);
pub.changed('foobarbaz', rootId, {bar: rootObj.bar})
},
changed(_id, fields) {
const index = rootObj.bar.findIndex(line => line._id === _id);
const changed = {};
_.each(fields, (value, field) => {
rootObj.bar[index][field] = value;
changed[`bar.${index}.${field}`] = value;
});
pub.changed('foobarbaz', rootId, changed);
},
added(_id, fields) {
rootObj.bar.push(_.extend({}, fields, {_id}));
if (!startup) {
// deeper children stuff
pub.changed('foobarbaz', rootId, {bar: rootObj.bar});
}
}
});
// deeper children stuff
startup = false;
// if startup was true, expect caller to publish this
}
As we can see, the publish works fine when I'm pub.changeding on just bar, but attempting to update a specific subdocument field (e.g. bar.0.prop) results in the inconsistent behaviour
If possible I want to avoid re-publishing the whole of bar as it is huge compared to updating a simple property.
How can I publish the change to a single field of a subdocument?