How can I pass a server-side variable to HTML and have the HTML page return an auto-filled form? - javascript

I am building an HTML page in Google Apps Script with CRUD functionality. The user can currently add data, edit data, and search data (I am not adding a delete feature). I would like the user to receive the form url link with an ID that when they go BACK to that link, it auto-fills the form with the previously added data.
In my HTML file, I have the following button defined:
document.getElementById("sbtn").addEventListener("click",getTID);
Once a user has entered data, it gets sent to a Google Sheet. The user HAS to enter a unique ID that they've already been provided. Using this ID, they can enter it, hit search, and it runs getTID():
function getTID() { //TID CODE
var transID = document.getElementById("tid").value;
if (transID.length === 36) {
google.script.run.withSuccessHandler(updateAllData).getID(transID);
} else {
alert("Transaction ID is not long enough.\nPlease copy the Transaction ID EXACTLY!\n\nFor Example: https:/workwebsiteconcealedforprivacy/w?txid=36275284-2ed6-4868-97b2-16bc1fde1a08\n\nThe Transaction ID is: 36275284-2ed6-4868-97b2-16bc1fde1a08")
}
}
This takes the ID they gave, references the spreadsheet and then returns values it found by index. Now, I have in my server-side GS file, the following in doGet:
var urlValue = '';
function doGet(e) {
// Test Code
var ss = SpreadsheetApp.openById(id);
var ws = ss.getSheetByName("Options");
var list = ws.getRange(1, 1, ws.getRange("A1").getDataRegion().getLastRow(), 1).getValues();
var htmlListArray = list.map(function (r) { return '<option>' + r[0] + '</option>'; }).join('');
var title = "Please Work";
var vals = JSON.stringify(e);
if ('v' in e.parameter){
urlValue = String(e.parameter['v']);
//return HtmlService.createHtmlOutput(urlValue);
}
return render("page",{list: htmlListArray, title});
and the following:
function include(filename) {
return HtmlService.createHtmlOutputFromFile(filename).getContent();
}
function render(file, argsObject) {
var tmp = HtmlService.createTemplateFromFile(file);
if (argsObject) {
var keys = Object.keys(argsObject);
keys.forEach(function (key) {
tmp[key] = argsObject[key]
});
}
return tmp.evaluate();
}
If I uncomment the return HtmlService.createHtmlOutput(urlValue); line, I can see that IF an ID is in the URL, it returns the correct parameter.
My problem is that I cannot get the HTML to read the urlValue variable and autorun getTID() when the user enters the url with the correct parameter. The correct functionality is that IF the parameter is found, it auto populates the HTML form. If it doesn't, it returns the blank form.

There is an error on
return render("page",{list: htmlListArray, title});
On {list: htmlListArray, title} the name of the second property is missing.
To "read the urlValue variable" there are two options:
pass the parameters from the URL using the event object of the doGet function. For this you have two options, create the HtmlService.HtmlOutput object from an html string generated using "vanilla" JavaScript or create it from a HtmlService.HtmlTemplate object.
get the parameters from the URL directly on the client-side code using google.script.url.getLocation .
If you go for the first option, then you should pass someway the urlValue to the render function. In the question code urlValue is a global variable, so you might add the following before the render's return statement.
tmp.urlValue = urlValue;
Then you have to add a scriptlet on the html file to handle this value and "autorun" getTID. Scriptlets is a feature of Templated HTML.

Related

Take selected text, send it over to Scryfall API, then take the link and put it in the selected text

I've been able to sort out the middle bit (the API seems to be called to just fine) along with the submenu displaying. Originally I thought that just the end part wasn't working but I'm now thinking that the selection part isn't either.
What am I doing wrong with the getSelection() and what do I need to do to insert a link into said selection? (to clarify, not to replace the text with a link, but to insert a link into the text)
//Open trigger to get menu
function onOpen(e) {
DocumentApp.getUi().createAddonMenu()
.addItem('Scry', 'serumVisions')
.addToUi();
}
//Installation trigger
function onInstall(e) {
onOpen(e);
}
//I'm not sure if I need to do this but in case; declare var elements first
var elements
// Get selected text (not working)
function getSelectedText() {
const selection = DocumentApp.getActiveDocument().getSelection();
if (selection) {
var elements = selection.getRangeElements();
Logger.log(elements);
} else {
var elements = "Lack of selection"
Logger.log("Lack of selection");
}
}
//Test run
// insert here
// Search Function
function searchFunction(nameTag) {
// API call + inserted Value
let URL = "https://api.scryfall.com/cards/named?exact=" + nameTag;
// Grabbing response
let response = UrlFetchApp.fetch(URL, {muteHttpExceptions: true});
let json = response.getContentText();
// Translation
let data = JSON.parse(json);
// Jackpot
let link = data.scryfall_uri;
// Output
Logger.log(link);
}
// Test run
searchFunction("Lightning Bolt");
//Let's hope this works how I think it works
function serumVisions() {
const hostText = getSelectedText();
const linkage = searchFunction(hostText);
// Unsure what class I'm supposed to use, this doesn't
const insertLink = DocumentApp.getActiveDocument().getSelection().newRichTextValue()
.setLinkUrl(linkage);
Logger.log(linkage);
}
For the first part, I tried the getSelection() and getCursor() examples from the Google documentation but they don't seem to work, they all just keep returning null.
For the inserting link bit, I read all those classes from the Spreadsheet section of the documentation, at the time I was unaware but now knowing, I haven't been able to find a version of the same task for Google Docs. Maybe it works but I'm writing it wrong as well, idk.
Modification points:
In your script, the functions of getSelectedText() and searchFunction(nameTag) return no values. I think that this might be the reason for your current issue of they all just keep returning null..
elements of var elements = selection.getRangeElements(); is not text data.
DocumentApp.getActiveDocument().getSelection() has no method of newRichTextValue().
In the case of searchFunction("Lightning Bolt");, when the script is run, this function is always run. Please be careful about this.
When these points are reflected in your script, how about the following modification?
Modified script:
Please remove searchFunction("Lightning Bolt");. And, in this case, var elements is not used. Please be careful about this.
From your script, I guessed that in your situation, you might have wanted to run serumVisions(). And also, I thought that you might have wanted to run the individual function. So, I modified your script as follows.
function getSelectedText() {
const selection = DocumentApp.getActiveDocument().getSelection();
var text = "";
if (selection) {
text = selection.getRangeElements()[0].getElement().asText().getText().trim();
Logger.log(text);
} else {
text = "Lack of selection"
Logger.log("Lack of selection");
}
return text;
}
function searchFunction(nameTag) {
let URL = "https://api.scryfall.com/cards/named?exact=" + encodeURIComponent(nameTag);
let response = UrlFetchApp.fetch(URL, { muteHttpExceptions: true });
let json = response.getContentText();
let data = JSON.parse(json);
let link = data.scryfall_uri;
Logger.log(link);
return link;
}
// Please run this function.
function serumVisions() {
const hostText = getSelectedText();
const linkage = searchFunction(hostText);
if (linkage) {
Logger.log(linkage);
DocumentApp.getActiveDocument().getSelection().getRangeElements()[0].getElement().asText().editAsText().setLinkUrl(linkage);
}
}
When you select the text of "Lightning Bolt" in the Google Document and run the function serumVisions(), the text of Lightning Bolt is retrieved, and the URL like https://scryfall.com/card/2x2/117/lightning-bolt?utm_source=api is retrieved. And, this link is set to the selected text of "Lightning Bolt".
Reference:
getSelection()

Xrm.Navigation.openForm set lookup field automatically from the previous form

I added a new button inside my "Parcours" form that takes me to another form "Question" with a Xrm.Navigation.openForm
The "Question" form has a lookup field from "Parcours" that I want to populate set automatically from the previous form like this for example :
This is my code for my button, I want to detect the ID of the parcours automatically and set it in the new "Question" form
function NavigateQuestion(){
var entityFormOptions = {};
entityFormOptions["entityName"] = "zs_question";
var formParameters = {};
// Set lookup column
formParameters["zs_parcours"] = ""; // I want to put the ID here from the previous form.
formParameters["zs_parcoursname"] = ""; // Name of the parcours.
formParameters["zs_parcourstype"] = "zs_parcours"; // Table name.
Xrm.Navigation.openForm(entityFormOptions, formParameters).then(
function (success) {
console.log(success);
console.log(formParameters);
},
function (error) {
console.log(error);
});
}
You should be able to get the values and assign from the current record form itself.
Make sure to pass the formContext for latest client API methods (I put the syntax in commented code section).
// Set lookup column
formParameters["zs_parcours"] = Xrm.Page.data.entity.getId(); // or by using form context -- formContext.data.entity.getId();
formParameters["zs_parcoursname"] = Xrm.Page.getAttribute("zs_name").getValue(); // or by using form context -- formContext.getAttribute("zs_name").getValue();
formParameters["zs_parcourstype"] = "zs_parcours";

Return Value from function in gs file to javascript file (HTML)

I need to return value (idValues) from gs file to javascript file.
GS file
function getMachineDB() {
var url = "https://docs.google.com/spreadsheets/d/1ZI16KqM7Uy_ZDTsTUFYFRSXS-DBkRdigp-3SiIy-
CPA/edit#gid=693962232";
var ss = SpreadsheetApp.openByUrl(url);
var sheet = ss.getSheetByName("Equiplist");
var idValues = sheet.getRange(2,4,sheet.getLastRow()).getValues();
return idValues;
}
Javascript file
function regExp(machinenoNum){
google.script.run.withSuccessHandler(function(idValues) {
var vmachineno = "/(" + idValues.join("|") + ")/";
return (vmachineno.test(machinenoNum));
}).getMachineDB();
}
function isValid() {
var x = document.getElementById("machineno");
if (regExp(x.value)) {
return true
} else {
machineno.setCustomValidity('nomor mesin tidak terdaftar');
return false
}
}
HTML file
<input type="text" id="machineno" class="form-input" name="entry.1002184227" placeholder="xx-xxx-xx" onChange="return isValid()" oninput="this.setCustomValidity('')" required/>
These code is comparing value from text box form with some data from Spreadsheet. How to get value of idValue from GS file to javascript file? thank you
Current behavior
To reproduce your behavior, I created a new project to test. I also used the values 1-9 in the spreadsheet for validation.
When you run the web app currently, it displays this in the console:
This relates to this code:
function regExp(machinenoNum){
google.script.run.withSuccessHandler(function(idValues) {
var vmachineno = "/(" + idValues.join("|") + ")/";
return (vmachineno.test(machinenoNum));
}).getMachineDB();
}
If you add some console.log to this code:
function regExp(machinenoNum){
google.script.run.withSuccessHandler(function(idValues) {
console.log(idValues)
var vmachineno = "/(" + idValues.join("|") + ")/";
console.log(vmachineno)
return (vmachineno.test(machinenoNum));
}).getMachineDB();
}
You get:
idValues is [[1],[2],[3],[4],[5],[6],[7],[8],[9]]
vmachineno is "/(1|2|3|4|5|6|7|8|9|)/"
vmachineno is just a string, it does not have any method called test on it. So this is why you get an error here.
There is also no action attached to not finding the value in the Spreadsheet, so even if the error was resolved, this would still not warn the user about invalid input.
Solution
function regExp(machinenoNum){
google.script.run.withSuccessHandler(function(idValues) {
// This just converts the `idValues` into simple array of strings.
const values = idValues.map(val => `${val[0]}`)
// Checking if the values from SS contain the machinenoNum value
// If this is true, function returns and does nothing.
if (values.includes(machinenoNum)) return true
// If the above check is false, then this will make an alert in the browser window.
alert("invalid input!")
})
.getMachineDB();
}
So when you enter a value that is in the spreadsheet, nothing will happen. If the value is not in the spreadsheet, then you get this:
EDIT - Walkthrough of code:
The page is loaded and you have an empty text box
The user inserts a value, for example 11 and presses ENTER.
This causes onChange="return isValid()" from the HTML to run.
isValid() will get the value from the text box with var x = document.getElementById("machineno");
Then will use this value here: if (regExp(x.value)) {
So now regExp is running.
This will now get the values from the spreadsheet and test if the x.value is contained there.
If it is not, then it will produce an alert:

Functions are not being executed as intended. I expect the page to switch to 'setup' but that doesn't happen

I'm creating a very basic log-in function on GAS HTML. In my HTML, I have a form that collects and stores user input with assigned variables (fn, ln, em, pw). It then sends the info to the sheet (which has formulas that verify the credentials) then a certain cell ('E2') will display "Found" or "Not Found". Then it will print the name in cell ('E1') if the if statement is false. This part does execute correctly, but the 2 functions, render('setup') and notFound() will not execute (regardless of when the if statement is true or false, neither one works.
I know the render('setup') function works perfectly fine in the doGet and when it is called in other functions.
function signIn(fn,ln,em,pw) {
var url = 'www.somelink.com'
var ss = SpreadsheetApp.openByUrl(url);
var login = ss.getSheetByName('login');
var fname = login.getRange('F1');
var lname = login.getRange('G1');
var email = login.getRange('H1');
var pword = login.getRange('I1');
fname.setValue(fn);
lname.setValue(ln);
email.setValue(em);
pword.setValue(pw);
var check = login.getRange('E2').getDisplayValue();
var name = login.getRange('F2').getDisplayValue();
if (check === "Not Found"){
return notFound(); // THIS DOESN'T EXECUTE
} else {
login.getRange('E1').setValue(name);
return render('setup'); // AND THIS DOESN'T EXECUTE
}
}
EDIT: HTML for the call signIn function.
<div class="button" align="center">
<button id="submit" class="btn waves-effect waves-light" onclick="var fn = document.getElementById('first_name').value;
var ln = document.getElementById('last_name').value;
var em = document.getElementById('email').value;
var pw = document.getElementById('password').value;
google.script.run.signIn(fn,ln,em,pw)">Submit
<i class="material-icons right">send</i>
</button>
If I understand you correctly, you want your web app to redirect the user to another HTML page after submitting the values, if some conditions are met.
You cannot do this by just calling a server-side function from the client-side: google.script.run is used to retrieve data that is not available on the client, but not to render a different HTML file.
What you could do instead is the following:
Instead of returning a function (like render or notFound), return a parameter that can be used on the client-side. For example, you could do:
function signIn(fn,ln,em,pw) {
// Rest of your code
if (check === "Not Found") {
return "notFound";
} else {
return "setup";
}
}
Your client-side function (which I've called callSignIn) can then pass this returned parameter to another function called redirect, thanks to withSuccessHandler(function) (this handler is necessary since google.script.run.signIn(fn,ln,em,pw) by itself will always return void):
function callSignIn() {
var fn = document.getElementById('first_name').value;
// Rest of your code
google.script.run.withSuccessHandler(refresh).signIn(fn,ln,em,pw);
}
Next, the value returned by signIn is passed as an argument of the function refresh, which refreshes the web app with window.open if the argument is setup, all the while passing setup as a query parameter (pageRedirect), which can later be used by the event parameter (you can retrieve the web app URL dynamically from the server-side instead of writing it manually — see this answer):
function refresh(redirectPage) {
if (redirectPage === "setup") {
window.open("https://script.google.com/macros/s/{your-web-app-id}/exec?pageRedirect=" + redirectPage, "_top");
} else {
// Whatever you want to do if "notFound"
}
}
Finally, back to the server-side, the function doGet gets called by the GET request from window.open. This function can check if there is a query parameter in the request via e.parameter, thanks to the event object, and render a different page depending on this parameter:
function doGet(e) {
if (!e.parameter.pageRedirect) {
return HtmlService.createHtmlOutputFromFile("index");
} else {
return HtmlService.createHtmlOutputFromFile(e.parameter.pageRedirect)
}
}
Reference:
Class google.script.run (Client-side API)
Window.open()
Web Apps: Request parameters

getting responseID of latest form submission in google apps

I have a google form. Every time it is submitted, the answers go into a google spreadsheet. I am trying to write an app script that triggers every time the form is submitted, and adds an "edit" link in the column to the right of the data from the form. The link itself is easy to generate, google has a method called getEditResponseURL(). (https://developers.google.com/apps-script/reference/forms/form-response)
But everytime I run it, I am getting the error "TypeError: Cannot call method "getResponses" of null."
Here is my code:
function addeditlink(e) {
// Get the active sheet
var sheet = SpreadsheetApp.getActiveSheet();
// Get the active row
var row = sheet.getActiveCell().getRowIndex();
//get the form
var form = FormApp.getActiveForm();
//get latest form response
var responses = form.getResponses();
var lastResponse = responses[responses.length - 1];
//get edit URL
var editurl = lastResponse.getEditResponseUrl();
//build link
var editlink = "Edit";
//place edit link in column R (index 18)
if (sheet.getRange(row, 18).getValue() == "") {
sheet.getRange(row, 18).setValue(editlink);
}
}
Any help? Thanks!
The solution is to remove:
var form = FormApp.getActiveForm(); //this is WRONG
and replace with:
var form = FormApp.openByID(' ID here ')
There is no "active form", because this script is being run in sheets, not forms.
I think you can only call FormApp.getActiveForm() from a script attached to a form, whereas your script is contained in a GSheet. I couldn't find a way to easily gets forms that used this sheet as its destination so what I've did was get all of the forms and then looked at the destination id of each and checked if it is the same as this spreadsheet. Once you've got your Form object you can get the responses. Feels a bit long winded would love to know if anyone knows a quicker way.
There are also a few exceptions that FormApp throws that you have to cope with.
Here's the function I use:
/**
* Find the first form that is linked to a specific spreadsheet
*
* #param {string} spreadsheet id
* #return {object} Form or null
*/
function getFormByDestinationId_(spreadsheetId) {
var formFiles = DriveApp.getFilesByType('application/vnd.google-apps.form');
var form;
var formFile;
var formId;
var destinationId;
while (formFiles.hasNext()) {
formFile = formFiles.next();
formId = formFile.getId();
// Throws an error if ID invalid
try {
form = FormApp.openById(formId);
} catch (error) {
if (error.name === "Exception") {
// Just ignore it
} else {
throw error;
}
}
// Form.getDestinationId() throws an error if there is no destination id
try {
destinationId = form.getDestinationId();
} catch (error) {
if (error.name === "Exception") {
// Just ignore it
} else {
throw error;
}
}
if (destinationId !== spreadsheetId) {
continue;
}
return form;
}
return null;
} // getFormByDestinationId_()
The only line using: getResponses() method is this one:
var responses = form.getResponses();
Your error:
Cannot call method "getResponses" of null
Means that form is null. If form is null, then this line:
//get the form
var form = FormApp.getActiveForm();
is not working. So, why isn't it working? There is nothing wrong with the code, so it must be a different problem. If there was an active form, that code would return a form type. This means that there is no form bound to the script. getActiveForm()
Returns the form to which the script is container-bound.
Your script is not "container-bound" to the form. Your script is bound to the spreadsheet.
The documentation states:
To interact with forms to which the script is not container-bound, use openById(id) or openByUrl(url) instead.
You can bind your script to the form by opening the script editor from the edit page of the form. But, there's no need to do that if you want to keep your script bound to the spreadsheet.
The line var form = FormApp.getActiveForm(); isn't going to work in your spreadsheet script.
The problem with using the Event Object e with an installable trigger, is that it looks like you can't get the response URL.
google_sheets_events
This means that you need to use openById(id) or openByUrl(url) inside the script bound to the spreadsheet, or move all your script to the form.
Here is how to get the edit url from script in the spreadsheet:
// Open a form by ID.
var form = FormApp.openById('1234567890abcdefghijklmnopqrstuvwxyz');
Now the problem is, that you can only get the Edit Response URL: getEditResponseUrl() through the "FormResponse" class. So you need the Form Responses.
var formResponses = form.getResponses();
But that's all the responses, you need the last one.
var lastResponseIndex = formResponses.length - 1;
var lastResponse = formResponses[lastResponseIndex];
var editURL = lastResponse.getEditResponseUrl();
or:
function getEditURLofLastResponse() {
// Open a form by ID.
var form = FormApp.openById('Your Form ID');
var formResponses = form.getResponses();
//get last respnse
var lastResponseIndex = formResponses.length - 1;
var lastResponse = formResponses[lastResponseIndex];
var editURL = lastResponse.getEditResponseUrl();
Logger.log(editURL);
}
Just an observation:
You are using an e argument: function addeditlink(e) {. But I don't see it being used in your code. That makes me wonder if you are using an "installable" trigger, as opposed to a "simple" trigger.
It's possible to get the values that were just submitted with e.values or e.namedValues. But you can't get the Edit URL with the Event Object.

Categories