javascript - scripts - Cómo sondear un Google Doc desde un complemento
javascript google sheets (1)
El sondeo se realiza desde el código html en la interfaz de usuario de su complemento, llamando a las funciones de script de aplicaciones del lado del servidor utilizando google.script.run
.
Usar jQuery simplifica esto, e incluso podemos comenzar con las respuestas de jQuery, ejemplo de encuesta simple .
function doPoll(){
$.post(''ajax/test.html'', function(data) {
alert(data); // process results here
setTimeout(doPoll,5000);
});
}
La idea básica puede funcionar para Google Apps Script, si reemplazamos las llamadas ajax con los equivalentes de GAS.
Aquí está el esqueleto de la función de sondeo que usaría en su archivo 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);
};
Ejemplo de complemento, Poller del documento
Esta es una demostración de la técnica de sondeo de jQuery que llama a las funciones del script de Google Apps del lado del servidor para detectar el comportamiento del usuario en un documento de Google. No hace nada útil, pero muestra algunas cosas que típicamente requerirían el conocimiento de la actividad del usuario y el estado del documento, por ejemplo, control sensible al contexto sobre un botón.
El mismo principio podría aplicarse a una hoja de cálculo, o una aplicación web de GAS independiente.
Al igual que el ejemplo de la aplicación de interfaz de usuario en esta pregunta , esta técnica podría utilizarse para evitar los límites de tiempo de ejecución, al menos para las operaciones con una interfaz de usuario.
El código se basa en el complemento de ejemplo del inicio rápido de 5 minutos de Google. Siga las instrucciones de esa guía, utilizando el siguiente código en lugar de eso en el inicio 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.<string>} 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 = "<selection>";
if (cursor) {
var offset = cursor.getSurroundingTextOffset();
var text = cursor.getSurroundingText().getText();
word = getWordAt(text,offset);
if (word == "") word = "<whitespace>";
}
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
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css">
<!-- The CSS package above applies Google styling to buttons and other elements. -->
<div class="sidebar branding-below">
<form>
<div class="block" id="button-bar">
<button class="blue" id="get-selection" disabled="disable">Get selection</button>
</div>
</form>
</div>
<div class="sidebar bottom">
<img alt="Add-on logo" class="logo" height="27"
id="logo"
src="https://www.gravatar.com/avatar/adad1d8ad010a76a83574b1fff4caa46?s=128&d=identicon&r=PG">
<span class="gray branding-text">by Mogsdad, D.Bingham</span>
</div>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js">
</script>
<script>
/**
* On document load, assign click handlers to button(s), add
* elements that should start hidden (avoids "flashing"), and
* start polling for document selections.
*/
$(function() {
// assign click handler(s)
$(''#get-selection'').click(getSelection);
// Add elements that should start hidden
var newdiv1 = $( "<div class=''block'' id=''cursor-word''/>" ).hide(),
newdiv2 = $( "<div class=''block'' id=''selected-text''/>" ).hide();
$(''#button-bar'').after( newdiv1, newdiv2 );
$(''#cursor-word'').html(''<H2>Word at cursor:</H2><p id="cursor-word-content"></p>'');
$(''#selected-text'').html(''<H2>Selected text:</H2><p id="selected-text-content"></p>'');
// Start polling for updates
poll();
});
/**
* Poll the server-side ''checkSelection'' function at the given
* interval for document selection, and enable or disable the
* ''#get-selection'' button.
*
* @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(cursor) {
if (cursor.isSelection) {
// Text has been selected: enable button, hide cursor word.
$(''#get-selection'').attr(''disabled'', false);
$(''#cursor-word'').hide();
// $(''#selected-text'').show(); // Not so fast - wait until button is clicked.
}
else {
$(''#get-selection'').attr(''disabled'', true);
$(''#cursor-word'').show();
$(''#selected-text'').hide();
}
$(''#cursor-word-content'').text(cursor.cursorWord);
//Setup the next poll recursively
poll(interval);
})
.withFailureHandler(
function(msg, element) {
showError(msg, $(''#button-bar''));
element.disabled = false;
})
.checkSelection();
}, interval);
};
/**
* Runs a server-side function to retrieve the currently
* selected text.
*/
function getSelection() {
this.disabled = true;
$(''#error'').remove();
google.script.run
.withSuccessHandler(
function(selectedText, element) {
// Show selected text
$(''#selected-text-content'').text(selectedText);
$(''#selected-text'').show();
element.disabled = false;
})
.withFailureHandler(
function(msg, element) {
showError(msg, $(''#button-bar''));
element.disabled = false;
})
.withUserObject(this)
.getSelectedText();
}
/**
* Inserts a div that contains an error message after a given element.
*
* @param msg The error message to display.
* @param element The element after which to display the error.
*/
function showError(msg, element) {
var div = $(''<div id="error" class="error">'' + msg + ''</div>'');
$(element).after(div);
}
</script>
Intervalo de votación
La función setTimeout()
acepta un intervalo de tiempo expresado en milisegundos, pero a través de la experimentación descubrí que una respuesta de dos segundos era la mejor que se podía esperar. Por lo tanto, la poll()
esqueleto poll()
tiene un intervalo de 2000 ms como valor predeterminado. Si su situación puede tolerar un retraso mayor entre los ciclos de encuesta, proporcione un valor mayor con la llamada onLoad a poll()
, por ejemplo, poll(10000)
para un ciclo de encuesta de 10 segundos.
Hojas
Para ver un ejemplo de hoja, consulte ¿Cómo hago para que una barra lateral muestre los valores de las celdas?
Una restricción documentada con complementos de documentos y hojas es que Apps Script no puede distinguir qué hace un usuario fuera del complemento. Esta sugerencia tentadora se da:
Es posible sondear los cambios en el contenido de un archivo desde el código del lado del cliente de la barra lateral, aunque siempre tendrá un ligero retraso. Esa técnica también puede alertar a su secuencia de comandos de los cambios en las celdas seleccionadas del usuario (en Hojas) y el cursor o la selección (en Documentos).
Lamentablemente, esto no se muestra en ninguno de los códigos de demostración. ¿Cómo puedo hacerlo?