如何从附加组件查询Google文档 [英] How to poll a Google Doc from an add-on

查看:131
本文介绍了如何从附加组件查询Google文档的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

具有文档和工作表附件的的示例插件。请遵循该指南中的说明,使用下面的代码而不是快速入门中的代码。



Code.gs



  / ** 
*打开文档时在Google Docs UI中创建一个菜单条目。
*
* @param {object} e一个简单的onOpen触发器的事件参数。要
*确定哪个授权模式(ScriptApp.AuthMode)触发器是
*运行,请检查e.authMode。
* /
函数onOpen(e){
DocumentApp.getUi()。createAddonMenu()
.addItem('Start','showSidebar')
.addToUi ();
}

/ **
*安装加载项时运行。
*
* @param {object} e一个简单的onInstall触发器的事件参数。要
*确定哪个授权模式(ScriptApp.AuthMode)触发器是
*运行,请检查e.authMode。 (实际上,onInstall触发器总是
*在AuthMode.FULL中运行,但onOpen触发器可能是AuthMode.LIMITED或
* AuthMode.NONE。)
* /
函数onInstall e){
onOpen(e);
}

/ **
*在包含附件用户界面的文档中打开边栏。
* /
函数showSidebar(){
var ui = HtmlService.createHtmlOutputFromFile('Sidebar')
.setTitle('Document Poller');
DocumentApp.getUi()。showSidebar(ui);
}

/ **
*检查是否有当前的文本选择。
*
* @return {boolean}如果选择任何文档文本,则为'true'
* /
函数checkSelection(){
return {isSelection:!!( DocumentApp.getActiveDocument()。getSelection()),
cursorWord:getCursorWord()};
}

/ **
*获取用户选择的文本。如果没有选择,
*此功能显示错误消息。
*
* @return {Array。< string>}选定的文本。
* /
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();
//只翻译可以编辑为文本的元素;跳过图像和
//其他非文本元素。
if(element.editAsText){
var elementText = element.asText()。getText();
//这个检查对排除图像是必要的,它会返回一个空白的
//文本元素。
if(elementText!=''){
text.push(elementText);
}
}
}
}
if(text.length == 0){
throw'请选择一些文本。
}
返回文本;
} else {
throw'请选择一些文字。';
}
}

/ **
*返回文档中当前光标位置处的单词。
*
* @return {string}光标位置处的单词。
* /
函数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>;
}
返回单词;
}

/ **
*返回'str'中索引'pos'处的单词。
*从https://stackoverflow.com/questions/5173316/finding-the-word-at-a-position-in-javascript/5174867#5174867
* /
函数getWordAt( str,pos){

//执行类型转换。
str = String(str);
pos = Number(pos)>>> 0;

//搜索单词的开始和结束。
var left = str.slice(0,pos + 1).search(/ \ S + $ /),
right = str.slice(pos).search(/ \ s /);

//字符串中的最后一个字是特例。
if(right< 0){
return str.slice(left);
}

//返回单词,使用所在的边界从字符串中提取它。
return str.slice(left,right + pos);
}



Sidebar.html



< pre class =lang-js prettyprint-override> < link rel =stylesheethref =https://ssl.gstatic.com/docs/script/css/add-ons.css >
<! - 上面的CSS包将Google样式应用于按钮和其他元素。 - >


< div class =sidebar branding-below>
< form>
< div class =blockid =button-bar>
< button class =blueid =get-selectiondisabled =disable>获取选择< /按钮>
< / div>
< / form>
< / div>

< div class =sidebar bottom>
id =logo
src =https://www.gravatar.com /化身/ adad1d8ad010a76a83574b1fff4caa46 S = 128&安培; d =&identicon放大器; R = PG>
< span class =grey branding-text> by Mogsdad,D.Bingham< / span>
< / div>

< script src =// ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js\">
< / script>
< script>
/ **
*在文档加载时,将点击处理程序分配给按钮,添加应该开始隐藏(避免闪烁)的
*元素以及
*开始轮询文件选择。
* /
$(function(){
//赋值点击处理程序
$('#get-selection')。click(getSelection);


//添加应该开始隐藏的元素
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> ;< / H2>< p id =selected-text-content;< / p>');
$('#selected-text')。 >< / p>');

//开始轮询更新
poll();
});
$ b $ **
*以给定的
*间隔轮询服务器端的checkSelection函数以选择文档,并启用或禁用
*'#获取选择按钮。
*
* @param {Number}时间间隔(可选)轮询之间的时间(以毫秒为单位)。
*默认为2s(2000ms)
* /
函数poll(interval){
interval = interval || 2000;
setTimeout(function(){
google.script.run
.withSuccessHandler(
function(cursor){
if(cursor.isSelection){

$('#get-selection')。attr('disabled',false);
$('#cursor-word')//文本已被选中:启用按钮,隐藏光标文字。 .hide();
// $('#selected-text')。show(); //不是那么快 - 等到按钮被点击。
}
else {
$('#get-selection')。attr('disabled',true);
$('#cursor-word')。show();
$('#selected-text ').hide();
}
$('#cursor-word-content').text(cursor.cursorWord);
//递归设置下一个轮询
poll(interval);
})
.withFailureHandler(
function(msg,element){
showError(msg,$('#button-bar'));
element.disabled = false;
})
.checkSelection();
},区间);
};

/ **
*运行服务器端函数来检索当前
*选定的文本。
* /
函数getSelection(){
this.disabled = true;
$('#error')。remove();
google.script.run
.withSuccessHandler(
function(selectedText,element){
//显示选定文本
$('#selected-text-content' ).text(selectedText);
$('#selected-text')。show();
element.disabled = false;
})
.withFailureHandler(
函数(msg,element){
showError(msg,$('#button-bar'));
element.disabled = false;
})
.withUserObject (this)
.getSelectedText();
}


/ **
*在给定元素后面插入包含错误消息的div。
*
* @param msg要显示的错误消息。
* @param元素显示错误的元素。
* /
function showError(msg,element){
var div = $('< div id =errorclass =error>'+ msg +'< / DIV>');
$(element).after(div);
}
< / script>



轮询间隔



setTimeout()函数接受以毫秒为单位的时间间隔,但我通过实验发现,两秒响应是可以预期的最佳响应。因此,骨架 poll()的默认间隔为2000毫秒。如果您的情况可以容忍轮询周期之间的较长延迟,则通过对 poll()的onLoad调用提供更大的值,例如,
$ b

表格


$ poll(10000) b $ b

有关表单示例,请参阅如何使单元格的边栏显示值?


A documented restriction with document and sheet add-ons is that Apps Script cannot tell what a user does outside of the add-on. This tantalizing tip is given:

It is possible to poll for changes in a file's contents from a sidebar's client-side code, although you'll always have a slight delay. That technique can also alert your script to changes in the user's selected cells (in Sheets) and cursor or selection (in Docs).

Sadly, this isn't shown in any of the demo code. How can I do it?

解决方案

The polling is done from the html code in your add-on's User Interface, calling across to server-side Apps Script functions using google.script.run.

Using jQuery simplifies this, and we can even start with the answers from jQuery, simple polling example.

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

The basic idea can work for Google Apps Script, if we replace the ajax calls with the GAS equivalents.

Here's the skeleton of the poll function that you would use in your html file:

  /**
   * 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);
  };

Add-on Example, Document Poller

This is a demonstration of the jQuery polling technique calling server-side Google Apps Script functions to detect user behavior in a Google Document. It does nothing useful, but it showcases a few things that would typically require knowledge of the user's activity and state of the document, for instance context-sensitve control over a button.

The same principle could apply to a spreadsheet, or a stand-alone GAS Web Application.

Like the UI App example in this question, this technique could be used to get around execution time limits, for operations with a User Interface at least.

The code builds upon the example add-on from Google's 5-minute quickstart. Follow the instructions from that guide, using the code below instead of that in the quickstart.

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>

Polling Interval

The setTimeout() function accepts a time interval expressed in milliseconds, but I found through experimentation that a two-second response was the best that could be expected. Therefore, the skeleton poll() has a 2000ms interval as its default. If your situation can tolerate a longer delay between poll cycles, then provide a larger value with the onLoad call to poll(), e.g. poll(10000) for a 10-second poll cycle.

Sheets

For a sheet example see How do I make a Sidebar display values from cells?

这篇关于如何从附加组件查询Google文档的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆