Google関連

Google Docs用アドオンを作成する。

Google Docs用アドオンストアがオープン、表計算や文書作成に新機能を追加可能に」にあるように、Google ドキュメントの文書とスプレッドシートに機能を追加・拡張する「アドオン」を公開するためのプラットフォーム、「アドオンストア」がオープンしました。

このアドオンは一般の開発者もGoogle Apps Scriptを使って簡単に開発できるとのことなので、早速試してみました。

  1. Google ドキュメントで文書を新規作成します。
  2. ツール」メニューから「スクリプト エディタ」をクリックします。
  3. GoogleDocsAddOn_01_01

  4. ようこそ画面(welcome screen)が表示されるので「空のプロジェクト」をクリックします。
  5. GoogleDocsAddOn_01_02

  6. 最初に表示されるコードをすべて選択して削除します。
  7. GoogleDocsAddOn_01_03

  8. 下記コードを貼りつけます。
  9. /**
     * Creates a menu entry in the Google Docs UI when the document is opened.
     */
    function onOpen() {
      DocumentApp.getUi().createAddonMenu()
          .addItem('Start', 'showSidebar')
          .addToUi();
    }
    
    /**
     * Runs when the add-on is installed.
     */
    function onInstall() {
      onOpen();
    }
    
    /**
     * Opens a sidebar in the document containing the add-on's user interface.
     */
    function showSidebar() {
      var ui = HtmlService.createHtmlOutputFromFile('Sidebar')
          .setTitle('Translate');
      DocumentApp.getUi().showSidebar(ui);
    }
    
    /**
     * 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.';
      }
    }
    
    /**
     * Gets the stored user preferences for the origin and destination languages,
     * if they exist.
     *
     * @return {Object} The user's origin and destination language preferences, if
     *     they exist.
     */
    function getPreferences() {
      var userProperties = PropertiesService.getUserProperties();
      var languagePrefs = {
        originLang: userProperties.getProperty('originLang'),
        destLang: userProperties.getProperty('destLang')
      };
      return languagePrefs;
    }
    
    /**
     * Gets the user-selected text and translates it from the origin language to the
     * destination language. The languages are notated by their two-letter short
     * form. For example, English is 'en', and Spanish is 'es'. The origin language
     * may be specified as an empty string to indicate that Google Translate should
     * auto-detect the language.
     *
     * @param {string} origin The two-letter short form for the origin language.
     * @param {string} dest The two-letter short form for the destination language.
     * @param {boolean} savePrefs Whether to save the origin and destination
     *     language preferences.
     * @return {string} The result of the translation.
     */
    function runTranslation(origin, dest, savePrefs) {
      var text = getSelectedText();
      if (savePrefs == true) {
        var userProperties = PropertiesService.getUserProperties();
        userProperties.setProperty('originLang', origin);
        userProperties.setProperty('destLang', dest);
      }
    
      var translated = [];
      for (var i = 0; i < text.length; i++) {
        translated.push(LanguageApp.translate(text[i], origin, dest));
      }
    
      return translated.join('\n');
    }
    
    /**
     * Replaces the text of the current selection with the provided text, or
     * inserts text at the current cursor location. (There will always be either
     * a selection or a cursor.) If multiple elements are selected, only inserts the
     * translated text in the first element that can contain text and removes the
     * other elements.
     *
     * @param {string} newText The text with which to replace the current selection.
     */
    function insertText(newText) {
      var selection = DocumentApp.getActiveDocument().getSelection();
      if (selection) {
        var replaced = false;
        var elements = selection.getSelectedElements();
        if (elements.length == 1 &&
            elements[0].getElement().getType() ==
            DocumentApp.ElementType.INLINE_IMAGE) {
          throw "Can't insert text into an image.";
        }
        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();
    
            var remainingText = element.getText().substring(endIndex + 1);
            element.deleteText(startIndex, endIndex);
            if (!replaced) {
              element.insertText(startIndex, newText);
              replaced = true;
            } else {
              // This block handles a selection that ends with a partial element. We
              // want to copy this partial text to the previous element so we don't
              // have a line-break before the last partial.
              var parent = element.getParent();
              parent.getPreviousSibling().asText().appendText(remainingText);
              // We cannot remove the last paragraph of a doc. If this is the case,
              // just remove the text within the last paragraph instead.
              if (parent.getNextSibling()) {
                parent.removeFromParent();
              } else {
                element.removeFromParent();
              }
            }
          } else {
            var element = elements[i].getElement();
            if (!replaced && element.editAsText) {
              // Only translate elements that can be edited as text, removing other
              // elements.
              element.clear();
              element.asText().setText(newText);
              replaced = true;
            } else {
              // We cannot remove the last paragraph of a doc. If this is the case,
              // just clear the element.
              if (element.getNextSibling()) {
                element.removeFromParent();
              } else {
                element.clear();
              }
            }
          }
        }
      } else {
        var cursor = DocumentApp.getActiveDocument().getCursor();
        var surroundingText = cursor.getSurroundingText().getText();
        var surroundingTextOffset = cursor.getSurroundingTextOffset();
    
        // If the cursor follows or preceds a non-space character, insert a space
        // between the character and the translation. Otherwise, just insert the
        // translation.
        if (surroundingTextOffset > 0) {
          if (surroundingText.charAt(surroundingTextOffset - 1) != ' ') {
            newText = ' ' + newText;
          }
        }
        if (surroundingTextOffset < surroundingText.length) {
          if (surroundingText.charAt(surroundingTextOffset) != ' ') {
            newText += ' ';
          }
        }
        cursor.insertText(newText);
      }
    }

    GoogleDocsAddOn_01_04

  10. ファイル」メニューの「新規作成」から「HTMLファイル」をクリックします。
  11. GoogleDocsAddOn_01_05

  12. ファイルを作成ダイアログでファイル名を「Sidebar」として、「OK」ボタンをクリックします。
  13. GoogleDocsAddOn_01_06

  14. Sidebar.htmlが表示されるので、コードをすべて選択して削除します。
  15. GoogleDocsAddOn_01_07

  16. 下記コードを貼りつけます。
  17. <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. -->
    
    <style>
    .branding-below {
      bottom: 56px;
      top: 0;
    }
    
    .branding-text {
      left: 7px;
      position: relative;
      top: 3px;
    }
    
    .col-contain {
      overflow: hidden;
    }
    
    .col-one {
      float: left;
      width: 50%;
    }
    
    .logo {
      vertical-align: middle;
    }
    
    .radio-spacer {
      height: 20px;
    }
    
    .width-100 {
      width: 100%;
    }
    </style>
    
    <div class="sidebar branding-below">
      <form>
        <div class="block col-contain">
          <div class="col-one">
            <b>Selected text</b>
            <div>
              <input type="radio" name="origin" id="radio-origin-auto" value="" checked="checked">
              <label for="radio-origin-auto">Auto-detect</label>
            </div>
            <div>
              <input type="radio" name="origin" id="radio-origin-en" value="en">
              <label for="radio-origin-en">English</label>
            </div>
            <div>
              <input type="radio" name="origin" id="radio-origin-fr" value="fr">
              <label for="radio-origin-fr">French</label>
            </div>
            <div>
              <input type="radio" name="origin" id="radio-origin-de" value="de">
              <label for="radio-origin-de">German</label>
            </div>
            <div>
              <input type="radio" name="origin" id="radio-origin-ja" value="ja">
              <label for="radio-origin-ja">Japanese</label>
            </div>
            <div>
              <input type="radio" name="origin" id="radio-origin-es" value="es">
              <label for="radio-origin-es">Spanish</label>
            </div>
          </div>
          <div>
            <b>Translate into</b>
            <div class="radio-spacer">
            </div>
            <div>
              <input type="radio" name="dest" id="radio-dest-en" value="en">
              <label for="radio-dest-en">English</label>
            </div>
            <div>
              <input type="radio" name="dest" id="radio-dest-fr" value="fr">
              <label for="radio-dest-fr">French</label>
            </div>
            <div>
              <input type="radio" name="dest" id="radio-dest-de" value="de">
              <label for="radio-dest-de">German</label>
            </div>
            <div>
              <input type="radio" name="dest" id="radio-dest-ja" value="ja" checked="checked">
              <label for="radio-dest-ja">Japanese</label>
            </div>
            <div>
              <input type="radio" name="dest" id="radio-dest-es" value="es">
              <label for="radio-dest-es">Spanish</label>
            </div>
          </div>
        </div>
    
        <div class="block form-group">
          <label for="translated-text"><b>Translation</b></label>
          <textarea class="width-100" id="translated-text" rows="10"></textarea>
        </div>
    
        <div class="block">
          <input type="checkbox" id="save-prefs">
          <label for="save-prefs">Use these languages by default</label>
        </div>
    
       <div class="block" id="button-bar">
          <button class="blue" id="run-translation">Translate</button>
          <button id="insert-text">Insert</button>
        </div>
      </form>
    </div>
    
    <div class="sidebar bottom">
      <img alt="Add-on logo" class="logo" width="27" height="27"
          src="https://googledrive.com/host/0B0G1UdyJGrY6XzdjQWF4a1JYY1k/translate-logo-small.png">
      <span class="gray branding-text">Translate sample by Google</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 each button and try to load the
       * user's origin and destination language preferences if previously set.
       */
      $(function() {
        $('#run-translation').click(runTranslation);
        $('#insert-text').click(insertText);
        google.script.run.withSuccessHandler(loadPreferences)
            .withFailureHandler(showError).getPreferences();
      });
    
      /**
       * Callback function that populates the origin and destination selection
       * boxes with user preferences from the server.
       *
       * @param {Object} languagePrefs The saved origin and destination languages.
       */
      function loadPreferences(languagePrefs) {
        $('input:radio[name="origin"]')
            .filter('[value=' + languagePrefs.originLang + ']')
            .attr('checked', true);
        $('input:radio[name="dest"]')
            .filter('[value=' + languagePrefs.destLang + ']')
            .attr('checked', true);
      }
    
      /**
       * Runs a server-side function to translate the user-selected text and update
       * the sidebar UI with the resulting translation.
       */
      function runTranslation() {
        this.disabled = true;
        $('#error').remove();
        var origin = $('input[name=origin]:checked').val();
        var dest = $('input[name=dest]:checked').val();
        var savePrefs = $('#save-prefs').is(':checked');
        google.script.run
            .withSuccessHandler(
              function(translatedText, element) {
                $('#translated-text').val(translatedText);
                element.disabled = false;
              })
            .withFailureHandler(
              function(msg, element) {
                showError(msg, $('#button-bar'));
                element.disabled = false;
              })
            .withUserObject(this)
            .runTranslation(origin, dest, savePrefs);
      }
    
      /**
       * Runs a server-side function to insert the translated text into the document
       * at the user's cursor or selection.
       */
      function insertText() {
        this.disabled = true;
        $('#error').remove();
        google.script.run
            .withSuccessHandler(
              function(returnSuccess, element) {
                element.disabled = false;
              })
            .withFailureHandler(
              function(msg, element) {
                showError(msg, $('#button-bar'));
                element.disabled = false;
              })
            .withUserObject(this)
            .insertText($('#translated-text').val());
      }
    
      /**
       * 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>

    GoogleDocsAddOn_01_08

  18. ファイル」メニューから「すべてを保存」をクリックします。
  19. GoogleDocsAddOn_01_09

  20. プロジェクト名を変更ダイアログでプロジェクト名を「Translate Quickstart」として、「OK」ボタンをクリックします。
  21. GoogleDocsAddOn_01_10

  22. スクリプト エディタを終了し、1.で作成した文書も一度閉じます。
  23. 12.で閉じた文書を再度開くと「アドオン」メニューに「Translate Quickstart」が追加されていることが確認できます。
  24. GoogleDocsAddOn_01_11

  25. アドオン」メニューの「Translate Quickstart」から「Start」をクリックします。
  26. GoogleDocsAddOn_01_12

  27. 承認画面が表示されたら「続行」ボタンをクリックし、続いて表示されるウィンドウで「承認する」ボタンをクリックします。
  28. GoogleDocsAddOn_01_13

    GoogleDocsAddOn_01_14

  29. Google ドキュメントの画面右側に「Translate」が表示されることが確認できます。
  30. GoogleDocsAddOn_01_15

  31. 文書中の文字列を選択後、Translate上で言語を選び「Translate」ボタンをクリックすると、テキストエリアに翻訳された文字列が表示されます。
  32. GoogleDocsAddOn_01_16

  33. さらに「Insert」ボタンをクリックすることで、テキストエリアの文字列を文書に挿入することができます。
  34. GoogleDocsAddOn_01_17

以上の手順で、自分でアドオンを作成・実行することができます。
上記手順は、「Quickstart: Add-on for Google Docs」に載っている手順に画像を付け足しただけですが、おおよその概要は掴めるかと思います。

作成したアドオンをストアで公開するには、審査にパスする必要があるようです。詳しくは下記ページをご参照ください。

・Publishing an Add-on – Google Apps Script – Google Developers
https://developers.google.com/apps-script/add-ons/publish

しかしながらこの機能、当ブログで取り扱っている、Microsoft Officeの「Office 用アプリ」にそっくりですね!
今後も要注目の機能です。

■ 関連Webページ

・Google Developers Blog: Building Sheets and Docs Add-ons
http://googledevelopers.blogspot.jp/2014/03/building-sheets-and-docs-add-ons.html
・Google Apps Script – Google Developers
https://developers.google.com/apps-script/add-ons/

コメント

  1. この記事へのコメントはありません。

  1. この記事へのトラックバックはありません。

Time limit is exhausted. Please reload CAPTCHA.

最近の記事

アーカイブ

RapidSSL_SEAL-90x50
PAGE TOP