Is it possible to view a list of all objects used in Google Slides? - google-apps-script

When some objects in Google Slides get hidden behind another object it may be later hard to find them on the slide.
Is it possible, for example, to see a panel with a list of all objects which are present on a given slide? And possibly edit them even if they are in the bottom layer (completely hidden behind another object)? This might be useful for animations when an object is displayed later and fully covers a previously displayed object.

Your goal I believe is as follows.
Your Google Slides has several text boxes of the same size and the same position.
You want to retrieve the list of texts from the text boxes and want to change the texts using a simpler method.
In this case, I thought that when the sidebar created by Google Apps Script is used for changing the texts, your goal might be able to be simply achieved.
The sample script is as follows.
Usage:
1. Prepare script:
Please copy and paste the following script to the script editor of Google Slides and save the script. And then, please reopen the Google Slides. By this, the custom menu "sample" is created for the Google Slides. When "RUN" in the custom menu "sample" is opened, the script is run.
Code.gs
Please copy and paste this script as Code.gs.
function onOpen() {
SlidesApp.getUi().createMenu("sample").addItem("RUN", "openSidebar").addToUi();
}
function openSidebar() {
const html = HtmlService.createHtmlOutputFromFile("index").setTitle("sample");
SlidesApp.getUi().showSidebar(html);
}
function getSelectedShapes() {
const select = SlidesApp.getActivePresentation().getSelection();
const pageElementRange = select.getPageElementRange();
if (pageElementRange) {
const obj = pageElementRange.getPageElements().reduce((ar, e) => {
if (e.getPageElementType() == SlidesApp.PageElementType.SHAPE) {
const shape = e.asShape();
ar.push({objectId: shape.getObjectId(), text: shape.getText().asString().trim()});
}
return ar;
}, []).reverse();
return obj;
}
return [];
}
function updatedTexts(o) {
const select = SlidesApp.getActivePresentation().getSelection();
const slide = select.getCurrentPage();
const obj = slide.getShapes().reduce((o, e) => Object.assign(o, {[e.getObjectId()]: {shape: e, text: e.getText().asString().trim()}}), {});
o.forEach(({objectId, text}) => {
if (obj[objectId] && obj[objectId].text != text) {
obj[objectId].shape.getText().setText(text);
}
});
return "Done";
}
index.html
Please copy and paste this script as index.html.
<input type="button" id="main" value="Get selected shapes" onClick="main()">
<div id="shapes"></div>
<input type="button" id="update" value="Updated texts" onClick="updatedTexts()" style="display:none">
<script>
function main() {
document.getElementById("main").disabled = true;
document.getElementById("shapes").innerHTML = "";
google.script.run.withSuccessHandler(o => {
if (o.length == 0) {
document.getElementById("update").style.display = "none";
return;
}
const div = document.getElementById("shapes");
o.forEach(({objectId, text}) => {
const input = document.createElement("input");
input.setAttribute("type", "text");
input.setAttribute("id", objectId);
input.setAttribute("value", text);
div.appendChild(input);
});
document.getElementById("update").style.display = "";
document.getElementById("main").disabled = false;
}).getSelectedShapes();
}
function updatedTexts() {
const inputs = document.getElementById("shapes").getElementsByTagName('input');
const obj = [...inputs].map(e => ({objectId: e.id, text: e.value}));
console.log(obj)
google.script.run.withSuccessHandler(e => console.log(e)).updatedTexts(obj);
}
</script>
2. Testing:
Please reopen Google Slides. By this, the custom menu is created. Please open "sample" -> "RUN". By this, the sidebar is opened.
Please select the text boxes on Google Slides.
Click "Get selected shapes" button.
By this, the selected text boxes are retrieved and you can see the texts of text boxes.
Modify the texts.
Click "Updated texts" button.
By this, the modified texts are reflected in the text boxes.
Also, you can see it with the following demonstration movie.
Note:
This is a simple sample script. So please modify the above script and HTML style for your actual situation.
References:
Custom sidebars

Related

Google Apps Script - Exception: Page element is not of type shape

I store in a Google Sheets table some data about quotes, which I inject in a Google Slides (duplicate of a template).
My script works replacing the placeholders with the cell content and injecting the images from the URLs. However, I get an error message that breaks the rest of the script.
function createSlide_(currentAppName) {
let presentation = SlidesApp.openByUrl('https://docs.google.com/presentation/d/###/edit');
let templateSlide = presentation.getSlideById('###');
let newSlide = templateSlide.duplicate();
newSlide.setSkipped(false);
newSlide.replaceAllText('{{AppName}}',currentAppName[0]);
newSlide.replaceAllText('{{Company}}',currentAppName[1]);
newSlide.replaceAllText('{{Founder}}',currentAppName[2]);
newSlide.replaceAllText('{{Designation}}',currentAppName[3]);
newSlide.replaceAllText('{{quote}}',currentAppName[4]);
newSlide.getShapes().forEach(s => {
if (s.getText().asString().trim() == "{{AppImage}}") s.replaceWithImage(DriveApp.getFileById(currentAppName[5].match(/[-\w]{25,}/)).getBlob());
if (s.getText().asString().trim() == "{{FounderImage}}") s.replaceWithImage(DriveApp.getFileById(currentAppName[6].match(/[-\w]{25,}/)).getBlob());
});
};
Exception: Page element is not of type shape.
I also tried to store var shapes = newSlide.getShapes(), check that it exists and then iterate if typeof shapes !== 'undefined' and shapes.length > 0.
I also tried the getPageElements method - the script does not work.
Any idea please?
Not know exactly what is failing in you script I created a simple example. Try this and see what you template consists of. I have a simple slide with just one text box.
function test() {
try {
let presentation = SlidesApp.getActivePresentation();
let slides = presentation.getSlides();
slides.forEach( slide => console.log(slide.getObjectId()) );
let template = presentation.getSlideById("p");
let slide = template.duplicate();
slide.replaceAllText("{{hello}}","goodbye");
let elements = slide.getPageElements();
elements.forEach( element => console.log(element.getPageElementType().toString()))
}
catch(err) {
console.log(err)
}
}
Execution log
3:52:46 PM Notice Execution started
3:52:47 PM Info p
3:52:47 PM Info SHAPE
3:52:47 PM Notice Execution completed

How to add text to an open Dialog window?

I am working on a Google Sheets macro that displays some text to the user, then presents some buttons for the user to interact with. The buttons run another function and I am struggling with how to have the button display the text to the user.
I can't find the method or object I am supposed to use to grab the currently open window and edit the html to add more text. Or if that isn't possible, how can I close the open window and then display a new window that also has the old text?
function example(text,title,height=90,width=350) {
const dialogbutton = '<br><input type="button" value="Do Stuff" onClick="google.script.run.doStuff();" />'
var html=HtmlService.createHtmlOutput(text+dialogbutton).setHeight(height).setWidth(width)
SpreadsheetApp.getUi().showModelessDialog(html, title);
}
function doStuff() {
const dialogWindow = ???//I am hoping to retrieve the open window as an object
const text = getText() //run some other function and get the new text to insert
dialogWindow.displayedText += text //modify the displayed window to add the new text
}
Here is a very simple example of how to communicate with the server (i.e. Spreadsheet or Doc). In this case a spreadsheet with Sheet1!A1 = hello
Here is a simple dialog
Server side code Code.gs bound to a spreadsheet
function showTest() {
var html = HtmlService.createTemplateFromFile("HTML_Simple");
html = html.evaluate();
SpreadsheetApp.getUi().showModalDialog(html,"Test");
}
function doStuff() {
try {
// let get a value from spreadsheet
let spread = SpreadsheetApp.getActiveSpreadsheet();
let sheet = spread.getSheetByName("Sheet1");
return sheet.getRange("A1").getValue();
}
catch(err) {
Logger.log("Error in doStuff() "+err);
}
}
HTML Page HTML_Simple.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<input type="button" value="Do Stuff" onClick="doStuffOnClick()">
<input type="text" id="whatStuff">
<script>
function doStuffOnClick() {
try {
google.script.run.withSuccessHandler(
function(response) {
document.getElementById("whatStuff").value = response;
}
).doStuff();
}
catch(err) {
alert(err);
}
}
</script>
</body>
</html>
Reference
HTML Service Best Practices
google.script.run()

How to get values of Semantic UI multiple select dropdown in the order chosen by user?

I am using the Semantic UI multiple search selection dropdown within an html dialog in an Apps Script project. It works perfectly but I can only get the values to return in alphabetical order.
I have found two ways to get the values:
Using <form> tag
Using the .dropdown("get value") method as shown in the documentation
Both output alphabetically, not in the order that the user selected them.
[This picture shows an example of a user selection.]
It outputs as Chinese, Hmong, and Spanish but I need it to come out as Hmong, Spanish, and Chinese.
<html><link href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.css" rel="stylesheet" />
<head><base target="_top"></head><body>
<form>
Translate to:
<select class="ui fluid search dropdown" multiple="" id='languages' name='languages'>
<option value='Chinese (Simplified)'>Chinese (Simplified)</option>"
<option value='Hmong'>Hmong</option>"
<option value='Spanish'>Spanish</option>"
<option value='Arabic'>Arabic</option>"
</select></form>
<button onclick='usingFormTags()'>Save Preferences</button>
<button onclick='passingAsVariable()'>Save Preferences</button>
<script>
function usingFormTags() {
google.script.run.getForm(document.forms[0]);
}
function passingAsVariable() {
var data1 = $("#languages").dropdown("get value");
google.script.run.getData(data1);
}
</script></body></html>
This is using the .dropdown("get value")
function doGet(){
return HtmlService.createHtmlOutput("html");}
function getData(data1){
Logger.log(data1)}
This is using the <form> tag
function doGet(){
return HtmlService.createHtmlOutput("html"); }
function getForm(form) { var languages = form.languages Logger.log(languages)}
I've also tried the .dropdown("get text") in place of "get values" but it returns nothing. Everything I can find online discusses how to get an array of values but nothing about how to get them in the user-defined order.
I believe your goal is as follows.
You want to retrieve the selected values in order.
In this case, how about the following modification? In this modification, I used onChange of the built-in actions. When this is reflected in your script, it becomes as follows.
Modified script:
From:
<script>
function usingFormTags() {
google.script.run.getForm(document.forms[0]);
}
function passingAsVariable() {
var data1 = $("#languages").dropdown("get value");
google.script.run.getData(data1);
}
</script>
To:
<script>
let ar = [];
$('#languages').dropdown({
onChange: function (value) {
const obj1 = ar.reduce((o, e) => (o[e] = true, o), {});
const obj2 = value.reduce((o, e) => (o[e] = true, o), {});
value.forEach(e => {
if (!obj1[e]) ar.push(e);
});
ar.forEach(e => {
if (!obj2[e]) ar.splice(ar.indexOf(e), 1);
});
}
});
function usingFormTags() {
console.log(ar);
google.script.run.getForm(ar);
}
function passingAsVariable() {
console.log(ar);
google.script.run.getData(ar);
}
</script>
In this modification, the same value is returned for both buttons. So, please modify this for your actual situation.
And, in this modification, even when the selected values are removed, the order of values is kept.
Testing:
Also, you can test this modification at jsfiddle.net as follows.
https://jsfiddle.net/dm7ubyst/
Reference:
Specifying Select Action

Any time a section is mentioned in the document, I want that mention to become a link to the corresponding bookmark

Goal: I have a very long document with many unique sections that each have bookmarks. Any time a section is mentioned in the document, I want that mention to become a link to the corresponding bookmark. It doesn't have to be event-driven, I intend to do it from a menu.
I have the below code written to get a list of the names of each bookmarked line so I can match it to the words in the doc. I'm trying to figure out what line of code to use to link specific text to that bookmark. I've tried to use the setLinkUrl("beginningofurl" + id[i]) code, but the ID of the bookmarks doesn't tell me if it's a header or regular text, and sometimes it is just regular text. I'm wondering if there's a better way of doing this?
var DOC = DocumentApp.getActiveDocument();
function Setlink() {
var bookmarks = DOC.getBookmarks();
var names = [];
for (var i = 0; i < bookmarks.length; i++){
names.push(bookmarks[i].getPosition().getSurroundingText().getText());
}
Logger.log(names);
}
Headings are a property of Paragraph elements. To check a Bookmark to see if it is in a paragraph of a certain Paragraph Heading, we need to get the Position, then the Element, and then check if the Element is indeed a Paragraph before we can check the Paragraph Heading.
We can put our test for if an Element is a heading in a predicate function named isElementInHeading that will return true or false when given an Element.
function isElementInHeading(element) {
if (element.getType() !== DocumentApp.ElementType.PARAGRAPH) {
return false;
}
const {ParagraphHeading} = DocumentApp;
switch (element.getHeading()) {
case ParagraphHeading.HEADING1:
case ParagraphHeading.HEADING2:
case ParagraphHeading.HEADING3:
case ParagraphHeading.HEADING4:
case ParagraphHeading.HEADING5:
case ParagraphHeading.HEADING6:
return true;
}
return false;
}
This can be used to both filter the bookmarks to include only those that mark headings, and to skip over the same headings when using setLinkUrl.
The strategy in this example is to collect both the bookmark's ID and the desired text in one go using a reducer function, then search through the document for each bit of text, check that we didn't just find the header again, and then apply the link.
I am not quite sure how you are getting the URL, but I found just copying and pasting the URL into the script as const url = "https://docs.google.com/.../edit#bookmark="; worked for me.
// for Array.prototype.reduce
function getHeadingBookmarksInfo(bookmarks, bookmark) {
const element = bookmark.getPosition().getElement();
if (isElementInHeading(element)) {
return [
...bookmarks,
{ id: bookmark.getId(), text: element.getText() }
];
}
return bookmarks;
}
function updateLinks() {
const doc = DocumentApp.getActiveDocument();
const bookmarks = doc.getBookmarks();
const headingBookmarksInfo = bookmarks.reduce(getHeadingBookmarksInfo, []);
const body = doc.getBody();
headingBookmarksInfo.forEach(function(info) {
const {id, text} = info;
let foundRef = body.findText(text);
while (foundRef !== null) {
const element = foundRef.getElement();
if (!isElementInHeading(element.getParent())) {
element.asText()
.setLinkUrl(
foundRef.getStartOffset(),
foundRef.getEndOffsetInclusive(),
url + id // assumes url is hardcoded in global scope
);
}
foundRef = body.findText(text, foundRef);
}
});
}

Create dropdown in Document

I would like to create a dropdown list in a popup using Google Drive Word document.
I'm using this example:
function doGet() {
var app = UiApp.createApplication();
var panel = app.createVerticalPanel();
panel.add(app.createButton("button 1"));
panel.add(app.createButton("button 2"));
app.add(panel);
return app;
}
When executing the script, nothing happens, i cannot see the popup anywhere?
What am i doing wrong?
You can't render UiApp elements within a Document on Drive, only in a Spreadsheet, Site, or published Web-App.
This is noted here: https://developers.google.com/apps-script/guides/ui-service#Overview
You can confirm this is the issue by trying the same script in a Spreadsheet.
doGet() isn't what you need to use. You need to use function onOpen() { code };
When you create a NEW Google Doc, and choose TOOLS, SCRIPT EDITOR, a list of all the sample scripts will pop up. If you choose a script for a DOC, you will get sample code. Just save the sample scripts, close the new doc, then open it up again. You'll see a menu item named SAMPLE.
That code doesn't cause a pop-up when the doc opens, but you should be able to just run whatever part of the code causes the alert, or side bar, whatever you want to show when the document opens.
Here is the official sample code:
/**
* The onOpen function runs automatically when the Google Docs document is
* opened. Use it to add custom menus to Google Docs that allow the user to run
* custom scripts. For more information, please consult the following two
* resources.
*
* Extending Google Docs developer guide:
* https://developers.google.com/apps-script/guides/docs
*
* Document service reference documentation:
* https://developers.google.com/apps-script/reference/document/
*/
function onOpen() {
// Add a menu with some items, some separators, and a sub-menu.
DocumentApp.getUi().createMenu('Sample')
.addItem('Show alert', 'showAlert')
.addItem('Show prompt', 'showPrompt')
.addSeparator()
.addItem('Show dialog', 'showDialog')
.addItem('Show sidebar', 'showSidebar')
.addSeparator()
.addSubMenu(DocumentApp.getUi().createMenu('Document interaction')
.addItem('Search and replace', 'searchAndReplace')
.addItem('Insert at cursor', 'insertAtCursor')
.addItem('Turn selection purple', 'turnSelectionPurple')
.addItem('Create report in document', 'createReport'))
.addToUi();
}
/**
* Shows a message box in the Google Docs editor.
*/
function showAlert() {
// Displays a dialog box with "Yes" and "No" buttons. Script execution will be
// halted until the dialog is dismissed.
var result = DocumentApp.getUi().alert(
'Dialog title',
'Are you sure you want to continue?',
DocumentApp.getUi().ButtonSet.YES_NO);
// Process the user's response.
if (result == DocumentApp.getUi().Button.YES) {
// The user clicked the "Yes" button.
DocumentApp.getUi().alert('Proceeding with the operation...');
} else {
// The user clicked the "No" button or the dialog's close button.
DocumentApp.getUi().alert('Canceling the operation...');
}
}
/**
* Shows an input box in the Google Docs editor.
*/
function showPrompt() {
// Displays a dialog box with "OK" and "Cancel" buttons, as well as a text box
// allowing the user to enter a response to a question.
var result = DocumentApp.getUi().prompt('Upgrade your user experience!',
'Please enter your name:', DocumentApp.getUi().ButtonSet.OK_CANCEL);
// Process the user's response:
if (result.getSelectedButton() == DocumentApp.getUi().Button.OK) {
// The user clicked the "OK" button.
DocumentApp.getUi().alert('The user\'s name is ' +
result.getResponseText() + '. (And what a lovely name that is!)');
} else if (result.getSelectedButton() == DocumentApp.getUi().Button.CANCEL) {
// The user clicked the "Cancel" button.
DocumentApp.getUi().alert('The user didn\'t want to provide a name.');
} else if (result.getSelectedButton() == DocumentApp.getUi().Button.CLOSE) {
// The user clicked the dialog's close button.
DocumentApp.getUi().alert(
'The user clicked the close button in the dialog\'s title bar.');
}
}
/**
* Shows a custom HTML user interface in a dialog above the Google Docs editor.
*/
function showDialog() {
DocumentApp.getUi().showDialog(
HtmlService
.createHtmlOutput('<p>Hello from Google Apps Script!</p>')
.setTitle('My custom dialog')
.setWidth(400 /* pixels */)
.setHeight(300 /* pixels */));
}
/**
* Shows a custom HTML user interface in a sidebar in the Google Docs editor.
*/
function showSidebar() {
DocumentApp.getUi().showSidebar(
HtmlService
.createHtmlOutput('<p>A change of speed, a change of style...</p>')
.setTitle('My custom sidebar')
.setWidth(350 /* pixels */));
}
/**
* Performs a simple search and replace on the document's contents. A subset of
* JavaScript regular expressions is supported; see the Apps Script reference
* documentation for more information.
*/
function searchAndReplace() {
var bodyElement = DocumentApp.getActiveDocument().getBody();
bodyElement.replaceText('name_placeholder', 'Joe Script-Guru');
bodyElement.replaceText('address_placeholder', '100 Script Rd');
bodyElement.replaceText('city_placeholder', 'Scriptville');
bodyElement.replaceText('state_placeholder', 'Scripting Bliss');
bodyElement.replaceText('zip_placeholder', '94043');
}
/**
* Inserts the sentence "Hey there!" at the current cursor location in boldface.
*/
function insertAtCursor() {
var cursor = DocumentApp.getActiveDocument().getCursor();
if (cursor) {
// Attempt to insert text at the cursor position. If insertion returns null,
// then the cursor's containing element doesn't allow text insertions.
var element = cursor.insertText('Hey there!');
if (element) {
element.setBold(true);
} else {
DocumentApp.getUi().alert('Cannot insert text at this cursor location.');
}
} else {
DocumentApp.getUi().alert('Cannot find a cursor in the document.');
}
}
/**
* Sets the background color of any selected text to purple. Not very useful,
* perhaps, but it demonstrates how to read and modify the current document
* selection.
*/
function turnSelectionPurple() {
// Try to get the current selection in the document. If this fails (e.g.,
// because nothing is selected), show an alert and exit the function.
var selection = DocumentApp.getActiveDocument().getSelection();
if (!selection) {
DocumentApp.getUi().alert('Cannot find a selection in the document.');
return;
}
var selectedElements = selection.getSelectedElements();
for (var i = 0; i < selectedElements.length; ++i) {
var selectedElement = selectedElements[i];
// Only modify elements that can be edited as text; skip images and other
// non-text elements.
var text = selectedElement.getElement().editAsText();
// Change the background color of the selected part of the element, or the
// full element if it's completely selected.
if (selectedElement.isPartial()) {
text.setBackgroundColor(selectedElement.getStartOffset(),
selectedElement.getEndOffsetInclusive(), '#69359c');
} else {
text.setBackgroundColor('#69359c');
}
}
}
/**
* Constructs a simple report with three body sections and a footer in the
* current Google Docs document.
*/
function createReport() {
var title = 'Script Center Report';
var summaryContents = 'This reports addresses...';
var overviewContents = 'We undertook this project because...';
var dataContents = 'We collected three samples of data...';
var doc = DocumentApp.getActiveDocument();
var body = doc.getBody();
// Build up the report's title and abstract.
var reportTitle = body.appendParagraph(title);
reportTitle.setFontFamily(DocumentApp.FontFamily.ARIAL);
reportTitle.setFontSize(24);
reportTitle.setForegroundColor('#4a86e8');
reportTitle.setAlignment(DocumentApp.HorizontalAlignment.CENTER);
var execSummary = body.appendParagraph('Executive Summary');
execSummary.setFontSize(14);
execSummary.setSpacingBefore(14);
execSummary.setBold(true);
var execBody = body.appendParagraph(summaryContents);
execBody.setFontFamily(DocumentApp.FontFamily.TIMES_NEW_ROMAN);
execBody.setFontSize(12);
execBody.setSpacingBefore(6);
// Build up the report's contents.
var overview = body.appendParagraph('Project Overview');
overview.setFontSize(14);
overview.setSpacingBefore(14);
overview.setBold(true);
var overviewBody = body.appendParagraph(overviewContents);
overviewBody.setFontFamily(DocumentApp.FontFamily.TIMES_NEW_ROMAN);
overviewBody.setFontSize(12);
overviewBody.setSpacingBefore(6);
var data = body.appendParagraph('Project Data');
data.setFontSize(14);
data.setSpacingBefore(14);
data.setBold(true);
var dataBody = body.appendParagraph(dataContents);
dataBody.setFontFamily(DocumentApp.FontFamily.TIMES_NEW_ROMAN);
dataBody.setFontSize(12);
dataBody.setSpacingBefore(6);
// Build up the report's footer.
var footer = doc.addFooter();
var divider = footer.appendHorizontalRule();
var footerText = footer.appendParagraph('Confidential and proprietary');
footerText.setFontSize(9);
footerText.setForegroundColor('#4a86e8');
footerText.setAlignment(DocumentApp.HorizontalAlignment.RIGHT);
return doc;
}