We are writing a form with google recaptcha v3. The form needs to get the token before actually submitted. A colleague wrote this code and it works, the form submits without any problem. But I'm confused on why it would work? Why isn't it caught in an infinite loop when .submit() function is being called recursively?
jQuery.fn.extend({
grecaptcha: function (options) {
this.submit(function (e) {
e.preventDefault();
var key = options["recaptcha_site_key"];
var acdata = options["action_data"];
var ele = this;
grecaptcha.execute(key, { action: acdata }).then(function (token) {
$("<input>")
.attr({
type: "hidden",
name: "g-recaptcha-response",
value: token,
}).appendTo($(ele));
ele.submit();
});
});
},
});
$("#formID").grecaptcha(option);
Are there any other better approaches to request a token before submitting?
Per :
https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/submit
This method is similar, but not identical to, activating a form's submit . When invoking this method directly, however:
No submit event is raised. In particular, the form's onsubmit event handler is not run.
Your code sample is not calling the jQuery method to trigger a submit event on the form. That would, in fact, result in a loop. Try wrapping the ele variable jQuery. $(ele).submit () should result in a loop. By not wrapping the reference to this (e.currentTarget) in a jQuery object, and instead calling the DOM submit function, you are submitting the form without triggering an event or running the handler.
Makes sense?
Related
I am sending the contents of a form, ie the name, upload fields etc to an api. After hitting the submit button, a new tab opens and I am getting a response:
{"success":false,"error":{"code":0,"message":"The given data failed to pass validation.","errors":{"something_id":["The something id field is required."]}}}
This (json?) doesn't make sense to a „normal“ user. So is it possible to get the response from the api before a new tab opens and display it in a way, so a user could understand? Like „Success – you can close the tab“ or „There was an error – you need to do this again“?
I don't know much about api and json, so it would be fine to learn if this could/would work?
here is a workaround:
First you need to load jquery on your page by adding this code within the tag or before the closing tag
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Then give your form an ID (say my_form_id)
Then add this within your HTML
<script>
$(document).ready(function() {
// listen for when the form is submitted
$('#my_form_id').submit(function(event) {
// get the form data
var formData = $('#my_form_id').serialize();
// send the form to API using Ajax
$.ajax({
type : 'POST', // define the type of HTTP we want to use
url : 'your_full_api_url', // the url where you want to POST the data
data : formData, // your form data object
dataType : 'json', // what type of data do you expect from the API
encode : true
}).done(function(data) {
if(data.success==='true') {
//No error... all went well
alert('Your data was successfully submitted');
//You can do any other thing you want here
} else {
//get the error message and alert it
alert(data.message);
}
});
event.preventDefault();
});
});
</script>
Now what happens is that each time the form is submitted, the script is called and the form data is collected and submitted to your API URL using ajax.
The response is then parsed and checked if data was successfully submitted.
If not, it will use the browser alert function to print our the error message from your API.
I create google form 'on the fly' using data in spreadsheet. Also I install trigger on submit form event.
ScriptApp.newTrigger('onSubmit')
.forForm(form)
.onFormSubmit()
.create();
onSubmit function placed in the spreadsheet script because there is no way to point the function on the form's side (I make the copy of existent form with script code but it is no use as I can't make that functions run).
Well, I process the submission event on the spreadsheet side. No problem. But when I tried to get the source of 'e' object:
function onSubmit(e) {
var response, items, i, item, hash, answer, id;
var sheet, arr, source;
sheet = SpreadsheetApp.openById(RESPONSE_SS_ID).getSheetByName(RESPONSE_SHEET);
response = e.response;
source = e.source;
Logger.log(e);
...
I get not the Form object as promissed in manual, but Spreadsheet object
Logger.log([{response=FormResponse, source=Spreadsheet, triggerUid=4071774310898422364, authMode=FULL}
Perhaps, I'm doing something wrong? How to get the Form source properly in this case?
Clearly the form is not behaving as its documentation says it does, which has been documented in Google Code Issue 4810
Luckily, there is at least one workaround, provided in the comments on that issue, which is to use the getEditResponseUrl method of the response to get to the form. Here is my implementation of the fix in the form of a function that fixes up the event object to add the missing source:
function fixBrokenEvent (event) {
if (! event.source ) {
var responseEditUrl = event.response.getEditResponseUrl(); //gets edit response url which includes the form url
var responseUrl = responseEditUrl.toString().replace(/viewform.*/,''); //returns only the form url
event.source = FormApp.openByUrl(responseUrl); //gets the submitted form id
}
return event
}
This workaround does the trick for me. Another solution would be to use the Trigger UID and search through the list of triggers from ScriptApp.getProjectTriggers() for the right trigger UID.
Something like...
function fixEventWithTriggers (event) {
ScriptApp.getProjectTriggers().forEach(function (trigger) {
if (trigger.getUniqueId()==event.triggerUid) {
event.source = FormApp.openFormById(trigger.getSourceId())
return event
}
}
}
This last workaround comes from Comment #5 on Issue 3786
I created a google form and added a trigger which triggers whenever the form submit event is triggered. I need to use the event object for this event and when I add any line of code which tries to access this event then, an error occurs.
function onSubmit(e) {
var s = e.values[0];
Logger.log(s);
}
I get this error message when the function is triggered:
Execution failed: TypeError: Cannot read property "0" from undefined. (line 2, file "Code")
My form has one text input field (basically its just a form where I'm testing and trying out things with Google App Script), so I'm trying to access the data in this field when the form is submitted.
You can use the ActiveForm object instead of the event object.
function onSubmit() {
var responses = FormApp.getActiveForm().getResponses();
var length = responses.length;
var lastResponse = responses[length-1];
var formValues = lastResponse.getItemResponses();
Logger.log(formValues[0].getResponse());
}
This code does basically what you need (after you set up the trigger like you did).
Better explanation can be found here: google script get the current response onSubmit
Let's say we have a variable. This variable was created in google app script. On that app script project, you have two files. First, the .gs file, where the variable came from. Next, you have the html file. How do you transfer the variable to html?
GAS:
function doGet() {
return HtmlService.createHtmlOutputFromFile('index');
}
function items() {
var exmp = 45;
document.getElementById("test").innerHTML = "You have " + exmp + " items";
HTML:
<script>
google.script.run.items();
</script>
<div id="test"></div>
However, this doesn't work. How can I make this work?
If you read over the Private Functions section of the HTML service documentation, you'll find an example that does almost exactly what you're trying. The code below adapts that example to yours.
You need to keep the GAS server stuff separate from the HTML client stuff. For example, document.getElementById("test").innerHTML = ... means nothing in the context of the server / GAS code. Instead, the modification of the document will be done by Javascript on the client side - in this case, by a success handler.
A success handler is a client-side Javascript callback function that will receive the asynchronous response from your server function items().
Client-side calls to server-side functions are asynchronous: after the
browser requests that the server run the function doSomething(), the
browser continues immediately to the next line of code without waiting
for a response.
This means that there is no waiting for the return code from the call to your server function... the browser just keeps going. You'll see this in this example, as the "More loading..." text gets displayed after the google.script.run call, but before the response is received.
What if items() needs to do something more advanced... like read info from a spreadsheet? Go ahead and change it... just make sure that you return the text you want displayed, and that what you're returning is going to be valid HTML (so the innerHTML operation is OK).
Code.gs
function doGet() {
return HtmlService.createHtmlOutputFromFile('index');
}
function items() {
Utilities.sleep(5000); // Added to allow time to see the div content change
var exmp = 45;
return( "You have " + exmp + " items" );
}
index.html
<div id="test">Loading...</div>
<script type="text/javascript">
function onSuccess(items) {
document.getElementById('test').innerHTML = items;
}
google.script.run.withSuccessHandler(onSuccess).items();
document.getElementById('test').innerHTML = "More loading...";
</script>
You need first to create the HTML using the createHTMLOutput function
In order for you to append strings youo have to use the var.append method
function items(){
var email = Session.getActiveUser().getEmail();
var output = HtmlService.createHtmlOutput(email);
var string1 = HtmlService.createHtmlOutput('<p>You have </p>')
string1.append(output);
string2= HtmlService.createHtmlOutput('<p> items</p>')
string1.append(string2);
Logger.log(string1.getContent());
}
Reference
I'm having an issue with Google Apps Script state tokens when called from a managed library. This means a The state token is invalid or has expired. Please try again. error is always received is the state token is created from a sub function.
Here's some example code that would be in the library (you can add with project key MP9K5nBAvEJwbLYG58qx_coq9hSqx7jwh)
var SCRIPT_ID = "1eC5VsM2vkJXa9slM40MTKTlfARGAGyK1myMCU3AB_-Ox_jGxQaoPM8P2";
// get a callback url to render in popup
function getAuthURL() {
var authorizeURL = getCallbackURL('testCallback');
return authorizeURL;
}
// generate a user callback url
function getCallbackURL(callback) {
var state = ScriptApp.newStateToken().withTimeout(3600).withMethod(callback).createToken();
return 'https://script.google.com/macros/d/'+SCRIPT_ID+'/usercallback?state='+state;
}
// generate login popup
function showLogin(doctype){
doctype.getUi().showDialog(
HtmlService
.createTemplate("<div><p><a href='<?=getAuthURL()?>' id='start-auth'><?=getAuthURL()?></a></p>" +
"<p><a href='<?=getAuthURLStored()?>' id='start-auth'><?=getAuthURLStored()?></a></p></div>")
.evaluate()
.setSandboxMode(HtmlService.SandboxMode.NATIVE)
);
}
// dummy callback function
function testCallback(e){
return HtmlService.createHtmlOutput('<b>Success. You can close this window. !</b>')
}
/*
Rather than using dynamic state url storing the callback url and getting from property
(you could set a script trigger to refresh this every 24 hours)
*/
function getAuthURLStored() {
var authorizeURL = getSetCallbackURL();
return authorizeURL;
}
function setCallbackURL(){
PropertiesService.getScriptProperties().setProperty('callbackURL', getCallbackURL('testCallback'))
}
function getSetCallbackURL(){
return PropertiesService.getScriptProperties().getProperty('callbackURL')
}
which could be called in a Google Document as (assuming managed library identifier is statetest.
function testFunction() {
statetest.showLogin(DocumentApp);
}
When testFunction is run the dialog in the Document presents two urls, the first with a dynamic state url is invalid the second with a stored state token works.
Is this a bug or expected behaviour?
What you are trying to do currently isn't supported. Specifically creating a state token in a library running in an outer script, but having the callback go straight to the library. As of today the callback must always be directed at the outer script, which can then delegate back to the library as needed. You can open a feature request on the issue tracker to support your use case and we'll consider it further.
An example to use a library to handle an authentication flow is to publish a web app from the library which the user is directed to to being the authentication process.
var SCRIPT_ID = "1eC5VsM2vkJXa9slM40MTKTlfARGAGyK1myMCU3AB_-Ox_jGxQaoPM8P2";
// get a callback url to render in popup
function getAuthURL() {
var authorizeURL = getCallbackURL('testCallback');
return authorizeURL;
}
// generate a user callback url
function getCallbackURL(callback) {
var state = ScriptApp.newStateToken().withTimeout(3600).withMethod(callback).createToken();
return 'https://script.google.com/macros/d/'+SCRIPT_ID+'/usercallback?state='+state;
}
// generate login
function doGet(e){
return HtmlService.createTemplate("<div><p><a href='<?=getAuthURL()?>' id='start-auth'><?=getAuthURL()?></a></p></div>")
.evaluate());
}
enter code here
// dummy callback function
function testCallback(e){
return HtmlService.createHtmlOutput('<b>Success. You can close this window. !</b>')
}