Google slides auto update links /tables linked from google sheet - google-apps-script

I have a Google Slides presentation that has some linked cells/table to data in Google Sheets. Currently, I have to manually click each linked cell/table to update values.
I need a script for Google Slides that would auto-update / batch-update / refresh these links, so that the values/tables get auto-updated. Is that possible?

Both yes and no.
Charts
Yes! those can be batch updated:
function onOpen() {
SlidesApp.getUi() // Or DocumentApp or FormApp.
.createMenu('Update Charts')
.addItem("Update now !!!!!", 'refreshCharts').addToUi();
}
function refreshCharts(){
var gotSlides = SlidesApp.getActivePresentation().getSlides();
for (var i = 0; i < gotSlides.length; i++) {
var slide = gotSlides[i];
var sheetsCharts = slide.getSheetsCharts();
for (var k = 0; k < sheetsCharts.length; k++) {
var shChart = sheetsCharts[k];
shChart.refresh();
}
}
}
Source: https://stackoverflow.com/a/48254442/
Shapes/Tables
No: https://issuetracker.google.com/issues/64027131
Update From Google 24/5/2019
https://gsuiteupdates.googleblog.com/2019/05/bulk-update-docs-slides.html

Has anyone gotten this to work?
function refreshCharts(){
var gotSlides = SlidesApp.getActivePresentation().getSlides();
for (var i = 0; i < gotSlides.length; i++) {
var slide = gotSlides[i];
var sheetsCharts = slide.getSheetsCharts();
for (var k = 0; k < sheetsCharts.length; k++) {
var shChart = sheetsCharts[k];
shChart.refresh();
}
}
}
I just get this error message
"
Unable to refresh chart. Please verify that the chart is a valid chart in Google Sheets."

The following example setup assumes there are five slides with some linked charts:
first slide - main slide title/subtitle (no chart)
second slide - contains a chart linked from the chart of a pivot table that counts the R&D staff and what they planned to do from the Google Form responses sheet
third slide - contains a chart linked from the chart of a pivot table that counts the IME staff and what they planned to do from the Google Form responses sheet
fourth slide - contains a chart linked from the chart of a pivot table that counts the PMO staff and what they planned to do from the Google Form responses sheet
fifth slide - contains a chart linked from the chart of a pivot table that counts the total staff and what they planned to do from the Google Form responses sheet
The function below will update the slides with linked charts.
For it to work, it requires the Advanced Google Services: Google Slides API https://developers.google.com/slides/.
You can activate this advanced feature from your Google Apps Script IDE under Resources > Advanced Google Services... > Google Slides API. The API version is set to v1. If you don't enable it, the script will complain Slides is not defined at updateSlideCharts(...)
See the link below for more detail about RefreshSheetsChartRequest in Google Slides API v1: https://developers.google.com/slides/reference/rest/v1/presentations/request#RefreshSheetsChartRequest
function updateSlideCharts() {
var presentation = SlidesApp.openById(YOUR_SLIDE_ID); //you need to get this slide id from your slide URL
//if empty
if (presentation == null) throw new Error('Presentation was not found');
Logger.log("%s id = %s", presentation.getName(), presentation.getId());
var slides = presentation.getSlides();
if (slides == null) throw new Error('Slides were not found');
Logger.log("Total of slides in %s: %d", presentation.getName(), slides.length);
var presentationId = presentation.getId();
var presentationRndChartId = slides[1].getSheetsCharts()[0].getObjectId();
Logger.log("ObjectId of \"%s\": %s", slides[1].getSheetsCharts()[0].getTitle(), presentationRndChartId);
var presentationImeChartId = slides[2].getSheetsCharts()[0].getObjectId();
Logger.log("ObjectId of \"%s\": %s", slides[2].getSheetsCharts()[0].getTitle(), presentationImeChartId);
var presentationPmoChartId = slides[3].getSheetsCharts()[0].getObjectId();
Logger.log("ObjectId of \"%s\": %s", slides[3].getSheetsCharts()[0].getTitle(), presentationPmoChartId);
var presentationStaffChartId = slides[4].getSheetsCharts()[0].getObjectId();
Logger.log("ObjectId of \"%s\": %s", slides[4].getSheetsCharts()[0].getTitle(), presentationStaffChartId);
var requests = [{
refreshSheetsChart: {
objectId: presentationRndChartId
}
}];
// Execute the request.
var batchUpdateResponse = Slides.Presentations.batchUpdate({
requests: requests
}, presentationId);
Logger.log('Refreshed linked Sheets charts for \"%s\"', slides[1].getSheetsCharts()[0].getTitle());
requests = [{
refreshSheetsChart: {
objectId: presentationImeChartId
}
}];
// Execute the request.
batchUpdateResponse = Slides.Presentations.batchUpdate({
requests: requests
}, presentationId);
Logger.log('Refreshed linked Sheets charts for \"%s\"', slides[2].getSheetsCharts()[0].getTitle());
requests = [{
refreshSheetsChart: {
objectId: presentationPmoChartId
}
}];
// Execute the request.
batchUpdateResponse = Slides.Presentations.batchUpdate({
requests: requests
}, presentationId);
Logger.log('Refreshed linked Sheets charts for \"%s\"', slides[3].getSheetsCharts()[0].getTitle());
var requests = [{
refreshSheetsChart: {
objectId: presentationStaffChartId
}
}];
// Execute the request.
var batchUpdateResponse = Slides.Presentations.batchUpdate({
requests: requests
}, presentationId);
Logger.log('Refreshed linked Sheets charts for \"%s\"', slides[4].getSheetsCharts()[0].getTitle());
}

Linked Table
As of Aug 2021, there is still no .refresh() function for linked tables, but if your use case allows you to know the source spreadsheet and range at script writing, you can update a linked table by reading the text values, font colors, font styles, etc., from the spreadsheet and writing them to the table. Something like this:
function updateSheetsChart()
{
// The range on the source spreadsheet
var sourceRange = SpreadsheetApp.openById(SOURCE_SPREADSHEET_ID).getRange(SOURCE_TABLE_RANGE)
var source = {
'values': sourceRange.getDisplayValues(),
'backgrounds': sourceRange.getBackgrounds(),
'textStyles': sourceRange.getTextStyles(),
'fontColors': sourceRange.getFontColors()
}
// The linked table on the presentation
var table = SlidesApp.getActivePresentation().getPageElementById(SHEETS_TABLE_OBJECT_ID).asTable()
var columnCount = table.getNumColumns()
var rowCount = table.getNumRows()
for (var col = 0; col < columnCount; col++)
{
for (var row = 0; row < rowCount; row++)
{
var cell = table.getCell(row, col)
// Cell text
var cellText = cell.getText()
cellText.setText(source.values[row][col])
// Background color
cell.getFill().setSolidFill(source.backgrounds[row][col])
// Font style (bold)
var cellTextStyle = cellText.getTextStyle()
cellTextStyle.setBold(source.textStyles[row][col].isBold())
// Text color
cellTextStyle.setForegroundColor(source.fontColors[row][col])
}
}
}
You can add more lines for font size, italics, link URLs, etc. See the documentation for Range, Sheets TextStyle, and Slides TextStyle classes for the corresponding methods.
There are plenty of limitations: this won't copy full rich text (multiple colors or font styles in one cell), for example.
Also see this answer for a simpler workaround (but it doesn't transfer the cell formatting).

Related

Custom functions workaround for Google Workspace add-ons

I'm looking for a workaround to the limitation that custom functions are not available in Google Workspace Add-ons. As proven by #Rubén in this SO answer.
I am building a Google Workspace Add-on. I want to get data from a specified range in the Spreadsheet, then run a function in the code, then output the data back to the user in the Spreadsheet.
I'm guessing I might use SpreadsheetApp.getActiveRange() to facilitate the spreadsheet data interactions. Something similar to the following pseudocode.
Pseudocode
const sourceRange = SpreadsheetApp.getActiveRange();
// do stuff
const destinationRange = foo;
destinationRange.setValues( bar, );
Is this a viable path forward?
Yes, you might use code like the shown in the question in Workspace add-ons for Google Sheets to make that your add-on UI interacts with the active spreadsheet.
The below code snippet is a complete helper function used in sample Workspace Add-on provided by Google Developers. It was taken from Translate text from Google Docs, Sheets, and Slides.
Breadcrumb: Samples by project type > Workspace Add-ons > Translate text
Specific URL: https://developers.google.com/apps-script/add-ons/translate-addon-sample#code.gs.
Please note that it uses
var ranges = SpreadsheetApp.getActive().getSelection().getActiveRangeList().getRanges();
/**
* Helper function to get the text of the selected cells.
* #return {CardService.Card} The selected text.
*/
function getSheetsSelection(e) {
var text = '';
var ranges = SpreadsheetApp.getActive().getSelection().getActiveRangeList().getRanges();
for (var i = 0; i < ranges.length; i++) {
const range = ranges[i];
const numRows = range.getNumRows();
const numCols = range.getNumColumns();
for (let i = 1; i <= numCols; i++) {
for (let j = 1; j <= numRows; j++) {
const cell = range.getCell(j, i);
if (cell.getValue()) {
text += cell.getValue() + '\n';
}
}
}
}
if (text !== '') {
var originLanguage = e.formInput.origin;
var destinationLanguage = e.formInput.destination;
var translation = LanguageApp.translate(text, e.formInput.origin, e.formInput.destination);
return createSelectionCard(e, originLanguage, destinationLanguage, text, translation);
}
}

Export Google Docs comments into Google Sheets - The Basics

Trying to produce a Comment Log in a Google sheet based on a Google Doc.
I applied the script as suggested by NaziA in this link. And I activated the DriveAPI service.
function listComments() {
// Change docId into your document's ID
// See below on how to
var docId = '1fzYPRldd16KjsZ6OEtzgBIeGO8q5tDbxaAcqvzrJ8Us';
var comments = Drive.Comments.list(docId);
var hList = [], cList = [];
// Get list of comments
if (comments.items && comments.items.length > 0) {
for (var i = 0; i < comments.items.length; i++) {
var comment = comments.items[i];
// add comment and highlight to array's first element
hList.unshift([comment.context.value]);
cList.unshift([comment.content]);
}
// Set values to A and B
var sheet = SpreadsheetApp.getActiveSheet();
sheet.getRange("A1:A" + hList.length).setValues(hList);
sheet.getRange("B1:B" + cList.length).setValues(cList);
}
}
I used the suggested code verbatim with one change. I replaced the DocumentID with the ID from the new Google sheet I was using as a target. After I approved the permission, it executed without errors but did not write any values to the Google Sheet. I had a demo Google Doc as a source with few comments.
Screenshot of Comment Doc
Any suggestions?

Google Apps Script - Can I modify one option of an embedded chart and preserve the other options?

I am trying to create a function (ultimately a menu button) that automatically resizes all the charts in a Sheet to a defined render width. I want to keep the other chart options (which might have changed from their default values before running this function) intact.
My code so far:
function resizeCharts() {
var newWidth = 1000;
var ss = SpreadsheetApp.getActiveSpreadsheet();
var currentSheet = ss.getActiveSheet();
var sheetCharts = currentSheet.getCharts();
for (var i = 0; i < sheetCharts.length; i++) {
var modifiedChart = sheetCharts[i].modify();
modifiedChart = modifiedChart.setOption('width', newWidth).build();
currentSheet.updateChart(modifiedChart);
}
}
This code will adjust the width of the chart, but it changes the other options (e.g. series colors, axis formats), which I don't want.
Is there a way to preserve all the existing options of a chart except for the one I want to change?
In case you just need to update chart's size or position, there is Advanced Sheets Service and specific UpdateEmbeddedObjectPosition request for that (which is not available via built-in Spreadsheet Service).
Example:
// your spreadsheet id
let ssId = SpreadsheetApp.getActiveSpreadsheet().getId();
// get all charts from sheet (replace 'YOUR_SHEET_TITLE')
let charts = Sheets.Spreadsheets
.get(ssId)
.sheets
.filter(sheet => sheet.properties.title === 'YOUR_SHEET_TITLE')[0]
.charts;
// array of requests
let requests = [];
// loop charts array
charts.forEach(chart => {
// create request for each chart
let request = {
updateEmbeddedObjectPosition: {
objectId: chart.chartId,
newPosition: {
overlayPosition: {
widthPixels: 1000 // specify your width
}
},
fields: 'widthPixels' // list fields you want to update
}
};
// push request to array
requests.push(request);
});
// send requests at once
// this may allow to improve script performance
Sheets.Spreadsheets.batchUpdate({'requests': requests}, ssId);
References:
Updating Spreadsheets - Apps Script
Updating Spreadsheets - Field masks
UpdateEmbeddedObjectPositionRequest
EmbeddedObjectPosition
OverlayPosition

This script does not populate sheet after parsing retrieved data

I hope this is well explained. First of all, sorry because my coding background is zero and I am just trying to "fix" a previously written script.
Problem The script does not populate sheet after parsing retrieved data if the function is triggered by timer and the sheet is not open in my browser .
The script works OK if run it manually while sheet is open.
Problem details:
When I open the sheet the cells are stuck showing "Loading" and after a short time, data is written.
Expected behavior is to get the data written no matter if I don't open the sheet.
Additional info: This is how I manually run the function
function onOpen() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var entries = [
{name: "Manual Push Report", functionName: "runTool"}
];
sheet.addMenu("PageSpeed Menu", entries);
}
Additional info: I set the triggers with Google Apps Script GUI See the trigger
Before posting the script code, you can see how the cells look in the sheet:
Script code
function runTool() {
var activeSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Results");
var rows = activeSheet.getLastRow();
for(var i=3; i <= rows; i++){
var workingCell = activeSheet.getRange(i, 2).getValue();
var stuff = "=runCheck"
if(workingCell != ""){
activeSheet.getRange(i, 3).setFormulaR1C1(stuff + "(R[0]C[-1])");
}
}
}
// URL check //
function runCheck(Url) {
var key = "XXXX Google PageSpeed API Key";
var strategy = "desktop"
var serviceUrl = "https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=" + Url + "&key=" + key + "&strategy=" + strategy +"";
var array = [];
var response = UrlFetchApp.fetch(serviceUrl);
if (response.getResponseCode() == 200) {
var content = JSON.parse(response.getContentText());
if ((content != null) && (content["lighthouseResult"] != null)) {
if (content["captchaResult"]) {
var score = content["lighthouseResult"]["categories"]["performance"]["score"];
} else {
var score = "An error occured";
}
}
array.push([score,"complete"]);
Utilities.sleep(1000);
return array;
}
}
You can try the code using the sheet below with a valid Pagespeed API key.
You only need to add a Trigger and wait for it's execution while the sheet is not open in your browser
https://docs.google.com/spreadsheets/d/1ED2u3bKpS0vaJdlCwsLOrZTp5U0_T8nZkmFHVluNvKY/copy
I suggest you to change your algorithm. Instead of using a custom function to call UrlFetchApp, do that call in the function called by a time-driven trigger.
You could keep your runCheck as is, just replace
activeSheet.getRange(i, 3).setFormulaR1C1(stuff + "(R[0]C[-1])");
by
activeSheet.getRange(i, 3, 1, 2).setValues(runCheck(url));
NOTE
Custom functions are calculated when the spreadsheet is opened and when its arguments changes while the spreadsheet is open.
Related
Cache custom function result between spreadsheet opens

Uploading Google Spreadsheet to a Google Site

I'd like to begin by stating that the end goal is to display our company directory (a list of our employees names/job title/extension#/office location/email), which is in a Google Sheet, on a page in one of our Google Sites.
I tried to use Google's embed function, and it works... but it is very clunky, does not have a "Sort" function, and it just looks weird.
I pulled a Google Apps Script from somewhere online like 3 months ago and it actually did pull in a way that made me happy:
(This is as it appears currently on the Google Sites page. So in this screenshot, the embedded Sheet is at the top. The Sheet when, imported via the script, is below. Yes, they are both on the same page. I'm in testing!)
This is the code I used (I THINK - I don't remember how I implemented it):
function myFunction() {
}
function onOpen(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
if(ScriptProperties.getProperty("page url") == null){
ss.addMenu("List page", [{name: "Create list", functionName: "create_list"},null,
{name: "Fetch list items", functionName: "fetch_items"}]);
}
else{
ss.addMenu("List page", [{name: "Push Items", functionName: "push_items"}]);
}
}
function create_list() {
var data = SpreadsheetApp.getActiveSheet().getDataRange().getValues();
var parent_page = Browser.inputBox("URL of the parent page:");
var title = Browser.inputBox("Choose a name for your list page:");
var data = SpreadsheetApp.getActiveSheet().getDataRange().getValues();
var list = SitesApp.getPageByUrl(parent_page).createListPage(title, title.split(' ').join(''), '', data[0]);
ScriptProperties.setProperty("page url", list.getUrl());
onOpen();
push_items();
}
function push_items(){
var done = false;
while(!done){
try{
var data = SpreadsheetApp.getActiveSheet().getDataRange().getValues();
var list = SitesApp.getPageByUrl(ScriptProperties.getProperty("page url"));
var list_items = list.getListItems();
for(i in list_items){
list_items[i].deleteListItem();
}
for(var i = 1; i < data.length; i++){
var item = list.addListItem(data[i]);
}
done = true;
}
catch(e){
}
}
SpreadsheetApp.getActiveSpreadsheet().toast(ScriptProperties.getProperty("page url"), "List page updated", 10);
}
function fetch_items(){
var url = Browser.inputBox("URL of your list page:");
var col_number = Browser.inputBox("Number of columns in the list:");
var data = new Array();
var list_items = SitesApp.getPageByUrl(url).getListItems();
for(i in list_items){
var row = new Array();
for(j = 0; j < col_number; j++){
row.push(list_items[i].getValueByIndex(j));
}
data.push(row);
}
SpreadsheetApp.getActiveSheet().getRange(1, 1, data.length, data[0].length).setValues(data);
}
[I do not take credit for writing this!]
So I would like to ask (since this ceases to make much sense to me) is if this is viable code for a Google Apps Script, and if so, how do I implement it to output Sheet data similarly in the same type of format as in the screenshot?
Alternatively, is there a better way to display this Sheet data in Google Sheets?
A totally different alternative would be to use Romain Vialard's "awesome tables" gadget. It works... awesome, and it is really easy to use. Besides, it admits filters, ...