The other day, Google
announced the release of a new platform that allows developers to create applications that work within Google Docs and expand the basic functionality of Google Docs editor.
We will understand what it is, how it works and write a small application that will allow us to translate the text of the document without leaving Google Docs.
A bit of theory.
Google Docs dd-on is an application written in javascript that exists and works in Google Drive (currently only for Google Docs), which has its own UI ('sidebar' to the right of the document or a modal window on top), extending the functionality of Google Docs editor . Google Docs Add-on is the next step in the development of Google Apps Script. In fact, this is a Google Apps Script project with the ability to distribute and install it by other Google Docs users.
There are several types of Google Apps Script projects, the main ones are a separate project file in Google Drive (in Google Drive you see it as a separate file, 'standalone script') or a project with a container document (
'container-bounds script' ). You can see it by opening the document container, menu 'Tools' -> 'Script editor'. Google Docs Add-on is just the second option; to create it, you need a document container that you will use to test and debug your application.
Spread
Distributed Google Docs Add-on through the Chrome Web store (although not a browser extension). Before adding your add-on to the Chrome Web Store, you must
review Google. For Google Docs users, all add-ons are available for installation through the new menu item - 'Add-ons' -> 'Get Add-ons'.
')
Development environment
For Google Apps Script projects, Google provides a development environment integrated into Google Drive, with the ability to run, debug, and code completion. Is it possible to use another development environment and store the code not in Google Drive, but in CVS? This would allow several developers to work on the project at the same time and support versioning. For 'standalone' projects there is such an opportunity, for 'Sontainer-bounds' - unfortunately not yet. Therefore, for the development of Google Docs Add-on'a have to use an integrated development environment. Here is
an example of a python script that allows you to “dump” the 'standalone' project to the file system and update it in Google Drive after the change.
What's inside
A little more about how Google Apps Script works. Project files contain the “server” (.gs) and “client” (.html) parts of the application. The “server” part is a set of javascript functions that can
access third-party services , various Google APIs (Drive, GMail, Calendar), including the API of the document in which this add-on is installed.
Also, the '.gs' files contain predefined functions that are called when the user sets / opens an add-on, which allows you to create your application's UI within the standard Google Docs editor. In turn, the “client part” is standard html files, with the possibility of calling any function from any “server” file. And also, of course, with the ability to connect third-party CSS / javascript files.
Example
We will write an application that will translate the selected text in the document from English to Russian. Create a new Google Docs document in Google Drive, open the 'Tools' -> 'Script editor' menu. In the dialog that appears, select 'Create script for -> Document'. After that you will see that integrated development environment. In the project, by default, the file 'Code.gs' is created, containing a sample application from Google. We do not need it yet, so I immediately propose to clear its contents, and rename the file itself to 'Server.gs'. It is also better to immediately rename the project from the 'Untitled project', say in 'Translate example'.
So, in 'Server.gs' we define the functions that will create our add-on UI. To do this, simply declare the function 'onOpen'.
function onOpen() { DocumentApp.getUi().createAddonMenu() .addItem('Translate', 'openSidebar') .addToUi(); } function openSidebar( ) {}
Now, when you open the document in which the given add-on is installed, the 'Translate' item will appear in the special 'Add-ons' menu. Clicking on this menu item will call the 'openSidebar' function, which does nothing so far. To test this, in the development environment, select the “onOpen” function and click 'Run'

Now your document has a menu item 'Add-ons' -> 'Translate example' (the name of your project) -> 'Translate'
Let's develop the UI for our add-on. Since the add-on UI is an html page, it makes sense to immediately separate the HTML markup, CSS and javascript code into different files. To do this, we will create the files 'Sidebar.html', 'Styles.html', 'Scripts.html' and 'Content.html'. 'Sidebar.html' will be a template file that includes the rest of the files.

The UI of all add-ons must follow a certain
style guide , so Google provides a special CSS file with predefined styles, which you see a link to in the example.
Open 'Content.html' and 'Styles.html' and create markup.
<div class="content"> <p>Select text and click 'Translate'</p> <button class="action btn-block">Translate</button> <p class="result"></p> </div>
and styles
<style> .content { padding: 20px; margin : 20px; } .btn-block { display: block; width: 100%; padding-left: 0; padding-right: 0; } </style>
and in 'Server.gs' we add the body of the function 'openSidebar'
function onOpen() { DocumentApp.getUi().createAddonMenu() .addItem('Translate', 'openSidebar') .addToUi(); } function openSidebar( ) { var html = HtmlService.createTemplateFromFile('Sidebar') .evaluate() .setSandboxMode(HtmlService.SandboxMode.NATIVE) .setTitle('Translate example') .setWidth(300); DocumentApp.getUi().showSidebar(html); }
Now by pressing the menu item "Translate" in the document on the left, the 'sidebar' opens.

It remains to add logic to translate the text. To do this, we will use the various Google services APIs that are available to us in the '.gs' files and allow us to find the text selected in the document and translate it into the selected language.
'Server.gs'
function translate(pollingCounter) { var text = getSelectedText(getSingleSeletetedElement()); if (text) { return LanguageApp.translate(text, 'en', 'ru'); } } function getSelectedText(rangeElement) { if (!rangeElement) { return; } var element = rangeElement.getElement(); var from = rangeElement.getStartOffset(); var to = rangeElement.getEndOffsetInclusive() + 1; if (element.getType() === DocumentApp.ElementType.TEXT || element.getType() === DocumentApp.ElementType.PARAGRAPH) { var text = element.getText(); return text.substring(from, to); } } function getSingleSeletetedElement() { var selection = DocumentApp.getActiveDocument().getSelection(); if (selection) { var elements = selection.getSelectedElements(); if (elements.length === 1) { return elements[0]; } } }
The 'translate' function searches for the selected element in the document, and if it is a text or a text block, it receives the selected part of the text and translates it from English to Russian. Now we need to somehow call this function from our UI. For this, the javascript code running in the sidebar has access to the global google.script.run object, which allows you to call any function from any .gs file.
'Scripts.html'
$(function() { $('.action').click(function() { google.script.run.withSuccessHandler(function(text) { if (text) { $('.result').text('Result: ' + text); } else { $('.result').text('Please, select text to translate'); } }).translate(); }); });
Done, now our add-on can translate the selected text by pressing the 'Translate' key.
On the move, you can make 2 improvements. First, when opening the 'sidebar', while all the files necessary for it are loaded, it would be nice to add some loading indicator. Create the file 'Loading.html', add a link to it in our 'Sidebar.html', and hide the main content until all resources are fully loaded. Once everything is ready - we hide the indicator and show our UI.
'Loading.html'
<div class="app-loading" style="text-align:center"> <br><br><br><br><br><br> Loading... </div>
Content.html
<div class="content" style="display:none"> <p>Select text and click 'Translate'</p> <button class="action btn-block">Translate</button> <p class="result"></p> </div>
Sidebar.html
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css"> <?!= HtmlService.createHtmlOutputFromFile('Styles').getContent(); ?> <?!= HtmlService.createHtmlOutputFromFile('Loading').getContent(); ?> <?!= HtmlService.createHtmlOutputFromFile('Content').getContent(); ?> <script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script> <?!= HtmlService.createHtmlOutputFromFile('Scripts').getContent(); ?>
and finally in Scripts.html
$(function() { $('.app-loading').hide(); $('.content').show(); $('.action').click(function() { google.script.run.withSuccessHandler(function(text) { if (text) { $('.result').text('Result: ' + text); } else { $('.result').text('Please, select text to translate'); } }).translate(); }); });
And secondly, it would be nice to automatically receive a translation of the selected text from the document, without pressing the 'Translate' button. To solve this problem, you need to figure out how to get around one of the limitations that currently exists in Google Docs Add-on - there is no event model that would allow Google Script Add-on to respond to user actions in the document. We will have to mimic add-on interactivity using long polling - constant requests from the UI, which will force our 'Server.gs' to check the document for selection and translate the text. To do this, we write in the 'Script.html' function, which will run 2 processes with a certain interval. The first process will call the 'translate' function and simply save the translation result. The second process will check for the result of the translation and update the UI.
'Scripts.html'
var LongPollingManager = (function() { var process = function(options) { var pollingTimeout = 1500, notificationTimeout = 1500, pollingCounter = 0, pollingStopped = false, pollingResult, pollingProcess, notificationProcess; var start = function() { pollingStopped = false; pollingProcess = setInterval(function() { if (pollingStopped) { return; } pollingCounter++; google.script.run.withSuccessHandler(function(response) { if (pollingStopped) { return; } if (!pollingResult || response.pollingCounter > pollingResult.pollingCounter) { pollingResult = response; } })[options.method](pollingCounter); }, pollingTimeout); setTimeout(function() { notificationProcess = setInterval(function() { if (pollingStopped) { return; } options.notification(getLastResult()); }, notificationTimeout); }, notificationTimeout/2); }; var getLastResult = function() { return pollingResult; }; var stop = function() { pollingResult = null; pollingStopped = true; clearInterval(pollingProcess); clearInterval(notificationProcess); }; return { start: start, stop : stop, isActive: function() { return !pollingStopped; }, getLastResult : getLastResult };; }; return { process: process }; })(); var showResult = function(result) { if (result && result.text) { $('.result').text('Result: ' + result.text); } else { $('.result').text(''); } }; $(function() { $('.loading').hide(); $('.content').show(); $('.action').click(function() { google.script.run.withSuccessHandler(function(text) { if (text) { $('.result').text('Result: ' + text); } else { $('.result').text('Please, select text to translate'); } }).translate(); }); LongPollingManager.process({ method: 'translate', notification: showResult }).start(); });
'Server.gs'
function translate(pollingCounter) { var result = { pollingCounter : pollingCounter }; var text = getSelectedText(getSingleSeletetedElement()); if (text) { result.text = LanguageApp.translate(text, 'en', 'ru'); } return result; }
Done, now our add-on automatically translates the selected text.
Document + project code on Google DriveGithub code
Conclusion
So, Google Docs add-on is a new platform that allows you to create applications that extend the capabilities of the basic Google Docs editor.
Pros and cons, in my opinion:
- + Huge audience of Google Docs users.
- + Developing an application entirely in javascript.
- + Access to numerous Google API and any third-party service (HTTP requests).
- - Development of the project within Google Docs, there is no possibility to conduct development locally, it is difficult to divide the work among several developers.
- - Lack of interactive interaction with the document (there is no possibility to receive notifications about any user actions in the document.
PS
I think that in the rivalry of MS Office - Google Drive, Google decided to bet on simplicity, convenience and the developer community. Let me explain with the example of MS Word vs Goodle Docs. MS Word has a huge number of user-accessible functions (and as a result, an overloaded and complicated UI) and does not have an open API. Google Docs - on the contrary, has the minimum necessary set of tools and an open API. Now you can find any missing function in the Google Docs gallery of add-ons or write it yourself. And the user can add to his document only the functions he needs.