A script to update linked sheet from google doc - google-api

I have linked part of a sheet in google doc (as linked object). Now, whenever I change the sheet data, I can click a button in google doc and the data is reflected in the google doc linked sheet too (this is all built in google doc stuff).
What I want to do is the other side of this. I am able to see a bunch of data in one place (google doc) based on the sheets I have linked. I would like to update the data in the google doc, and "upload" it to the linked google sheets.
I am trying to write a script to do that. But cannot seem to find any method to access linked sheets. I found this slides API page that can does the sheet -> slide syncing.
I am looking at the document API page, but I scanning through add... and get... methods, I don't see to find any way to get linked objects. Is it represented as NamedRange? If so, how do I access it?
There was another similar question, but without any satisfactory answer.
If you can share some pointers to get started, I would appreciate it.
Edit: Here is an example doc (and a spreadsheet contained their in) to explain the situation clearer.
Test document for updating spreadsheet - Google Docs

You can find the Table elements in your Document via findElement(elementType). If that's the only Table in your Document, as in the sample you shared, this is immediate.
Once you retrieve the Table, you can loop through its rows and cells and come up with a 2D array with the values from the table:
function getTableValues() {
const doc = DocumentApp.getActiveDocument();
const body = doc.getBody();
const table = body.findElement(DocumentApp.ElementType.TABLE).getElement();
let tableValues = [[]];
for (let i = 0; i < table.getNumRows(); i++) {
const tableRow = table.getRow(i);
for (let j = 0; j < tableRow.getNumCells(); j++) {
const tableCell = tableRow.getCell(j);
const text = tableCell.getText();
tableValues[i].push(text);
}
if (i == table.getNumRows() - 1) break;
tableValues.push([]);
}
return tableValues;
}
Once you've done that, you just need to copy it to your spreadsheet via setValues(values):
function copyToSheet(tableValues, spreadsheetId) {
const sheet = SpreadsheetApp.openById(spreadsheetId).getSheetByName("Sheet1");
sheet.getRange(1, 1, tableValues.length, tableValues[0].length).setValues(tableValues);
}
Calling both of these in the same function, you would get this:
function main() {
const tableValues = getTableValues();
const spreadsheetId = "{your-spreadsheet-id}";
copyToSheet(tableValues, spreadsheetId);
}
Note:
getActiveDocument() is used to retrieve the Document, so this assumes the script is bound to your Document. If that's not the case, use openById(id) or openByUrl(url) instead.

Related

Automatically copy values from an importrange column to another column before it updates [duplicate]

I have a sheet with data that is imported through IMPORTRANGE to another sheet. When I make changes to Spreadsheet 1 >>> Spreadsheet 2 I have a script that copy the data from Copy to Paste. It is working all fine however I want to make sure that the script only runs when changes are made in the 'Copy' sheet and not any other. At the moment it runs independent on what sheet I make changes in.
Spreadsheet 1
Spreadsheet2
I tried onChange trigger two different ways...
This one makes the change but triggers when changes are made in any of the sheets
function Trigger2(e) {
var sheet = e.source.getActiveSheet();
if (sheet.getName() === 'Copy') {
copyInfo();
}
}
AND
(this does not work)
function Trigger2(e) {
var sheet = e.source.getActiveSheet();
var sheet = SpreadsheetApp.getActiveSpreadsheet();
if( sheet.getActiveSheet().getName() !== "Copy" ) return;{
copyInfo();
}}
The copy code looks like this...
function copyInfo() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var copySheet = ss.getSheetByName("Copy");
var pasteSheet = ss.getSheetByName("Paste");
// get source range
var source = copySheet.getRange(2,1,12,8);
// get destination range
var destination = pasteSheet.getRange(2,1,12,8);
// copy values to destination range
source.copyTo(destination);
}
Copy Changes from one Sheet To Another after Imported Range Changes
Spreadsheet 2 Imports a range to Sheet1 from Spreadsheet 1 Sheet1 and when a change occurs in Spreadsheet 2 Sheet1 we copy data from Spreadsheet2 Sheet1 to Spreadsheet2 Sheet 2.
function onMyChange(e) {
//Logger.log(JSON.stringify(e));//useful during setup
//e.source.toast("Entry");//useful during setup
const sh = e.source.getSheetByName("Sheet1")
Logger.log(sh.getName());
if(e.changeType == "OTHER") {
let tsh = e.source.getSheetByName("Sheet2");
sh.getRange(2,1,12,8).copyTo(tsh.getRange(2,1));//Only need the upper left hand corner of the range. Thus you change change the size of the source data without having to change the target range.
}
}
One might be inclined to attempt to use e.source.getActiveSheet() unfortunately this does not necessary get the sheet that you have written unless in is SpreadsheetApp.getActive().getSheets()[0]. onChange event object always returns the most left sheet in the spreadsheet in this situation so it can't be used to identify the sheet that is being written to from the importRange function.
With this function you no longer require another function.
A simple if should work, but you're trying to compare the name to the wrong field. According to Google's documentation, the source field in the Event Object e returns the Spreadsheet object, which is the entire document.
Instead you can use the e.range field, which contains the exact range where the edit was made, and the Range class has a getSheet() method to get the parent Sheet.
So you can modify your sample to the following:
function Trigger2(e) {
var sheet = e.range.getSheet();
if (sheet.getName() === 'Copy') {//Edit: this will only work if sheet is the most left sheet in the spreadsheet
copyInfo();
}
}

For loop over a Google Sheets Range fails to change file owner

I need to transfer ownership of thousands of files I own to someone else with editing access, but I've learned that Google Drive requires you to do this one file at a time. With intermediate but growing JS experience I decided to write a script to change the owner from a list of file IDs in a spreadsheet, however I've run into trouble and I have no idea why. The debugger just runs through the script and everything looks sound for me. I'd greatly appreciate any help.
function changeOwner() {
var ss = SpreadsheetApp.openByUrl('https://docs.google.com/spreadsheets/d/********/').getSheetByName('Sheet1');
var range = ss.getRange(1,1,2211);
for (i = 0; i < range.length; i++){
var file = range[i][0].getValue();
var fileid = DriveApp.getFileById(file);
fileid.setOwner('******#gmail.com');
}
}
Tested below code working fine with IDs,
function myFunction() {
var spreadsheet = SpreadsheetApp.openById('ID');
var range = spreadsheet.getDataRange().getValues();
for (i = 0; i < range.length; i++){
var file = range[i][0].toString();
var fileid = DriveApp.getFileById(file);
fileid.setOwner('***#gmail.com');
}
}
Your issue is that the Range class had no length property. Instead, perform a getValues() call on the range to efficiently create a JavaScript array, and iterate on it:
function changeOwner() {
var ss = SpreadsheetApp.openByUrl('https://docs.google.com/spreadsheets/d/********/').getSheetByName('Sheet1');
var values = ss.getRange(1, 1, 2211).getValues();
for (i = 0; i < values.length; ++i){
var fileid = value[i][0];
var file = DriveApp.getFileById(fileid);
file.setOwner('******#gmail.com');
}
}
There are other improvements you can make, such as:
dynamic range read (rather than the fixed assumption of 2211 rows of data)
writing to a second array to keep track of which files you have yet to process
checks for if the file exists
checks for if the file is actually owned by you
checks for execution time remaining, to allow serializing any updates/tracking you do that prevents the next execution from repeating what you just did
and so on. I'll let you research those topics, and the methods available in Apps Script documentation for the DriveApp, SpreadsheetApp, Sheet, and Range classes. Note that you also have all features of Javascript 1.6 available, so many of the things you find on a Javascript Developer Reference (like MDN) are also available to you.

Dynamic data validation based on other cell values, and copying/filling validation criteria relatively down each row (Google Sheets)

I am trying to achieve the following situation using Data Validation in Google Sheets. I've provided a truncated version of situation in the image below.
I would like to set up data validation in Column B that automatically checks the options in column C (or multiple columns in the same row) to populate the dropdown menu for that row.
I did notice that Google Sheets has an option for formulas in the data validation screen and I tried writing an array formula in this area but I have not been having any luck getting any sort of output.
If it can't be done through this menu I would appreciate any idea how to achieve this through scripts.
One way to achieve this is to select list from a range in the "Criteria" section of the Data Validation editor. Then, select the cells whose values you want to appear in your dropdown.
The only issue is that when you try to fill this down across the column, Google Sheets will not update the criteria range. Say you do the following:
In cell B2, use data validation based on list from a range and select D2:F2 as the criteria range. Now the dropdown in B2 will have the options John's Mom, John's Dad, John's Grandma
Then, fill down or copy this cell down across the whole column
When you open the dropdown for cell B3, it will still have the options John's Mom, John's Dad, John's Grandma
Unfortunately Google Sheets does not currently have a built-in solution for copying/filling data validation references or formulas relatively, as far as I know. But it looks like somebody already wrote a nice script in this Google Docs forum post. To avoid just a link as an answer, I'm going to copy in the script and instructions here. Credit to AD:AM from Google Docs forum.
How to use their script:
Select a range of cells across which you want to copy a data validation rule, relatively
From the Validation+ custom menu, select the appropriate option (all references relative, columns absolute, or rows absolute)
The validation of the upper-left cell will be copied to the rest of the range
Link to original solution's example Google Sheets with script already included - you can save your own copy and then start using.
Or to recreate from scratch, here is the script.
function onOpen()
{
SpreadsheetApp.getActiveSpreadsheet().addMenu
(
"Validation+",
[
{name: "Copy validation (all relative references)", functionName: "copyValidation"},
{name: "Copy validation (relative rows, absolute columns)", functionName: "copyValidationColumnsAbsolute"},
{name: "Copy validation (absolute rows, relative columns)", functionName: "copyValidationRowsAbsolute"}
]
);
}
function copyValidation(rowsAbsolute, columnsAbsolute)
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var r = ss.getActiveRange();
var dv = r.getDataValidations();
var dvt = dv[0][0].getCriteriaType();
if (dvt != SpreadsheetApp.DataValidationCriteria.VALUE_IN_RANGE) return;
var dvv = dv[0][0].getCriteriaValues();
Logger.log(dvv);
for (var i = 0; i < dv.length; i++)
{
for (var j = i ? 0 : 1; j < dv[0].length; j++)
{
dv[i][j] = dv[0][0].copy().withCriteria(dvt, [dvv[0].offset(rowsAbsolute ? 0 : i, columnsAbsolute ? 0 : j), dvv[1]]).build();
}
}
r.setDataValidations(dv);
}
function copyValidationRowsAbsolute()
{
copyValidation(true, false);
}
function copyValidationColumnsAbsolute()
{
copyValidation(false, true);
}

Find&Replace script in Google Docs SpreadSheets

I have google spreadsheet with direct links to images (jpg and png):
https://docs.google.com/spreadsheet/ccc?key=0AoPGWppcjtzhdDh6MW1QNVJhSHlwVTlfRnRtd0pvNGc&usp=sharing
I want to increase rows heights starting from "2nd row" to 100px and render images there.
It's possible to do via Find&Replace:
Find jpg and Replace to jpg", 1)
Find http://img and Replace to =image("http://img)
Select rows and Scale them
and the same for png image-urls.
Watch this screencast http://www.screenr.com/S0RH
Is it possible to automate it via script? I think - YES! It have to be pretty simple but I googled a lot but haven't found the solution. I can't do it myself as don't know coding. Will anyone help and make this script?
A function to do what you ask is simple, if you have a basic understanding of the language (Javascript), know how to use the development environment, and read the API documentation.
For example, see this script. It's been added to your shared spreadsheet, so you can also view it (and run it) in the script editor there.
/**
* Scan column A, looking for images that have been inserted using
* =image() function. For any row with an image, set the row height
* to 100 pixels.
*/
function resizeImageRows() {
var sheet = SpreadsheetApp.getActiveSheet(); // Get a handle on the sheet
var HEADERS = 1; // Number of header rows at top
var firstRow = HEADERS + 1; // First row with data
var lastRow = sheet.getLastRow(); // Last row with data
var imageRange = sheet.getRange(1, 1, lastRow, 1); // Column A
// Get all formulas from Column A, without Headers
var formulas = imageRange.getFormulas().slice(HEADERS);
// Look for image() formulas, and set the row height.
for (var i = 0; i< formulas.length; i++) {
if (formulas[i][0].indexOf('image') !== -1) {
sheet.setRowHeight(i+firstRow, 100); // Set height to 100 pixels
}
}
}
You can absolutely do this with the find and replace function under the edit menu, just make sure you click "search in formulas" and it will find and replace in the formula.

Greasemonkey script to find rows with certain conditions

I tried some different ways do find rows in a table where a columns contain a particular link.
My goal: replace an icon when a link to xyz is in this same row as the image.
This is my snippet so far:
var rows = document.getElementsByTagName("tr");
for(var i = rows.length - 1; i >= 0; i--) {
var links = rows[i].getElementsByTagName("a");
for(var k = links.length - k; k >= 0; k--) {
if (links[k].href =="http://www.XXXX.net/forum/index.php?showforum=121"){
var images = rows[i].getElementsByTagName("img");
for (var j=0;j<images.length;j++) {
images[j].src = "http://www.XXXX.net/forum/folder_post_icons/icon7.gif";
}
}
}
}
I'm pretty sure this is not really the best concept. But as you might see I try to search links in all rows and once the link to forum "121" is found, I try to replace all images in this particular row.
What I get is every image at the site getting replaced.
Since it's simple enough, here's a complete script that does that.
It uses jQuery and here's a handy jQuery reference. See, especially, the Selectors section (which are almost the same as CSS Selectors).
Re: "What I get is every image at the site getting replaced." ...
This maybe because the search criteria is too broad. If it's a poorly designed (uses table layouts) page, every image may be in a table row with a target link!
When posting Greasemonkey questions, link to the target page, or at the very minimum, post enough of the page's HTML that we can adjust the GM script to match.
Anyway, this will work, possibly pending more information about the target page:
// ==UserScript==
// #name _Replace image on custom-targeted row
// #include http://www.XXXX.net/forum/*
// #require http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js
// ==/UserScript==
//--- This may need tuning based on information not provided!
var targetLinks = $("tr a[href*='showforum=121']");
//--- Loop through the links and rewrite images that are in the same row.
targetLinks.each ( function () {
//--- This next assumes that the link is a direct child of tr > td.
var thisRow = $(this).parent ().parent ();
//--- This may need tuning based on information not provided!
var images = thisRow.find ("td img");
//--- Replace all target images in the current row.
images.each ( function () {
$(this).attr (
'src',
'http://www.XXXX.net/forum/folder_post_icons/icon7.gif'
);
} );
} );

Resources