I'm using GAS to send and receive texts. There is one function that send texts (sendTexts.gs) and one that receives (receiveTexts.gs). I have both of these functions linked to individual buttons on the sheet, but when I run one function, both are running (texts get sent every time). Is there a cache or something that needs to be cleared? The receiveTexts has no commands in the code that could send messages in it, and based on logger testing, I know that both are running when I only click one.
EDIT: This also occurs in the GAS "terminal". If I click run in the script editor both run.
Here is the code with personal/individual info (codes/phone numbers edited out):
function sendSms(to, body) {
var playerArray = getMeta();
Logger.log(playerArray);
var messages_url = "https://api.twilio.com/2010-04-01/Accounts/EDIT/Messages.json";
var payload = {
"To": to,
"Body" : body,
"From" : "EDIT"
};
var options = {
"method" : "post",
"payload" : payload
};
options.headers = {
"Authorization" : "Basic " + Utilities.base64Encode("EDIT")
};
UrlFetchApp.fetch(messages_url, options);
}
function sendAll() {
var spreadsheet = SpreadsheetApp.getActive();
var text = SpreadsheetApp.setActiveSheet(spreadsheet.getSheetByName('Meta')).getRange('B4').getValue();
var playerArray = getMeta();
Logger.log(text);
for (i=0; i<playerArray.length;i++) {
try {
var number = playerArray[i][1];
Logger.log(number);
response_data = sendSms(number, text);
status = "sent";
} catch(err) {
Logger.log(err);
status = "error";
}
Logger.log(status);
}
}
function sendTexts() {
sendAll();
}
Logger.log("ran send texts");
Here is the receive texts code with the same adjustments:
function receiveTexts() {
var spreadsheet = SpreadsheetApp.getActive();
var ACCOUNT_SID = "EDIT";
var ACCOUNT_TOKEN = "EDIT";
var toPhoneNumber = "+EDIT";
var sheet = spreadsheet.setActiveSheet(spreadsheet.getSheetByName('Meta'), true);
var playerArray = getMeta();
var numberToRetrieve = playerArray.length;
var hoursOffset = 0;
var options = {
"method" : "get"
};
options.headers = {
"Authorization" : "Basic " + Utilities.base64Encode(ACCOUNT_SID + ":" + ACCOUNT_TOKEN)
};
var url="https://api.twilio.com/2010-04-01/Accounts/" + ACCOUNT_SID + "/Messages.json?To=" + toPhoneNumber + "&PageSize=" + numberToRetrieve;
var response = UrlFetchApp.fetch(url,options);
// -------------------------------------------
// Parse the JSON data and put it into the spreadsheet's active page.
// Documentation: https://www.twilio.com/docs/api/rest/response
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('Meta'), true);
var numRounds = spreadsheet.getRange('B2').getValue();
var theSheet = SpreadsheetApp.setActiveSheet(spreadsheet.getSheetByName('Attacks'),true);
var theColumn = (numRounds*2)+1;
var dataAll = JSON.parse(response.getContentText());
for (i=0; i<dataAll.messages.length; i++){
var sentNumber = dataAll.messages[i].from;
Logger.log(sentNumber);
for (k=0; k<playerArray.length;k++){
Logger.log(playerArray[k][1]);
if (playerArray[k][1]==sentNumber){
var player = k;
Logger.log('Success');
Logger.log(player);
break;
}
}
var playerRow = playerArray[player][0];
Logger.log(playerRow);
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('Attacks'),true);
theSheet.getRange(playerRow, theColumn).setValue(dataAll.messages[i].body);
}
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('Meta'), true);
sheet.getRange(2,2).setValue(numRounds +1);
}
Logger.log("texts received ran");
EDIT2: I separated out getMeta into a separate file. Now when I run getMeta, it also runs the other two scripts. Anytime any one script is run, all three run. This makes me think it's not related to the code, but related to some setting or something. Does the order of the scripts in the sidebar matter? I feel like that's not it because running any of the three causes all three to be run.
Related
I'm trying to have a function run with a time-driven (clock) trigger. My issue is, the function needs variables sent to it as parameters. Normally if I'm not using a trigger I'll just send it directly like this.
<script>
function runFunction(){
google.script.run.myFunction(x,y,z);
}
</script>
And on the server side, I'll call them easily.
function myFunction(x,y,z){
var a = x;
var b = y;
var c = z;
Logger.log(a+b+c);
}
But when I'm using a time-driven (clock) trigger to run the function how can I get x,y,z into the function.
I searched around and saw one method of creating scriptProperties out of the parameters in the trigger function like this.
EDITED This is the actual code.
Client side.
<script>
function sendCall() {
var testNumber = document.getElementById('numberCall').value;
var testGroup = document.getElementById('groupsCall').value;
var testtime = document.getElementById('scheduleCall').value;
var now = document.getElementsByName('sendTimeCall')[0].checked;
var number;
if(testNumber == ''){
number = null;
}else{
number = testNumber;
}
var group;
if(testGroup == ""){
group = null;
}else{
group = testGroup;
}
var time;
if(testtime == ''){
time = null;
}else{
time = testtime;
}
var file = document.getElementsByName('audio')[0].files[0];
var name = file.name;
var reader = new FileReader();
reader.onload = function (e) {
var content = reader.result;
google.script.run.withSuccessHandler(success2).triggerCall(group, number, content, time, now, name);
return false;
}
reader.readAsDataURL(file);
}
</script>
Server side - Trigger Function.
function triggerCall(group, number, content, time, now, name){
var scriptProperties = PropertiesService.getScriptProperties();
scriptProperties.setProperties({
'GROUP_CALL': group,
'AUDIO': content,
'NUMBER_CALL': number,
'FILE_NAME': name
});
var status;
if(now){
status = 'Call Sent';
}else{
status = 'Call Scheduled';
}
if(now){
return makeCall(status);
}else{
// Set here the date you want to schedule the one-time trigger
var rawdate = time;
var today_D = new Date(new Date().toLocaleString("en-US", {timeZone: "America/New_York"}));
var scheduled_D = new Date(rawdate);
var time_af = Math.abs(scheduled_D - today_D) / 36e5;
ScriptApp.newTrigger("makeCall")
.timeBased()
.after(time_af * 60 *60 * 1000)
.create();
}
return status;
}
Server side - Here is The function that actually does the work.
function makeCall(status) {
var scriptProperties = PropertiesService.getScriptProperties();
var blob = scriptProperties.getProperty('AUDIO');
var number = scriptProperties.getProperty('NUMBER_CALL');
var group = scriptProperties.getProperty('GROUP_CALL');
var name = scriptProperties.getProperty('FILE_NAME');
var fullNumber;
if(group){
var ss = SpreadsheetApp.openById('xxxxxxxxxxxxxxxxxxx');
var sheet = ss.getSheetByName(group)
var length = sheet.getLastRow();
var values = sheet.getRange(1, 1, length).getValues();
fullNumber = values.flat();
}else{
var num = number;
var prefix = '+1';
var removeDashes = num.replace(/-/g,"");
var format = prefix + removeDashes;
var comma = format.replace(/ /g, ' +1');
fullNumber = comma.split(' ')
}
//upload file to drive
var folder = DriveApp.getFolderById('xxxxxxxxxxxxxxxxxxxxxx');
var blob = blob.split(",");
var blob = Utilities.newBlob(Utilities.base64Decode(blob[1]), 'audio/mpeg', name);
var file = folder.createFile(blob);
file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);
var id = file.getId();
for (var i = 0; i < fullNumber.length; i++){
//the url with HTTP request to create a call and parameters
var callsUrl = "https://api.twilio.com/2010-04-01/Accounts/ACxxxxxxxxxxxxxxxx/Calls.json";
var payload = {
"To": fullNumber[i],
"From" : "+177777777",
"Twiml" : "<Response><Play>https://docs.google.com/uc?export=play&id=" + id + "</Play></Response>",
};
var options = {
"method" : "post",
"payload" : payload
};
options.headers = {
"Authorization" : "Basic " + Utilities.base64Encode("xxxxxxxxxxx:xxxxxxxx")
};
UrlFetchApp.fetch(callsUrl, options);
}
scriptProperties.deleteProperty('AUDIO');
scriptProperties.deleteProperty('NUMBER_CALL');
scriptProperties.deleteProperty('GROUP_CALL');
scriptProperties.deleteProperty('FILE_NAME');
return status;
}
The problem is when I run the above code without the file input it works, But when I run it as above the function doesn't work. I did some trouble shooting and I think it has to do with transferring the file as a Data URL via the properties method. Is there a limit to how long of a string the VALUE can be?
in a nut shell these are the 2 points of my question.
Any other ideas how to send parameters to a triggered function
How I could do it using PropertiesService.
I would like to propose the following modification.
Modification points:
I think that the reason of your issue might be due to the maximum data size for PropertiesService. In the current stage, it seems that "Properties total storage" is "500kB / property store". I thought that in this case, when you upload a file, the file size might be over than it.
In order to remove your issue, how about creating content as a temporal file and put the file ID to the PropertiesService?
When above points are reflected to your script, it becomes as follows.
Pattern 1:
In this pattern, content is saved as a temporal file and that is used in the function of makeCall().
triggerCall()
function triggerCall(group, number, content, time, now, name){
var scriptProperties = PropertiesService.getScriptProperties();
var tempFile = DriveApp.createFile("tempFile.txt", content, MimeType.PLAIN_TEXT); // Added
scriptProperties.setProperties({'GROUP_CALL': group,'AUDIO': tempFile.getId(),'NUMBER_CALL': number,'FILE_NAME': name}); // Modified
var status;
if(now){
status = 'Call Sent';
}else{
status = 'Call Scheduled';
}
if(now){
return makeCall(status);
}else{
var rawdate = time;
var today_D = new Date(new Date().toLocaleString("en-US", {timeZone: "America/New_York"}));
var scheduled_D = new Date(rawdate);
var time_af = Math.abs(scheduled_D - today_D) / 36e5;
ScriptApp.newTrigger("makeCall").timeBased().after(time_af * 60 *60 * 1000).create();
}
return status;
}
makeCall()
function makeCall(status) {
var scriptProperties = PropertiesService.getScriptProperties();
var tempfileId = scriptProperties.getProperty('AUDIO'); // Modified
var number = scriptProperties.getProperty('NUMBER_CALL');
var group = scriptProperties.getProperty('GROUP_CALL');
var name = scriptProperties.getProperty('FILE_NAME');
var fullNumber;
if(group){
var ss = SpreadsheetApp.openById('xxxxxxxxxxxxxxxxxxx');
var sheet = ss.getSheetByName(group)
var length = sheet.getLastRow();
var values = sheet.getRange(1, 1, length).getValues();
fullNumber = values.flat();
}else{
var num = number;
var prefix = '+1';
var removeDashes = num.replace(/-/g,"");
var format = prefix + removeDashes;
var comma = format.replace(/ /g, ' +1');
fullNumber = comma.split(' ')
}
var folder = DriveApp.getFolderById('xxxxxxxxxxxxxxxxxxxxxx');
var tempFile = DriveApp.getFileById(tempfileId); // Added
var text = tempFile.getBlob().getDataAsString(); // Added
tempFile.setTrashed(true); // Added
var blob = text.split(","); // Modified
var blob = Utilities.newBlob(Utilities.base64Decode(blob[1]), 'audio/mpeg', name);
var file = folder.createFile(blob);
file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);
var id = file.getId();
for (var i = 0; i < fullNumber.length; i++){
var callsUrl = "https://api.twilio.com/2010-04-01/Accounts/ACxxxxxxxxxxxxxxxx/Calls.json";
var payload = {"To": fullNumber[i],"From" : "+177777777","Twiml" : "<Response><Play>https://docs.google.com/uc?export=play&id=" + id + "</Play></Response>"};
var options = {"method" : "post","payload" : payload};
options.headers = {"Authorization" : "Basic " + Utilities.base64Encode("xxxxxxxxxxx:xxxxxxxx")};
UrlFetchApp.fetch(callsUrl, options);
}
scriptProperties.deleteProperty('AUDIO');
scriptProperties.deleteProperty('NUMBER_CALL');
scriptProperties.deleteProperty('GROUP_CALL');
scriptProperties.deleteProperty('FILE_NAME');
return status;
}
Pattern 2:
In this pattern, content is saved to a file as the decoded data and that is used in the function of makeCall().
triggerCall()
function triggerCall(group, number, content, time, now, name){
var scriptProperties = PropertiesService.getScriptProperties();
var folder = DriveApp.getFolderById('xxxxxxxxxxxxxxxxxxxxxx'); // Added
var blob = content.split(","); // Added
var blob = Utilities.newBlob(Utilities.base64Decode(blob[1]), 'audio/mpeg', name); // Added
var file = folder.createFile(blob); // Added
scriptProperties.setProperties({'GROUP_CALL': group,'AUDIO': file.getId(),'NUMBER_CALL': number,'FILE_NAME': name}); // Modified
var status;
if(now){
status = 'Call Sent';
}else{
status = 'Call Scheduled';
}
if(now){
return makeCall(status);
}else{
var rawdate = time;
var today_D = new Date(new Date().toLocaleString("en-US", {timeZone: "America/New_York"}));
var scheduled_D = new Date(rawdate);
var time_af = Math.abs(scheduled_D - today_D) / 36e5;
ScriptApp.newTrigger("makeCall").timeBased().after(time_af * 60 *60 * 1000).create();
}
return status;
}
makeCall()
function makeCall(status) {
var scriptProperties = PropertiesService.getScriptProperties();
var fileId = scriptProperties.getProperty('AUDIO'); // Modified
var number = scriptProperties.getProperty('NUMBER_CALL');
var group = scriptProperties.getProperty('GROUP_CALL');
var name = scriptProperties.getProperty('FILE_NAME');
var fullNumber;
if(group){
var ss = SpreadsheetApp.openById('xxxxxxxxxxxxxxxxxxx');
var sheet = ss.getSheetByName(group)
var length = sheet.getLastRow();
var values = sheet.getRange(1, 1, length).getValues();
fullNumber = values.flat();
}else{
var num = number;
var prefix = '+1';
var removeDashes = num.replace(/-/g,"");
var format = prefix + removeDashes;
var comma = format.replace(/ /g, ' +1');
fullNumber = comma.split(' ')
}
var file = DriveApp.getFileById(fileId); // Modified
file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);
var id = file.getId();
for (var i = 0; i < fullNumber.length; i++){
var callsUrl = "https://api.twilio.com/2010-04-01/Accounts/ACxxxxxxxxxxxxxxxx/Calls.json";
var payload = {"To": fullNumber[i],"From" : "+177777777","Twiml" : "<Response><Play>https://docs.google.com/uc?export=play&id=" + id + "</Play></Response>"};
var options = {"method" : "post","payload" : payload};
options.headers = {"Authorization" : "Basic " + Utilities.base64Encode("xxxxxxxxxxx:xxxxxxxx")};
UrlFetchApp.fetch(callsUrl, options);
}
scriptProperties.deleteProperty('AUDIO');
scriptProperties.deleteProperty('NUMBER_CALL');
scriptProperties.deleteProperty('GROUP_CALL');
scriptProperties.deleteProperty('FILE_NAME');
return status;
}
Note:
In this modified script, when a file is uploaded, content is saved as a temporal file and the file ID is put to the PropertiesService. When makeCall is run, content is retrieved by the file ID and convert to the blob and saved it as a file. And the temporal file is removed. By this, the limitation of PropertiesService can be cleared.
But, in the current stage, the maximum blob size of Google Apps Script is 50 MB (52,428,800 bytes). So when the uploaded file size is over 50 MB, an error occurs. In this case, the file is converted to the base64 data. So, the maximum size is the size of base64 data. Please be careful this.
Reference:
Current limitations
I want to convert a dynamically created document to a PDF. It works when there is not a lot of data in the doc. However if my table's lenght goes above a certain value it does not want to work. Does anyone know what I can do?
function myFunction() {
var tempFolder = DriveApp.getFolderById("FOLDER_ID");
var tempDoc = DriveApp.getFileById("tEMP_DOC_ID");
var tempFile = tempDoc.makeCopy(tempFolder);
var table = tableDat2.map(function(r) {
return [r[0].toString(), r[1], r[4], Number(r[7]).toFixed(2), Number(r[9]).toFixed(2), Number([10]).toFixed(2), Number(r[11]).toFixed(2)]
});
table.unshift(headingData[0]);
var DocUse = DocumentApp.openById("DOCUMENT_O_USE ID");
var body = DocUse.getBody();
var style = {};
style[DocumentApp.Attribute.FONT_SIZE] = 6;
body.replaceText("{shopName}", shopName);
body.replaceText("{Street}", street);
body.replaceText("{Suburb}", suburb);
body.replaceText("{City}", city);
body.replaceText("{Country}", country);
body.replaceText("{telephoneNumber}", telephoneNo);
body.replaceText("{email}", email);
body.replaceText("{orderDate}", orderDate);
body.replaceText("{sdd}", sDD);
body.replaceText("{rd}", rD);
body.replaceText("{parameter}", param);
body.replaceText("{order}", orderNo);
body.replaceText("{totalValue}", totalVal);
body.replaceText("{totalWeight}", totalWeight);
body.replaceText("{totalPallets}", totalPal);
body.appendTable([headingData[0]]);
var tab = body.appendTable(table);
tab.setAttributes(style);
DocUse.saveAndClose();
var pdfFolder = DriveApp.getFolderById("FOLDER'S ID");
var PDF1 = DocUse.getAs(MimeType.PDF);
var PDF = pdfFolder.createFile(PDF1).setName("Quotation of: " + shopName + "Order no: " + orderNo);
return PDF;
}
With my spreadsheet, I have 2 Google forms tied to 2 sheets. When a form gets submitted the script executes and does it's thing. However, I only want the script to execute based on a submission from a single sheet. As it is now, the script executes when either of the forms get submitted.
My two sheets are:
Job Submission
and Order Submission
Any advice?
// Work Order
// Get template from Google Docs and name it
var docTemplate = ""; // *** replace with your template ID ***
var docName = "Work Order";
var printerId = "";
function addDates() {
var date = new Date(); // your form date
var holiday = ["09/04/2017","10/09/2017","11/23/2017","12/24/2017","12/25/2017","01/01/2018"]; //Define holiday dates in MM/dd/yyyy
var days = 5; //No of days you want to add
date.setDate(date.getDate());
var counter = 0;
if(days > 0 ){
while (counter < days) {
date.setDate(date.getDate() + 1 );
var check = date.getDay();
var holidayCheck = holiday.indexOf(Utilities.formatDate(date, "EDT", "MM/dd/yyyy"));
if (check != 0 && check != 6 && holidayCheck == -1) {
counter++;
}
}
}
Logger.log(date) //for this example will give 08/16/2017
return date;
}
function createNewDoc(values) {
//Get information from form and set as variables
var email_address = "";
var job_name = values[1];
var order_count = values[2];
var order_form = values[7];
var print_services = values[3];
var priority = values[5];
var notes = values[6];
var formattedDate = Utilities.formatDate(new Date(), "EDT", "MM/dd/yyyy");
var expirationDate = Utilities.formatDate(addDates(), "EDT", "MM/dd/yyyy");
// Get document template, copy it as a new temp doc, and save the Doc's id
var copyId = DriveApp.getFileById(docTemplate)
.makeCopy(docName+' for '+job_name)
.getId();
// Open the temporary document
var copyDoc = DocumentApp.openById(copyId);
// Get the document's body section
var copyBody = copyDoc.getActiveSection();
// Replace place holder keys,in our google doc template
copyBody.replaceText('keyJobName', job_name);
copyBody.replaceText('keyOrderCount', order_count);
copyBody.replaceText('keyOrderForm', order_form);
copyBody.replaceText('keyPrintServices', print_services);
copyBody.replaceText('keyPriority', priority);
copyBody.replaceText('keyNotes', notes);
copyBody.replaceText('keyDate', formattedDate);
copyBody.replaceText('keyDue', expirationDate);
// Save and close the temporary document
copyDoc.saveAndClose();
// Convert temporary document to PDF by using the getAs blob conversion
var pdf = DriveApp.getFileById(copyId).getAs("application/pdf");
// Attach PDF and send the email
var subject = "New Job Submission - " + job_name;
var body = "Here is the work order for " + job_name + ". Job is due " + expirationDate + ".";
MailApp.sendEmail(email_address, subject, body, {htmlBody: body, attachments: pdf});
// Move file to folder
var file = DriveApp.getFileById(copyId);
DriveApp.getFolderById("").addFile(file);
file.getParents().next().removeFile(file);
var newDocName = docName + ' for ' + job_name;
return [copyId, newDocName];
}
function printGoogleDocument(copyId, docName) {
// For notes on ticket options see https://developers.google.com/cloud-print/docs/cdd?hl=en
var ticket = {
version: "1.0",
print: {
color: {
type: "STANDARD_COLOR"
},
duplex: {
type: "NO_DUPLEX"
},
}
};
var payload = {
"printerid" : "",
"content" : copyId,
"title" : docName,
"contentType" : "google.kix", // allows you to print google docs
"ticket" : JSON.stringify(ticket),
};
var response = UrlFetchApp.fetch('https://www.google.com/cloudprint/submit', {
method: "POST",
payload: payload,
headers: {
Authorization: 'Bearer ' + GoogleCloudPrint.getCloudPrintService().getAccessToken()
},
"muteHttpExceptions": true
});
// If successful, should show a job here: https://www.google.com/cloudprint/#jobs
response = JSON.parse(response);
if (response.success) {
Logger.log("%s", response.message);
} else {
Logger.log("Error Code: %s %s", response.errorCode, response.message);
}
return response;
}
// When Form Gets submitted
function onFormSubmit(e) {
var values = e.values;
var returnedDocValues = createNewDoc(values);
var copyId = returnedDocValues[0];
var docName= returnedDocValues[1];
printGoogleDocument(copyId, docName);
}
Edit:
I'm not sure how to do a complete or verifiable example since it's dependent on the form submission. I don't often work with javascript so I'm still learning.
Anyway, I updated my onFormSubmit function, however, my other functions have failed to execute. The script doesn't create the doc and in turn doesn't get sent to the google cloud print
// When Form Gets submitted
function onFormSubmit(e) {
// Initialize
var rng = e.range;
var sheet = rng.getSheet();
var name = sheet.getName();
// If the response was not submitted to the right sheet, exit.
if(name != "Job Submission") return;
var values = e.values;
var returnedDocValues = createNewDoc(values);
var copyId = returnedDocValues[0];
var docName= returnedDocValues[1];
printGoogleDocument(copyId, docName);
}
If your onFormSubmit function is on a script bounded to the spreadsheet, the event object includes a range object. You could use getSheet to get the sheet and then getName to get the sheet name.
Example:
function onFormSubmit(e){
// Initialize
var rng = e.range;
var sheet = rng.getSheet();
var name = sheet.getName();
// If the response was not submitted to the right sheet, exit.
if(name != "Responses 1") return;
//Otherwise continue
}
Going off of Ruben's suggestion, here is what I ended up with
function onFormSubmit(e) {
// Initialize
var name = e.range.getSheet().getName();
// If the response was not submitted to the right sheet, exit.
if (name != "Job Submission") return;
var values = e.values;
var returnedDocValues = createNewDoc(values);
var copyId = returnedDocValues[0];
var docName = returnedDocValues[1];
printGoogleDocument(copyId, docName);
}
I'm trying to add a sort of 'typewriter effect' on my google apps script for google docs. I want to make it type out text, in this case a wikipedia article, as if a user was typing it, so add a delay. Unfortunately the function appendText(), even if you use Utilities.sleep, it still just types the entire article out as soon as the script finishes. What function would I use to accomplish something like this?
function onOpen(e) {
DocumentApp.getUi().createAddonMenu()
.addItem('Start', 'myFunction')
.addToUi();
}
function onInstall(e) {
onOpen(e);
}
function myFunction() {
var body = DocumentApp.getActiveDocument().getBody();
var text = body.editAsText();
var response = UrlFetchApp.fetch('https://en.wikipedia.org/w/api.php?&format=json&action=query&generator=random&grnnamespace=0&prop=title&grnlimit=1');
var json = JSON.parse(response);
for (key in json.query.pages) {
var title = json.query.pages[key].title;
}
var url = 'https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&explaintext=&titles=' + title
var response2 = UrlFetchApp.fetch(url);
var json2 = JSON.parse(response2);
for (key in json2.query.pages) {
var content = json2.query.pages[key].extract;
}
//content = content.replace(/==.*==/, '====')
var all = title + '\n' + content;
text.appendText("Start\n");
Utilities.sleep(1000);
text.appendText(content);
}
You need to flush the document. The DocumentApp API does not have a flush method (like SpreadsheetApp) but you can still flush it by using saveAndClose and then re-opening the document (for example with document=DocumentApp.openById("myid")
saveAndClose is automatically called when a script finishes, but not after every change you make as Google batches those changes for performance.
https://developers.google.com/apps-script/reference/document/document
I have tried #ZigMandel suggestion. It appears to work but the text is being typed from the left out.
function onOpen(e) {
DocumentApp.getUi().createAddonMenu()
.addItem('Start', 'myFunction')
.addToUi();
}
function onInstall(e) {
onOpen(e);
}
function myFunction() {
var body = DocumentApp.getActiveDocument().getBody();
var text = body.editAsText();
var response = UrlFetchApp.fetch('https://en.wikipedia.org/w/api.php?&format=json&action=query&generator=random&grnnamespace=0&prop=title&grnlimit=1');
var json = JSON.parse(response);
for (key in json.query.pages) {
var title = json.query.pages[key].title;
}
var url = 'https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&explaintext=&titles=' + title
var response2 = UrlFetchApp.fetch(url);
var json2 = JSON.parse(response2);
for (key in json2.query.pages) {
var content = json2.query.pages[key].extract;
}
//format(content);
//var par1 = body.insertParagraph(0, title);
//par1.setAlignment(DocumentApp.HorizontalAlignment.CENTER);
var str = "Sphinx of black quartz, judge my vow."
var split = str.split("");
for (var i = split.length - 1; i >= 0; i--) {
text.appendText(split[i]);
DocumentApp.getActiveDocument().saveAndClose();
body = DocumentApp.getActiveDocument().getBody();
text = body.editAsText();
}
}
function format(txt) {
txt = '\n' + txt;
txt = txt.replace(/\===(.+?)\===/g, "").replace(/\==
(.+?)\==/g,"").replace(/\n+/g, "\n").replace(/\n/g, "\n" + " ");
return txt;
}
To get the script to print out each character with a delay between, you can put your sleep method into a for loop.
for(var i = 0; i < content.length; i++) {
text.appendText(content[i]);
Utilities.sleep(200);
}
I think this gives you the effect you are looking for.
I am trying to send multiple asyncronous xmlhttprequest's using the oData endpoint. Some of them are creating Order details and are updating Order details in Microsoft Dynamics CRM 2013.
If I use the developer tools and manually trace through the code it works fine. However, if I run it from my web resource I constantly get 500 responses from the server. Some of the requests complete correctly, while the others fail.
I am looking for a purely javascript solution. I have tried Googling it and looking at multiple posts on stackoverflow but to no avail. I have used Fiddler2 but the response text is 'Generic SQL Error'. If I run the request again in the composer, it works just fine. Could it be a db locking issue?
Thanks in advance and I can provide more info if need be.
Here is my code with the for-loop:
var updateDetails = function (data) {
var table = document.getElementById("selectedItemTable");
var tbody = table.getElementsByTagName("tbody")[0];
var upsaleQty, qty;
var salesOrderDetailId;
for (var i = 0; i < tbody.childElementCount; i++) {
var prodName = tbody.rows[i].cells[0].innerHTML;
var match = false;
for (var j = 0; j < data.length; j++) {
if (prodName === data[j].product_order_details.tf_ShortName) {
match = true;
upsaleQty = data[j].tf_UpsaleQty ? parseFloat(data[j].tf_UpsaleQty) : 0;
qty = parseFloat(data[j].Quantity) + parseFloat(tbody.rows[i].cells[1].innerHTML);
salesOrderDetailId = data[j].SalesOrderDetailId;
}
}
if (!match) {
var productQuery = odataBaseUrl + "/ProductSet?$filter=tf_ShortName eq '" + prodName + "'&$select=Name,tf_ShortName,ProductId,DefaultUoMId";
performRequest(productQuery, createDetail);
} else {
upsaleQty = upsaleQty + parseFloat(tbody.rows[i].cells[1].innerHTML);
// Update Order Detail
var updateObj = {};
updateObj.tf_UpsaleQty = upsaleQty.toFixed(5);
updateObj.Quantity = qty.toFixed(5);
var updateDetail = JSON.stringify(updateObj);
console.dir("Update " + prodName + ":" + updateDetail);
createUpdateDetail(true, salesOrderDetailId, updateDetail);
}
}
makePdf();
document.getElementById("save").style.visibility = "hidden";
}
Here is the code that sends the create/update request:
var createUpdateDetail = function (update, orderDetailGuid, json) {
var odataReq = odataBaseUrl + "/SalesOrderDetailSet";
if (update) {
odataReq += "(guid'" + orderDetailGuid + "')";
}
var oReq = getXMLHttpRequest();
if (oReq != null) {
oReq.open("POST", encodeURI(odataReq), true);
oReq.setRequestHeader("Accept", "application/json");
oReq.setRequestHeader("Content-Type", "application/json; charset=utf-8");
if (update) {
oReq.setRequestHeader("X-HTTP-Method", "MERGE");
}
oReq.send(json);
} else {
alert('Error in creating request.');
}
}
Here is the perform request function:
var performRequest = function (odataUrl, onReadyFunction, concatResults) {
console.dir(odataUrl);
var oReq = getXMLHttpRequest();
if (oReq != null) {
oReq.open("GET", encodeURI(odataUrl), true);
oReq.setRequestHeader("Accept", "application/json");
oReq.setRequestHeader("Content-Type", "application/json; charset=utf-8");
oReq.onreadystatechange = function () {
if (oReq.readyState == 4 && oReq.status == 200) {
// Parse the result
if (!concatResults) {
concatResults = new Object();
concatResults.results = new Array();
}
oReq.onreadystatechange = null; //avoids memory leaks
console.dir(oReq.responseText);
var result = window.JSON.parse(oReq.responseText).d;
for (var i = 0; i < result.results.length; i++) {
concatResults.results.push(result.results[i])
}
if (result.__next != null)
performRequest(decodeURI(result.__next), onReadyFunction, concatResults);
else
onReadyFunction(concatResults.results);
}
};
oReq.send();
} else {
alert('Error in creating request.');
}
}
Create Detail function:
var createDetail = function (data) {
// Create Order Detail
var table = document.getElementById("selectedItemTable");
var tbody = table.getElementsByTagName("tbody")[0];
var qty = 0;
for (var i = 0; i < tbody.childElementCount; i++) {
if (data[0].tf_ShortName === tbody.rows[i].cells[0].innerHTML) {
qty = parseFloat(tbody.rows[i].cells[1].innerHTML).toFixed(5);
}
}
var createObj = {};
createObj.SalesOrderId = { Id: orderGuid, LogicalName: "salesorder" };
createObj.ProductId = { Id: data[0].ProductId, LogicalName: "product" };
createObj.Quantity = qty;
createObj.tf_UpsaleQty = qty;
createObj.UoMId = { Id: data[0].DefaultUoMId.Id, LogicalName: data[0].DefaultUoMId.LogicalName };
var createDet = JSON.stringify(createObj);
console.dir("Create:" + createDet);
createUpdateDetail(false, "", createDet);
}
I think ExecuteMultipleRequest to SOAP endpoint it's your solution. As a result you get only one service call instead making multiple service call which is currently implemented in your solution.
In case you avoid generating request string to soap endpoint in your code I would like to recommend you this JS library.
I ended up creating an array and treated it like a queue. I put all of the odata requests to create and update the Order Details in the array and then processed them sequentially. The onreadystatechange would trigger the next request. Granted, it's not as efficient as running the processed in parallel, but it worked for my needs and resolved the 500 errors. Thanks for your help.