Google Apps Script/Drive API appendParagraph values insert twice - google-apps-script

I needed a solution that copies into the current Doc the content of another set of Docs that are used as templates. (we need to retain the current docId). I wrote a simple Google Doc add-on that works like a charm... in some Google Apps domains.
I installed (and made screencasts) the IDENTICAL code in four different domains. In two of the domains the content copied from the selected "template" Doc gets (correctly) copied ONCE in the current Doc, however, in two other domains it gets injected TWICE!
All domains have identical settings (script run as admin, authorizations accepted, etc). I even installed a second copy in the faulty domains and that showed the same behaviour.
Anyone seen this behaviour?
function runInsert(template) {
var targetDoc = DocumentApp.getActiveDocument();
if( targetDoc.getHeader() == null) {
targetDoc.addHeader();
}/
if( targetDoc.getFooter() == null) {
targetDoc.addFooter();
}
var templateDoc = DocumentApp.openById(template);
// check for header, get elements and add to current doc
var templateHeader = templateDoc.getHeader();
if( templateHeader != null) {
var totalElementsHeader = templateHeader.getNumChildren();
for( var j = 0; j < totalElementsHeader; ++j ) {
var header = targetDoc.getHeader();
var element = templateHeader.getChild(j).copy();
var type = element.getType();
if( type == DocumentApp.ElementType.PARAGRAPH ){
header.appendParagraph(element);
}
}
}
// check for body, get elements and add to current doc
var templateBody = templateDoc.getBody();
if( templateBody != null) {
var totalElementsBody = templateBody.getNumChildren();
for( var j = 0; j < totalElementsBody; ++j ) {
var body = targetDoc.getBody();
var element = templateBody.getChild(j).copy();
var type = element.getType();
if( type == DocumentApp.ElementType.PARAGRAPH ){
body.appendParagraph(element);
Logger.log("j is " + j + " element contains " + element.getText());
}
else if( type == DocumentApp.ElementType.TABLE){
body.appendTable(element);
....
}
}
... copy footer
}

Found that the insert executed twice and fixed it in the sidebar javascript.

Does the script run off of installed triggers? I had a similar issue with one of my scripts where I accidentally installed a trigger twice which was leading to the script running twice for every event.
Check your triggers and see if multiples are installed for your project in the script editor under Resources > Current Project's Triggers.

Related

Copying Paragraph from one document to another and pasting it at the top

I'm currently working with Google Apps Script and trying to achieve the following:
Have a source document with a paragraph
Have a target document
Run the function to copy the paragraph from the source documeent into the target document
Run the function again to paste new content always at the top of the document, prior to the previous addition.
I've been able to achieve steps 1 to 3 with the following script I came across:
function menuItem1() {
DocumentApp.getUi() // Or DocumentApp or FormApp.
var sourceDoc = DocumentApp.getActiveDocument().getBody();
var targetDoc = DocumentApp.openById('doc id goes here');
var totalElements = sourceDoc.getNumChildren()
for( var j = 0; j < totalElements; ++j ) {
var body = targetDoc.getBody()
var element = sourceDoc.getChild(j).copy();
var type = element.getType();
if( type == DocumentApp.ElementType.PARAGRAPH ){
body.appendParagraph(element);
}
else if( type == DocumentApp.ElementType.LIST_ITEM){
body.appendListItem(element);
}
}
targetDoc.saveAndClose()
DocumentApp.getUi().alert('New template added');
}
`
However it always pastes the new content at the bottom of the document. I now understand that this is due to the appendParagraph method and that insertParagraph is what I should be using. However I'm struggling to understand what index I should be using in order to have my text always paste at the top of the page.
SUGGESTION
You can use the insertParagraph & insertListItem methods as seen on this tweaked script below.
Tweaks made:
Reversed your loop to make sure copied paragraphs are still in order when being inserted to the top of the target docs file.
Used insertParagraph instead of appendParagraph.
Used insertListItem instead of appendListItem.
Script
function menuItem1() {
DocumentApp.getUi() // Or DocumentApp or FormApp.
var sourceDoc = DocumentApp.getActiveDocument().getBody();
var targetDoc = DocumentApp.openById('DOCS ID');
var totalElements = sourceDoc.getNumChildren();
for (var j = (totalElements - 1); j >= 0; j--) { //Reversed the loop to make sure paragraphs/lists are still in order when inserted to the target sheet.
var body = targetDoc.getBody()
var element = sourceDoc.getChild(j).copy();
var type = element.getType();
if (type == DocumentApp.ElementType.PARAGRAPH) {
body.insertParagraph(0, element); //Always insert at the top
}
else if (type == DocumentApp.ElementType.LIST_ITEM) {
body.insertListItem(0, element.copy());; //Always insert at the top
}
}
targetDoc.saveAndClose()
DocumentApp.getUi().alert('New template added');
}
Demo
source docs file.
After running the script, here's the target docs file.
When the source docs file gets updated.
After running the script again, here's the target docs file.

Service Unavailable: DOCS when copying Google Docs

Suddenly encountered one issue where Google DOCS service fails after making some calls to copy elements.
Funny thing is that it actually copies the first element, but when it goes for the second loop within the "for" it fails giving this error:
Service unavailable: Documents
This script used to work fine for a few months as we have it published within our company.
Suddenly last week it stopped working for all the users regardless of their browser etc...
It is a script linked to a Google Spreadsheet (where it gets the information to generate a Google Docs). But it also fails if I execute it, or debug it, from the Script console as well.
What I've tried after it started failing is to add a couple of lines to saveAndClose the document in case there was an issue with too many calls or buffering.
I've checked and the project does have access to Google Docs, Drive and Maps API.
The elements to be copied are correct, as I can do Logger.Log with them...
It just fails when it has to copy the actual element (appendParagraph, append ListItem, etc...)
I've enabled StackDriver logs and errors but it doesn't show any logs in the console, just "failed".
Any hint or directions will be very much appreciate it!
Thanks!
function copyDescription(ID,newDoc,newDocID){
var otherBody = DocumentApp.openById(ID).getBody();
newDoc.saveAndClose();
newDoc = DocumentApp.openById(newDocID);
var docbody = newDoc.getBody();
var totalElements = otherBody.getNumChildren();
//Run through template document and copies to the new one
for( var j = 0; j < totalElements; ++j ) {
var element = otherBody.getChild(j).copy();
var attributes = otherBody.getChild(j).getAttributes();
var type = element.getType();
if (type == DocumentApp.ElementType.PARAGRAPH) {
if (element.asParagraph().getNumChildren() != 0 && element.asParagraph().getChild(0).getType() == DocumentApp.ElementType.INLINE_IMAGE) {
var pictattr = element.asParagraph().getChild(0).asInlineImage().getAttributes();
var blob = element.asParagraph().getChild(0).asInlineImage().getBlob();
}
else {
docbody.appendParagraph(element);
}
}
else if( type == DocumentApp.ElementType.TABLE )
docbody.appendTable(element);
else if( type == DocumentApp.ElementType.LIST_ITEM )
docbody.appendListItem(element);
else
throw new Error("Unsupported element type: "+type);
newDoc.saveAndClose();
}
}

How to copy content and formatting between Google Docs?

I need to copy the content of a Google Document, and append it to another Document. If I use something like this:
newDoc.getBody().appendParagraph(template.getText());
...I get the text, but lose the formatting that was in my original file. (Bold, Italic, etc.)
How can I copy the contents and formatting to the new document? Is it possible to assign everything to one variable, and copy / paste it to the new document?
Not using only 1 variable , you'll have to iterate all the elements in the doc and copy them one by one.
there are multiple threads on the same subject, try for example this one : How to copy one or more existing pages of a document using google apps script
just read carefully the code and add all the content types that you are supposed to meet in your document (tables, images, pagebreaks...)
EDIT : here is a trial on that idea (to start with)
function copyDoc() {
var sourceDoc = DocumentApp.getActiveDocument().getBody();
var targetDoc = DocumentApp.create('CopyOf'+DocumentApp.getActiveDocument().getName());
// var targetDoc = DocumentApp.openById('another doc ID');
var totalElements = sourceDoc.getNumChildren();
for( var j = 0; j < totalElements; ++j ) {
var body = targetDoc.getBody()
var element = sourceDoc.getChild(j).copy();
var type = element.getType();
if( type == DocumentApp.ElementType.PARAGRAPH ){
body.appendParagraph(element);
}
else if( type == DocumentApp.ElementType.TABLE){
body.appendTable(element);
}
else if( type == DocumentApp.ElementType.LIST_ITEM){
body.appendListItem(element);
}
// ...add other conditions (headers, footers...
}
targetDoc.saveAndClose();
}

How to copy a template and insert content from another document?

I want to write a Google Docs script that copies a template (which just contains a container bound script) and appends the contents of another document chosen by the user. How would I accomplish this? I already have a way to select the file (the template has a static id), but figure out a way to copy all the content of the document (including inlineImages and hyperLinks) to the my new document.
I guess the only way is to copy elements one by one... there are a whole bunch of document elements but it shouldn't be too hard to be quite exhaustive.
Here is how it goes for the most common types, you'll have to add the other ones.
(original code borrowed from an answer by Henrique Abreu)
function importInDoc() {
var docID = 'id of the template copy';
var baseDoc = DocumentApp.openById(docID);
var body = baseDoc.getBody();
var otherBody = DocumentApp.openById('id of source document').getBody();
var totalElements = otherBody.getNumChildren();
for( var j = 0; j < totalElements; ++j ) {
var element = otherBody.getChild(j).copy();
var type = element.getType();
if( type == DocumentApp.ElementType.PARAGRAPH )
body.appendParagraph(element);
else if( type == DocumentApp.ElementType.TABLE )
body.appendTable(element);
else if( type == DocumentApp.ElementType.LIST_ITEM )
body.appendListItem(element);
else if( type == DocumentApp.ElementType.INLINE_IMAGE )
body.appendImage(element);
// add other element types as you want
else
throw new Error("According to the doc this type couldn't appear in the body: "+type);
}
}

Copy text, images, tables, ALL formatting, margins from on GDoc to another

After trying a few mail merge scripts, I decided t write my own. My merge script runs as a separate fiIt reads a template from a GDoc, reads data from a GSpreadsheet, and merges it either into Gmails or into a new GDoc - one page / email per SS row.
The problem is that it doesn't copy text formatting, margins or images into the Gmail or new GDoc ... only the plain text.
I am using DocumentApp.openById > getActiveSection > getText() to capture the text.
Here is the code in GDoc http://goo.gl/fO5vP
I can't seem to share a script so I had to put it in a doc. Copy it to a new script and it will be color coded.
You should copy the template first using DocsList so you start with a "complete" initial document.
var template = DocsList.getFileById(docIDs[0]);// get the template model, in this sample I had an array of possible templates, I took the first one
var newmodelName=template.substr(0,11)+'multipage'+template.substring(18);// define a new name, do what you need here...
var baseDocId = DocsList.copy(template,newmodelName).getId();// make a copy of firstelement and give it new basedocname build from the serie(to keep margins etc...)
var baseDoc = DocumentApp.openById(baseDocId);// this is the new doc to modify
then use the document class that has a direct replaceText method
EDIT : about your secondary question, here is a suggestion on how you could do. It works nicely except for inlineImage, I'll keep looking at this. You could also make the script more universal by adding other element types...
function myFunction() {
var template = DocsList.getFileById(key);// get the template model
var newmodelName='testcopy';// define a new name, do what you need here...
var baseDocId = DocsList.copy(template,newmodelName).getId();// make a copy of firstelement and give it new basedocname build from the serie(to keep margins etc...)
var baseDoc = DocumentApp.openById(baseDocId);// this is the new doc to modify
var body = baseDoc.getActiveSection();
body.appendPageBreak();
var totalElements = body.getNumChildren();
for( var j = 0; j < totalElements; ++j ) {
var element = body.getChild(j).copy();
var type = element.getType();
if( type == DocumentApp.ElementType.PARAGRAPH )
body.appendParagraph(element);
else if( type == DocumentApp.ElementType.TABLE )
body.appendTable(element);
else if( type == DocumentApp.ElementType.LIST_ITEM )
body.appendListItem(element);
else if( type == DocumentApp.ElementType.INLINE_IMAGE )
{ var blob = body.getChild(j).asInlineImage().getBlob();
body.appendImage(blob); }
}
}
Edit 2 Thanks to #Fausto, here is a fully working version. Inline images are included in a paragraph so we had to dig one level more to get the blob...
function myFunction() {
var template = DocsList.getFileById(key);// get the template model
var newmodelName='testcopy';// define a new name, do what you need here...
var baseDocId = DocsList.copy(template,newmodelName).getId();// make a copy of firstelement and give it new basedocname build from the serie(to keep margins etc...)
var baseDoc = DocumentApp.openById(baseDocId);// this is the new doc to modify
var body = baseDoc.getActiveSection();
body.appendPageBreak();
var totalElements = body.getNumChildren();
for( var j = 0; j < totalElements; ++j ) {
var element = body.getChild(j).copy();
var type = element.getType();
if (type == DocumentApp.ElementType.PARAGRAPH) {
if (element.asParagraph().getNumChildren() != 0 && element.asParagraph().getChild(0).getType() == DocumentApp.ElementType.INLINE_IMAGE) {
var blob = element.asParagraph().getChild(0).asInlineImage().getBlob();
body.appendImage(blob);
}
else body.appendParagraph(element.asParagraph());
}
else if( type == DocumentApp.ElementType.TABLE )
body.appendTable(element);
else if( type == DocumentApp.ElementType.LIST_ITEM )
body.appendListItem(element);
}
}