I know some JS and I have a fair amount of experience with VBA scripts for MSO, but I'm just starting to learn how to script google docs, and I'm having difficulty with the google scripting environment.
I created a doc on my google drive, and I am trying to log outputs in the code, but I get an error when I get the error TypeError: Cannot find function getBody in object Excel Download Macro Log File. (line 56, file "Code") when I try to run this code:
function logDataFromCode() {
var LogDoc = DriveApp.getFilesByName('Excel Download Macro Log File').next();
// Access the body of LogDoc, then add a paragraph with relevant data from script:
LogDoc.getBody()
.appendPageBreak()
.appendParagraph("[put variables to log here]");
}
Can someone please explain why I am getting this error?
You are getting that error because the class returned by getFilesByName("name").next() is of type File, not Document.
The solution is to use the File from DriveApp to explicitly open a Document via DocumentApp:
var LogDoc, files = DriveApp.getFilesByName("the file name");
if(files.hasNext()) {
LogDoc = DocumentApp.openById(files.next().getId());
} else {
// No files matched the name supplied to DriveApp
return;
}
Your variable LogDoc is a File, but you want it to be a Document
The best way to cast it is to get it's id and then accesing it using the DocumentApp to open it.
Like this:
var LogFile = DriveApp.getFilesByName('Excel Download Macro Log File').next();
var LogDoc = DocumentApp.openById(LogFile.getId());
// Access the body of LogDoc, then add a paragraph with relevant data from script:
LogDoc.getBody()
Related
I'm new to Web Development (including JavaScript and HTML) and have a few issues within my personal project that seem to have no clear fixes.
Overview
My project is taking input from a user on the website, and feeding it to my back-end to output a list of word completion suggestions.
For example, input => "bass", then the program would suggest "bassist", "bassa", "bassalia", "bassalian", "bassalan", etc. as possible completions for the pattern "bass" (these are words extracted from an English dictionary text file).
The backend - running on Node JS libraries
trie.js file:
/* code for the trie not fully shown */
var Deque = require("collections/deque"); // to be used somewhere
function add_word_to_trie(word) { ... }
function get_words_matching_pattern(pattern, number_to_get = DEFAULT_FETCH) { ... }
// read in words from English dictionary
var file = require('fs');
const DICTIONARY = 'somefile.txt';
function preprocess() {
file.readFileSync(DICTIONARY, 'utf-8')
.split('\n')
.forEach( (item) => {
add_word_to_trie(item.replace(/\r?\n|\r/g, ""));
});
}
preprocess();
module.exports = get_words_matching_trie;
The frontend
An HTML script that renders the visuals for the website, as well as getting input from the user and passing it onto the backend script for getting possible suggestions. It looks something like this:
index.html script:
<!DOCTYPE HTML>
<html>
<!-- code for formatting website and headers not shown -->
<body>
<script src = "./trie.js">
function get_predicted_text() {
const autofill_options = get_words_matching_pattern(input.value);
/* add the first suggestion we get from the autofill options to the user's input
arbitrary, because I couldn't get this to actually work. Actual version of
autofill would be more sophisticated. */
document.querySelector("input").value += autofill_options[0];
}
</script>
<input placeholder="Enter text..." oninput="get_predicted_text()">
<!-- I get a runtime error here saying that get_predicted_text is not defined -->
</body>
</html>
Errors I get
Firstly, I get the obvious error of 'require()' being undefined on the client-side. This, I fix using browserify.
Secondly, there is the issue of 'fs' not existing on the client-side, for being a node.js module. I have tried running the trie.js file using node and treating it with some server-side code:
function respond_to_user_input() {
fs.readFile('./index.html', null, (err, html) => {
if (err) throw err;
http.createServer( (request, response) => {
response.write(html);
response.end();
}).listen(PORT);
});
respond_to_user_input();
}
With this, I'm not exactly sure how to edit document elements, such as changing input.value in index.html, or calling the oninput event listener within the input field. Also, my CSS formatting script is not called if I invoke the HTML file through node trie.js command in terminal.
This leaves me with the question: is it even possible to run index.html directly (through Google Chrome) and have it use node JS modules when it calls the trie.js script? Can the server-side code I described above with the HTTP module, how can I fix the issues of invoking my external CSS script (which my HTML file sends an href to) and accessing document.querySelector("input") to edit my input field?
I am trying to scrape data from an interactive map (looking to get crime data for a county). I am using R (rvest) and trying to use phantomjs too. I'm new to web scraping so I am not really understanding how all the elements work together (trying to get there).
The problem I believe I am having is that after I run the phantomjs and upload the html using R's rvest package, I end up with more scripts and no clear data in the html. My code is below.
writeLines("var url = 'http://www.google.com';
var page = new WebPage();
var fs = require('fs');
page.open(url, function (status) {
just_wait();
});
function just_wait() {
setTimeout(function() {
fs.write('cool.html', page.content, 'w');
phantom.exit();
}, 2500);
}
", con = "scrape.js")
A function that takes in the url that I want to scrape
s_scrape <- function(url = "https://gis.adacounty.id.gov/apps/crimemapper/",
js_path = "scrape.js",
phantompath = "/Users/alihoop/Documents/phantomjs/bin/phantomjs"){
# this section will replace the url in scrape.js to whatever you want
lines <- readLines(js_path)
lines[1] <- paste0("var url ='", url ,"';")
writeLines(lines, js_path)
command = paste(phantompath, js_path, sep = " ")
system(command)
}
Execute the js_scrape() function and get a html file saved as "cool.html"
js_scrape()
Where I am not understanding what to do next is the below R code:
map_data <- read_html('cool.html') %>%
html_nodes('script')
The output I get in the HTML via phantomjs is just scripts again. Looking for help on how to proceed when faced (in my mind) is javascript nested in javascript scripts(?)
Thank you!
This site uses javascript to make queries to the server. One solution is to reproduce the rest request and read the returning JSON file directly. This avoids the need to use Phantomjs.
From the developer tools options from your browser and looking through the xhr files, you will find a file(s) named "query" with a link similar to: "https://gisapi.adacounty.id.gov/arcgis/rest/services/CrimeMapper/CrimeMapperWAB/FeatureServer/11/query?f=json&where=1%3D1&returnGeometry=true&spatialRel=esriSpatialRelIntersects&outFields=*&outSR=102100&resultOffset=0&resultRecordCount=1000"
Read this JSON response directly and convert to a list with the use of the jsonlite package:
library(jsonlite)
output<-jsonlite::fromJSON("https://gisapi.adacounty.id.gov/arcgis/rest/services/CrimeMapper/CrimeMapperWAB/FeatureServer/11/query?f=json&where=1%3D1&returnGeometry=true&spatialRel=esriSpatialRelIntersects&outFields=*&outSR=102100&resultOffset=0&resultRecordCount=1000")
output$features
Find the first number in the link, (11 in this case) "FeatureServer/11/query?f=json". This number will determine which crime to query the server with. I found, it can take a value from 0 to 11. Enter 0 for arson, 4 for drugs, 11 for vandalism, etc.
I would like to have an option to save the geoJSON file from Google Spreadsheets.
The issue has been raised here:
https://gis.stackexchange.com/questions/140995/publishing-google-sheet-to-web-as-geojson-file
although the plugin available here:
https://github.com/mapbox/geo-googledocs/
doesn't work because the geocode receiving the error message:
https://github.com/mapbox/geo-googledocs/issues/42
UiApp has been deprecated. Please use HtmlService instead.
I tired to combine the existing code, provided by this plugin with the code proposed as a way to fixing this issue:
https://gist.github.com/hidrodixtion/c3a6b6ba7af624d1800625efb7a40fbd
The code name is Geo.js as per the plugin provided above
I put the function doGet above the Geocoders as per below:
// Global variables
function doGet() {
return HtmlService.createHtmlOutputFromFile('Index');
}
var ss = SpreadsheetApp.getActiveSpreadsheet(),
sheet = ss.getActiveSheet(),
activeRange = ss.getActiveRange(),
settings = {};
var geocoders = {
yahoo: {
... another part of the code available here:
https://raw.githubusercontent.com/mapbox/geo-googledocs/master/MapBox.js
unfortunately it doesn't work.
It seems like I don't know where I must superseed the depreciated UiApp function with the HTML service.
The problem is, that I don't know what part of the code in the Geo plugin should be replaced. It might be really helpful as the plugin can do the geocoding either.
My another part of combination was as follows:
I changed the code:
// Create a new UI
var app = UiApp.createApplication()
.setTitle('Export GeoJSON')
.setStyleAttribute('width', '460')
.setStyleAttribute('padding', '20');
into:
var app = HtmlService.createHtmlOutput().setWidth(800).setHeight(600);
.setTitle('Export GeoJSON') //line 105
.setStyleAttribute('width', '460')
.setStyleAttribute('padding', '20');
but once I save now I get this:
Syntax error. (line 105, file "Geo")
I picked up this code from here:
https://gist.github.com/hidrodixtion/c3a6b6ba7af624d1800625efb7a40fbd
line 141
Does anybody knows how to fix this issue?
My english is a colander and my programming skills in Javascript the same. I have in a specific folder of Google Drive several PDF files (about 10). Once a day, near midnight, I have to move these files separately (one for one) in specific Google Drive folders.
Actually I make this activity by hand, so I tried to automate it with a simple script.
I'm able to point to the specific folder, but NOT to the specific file.
In the samples I found around in Internet all files contained in a folder are moved to another folder, so the filesIterator technique are used.
But I don't need to iterate, I need to point the specific file and move it to anther folder.
Here my five (confused) program lines:
function moveFiles(source_folder, dest_folder) {
var source_folder = DriveApp.getFolderById('0B42Jhhzp_5X7QUlMdE9SZS0wMms');
var dest_folder = DriveApp.getFolderById('0B42Jhhzp_5X7TFZBMXpfeWpZNFk');
var file = getFilesByName('caa20170829.pdf');
// var files = source_folder.getFiles();
// Logger.log(file.getAs(MimeType.HTML).getDataAsString());
dest_folder.addFile(file);
source_folder.removeFile(file);
}
The error message I'm getting:
ReferenceError: "getFilesByName" not defined. (line 6, file "move")
Any help would be appreciated!
As suggested from Max Deepfield (thanks, Max), I edited the line 6 as follows
var file = DriveApp.getFilesByName('caa20170829.pdf');
and the error message changed to
Impossible to find the method addFile(FileIterator). (line 11, file "move")
Now i edited the 11. line as follows
DriveApp.dest_folder.addFile(file);
and the error message changes another time...
TypeError: Impossible to call the method "addFile" of undefined. (line 11, file "move")
Thanks to Hassan too, i am studying the examples he suggested.
But if someone would give further help... I thank in advance.
The problem is that the DriveApp Api doesn't have any methods to return single File. So you have to pass by the FileIterator.
First you don't have to put parameters in function moveFiles(source_folder, dest_folder), source_folder and dest_folder are defined inside the function. But I think you did this mistake du to the code you find on internet :)
Moreover, there is some wrong advice here: when you call DriveApp.getFolderById(), the API will return you a Folder. So you don't need to call DriveApp on this var anymore (for example DriveApp.dest_folder.addFile(file); is a bad call because the method addFile() is for Folder type, and dest_folder is already a Folder.
So you could try this, it works for me:
source_folder= DriveApp.getFolderById('YourFolderID1')
dest_folder=DriveApp.getFolderById('YourFolderID2')
var files = source_folder.getFilesByName("YourFileName").next();
dest_folder.addFile(files);
source_folder.removeFile(files);
I hope it helped you :)
I need to backup a file on a daily basis, I have resolved this issue using the following script:
function myFunction() {
DocsList.getFileById(SpreadsheetApp.getActiveSpreadsheet().getId()).makeCopy(SpreadsheetApp.getActiveSpreadsheet().getName() + "_Backup");
}
And I'm using the Time-driven trigger to set the hour I want the script creates the backup file. However, I would like these daily backups going to a specific Subfolder, lets call it "Daily Backup Folder".
Can someone help me with a script for that?
Thanks in advance!
using DocsList service:
try:
function backUp() {
var backup = DocsList.getFileById(SpreadsheetApp.getActiveSpreadsheet()
.getId())
.makeCopy(SpreadsheetApp.getActiveSpreadsheet()
.getName() + "_Backup");
backup.addToFolder(DocsList.getFolder('TEST BACKUP'));
backup.removeFromFolder(DocsList.getRootFolder());
}
However, since DocsList service is depreciated, you may want to consider Drive service.
Try:
function backUP() {
DriveApp.getFileById(SpreadsheetApp.getActiveSpreadsheet()
.getId())
.makeCopy(SpreadsheetApp.getActiveSpreadsheet()
.getName() + "_Backup", (DriveApp.getFolderById('folder_id')));
}
Fill in the actual id of the folder 'Daily Backup folder' in the last line of the script and see if that works ?
makeCopy(name) is what you are using.
makeCopy(name, destination) is what you need to use.
Solution:
You will need to get your folder's ID. Open your the folder where you want to save your backup files in google drive and check it's URL in the browser's address bar.
It will look like:"https ://drive.google.com/drive/folders/zzzzz". Copy this zzzzz and replace the xxxxxx in the code below.
function myFunction() {
DocsList.getFileById(SpreadsheetApp.getActiveSpreadsheet().getId()).makeCopy(SpreadsheetApp.getActiveSpreadsheet().getName() + "_Backup", DriveApp.getFolderById("xxxxxxxxxx"));
}