Global variable not updating as expected - javascript

I am building a small tool using Apps Script. I can't seem to get the global variable (URL) to update to include the docId.
What I expect
Run the App function, which first creates a Google Doc and sets the global variable for the documentId (docId). Second it calls myOnClickhandler, which should take the global url variable that includes the updated docId.
What is happening:
The variable 'url' in myClickHandler is not updated to the value of 'docId' set in the createProject function.
var docId = "";
var url = "https://docs.google.com/document/d/" + docId + "/";
function doGet() {
var output = HtmlService.createHtmlOutputFromFile('MyUI');
return output;
}
function createProject(test="test"){
const doc = DocumentApp.create(test);
docId = doc.getId();// <-- Update global docId variable. Doesn't seem to be working.
Logger.log("Doc ID is: " + docId);
}
function myOnClickHandler(){
const myList = ["Hello from the server", url]
Logger.log("URL is: " + url);
}
function app(){
createProject();
myOnClickHandler();
}
Probably a simple mistake on my part, but I am banging my head against the wall on this one. Screenshot attached.

In JavaScript, strings are immutable. Use an arrow function and a template literal instead of a static text string, like this:
let docId;
const getUrl_ = (docId) => `https://docs.google.com/document/d/${docId}/`;
function test() {
docId = 'testString';
const url = getUrl_(docId);
console.log(url);
}

Related

Best way to handle parameters when calling functions in functions

I'm writing a function in Javascript that will verify the existence of a particular kind of file and if it does not exist, then it will copy the file from a known location in a git repository to the correct location.
To do this, I'm also using a function I wrote that verifies the existence of any file (only at certain paths that we've pre-defined). Also, file.exists is a function prebuilt in our IDE.
That function looks like this:
function verifyFileExistence(file, path, existState)
{
var result;
var logMessage;
var resultMessage;
if (existState == true)
{
logMessage = "Verify that \"\"" + file + "\"\" exists.";
result = (File.exists(path + file));
if (result)
{
resultMessage = "\"\"" + file + "\"\" exists.";
}
else
{
resultMessage = "\"\"" + file + "\"\" does not exist.";
}
}
else
{
logMessage = "Verify that \"\"" + file + "\"\" does not exist.";
result = (!File.exists(path + file ));
if (result)
{
resultMessage = "\"\"" + file + "\"\" does not exist.";
}
else
{
resultMessage = "\"\"" + file + "\"\" exists.";
}
}
resultVP(logMessage, resultMessage, result)
}
Side Note: Each of these functions will write results to a log file which is why the different result/log/message variables appear. I left them in because I think they help to show make the logic clear.
So far, my function to check for the specific file type looks something like this:
import {copyFile,verifyFileExistence} from 'Path\\to\\FileUtilityLibrary.js';
function verifyLoadFile(file, path, existState, inFile, outFile)
{
var exist;
exist = (verifyFileExistence(file, path, existState));
if (exist != true)
{
copyFile(inFile,outFile)
}
}
I feel like having this many parameters in the function is inefficient and that maybe there's a more efficient way of handling them. Can I somehow simply this or is this the best way to handle parameters when calling functions inside a function?
You can do one object, for example:
const object = {
file,
path,
existState,
inFile,
outFile
}
and handle only one parameter.
I'm going to assume you are using javascript ES6 with your import notation, that means you can use the spread operator to do this
import {copyFile,verifyFileExistence} from 'Path\\to\\FileUtilityLibrary.js';
function verifyLoadFile(inFile, outFile, ...fileParams)
{
var exist;
exist = (verifyFileExistence(...fileParams));
if (exist != true)
{
copyFile(inFile,outFile)
}
}
with fileParams being all the params you need to pass to your child function. With this notation you can have a variable number of parameters passed into this function.
As liskaandar's answer points out, an object would be the best way to reduce the number of parameters.
If you don't want to reference the object each time you use those variables, you can destructure the object to give you workable variables.
For example, if passing an object that looks like this:
const object = {
file,
path,
existsState,
infile,
outfile
}
you can destructure it within your verifyFileExistence(fileObject) function by doing the below:
var {file,
path,
existsState,
infile,
outfile} = fileObject;
You can then reference those objects like normal by calling the normal variable names like file and path again.
I thought in a way to optimize the code and this is what I would use in a scenario like yours:
// verifyLoadFile function only needs three params (repositoryPath, localPath, fileName) and return an object with the log result and message
function verifyLoadFile(repositoryPath, localPath, fileName){
var exists = (!File.exists(localPath+ fileName)); //Check if file exists
var logMessageHeader = "Verify that "; //Header of message log
var messageSuccess = "\"\" exists.";
var messageWarning = "\"\" does not exists.";
var resultMessage = "\"\"" +fileName; //Initialize resultMessage with redundant parth of message
if(exists){
resultMessage = resultMessage + messageSuccess;
} else {
resultMessage = resultMessage + messageWarning;
copyFile(repositoryPath+fileName, localPath+fileName);
}
return {
logMessage: resultMessage
resultMessage: logMessageHeader + resultMessage;
};
};
// This is a function that initialize the sync of repository
function syncRepository(typeOfRepository){
var repositoryConfig = getConfig(typeOfRepository); // In the example I get an array of objects or arrays that contains config needed
repositoryConfig.forEach((obj) => {
const {logMessage,resultMessage} = verifyLoadFile(obj.repositoryPath, obj.localPath, obj.fileName); //Retrieve log values for each file
resultVP(logMessage, resultMessage); //Log the result
});
}
In this way, you only need a 14-line function (verifyLoadFile) that verify if file exist, generate log messages and copy the file if it not exists, then only if needed log the result returned in each iteration
So i think, answering your initial question, that the best way to handle parameters in functions is optimize the code.
I hope it help you.

Access Firebase database

I have a project with Firebase. I want to have access to the attribute "text" of my database with a cloud function as soon as there is a recent branch added to my database.
I am not get used to using their database.
My code in NodeJs is bellow :
exports.messageAnswer = functions.database.ref('/messages')
.onWrite(event => {
const snapshot = event.data;
const val = snapshot.val();
var textMsg = val.text;
var regex = "(bot)";
//var database = firebase.database();
if(!textMsg.match(regex) && textMsg.length > 0){
var id = new Date().getTime().toString();
var object = {
text : "I am the Cloud bot #" + id,
};
admin.database().ref('/messages').push(object);
}
return 0;
});
My problem is the fact that there is a "PUSH ID" : I don't know how to contourne this unique string to get the value of the attribute "text".
You can use wildcards to get the text attribute some something like this:
exports.messageAnswer = functions.database.ref('/messages/{pushid}')
.onWrite(event => {
for more info check this: https://firebase.google.com/docs/database/extend-with-functions
You can specify a path component as a wildcard by surrounding it with curly brackets; ref('foo/{bar}') matches any child of /foo. The values of these wildcard path components are available within the event.params object of your function. In this example, the value is available as event.params.bar
Your current code is being triggered whenever any data under /messages changes. From your description it seems you only want to be triggered when a new message is added. You do that by using onCreate instead of onWrite. In addition you'll want to listen for changes to a specific message, instead of to the entire /messages node:
exports.messageAnswer = functions.database.ref('/messages/{messageId}')
.onCreate(event => {
Also see Doug's blog post on these triggers: https://firebase.googleblog.com/2017/07/cloud-functions-realtime-database.html

In Karate DSL, calling a javascript file returns java.lang.RuntimeException

I have a javascript file I want to call. contents are below. When I tried calling the file, I keep getting a "no variable found with name: response" even though there is clearly a variable defined. The file executes fine within command-line using node so the javascript function is valid. Any thoughts? I attached the error message in a screenshot.
Javascript content in snippet below.
Karate script:
Scenario: Call JavaScript:
* def sample = read('classpath:reusable/gen-data.js')
* print someValue
function createTestData(sampleJson, fieldsToChange, numRecords) {
var testData = [];
for (var i = 0; i < numRecords; i++) {
var copy = JSON.parse(JSON.stringify(sampleJson));
fieldsToChange.forEach(function(fieldToChange) {
copy[fieldToChange] = copy[fieldToChange] + i;
});
testData.push(copy);
}
return {content: testData};
}
var testData = {
"country": "US",
"taskStatusCode" : "Closed",
"facilityCode" : "US_203532",
};
function getTestData() {
String testData = JSON.stringify(createTestData(testData, ["taskStatusCode", "facilityCode"], 1), null, 1);
console.log("all done getTestData()");
console.log("test data: \n" + testData);
return testData;
};
console.log("calling getTestData()");
getTestData();
I think this error is thrown when the JavaScript is not correct. For example in my case this JS file:
/* Set the custom authentication header */
function fn() {
var authToken = karate.get('authToken');
var out = {};
out['Auth-Token'] = authToken
return out;
}
This file will produce the "no variable found with name: response".
The reason is because "the right-hand-side (or contents of the *.js file if applicable) should begin with the function keyword." according to the karate docs (link).
Now by moving the comment and making the function keyword the first bit of text it works as expected:
function fn() {
/* Set the custom authentication header */
var authToken = karate.get('authToken');
var out = {};
out['Auth-Token'] = authToken
return out;
}
In the OP, the function keyword is the first thing in the file, but there is javascript outside the original function -- which I don't think is legal for karate syntax. In other words, everything has to be in the outer function.
My workaround was to use java instead of JavaScript.

Programmatically referencing a javascript file at runtime

I am having a problem programatically referencing a javascript file from
a javascript file. Normally I would just reference it in an html page but can't in this case as the directory location of the file is only available at runtime. I have checked out several posts on this topic but as of yet have been unable to find anything that works. Below are code snippets from 4 files:
index.html assigns an ID to the header tag and references the first of two javascript files called scriptFile.js.
scriptFile.js, is a global data file for all new users.
A second file, also called scriptFile.js, is a user specific data file created for each user at runtime. The second javascript file contains data specific to a single user, is located in a different directory than index.html scriptFile.js, but otherwise is structurally identical to the first scriptFile.js. In both cases the files consist of a function returning an array object.
The last file is index.js. Its purpose is to programmatically reference the second scriptFile.js file identified above.
When index.js runs, the value for scripts.length = 2 which is what it is supposed to be. scripts[0].getAttribute('id') and
scripts[0].getAttribute('src') both output correctly. However, while scripts[1].getAttribute('id') outputs correctly as
id = "scriptOneID", the scripts[1].getAttribute('src') outputs as null which is obviously incorrect. A second problem is
that the data for the scriptOneID.enabled property outputs as true, when it should be outputing as false. This tells me
that the object = loadData() statement is referencing data from the index.html scriptFile.js and not the
index.js userNumber/scriptFile.js. This has been verified in testing.
Here are the code snippets. Suggestions or ideas on how to correct this are appreciated. Thanks...
// 1. index.html page
<head id="headID">
<script id="scriptZeroID" type="text/javascript" src="scriptFile.js">
</script>
// 2. scriptFile.js
function loadData()
{
var object = [{ property: 0, enabled: true }];
return object;
};
// 3. userNumber/scriptFile.js
function loadData()
{
var object = [{ property: 0, enabled: false }];
return object;
};
// 4. index.js page
//global declarations and assignments
var object = new Array;
object = loadData(); // assign the data in loadData function to an object array
// local declarations and assignments
var head = document.getElementById("headID");
var script = document.createElement("script");
var userNumber = sessionStorage.getItem("userNumber");
var scriptPath = userNumber + "/scriptFile.js";
script.id = "scriptOneID";
script.type = "text/javascript";
script.scr = scriptPath;
head.appendChild( script );
var scripts = document.getElementsByTagName("script");
console.log("script: " + scripts.length); // output = 2
for (var i = 0; i < scripts.length; i++)
{
console.log("output: " + scripts[i].getAttribute('id') + " / " + scripts[i].getAttribute('src'));
// output for scripts[0]: id = "scriptZeroID", scr = "scriptFile.js"
// output for scripts[1]: id = "scriptOneID", scr = null
};
object = loadData(); // assign the data in loadData function to an object array
console.log("print test value: " + object[1].enabled ); // output = true // this is incorrect, it should = false
Your issue is a script load time problem. In other words it's an asynchronous problem.
Javascript these days is fast. Really fast. Your script gets to your last call to "loadData" long before the script you injected into the head gets loaded. Therefore you are accessing the original version of "loadData" twice.
Just to clarify my comment on the "notification". Add a function like this (as an example) to you index.js file:
function populateObj() {
object = loadData();
}
Then at the bottom of your injected script add:
populateObj();
That's all there is too it. When the injected script finishes loading and is executed your "object" global variable will have the correct data in it.
JQuery has a function to load scripts at any time after page load.
Looks like this:
$.getScript("yourscripttoload.js", function(){
console.log("Script loaded and executed.");
});
Maybe look into that to see if it will work in your situation.
This is a complete poc solution on how to the programatically reference a javascript file from within a javascript file using a callback function that does not require jQuery. It is based on the premise that the same script file will be run during the page load and then again from a different location, with different data, during runtime . The following solution replaces index.js file in the above example.
// global declarations & assignments
var head = document.getElementById("headID");
var script = document.createElement("script");
var userNumber = sessionStorage.getItem("userNumber");
var scriptPath = userNumber + "/scriptFile.js";
// call back function
function loadScript(scriptPath, callback)
{
script.id = "scriptOneID";
script.type = "text/javascript";
script.src = scriptPath;
script.async = false;
script.defer = false;
// bind the load event to the callback function
script.onreadystatechange = callback;
script.onload = callback;
// append the script to the page header
head.appendChild(script);
};
// place the code that you want to run inside an anonymous function
var anonymousFunction = function()
{
var scripts = document.getElementsByTagName("script");
console.log("script: " + scripts.length); // output = 2
for (var i = 0; i < scripts.length; i++)
{
console.log("output: " + scripts[i].getAttribute('id') + " / " +
scripts[ i].getAttribute('src'));
// output for scripts[0]: id = "scriptZeroID", scr = "scriptFile.js"
// output for scripts[1]: id = "scriptOneID", scr = "1234567890/scriptFile.js" where userNumber = "1234567890"
};
object = loadData(); // assign the data in loadData function to an object array
console.log("print test value: " + object[1].enabled ); // output = false this is correct
};
// execute the loadScript function
loadScript( scriptPath, anonymousFunction );

Passing through JS variables onchange

I'm trying to pass through three variables (artistID, albumID and currently selected value) to function player but I keep getting an error each time I try this:
var carry_else = 'actorID,movieID,this.value'
This is the code as it stands I have been trying to edit var carry_else to no success:
actorID = movie_select.substring(0, movie_select.indexOf(':'));
movieID = movie_select.substring(movie_select.indexOf(':')+1, movie_select.length);
// this line needs to be edited to var carry_else = 'actorID,movieID,this.value' but doing this doesn't work
var carry_else = 'this.value,this.value,this.value'
hstr+="<form><select size='"+count+"' onChange='parent.info(" + carry_else + ");'>";
How can I solve this issue?
Since actorID and movieID appear to be strings, why not put them in as literals:
function escapeHtml(str) {
return str.replace(/&/g,'&').replace(/>/g,'>').replace(/</g,'<').replace(/"/g,'"');
};
function quoteString(str) {
return '"'+str.replace(/"/g,'\\"')+'"';
}
hstr+="<form><select size='"+count+"' onChange='parent.info("+escapeHtml(quoteString(actorID))+","+escapeHtml(quoteString(movieID))+",this.value);'>";
If available, I'd use JSON.stringify() instead of the quoteString function I put above.

Categories