Google Apps Script withSuccessHandler() doesn't work as expected - javascript

I'm learning Apps Script and I'm getting very frustrated because I can't see what is wrong with my approach.
According to the docs, when running google.script.run.withSuccessHandler(successFunc).myFunction() from within an html file script, whatever myFunction returns should be available as a parameter for successFunc, or shouldn't it? I'm dubious now because I can't seem to make it work.
Here's my HTML:
<body>
<form>
<label for="fn">Nombre</label>
<input id="fn" name="fn" type="text">
<label for="ln">Apellido/s</label>
<input id="ln" name="ln" type="text">
<button type="button" id="search">Buscar</button>
<label for="found">Resultado</label>
<input type="text" name="found" id="found" disabled>
<button type="submit" id="setClient">Continuar</button>
</form>
<script>
const fn = document.getElementById('fn').value;
const ln = document.getElementById('ln').value;
const search = document.getElementById('search');
const found = document.getElementById('found');
const setClient = document.getElementById('setClient');
let clientId; // The ID of the client in the database;
let data;
search.addEventListener("click", () => {
google.script.run.withSuccessHandler(addClient).searchDb(fn, ln);
});
function addClient(data) {
alert(data); // returns null
if (!data || data === null) throw "No data found"; // don't care about gracefully catching this right now
found.value = `${data.clientFn}, ${data.clientLn}`; // adds 'undefined, undefined'
clientId = data.clientId;
// google.script.host.close();
}
setClient.addEventListener("click", ()=>{
google.script.run.setClient(fn, ln, clientId);
})
</script>
</body>
As you can see, I've added an "alert" in the success handler just to keep track of data and even though I have tested the code ad infinitum and it always return a fully workable object, this seems to always show null.
Here's also my .gs function:
function searchDb(fn, ln) {
const ws = SpreadsheetApp.openById("the ID of the SS, obviously").getSheets()[0];
const dataRange = ws.getRange(2, 1, ws.getLastRow() - 1, 3).getValues();
const lnRange = dataRange.map(r => { return r[2] });
const fnRange = dataRange.map(r => { return r[1] });
const idRange = dataRange.map(r => { return r[0] });
for (let a = 0; a < lnRange.length; a++) {
const clientLn = lnRange[a];
const clientFn = fnRange[a];
const clientId = idRange[a];
if (clientLn.includes(ln) && clientFn === fn) {
const data = {
clientFn: clientFn,
clientLn: clientLn,
clientId: clientId
}
return data;
}
}
return;
}
It is, indeed, a very simple program and it does return the data object with the appropriate data when I test it, so not sure what's going on here.
I'm pretty sure I'm missing something pretty obvious, but I got to the point where I'm just banging my head against the wall...
Any help will be appreciated!
EDIT
I've edited the code because what I originally posted wasn't consistent - and the result of not reverting back completely from trying other approaches -. Still, the problem remains.

This is not an array:
const data = {'clientFn': clientFn,'clientLn': clientLn,'clientId': clientId}
But your treating it as an array:
found.value = `${data[1]}, ${data[0]}`;
clientId = data[2];
Try referring to them with their properties 'clientFn','clientLn','clientId'
found.value = `${data['clientLn']}, ${data['clientFn']}`;
clientId = data['clientId'];
Also you may want to refer to this link to insure that they are all legal types.

It turns out the problem was not to do with withSuccessHandler, as I expected, but with how the data was passed on to searchDb because the vars fn and ln haven't changed value as far as the script is concerned when the search button is clicked. The solution is as simple as declaring those variables after the search button has been clicked:
<script>
const search = document.getElementById('search');
const fullName = document.getElementById('fullName');
const country = document.getElementById('country');
const email = document.getElementById('email');
const noClient = document.getElementById('noClient');
const useClient = document.getElementById('useClient');
let clientId; // The ID of the client in the database;
search.addEventListener("click", () => {
const fn = document.getElementById('fn').value;
const ln = document.getElementById('ln').value;
google.script.run.withSuccessHandler(addClient).searchDb(fn, ln);
});
// ...etc

Related

search-bar for JSON (API), need help:)

i am currently stuck trying to code a search-bar for an JSON fil (linked via an API link and key). I am fairly new to coding and especially new to this field.
i currently cant get my function to work with onkeyup() maybe because its an async function? as i said im fairly new to this so idk, and I get an error for my "x.innerHTML = "" " saying "Cannot set properties of null", now i may look like an idiot here and missing something obvious but I would very much appreciate all answers:)
thanku for your time!
HTML:
<div id="søk"><input id="søkefelt" onkeyup="søk()" type="text" placeholder="Search..">
<ul id="listHolder"></ul>
</div>
javascript:
window.addEventListener("DOMContentLoaded", (Event) => {
async function søk() {
const apiKey = await fetch ("nyheter.json")
.then ((response) => response.json());
var data = await fetch ("https://newsapi.org/v2/top-headlines?country=us&category=business&apiKey=" + apiKey[0].apikey)
.then((response) => response.json());
let input = document.getElementById("søkefelt").value
input = input.toLowerCase();
let x = document.querySelector("listHolder");
x.innerHTML = ""
for (let i = 0; i < data.length; i++) {
const obj = data[i];
if (obj.title.toLowerCase().includes(input)) {
const elem = document.createElement("li")
elem.innerHTML = '${obj.title} - ${author}'
x.appendChild(elem)
}
};
}
søk();
});
querySelector requires # before ids
querySelector("#listHolder")

Initial load and when I change value of input doesn't show message in JavaScript

everyone, I have some problem with fetching data and displaying message on initial loading as well as when I change some of the input filed value. The idea here is to display specific message in two cases after doing some calculation.
const potrosnja = document.getElementById('potrosnja');
const nagib = document.getElementById('nagib');
const input = document.querySelectorAll('input[type="number"]');
const submitBtn = document.getElementById('submitBtn');
const poruka = document.getElementById('poruka');
let str = document.querySelector('input[name="strane-sveta"]:checked').value;
let godisnjaPotrosnja = parseInt(potrosnja.value);
let nagibKrovaInput = nagib.value;
//On button submit it fetches data and calculate the value needed for yearly consumption of energy
//fetching data
async function dataFetch(){
let response = await fetch('./csvjson.json')
let data = await response.json();
data.map(strana => {
strana.strana.map((item, i) => {
try {
if(item == str && nagibKrovaInput == strana.nagib) {
let result = Math.ceil(godisnjaPotrosnja / strana.vrednost[i]);
console.log("try works")
poruka.innerHTML = `You need <span class="kw">${result}</span>`
}
}
catch(err) {
poruka.innerHTML = `Please fill required fields.`
console.log(err)
}
})
})
}
//event listeners
submitBtn.addEventListener('click', () => {
dataFetch()
console.log('clicked')
input.forEach(input => {
if(input.value == ''){
input.classList.add("active");
}
})
})
I can see that the problem is inside try function, it like condition isn't treated on initial load, and I have to reload page so it would work. Can someone help me understanding what is the problem?
Ok, I found solution... First thing I have to do Is to check if nagibKrovaInput == strana.nagib, after I get true, I compared does the indexOf item is equal as str and after I get true, it will display something. I also changed on click on the button to send values to data function as an arguments and It solved the problem. Tnx for help.

How to chain filter and map methods in nodejs?

So I'm working on a project where I'm making a call to a database to retrieve the data stored there. This data comes as an array. here is the code:
const allLogins = await Login.find().sort("name");
const token = req.header("x-auth-token");
const user = jwt.verify(token, config.get("jwtPrivateKey"));
const logins = allLogins
.filter((login) => login.userId === user._id)
.map((login) => {
login.password = decrypt(login.password);
});
If I call a console.log after the decrypt has been run I see that it has been completed correctly. The issue I have is if I console.log(logins) it says it is an array of two items that are both undefined. If instead I run it like this...
const allLogins = await Login.find().sort("name");
const token = req.header("x-auth-token");
const user = jwt.verify(token, config.get("jwtPrivateKey"));
let logins = allLogins.filter((login) => login.userId === user._id);
logins.map((login) => {
login.password = decrypt(login.password);
});
Then it works as it should. I'm not sure why the first set of code doesn't work and why the second set does work.
Any help would be appreciated!
Basic :
array. filter - accept a callback and call back return boolean (that match our criteria)
array.map - accept a callback and call back return transformed object
In the second working example:
logins.map((login) => {
// note: logins is iterated but not assigned to logins back
// so accessing login is working
login.password = decrypt(login.password); // map should return data
+ return login; // if we update all code will work
});
Now coming to first example:
const logins = allLogins
.filter((login) => login.userId === user._id)
.map((login) => {
login.password = decrypt(login.password);
+ return login; // this will fix the issue
});

How to trigger caching a file upon loading of the page?

I have an inventory item search tool that filters through the huge json file (about 8mb) and if item was found it simply displays all the information regarding that item in a card format.
So now when I load the page, the caching of the file that is 8mb only begins once I start to input characters in the input box of my page.
How can I change this behavior and have the file cached upon loading the page as well as pass the json object to "get_item" function so its ready to match items as soon as I start inputting characters.
I tried using DOMContent.onload and various other onload triggers but no luck :(
Please help.
This is the js code:
const match_items = document.getElementById('match_items');
const get_item = async input_text => {
const inventory_file = await fetch('/static/json/inventory_cookware_2020.json');
const inventory_json = await inventory_file.json();
let item_matches = inventory_json.filter(item => {
const regex = new RegExp(`^${input_text}$`, 'gi');
return item.match(regex);
});
if (input_text.length < 10){
item_matches = [];
match_item.innerHTML = '';
}
display_item_data(item_matches);
};
function display_item_data(item_matches){
code to display item data...
}
search.addEventListener('input', () => get_item(search.value)); ```
HTML--------------------------------------
<div id="input_box">
<input type="search" id="search" placeholder="Search Inventory">
</div>
<div id="match_item"></div>
To have the fetch of the JSON file start when the page is first accessed, add the following code to the beginning. This uses an IIFE (Immediately Invoked Function Expression) to fetch and populate the data when the page first loads. The search input is disabled until the file has finished loading.
let inventory_json;
const search = document.getElementById('search');
search.addEventListener('input', () => get_item(search.value));
(async () => {
const inventory = await fetch('/static/json/inventory_cookware_2020.json');
inventory_json = await inventory_file.json();
search.disabled = false;
})();
const match_items = document.getElementById('match_items');
const get_item = input_text => {
// This ensures the function won't run until the AJAX is complete
// and inventory_json is populated
if (!is_loaded) {
return;
}
let item_matches = inventory_json.filter(item => {
const regex = new RegExp(`^${input_text}$`, 'gi');
return item.match(regex);
});
if (input_text.length < 10){
item_matches = [];
match_item.innerHTML = '';
}
display_item_data(item_matches);
};
HTML
<input type="text" id="search" placeholder="Search Inventory" disabled />

How to refer to the current state in FP?

I've already asked that question but my explanation was pretty bad, so I decided to ask again with a better explanation and with actual code (I'll ask moderators to delete one of the posts). So let's consider the problem.
Following snippet represents rendering notes from array. However, during the adding note part, I mutate a state. So the question is: how can I add a new note in notes array without mutating? In other words, I want to remove replaceNotes and remain the same functionality. I know that it's possible to add notes without array at all, but I do need to update array with notes in due to the future reference. The ting is, in my original application I've got lists with notes, and while I switch between lists, I should get rendered notes that relies to the list I switch on. That's why I should keep the reference to notes array.
At the same time I'm wondering, would it be okay, if I just store notes in localStorage and then take notes from that data? Is it a good practice in functional programming?
const button = document.getElementById('button');
const notesContainer = document.querySelector('.notes');
const pipe = (f, g) => (...args) => f(g(...args));
let notes = [];
const createNote = (...fns) => fns.reduceRight(pipe);
const handleEvent = () =>
createNote(gatherContent, renderContent, replaceNotes)(notes);
function gatherContent(notes) {
const name = prompt('How do you want to name a note?');
return [...notes, { name }];
}
function renderContent(notes) {
function render(note) {
const noteEl = document.createElement('div');
noteEl.innerHTML = `<p>${note.name}</p>`;
notesContainer.append(noteEl);
}
notesContainer.innerHTML = '';
notes.map(render);
return notes;
}
const replaceNotes = newNotes => (notes = newNotes);
button.addEventListener('click', handleEvent);
<button id="button">Click me!</button>
<section class="notes"></section>
Here is how to create a simple task list app without mutating anything except for the DOM.
const button = document.getElementById("button");
const section = document.getElementById("notes");
const template = document.getElementById("template");
template.parentNode.removeChild(template);
const render = notes => {
button.onclick = event => {
const name = prompt("How do you want to name a note?");
render([...notes, { name }]);
};
while (section.lastChild) {
section.removeChild(section.lastChild);
}
for (const note of notes) {
const node = template.cloneNode(true);
node.firstChild.firstChild.nodeValue = note.name;
section.appendChild(node);
}
};
render([]);
<button id="button">Click me!</button>
<section id="notes"></section>
<div id="template"><p>name</p></div>
For a detailed explanation, read my previous answer. https://stackoverflow.com/a/58642199/783743
You can use this pattern with localStorage too.
const button = document.getElementById("button");
const section = document.getElementById("notes");
const template = document.getElementById("template");
template.parentNode.removeChild(template);
const render = notes => {
localStorage.setItem("notes", notes); // set notes
button.onclick = event => {
const name = prompt("How do you want to name a note?");
render([...notes, { name }]);
};
while (section.lastChild) {
section.removeChild(section.lastChild);
}
for (const note of notes) {
const node = template.cloneNode(true);
node.firstChild.firstChild.nodeValue = note.name;
section.appendChild(node);
}
};
render(localStorage.getItem("notes") || []); // get notes
Note that localStorage should only be used to save state that you want to use across sessions. It's not recommended to use localStorage as your application store. That would result in both bad performance and bad code structure.

Categories