Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Update/Replace inline image on Google Document

I'm trying to set a feature to update images on a Google Document, the same way Lucidchart Add-on does on its "Updated inserted diagram" feature. For this, I'm current doing the following:

  • Creating a Named Range and storing its id on document properties, together with the data to generate the image, for later retrieve.
  • On update, call body.getNamedRangeById() and replace the element with the new generated image.

This works, but I have the following problems that does not happen with Lucidchart:

  • Every update, a blank line is added after the image.
  • If the user drag and drop the image inside document for reposition it, the Named Range disappears and I'm not able to retrieve it later.
  • If the user centralize the image, after update the image comes back to left position, even copying its attributes

Does anybody knows a good strategy to replace/update a referenced image on Google Docs, the same way Lucidchart add-on update feature works?

Thanks

like image 370
Mael Avatar asked Oct 27 '14 21:10

Mael


2 Answers

NamedRanges indeed get lost when the range is moved, so they're not very good for your scenario. But there's no other way of identifying elements (which is a great misfeature of Google Docs).

In the case of an image you could use its LINK_URL to identify it, which seems to be what Lucidchart uses. It does not get in the way of the user, so it may be a good solution.

About getting a blank line and losing attributes when inserting an image, I imagine (since you haven't shared any code) you're inserting the image directly in the document body instead of a paragraph. Then a paragraph gets created automatically to wrap your image resulting in the blank line and lost of attributes.

Here's some code example:

function initialInsert() {
  var data = Charts.newDataTable().addColumn(
    Charts.ColumnType.STRING, 'Fruits').addColumn(
    Charts.ColumnType.NUMBER, 'Amount').addRow(
    ['Apple',15]).addRow(
    ['Orange',6]).addRow(
    ['Banana',14]).build();
  var chart = Charts.newPieChart().setDataTable(data).build();

  var body = DocumentApp.getActiveDocument().getBody()
  body.appendImage(chart).setLinkUrl('http://mychart');
  //here we're inserting directly in the body, a wrapping paragraph element will be created for us
}

function updateImage() {
  var data = Charts.newDataTable().addColumn(
    Charts.ColumnType.STRING, 'Fruits').addColumn(
    Charts.ColumnType.NUMBER, 'Amount').addRow(
    ['Apple',Math.floor(Math.random()*31)]).addRow( //random int between 0 and 30
    ['Orange',Math.floor(Math.random()*31)]).addRow(
    ['Banana',Math.floor(Math.random()*31)]).build();
  var chart = Charts.newPieChart().setDataTable(data).build();

  var img = getMyImg(DocumentApp.getActiveDocument().getBody(), 'http://mychart');
  //let's insert on the current parent instead of the body 
  var parent = img.getParent(); //probably a paragraph, but does not really matter
  parent.insertInlineImage(parent.getChildIndex(img)+1, chart).setLinkUrl('http://mychart');
  img.removeFromParent();
}

function getMyImg(docBody, linkUrl) {
  var imgs = docBody.getImages();
  for( var i = 0; i < imgs.length; ++i )
    if( imgs[i].getLinkUrl() === linkUrl )
      return imgs[i];
  return null;
}

About the link_url, you could of course do like Lucidchart does and link back to your site. So it's not just broken for the user.

like image 189
Henrique G. Abreu Avatar answered Sep 19 '22 12:09

Henrique G. Abreu


Take a look at my add-on called PlantUML Gizmo.

Here's the code to the insert image function, which deals with replacing images if there's already one selected:

function insertImage(imageDataUrl, imageUrl) {
  /*
   * For debugging cursor info
   */
//  var cursor = DocumentApp.getActiveDocument().getCursor();
//  Logger.log(cursor.getElement().getParent().getType());
//  throw "cursor info: " + cursor.getElement().getType() + " offset = " + cursor.getOffset() + " surrounding text = '" + cursor.getSurroundingText().getText() + "'  parent's type = " + 
//    cursor.getElement().getParent().getType();
  /*
   * end debug
   */
  var doc = DocumentApp.getActiveDocument();
  var selection = doc.getSelection();
  var replaced = false;
  if (selection) {
    var elements = selection.getSelectedElements();
    // delete the selected image (to be replaced)
    if (elements.length == 1 &&
        elements[0].getElement().getType() ==
        DocumentApp.ElementType.INLINE_IMAGE) {
          var parentElement = elements[0].getElement().getParent();  // so we can re-insert cursor
          elements[0].getElement().removeFromParent();
          replaced = true;
          // move cursor to just before deleted image
          doc.setCursor(DocumentApp.getActiveDocument().newPosition(parentElement, 0));
     } else {
          throw "Please select only one image (image replacement) or nothing (image insertion)"
     }
  }
  var cursor = doc.getCursor();
  var blob;

  if (imageDataUrl != "") {
    blob = getBlobFromBase64(imageDataUrl);
  } else {
    blob = getBlobViaFetch(imageUrl);
  }

  var image = cursor.insertInlineImage(blob);  

  image.setLinkUrl(imageUrl);

  // move the cursor to after the image
  var position = doc.newPosition(cursor.getElement(), cursor.getOffset()+1);
  doc.setCursor(position);

  if (cursor.getElement().getType() == DocumentApp.ElementType.PARAGRAPH) {
    Logger.log("Resizing");
    // resize if wider than current page
    var currentParagraph = DocumentApp.getActiveDocument().getCursor().getElement().asParagraph();
    var originalImageWidth = image.getWidth();  // pixels
    var documentWidthPoints = DocumentApp.getActiveDocument().getBody().getPageWidth() - DocumentApp.getActiveDocument().getBody().getMarginLeft() - DocumentApp.getActiveDocument().getBody().getMarginRight();
    var documentWidth = documentWidthPoints * 96 / 72;  // convert to pixels (a guess)
    var paragraphWidthPoints = documentWidthPoints - currentParagraph.getIndentStart() - currentParagraph.getIndentEnd();
    var paragraphWidth = paragraphWidthPoints * 96 / 72;  // convert to pixels (a guess)

    if (originalImageWidth > paragraphWidth) {
      image.setWidth(paragraphWidth);
      // scale proportionally
      image.setHeight(image.getHeight() * image.getWidth() / originalImageWidth);  
    }

  }

}
like image 36
Fuhrmanator Avatar answered Sep 19 '22 12:09

Fuhrmanator