Como pesquisar um documento do Google a partir de um complemento

Uma restrição documentada com complementos de documento e folha é que o Script do Google Apps não pode dizer o que um usuário faz fora do complemento. Esta dica tentadora é dada:

É possível pesquisar alterações no conteúdo de um arquivo a partir do código do lado do cliente da barra lateral, embora você sempre tenha um pequeno atraso. Essa técnica também pode alertar seu script para alterações nas células selecionadas do usuário (no Planilhas) e no cursor ou seleção (no Documentos).

Infelizmente, isso não é mostrado em nenhum dos códigos de demonstração. Como eu posso fazer isso?

A pesquisa é feita a partir do código html na interface do usuário do seu complemento, chamando as funções do Script Script do lado do servidor usando google.script.run .

Usar o jQuery simplifica isso, e podemos até começar com as respostas do jQuery, exemplo simples de pesquisa .

 function doPoll(){ $.post('ajax/test.html', function(data) { alert(data); // process results here setTimeout(doPoll,5000); }); } 

A ideia básica pode funcionar para o Script do Google Apps, se replacemos as chamadas do ajax pelos equivalentes do GAS.

Aqui está o esqueleto da function de pesquisa que você usaria no seu arquivo html:

  /** * On document load, assign click handlers to button(s), add * elements that should start hidden (avoids "flashing"), and * start polling for document updates. */ $(function() { // assign click handler(s) // Add elements that should start hidden // Start polling for updates poll(); }); /** * Poll a server-side function 'serverFunction' at the given interval * and update DOM elements with results. * * @param {Number} interval (optional) Time in ms between polls. * Default is 2s (2000ms) */ function poll(interval){ interval = interval || 2000; setTimeout(function(){ google.script.run .withSuccessHandler( function(results) { $('#some-element').updateWith(results); //Setup the next poll recursively poll(interval); }) .withFailureHandler( function(msg, element) { showError(msg, $('#button-bar')); element.disabled = false; }) .serverFunction(); }, interval); }; 

Exemplo de complemento, documento Poller

Esta é uma demonstração da técnica de pesquisa do jQuery que chama funções do Script do Google Apps do servidor para detectar o comportamento do usuário em um documento do Google. Ele não faz nada útil, mas mostra algumas coisas que normalmente exigem conhecimento da atividade do usuário e do estado do documento, por exemplo, controle de sensibilidade ao contexto em um botão.

O mesmo princípio pode ser aplicado a uma planilha ou a um aplicativo da Web GAS autônomo.

Como o exemplo da UI App nessa questão , essa técnica poderia ser usada para contornar limites de tempo de execução, para operações com uma interface de usuário, pelo menos.

Screenshot

O código se baseia no complemento de exemplo do início rápido de 5 minutos do Google. Siga as instruções desse guia, usando o código abaixo, em vez daquele no início rápido.

Code.gs

 /** * Creates a menu entry in the Google Docs UI when the document is opened. * * @param {object} e The event parameter for a simple onOpen trigger. To * determine which authorization mode (ScriptApp.AuthMode) the trigger is * running in, inspect e.authMode. */ function onOpen(e) { DocumentApp.getUi().createAddonMenu() .addItem('Start', 'showSidebar') .addToUi(); } /** * Runs when the add-on is installed. * * @param {object} e The event parameter for a simple onInstall trigger. To * determine which authorization mode (ScriptApp.AuthMode) the trigger is * running in, inspect e.authMode. (In practice, onInstall triggers always * run in AuthMode.FULL, but onOpen triggers may be AuthMode.LIMITED or * AuthMode.NONE.) */ function onInstall(e) { onOpen(e); } /** * Opens a sidebar in the document containing the add-on's user interface. */ function showSidebar() { var ui = HtmlService.createHtmlOutputFromFile('Sidebar') .setTitle('Document Poller'); DocumentApp.getUi().showSidebar(ui); } /** * Check if there is a current text selection. * * @return {boolean} 'true' if any document text is selected */ function checkSelection() { return {isSelection : !!(DocumentApp.getActiveDocument().getSelection()), cursorWord : getCursorWord()}; } /** * Gets the text the user has selected. If there is no selection, * this function displays an error message. * * @return {Array.} The selected text. */ function getSelectedText() { var selection = DocumentApp.getActiveDocument().getSelection(); if (selection) { var text = []; var elements = selection.getSelectedElements(); for (var i = 0; i < elements.length; i++) { if (elements[i].isPartial()) { var element = elements[i].getElement().asText(); var startIndex = elements[i].getStartOffset(); var endIndex = elements[i].getEndOffsetInclusive(); text.push(element.getText().substring(startIndex, endIndex + 1)); } else { var element = elements[i].getElement(); // Only translate elements that can be edited as text; skip images and // other non-text elements. if (element.editAsText) { var elementText = element.asText().getText(); // This check is necessary to exclude images, which return a blank // text element. if (elementText != '') { text.push(elementText); } } } } if (text.length == 0) { throw 'Please select some text.'; } return text; } else { throw 'Please select some text.'; } } /** * Returns the word at the current cursor location in the document. * * @return {string} The word at cursor location. */ function getCursorWord() { var cursor = DocumentApp.getActiveDocument().getCursor(); var word = ""; if (cursor) { var offset = cursor.getSurroundingTextOffset(); var text = cursor.getSurroundingText().getText(); word = getWordAt(text,offset); if (word == "") word = ""; } return word; } /** * Returns the word at the index 'pos' in 'str'. * From https://stackoverflow.com/questions/5173316/finding-the-word-at-a-position-in-javascript/5174867#5174867 */ function getWordAt(str, pos) { // Perform type conversions. str = String(str); pos = Number(pos) >>> 0; // Search for the word's beginning and end. var left = str.slice(0, pos + 1).search(/\S+$/), right = str.slice(pos).search(/\s/); // The last word in the string is a special case. if (right < 0) { return str.slice(left); } // Return the word, using the located bounds to extract it from the string. return str.slice(left, right + pos); } 

Sidebar.html

       

Intervalo de Polling

A function setTimeout() aceita um intervalo de tempo expresso em milissegundos, mas descobri, por meio de experimentação, que uma resposta de dois segundos era a melhor que se poderia esperar. Portanto, o esqueleto poll() tem um intervalo de 2000ms como padrão. Se a sua situação puder tolerar um atraso maior entre os ciclos de pesquisa, forneça um valor maior com a chamada onLoad para poll() , por exemplo, poll(10000) para um ciclo de poll de 10 segundos.

Folhas

Para um exemplo de planilha, consulte Como faço uma barra lateral exibir valores de células?