Fortgeschrittenes Outlook-Add-In-Lernprogramm: Git the Gist

In diesem Leitfaden werden Sie durch den Prozess des Erstellens eines Outlook-Add-Ins geführt, mit dem der Benutzer auf seine Inhalte auf GitHub zugreifen kann. Wenn Sie die hier aufgeführten Schritte befolgen, erhalten Sie den Quellcode in diesem Repository als Ergebnis.

In diesem Lernprogramm wird der Projekt-Generator für Microsoft Office-Add-Ins zum Generieren eines Add-In-Projekts verwendet.

Voraussetzungen

Outlook-Add-Ins bestehen aus HTML-, CSS- und JavaScript-Dateien, aus technischer Sicht ist die einzig echte Voraussetzung also ein Webserver zum Hosten der Dateien. In diesem Leitfaden werden allerdings auch einige Tools verwendet, die die ersten Schritte erleichtern. Sie benötigen Folgendes, um die Schritte in diesem Leitfaden auszuführen:

Tipp

Nachdem Sie Node.js installiert haben, können Sie alle anderen erforderlichen Komponenten über NPM installieren:

npm install -g yo generator-office

Sie benötigen außerdem Outlook 2016, das mit einem Office 365-Konto, einem Outlook.com-Konto oder einem Microsoft Exchange-Server und einem GitHub-Konto verbunden ist, um alle Features des Add-Ins zu testen, die wir erstellen werden.

Erstellen des Add-Ins

Öffnen Sie die Befehlszeile/Shell in einem leeren Verzeichnis. Geben Sie yo office ein, und befolgen Sie die Anweisungen.

  • Möchten Sie einen neuen Unterordner für Ihr Projekt erstellen?:No
  • Wie möchten Sie das Add-In benennen?:Git the Gist
  • Welche Office-Clientanwendung möchten Sie unterstützen?:Outlook
  • Möchten Sie ein neues Projekt erstellen?: Yes, I need to create a new web app and manifest file for my add-in
  • Möchten Sie TypeScript verwenden?:No
  • Framework auswählen: Jquery

Der Generator fragt Sie dann, ob Sie „resource.html“ öffnen möchten. Sie müssen die Datei für dieses Lernprogramm nicht öffnen, können dies aber gerne tun, wenn Sie neugierig sind. Wählen Sie „Ja“ oder „Nein“, um den Assistenten abzuschließen und den Generator seine Arbeit ausführen zu lassen.

Screenshot der Aufforderungen des Microsoft Office-Projekt-Generators

Der Generator erstellt das Projekt und installiert unterstützende Node-Komponenten.

Testen des generierten Add-Ins

Bevor wir mit dem Schreiben von Code beginnen, testen wir das grundlegende Add-In, das vom Generator generiert wurde. Auf diese Weise schaffen wir uns einen Ausgangspunkt für unsere Arbeit.

Aktualisieren des Manifests

Bevor wir das Add-In laden, müssen wir eine Änderung an der Manifestdatei vornehmen. Der Generator fügt einen Platzhalterwert für das SupportUrl-Element hinzu, das keine gültige URL ist. Deshalb schlägt die Überprüfung der Datei fehl.

Öffnen Sie git-the-gist-manifest.xml, und suchen Sie das SupportUrl-Element. Entfernen oder kommentieren Sie die Zeile, und speichern Sie Ihre Änderungen, bevor Sie fortfahren.

<!--If you plan to submit this add-in to the Office Store, uncomment the SupportUrl element below-->
<!--<SupportUrl DefaultValue="[Insert the URL of a page that provides support information for the app]" />-->

Querladen des Add-Ins

Tipp

Aktuell gibt es ein Problem mit den vom Microsoft Office-Projekt-Generator generierten selbstsignierten Zertifikaten, aufgrund dessen Browser melden, dass die Add-In-Website nicht sicher ist, und das Add-In in Outlook-Clients nicht geladen wird. Das Problem besteht auch dann noch, wenn die generierten Zertifikate als vertrauenswürdig anerkannt wurden. Weitere Informationen und Problemumgehungen finden Sie unter Running add-in locally no longer works, certificate invalid auf GitHub.

  1. Stellen Sie in der Eingabeaufforderung/Shell sicher, dass Sie sich im Stammverzeichnis Ihres Projekts befinden, und geben Sie npm start ein. Dadurch wird ein Webserver unter https://localhost:3000 gestartet und Ihr Standardbrowser an dieser Adresse geöffnet.
    • Wenn Ihr Browser angibt, dass das Websitezertifikat nicht vertrauenswürdig ist, müssen Sie das Zertifikat als vertrauenswürdiges Zertifikat hinzufügen. Outlook lädt keine Add-Ins, wenn die Website nicht vertrauenswürdig ist. Weitere Informationen finden Sie unter Hinzufügen von selbstsignierten Zertifikaten als vertrauenswürdiges Stammzertifikat.
    • Wenn der Browser nicht auf ein Problem mit dem Zertifikat hinweist, fahren Sie mit dem nächsten Schritt fort.
  2. Öffnen Sie Outlook 2016. Klicken Sie auf der Registerkarte Start im Menüband auf die Schaltfläche Store.
  3. Klicken Sie in der Store-Benutzeroberfläche auf den Text Klicken Sie hier, um ein benutzerdefiniertes Add-In hinzuzufügen, und wählen Sie Aus Datei hinzufügen... ** aus. Navigieren Sie zur Datei git-the-gist-manifest.xml, und klicken Sie auf **Öffnen. Klicken Sie auf Installieren, wenn Sie dazu aufgefordert werden. Ein Screenshot des Menüelements „Aus Datei hinzufügen“ im Outlook 2016-Add-In-Store
  4. Schließen Sie das Store-Fenster. Nun sollte eine neue Schaltfläche auf dem Menüband mit der Bezeichnung Alle Eigenschaften anzeigen angezeigt werden. Klicken Sie auf diese Schaltfläche, um den Aufgabenbereich zu öffnen. Nun sollte die Willkommensseite des Add-Ins angezeigt werden.

Screenshot der vom Beispiel hinzugefügten Schaltfläche und des Aufgabenbereichs

Schreiben des Codes

Da wir nun überprüft haben, dass das grundlegende Add-In funktioniert, können wir es beliebig anpassen. Wir beginnen mit dem Manifest.

Aktualisieren des Manifests

Das Manifest für ein Add-In steuert, wie dieses in Outlook angezeigt wird. Es definiert, wie das Add-In in der Add-In-Liste angezeigt wird, die Schaltflächen, die im Menüband angezeigt werden, und legt die URL für die HTML- und JavaScript-Dateien fest, die vom Add-In verwendet werden.

Beginnen wir zunächst damit, einige Eigenschaften des Add-Ins selbst zu aktualisieren.

  1. Öffnen Sie die Datei git-the-gist-manifest.xml. Suchen Sie die ProviderName-Element in der XML, und ersetzen Sie den Standardwert durch Ihren Unternehmensnamen. xml <ProviderName>Contoso</ProviderName>
  2. Aktualisieren Sie die Description mit einer Beschreibung des Add-Ins. xml <Description DefaultValue="Allows users to access their Gists on GitHub"/>

Jetzt werden wir die vom Add-In definierten Schaltflächen ändern. Für unser Add-In implementieren wir zwei Schaltflächen: Insert Gist und Insert Default Gist (im Fenster zum Verfassen der Nachricht). Das aktuelle Manifest fügt jedoch nur Schaltflächen zum Fenster zum Lesen von Nachrichten hinzu. Wir müssen den Oberflächenerweiterungspunkt für den Befehl zum Verfassen von Nachrichten hinzufügen.

Hinweis

Für den Moment belassen wir die Befehlsoberfläche zum Lesen von Nachrichten wie sie ist. In künftigen Teilen dieser Reihe werden wir uns das Szenario zum Lesen von Nachrichten noch einmal näher ansehen.

Suchen Sie die Zeile im Manifest, die </DesktopFormFactor> lautet. Fügen Sie über dieser Zeile den folgenden XML-Code ein:

<ExtensionPoint xsi:type="MessageComposeCommandSurface">
  <OfficeTab id="TabDefault">
    <Group id="msgComposeCmdGroup">
      <Label resid="groupLabel"/>
      <Control xsi:type="Button" id="msgComposeInsertGist">
        <Label resid="insertGistLabel"/>
        <Supertip>
          <Title resid="insertGistTitle"/>
          <Description resid="insertGistDesc"/>
        </Supertip>
        <Icon>
          <bt:Image size="16" resid="icon16"/>
          <bt:Image size="32" resid="icon32"/>
          <bt:Image size="80" resid="icon80"/>
        </Icon>
        <Action xsi:type="ShowTaskpane">
          <SourceLocation resid="insertGistPaneUrl" />
        </Action>
      </Control>
      <Control xsi:type="Button" id="msgComposeInsertDefaultGist">
        <Label resid="insertDefaultGistLabel"/>
        <Supertip>
          <Title resid="insertDefaultGistTitle"/>
          <Description resid="insertDefaultGistDesc"/>
        </Supertip>
        <Icon>
          <bt:Image size="16" resid="icon16"/>
          <bt:Image size="32" resid="icon32"/>
          <bt:Image size="80" resid="icon80"/>
        </Icon>
        <Action xsi:type="ExecuteFunction">
          <FunctionName>insertDefaultGist</FunctionName>
        </Action>
      </Control>
    </Group>
  </OfficeTab>
</ExtensionPoint>

Sehen Sie sich nun an, was genau passiert.

  • Die ExtensionPoint mit xsi:type="MessageComposeCommandSurface" gibt an, dass wir Schaltflächen definieren, die dem Fenster zum Verfassen von Nachrichten hinzugefügt werden sollen.
  • Mithilfe eines OfficeTab-Elements mit id="TabDefault" geben wir an, dass wir unsere Schaltflächen zu der Standardregisterkarte auf dem Menüband hinzufügen möchten.
  • Das Group-Element definiert die Gruppierung für unsere Schaltflächen, mit einer Bezeichnung, die von der groupLabel-Ressource festgelegt wird.
  • Das erste Control-Element enthält ein Action-Element mit xsi:type="ShowTaskPane", sodass diese Schaltfläche einen Aufgabenbereich öffnet.
  • Das zweite Control-Element enthält ein Action-Element mit xsi:type="ExecuteFunction", sodass diese Schaltfläche eine JavaScript-Funktion aufruft, die in der Funktionsdatei enthalten ist.

Schließlich aktualisieren wir unsere Ressourcen. Der Code oben verwies auf Bezeichnungen, QuickInfos und URLs, die definiert werden müssen, damit das Manifest gültig ist.

  1. Fügen Sie Folgendes als untergeordnetes Element des bt:Urls-Elements hinzu: xml <bt:Url id="insertGistPaneUrl" DefaultValue="https://localhost:3000/msg-compose/insert-gist.html"/>
  2. Ändern Sie das DefaultValue-Attribut des bt:String-Elements mit id="groupLabel" in Git the Gist. xml <bt:String id="groupLabel" DefaultValue="Git the Gist"/>
  3. Fügen Sie die folgenden Elemente als untergeordnete Elemente des bt:ShortStrings-Elements hinzu. xml <bt:String id="insertGistLabel" DefaultValue="Insert Gist"> <bt:Override Locale="es-ES" Value="Inserte el Gist"/> </bt:String> <bt:String id="insertGistTitle" DefaultValue="Insert Gist"> <bt:Override Locale="es-ES" Value="Inserte el Gist"/> </bt:String> <bt:String id="insertDefaultGistLabel" DefaultValue="Insert Default Gist"> <bt:Override Locale="es-ES" Value="Inserte el Gist predeterminado"/> </bt:String> <bt:String id="insertDefaultGistTitle" DefaultValue="Insert Default Gist"> <bt:Override Locale="es-ES" Value="Inserte el Gist predeterminado"/> </bt:String>
  4. Fügen Sie die folgenden Elemente als untergeordnete Elemente des bt:LongStrings-Elements hinzu. xml <bt:String id="insertGistDesc" DefaultValue="Displays a list of your Gists and allows you to insert their contents into the current message"> <bt:Override Locale="es-ES" Value="Muestra una lista de sus Gists y permite insertar su contenido en el mensaje actual"/> </bt:String> <bt:String id="insertDefaultGistDesc" DefaultValue="Inserts the contents of the Gist you mark as default into the current message"> <bt:Override Locale="es-ES" Value="Inserta el contenido de lo Gist que marca como predeterminado en el mensaje actual"/> </bt:String>

Dadurch werden die Zeichenfolgenwerte definiert, die für das Add-In verwendet werden. Außerdem werden die Zeichenfolgen in Spanisch lokalisiert, indem ein bt:Override-Element für jede Zeichenfolge bereitgestellt wird. Zusätzliche Sprachen können als zusätzliche bt:Override-Elemente hinzugefügt werden. Das DefaultValue wird für Clients verwendet, die das im DefaultLocale-Element im Manifest angegebene Gebietsschema verwenden.

Speichern Sie die Änderungen im Manifest. Da wir das Add-In aus einer Datei installiert haben, müssen wir es erneut installieren, damit die Änderungen wirksam werden.

  1. Öffnen Sie Outlook 2016. Klicken Sie auf der Registerkarte Start im Menüband auf die Schaltfläche Store.
  2. Klicken Sie auf den Link Meine Add-Ins auf der linken Seite.
  3. Klicken Sie auf die Sie Deinstallieren neben dem Eintrag Git the Gist.
  4. Schließen Sie das Store-Fenster.
  5. Die benutzerdefinierte Schaltfläche sollte vorübergehend aus dem Menüband ausgeblendet werden.
  6. Installieren Sie das Add-In mithilfe des neuen Manifests erneut.

Wenn Sie nun in Outlook eine neue Nachricht verfassen, sollten Sie die beiden Schaltflächen auf dem Menüband sehen: Insert Gist und Insert Default Gist. Jetzt können wir die Add-In-Funktionalität implementieren.

Implementieren einer ersten Ausführung

In diesem Add-In bitten wir den Benutzer, seine GitHub-URL anzugeben und dann einen der vorhandenen Gists als Standard-Gist auszuwählen. Wir werden dies als Einstellungsdialogfeld für das Add-In implementieren.

Beginnen wir mit dem Erstellen der HTML für das Dialogfeld selbst. Erstellen Sie einen neuen Ordner im Stammordner des Projekts namens settings. Erstellen Sie dann eine Datei im Ordner settings mit dem Namen dialog.html, und fügen Sie das folgende Markup hinzu:

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=Edge" />
  <title>Settings</title>

  <!-- Office JavaScript API -->
  <script type="text/javascript" src="https://appsforoffice.microsoft.com/lib/1.1/hosted/office.debug.js"></script>

  <!-- LOCAL -->
  <link rel="stylesheet" href="../node_modules/office-ui-fabric-js/dist/css/fabric.min.css" />
  <link rel="stylesheet" href="../node_modules/office-ui-fabric-js/dist/css/fabric.components.css" />

  <!-- Template styles -->
  <link href="../app.css" rel="stylesheet" type="text/css" />
  <link href="dialog.css" rel="stylesheet" type="text/css" />
</head>

<body class="ms-font-l">
  <main>
    <section class="ms-font-m ms-fontColor-neutralPrimary">
      <div class="not-configured-warning ms-MessageBar ms-MessageBar--warning">
        <div class="ms-MessageBar-content">
          <div class="ms-MessageBar-icon">
            <i class="ms-Icon ms-Icon--Info"></i>
          </div>
          <div class="ms-MessageBar-text">
            Oops! It looks like you haven't configured <strong>Git the Gist</strong> yet.
            </br>
            Please configure your GitHub user name and select a default Gist, then try that action again!
          </div>
        </div>
      </div>
      <div class="ms-font-xxl">Settings</div>
      <div class="ms-Grid">
        <div class="ms-Grid-row">
          <div class="ms-TextField">
            <label class="ms-Label">GitHub Username</label>
            <input class="ms-TextField-field" id="github-user" type="text" value="" placeholder="Please enter your GitHub username">
          </div>
        </div>
        <div class="error-display ms-Grid-row">
          <div class="ms-font-l ms-fontWeight-semibold">An error occurred:</div>
          <pre><code id="error-text"></code></pre>
        </div>
        <div class="gist-list-container ms-Grid-row">
          <div class="list-title ms-font-xl ms-fontWeight-regular">Choose Default Gist</div>
          <ul id="gist-list" class="ms-List">
          </ul>
        </div>
      </div>
      <div class="ms-Dialog-actions">
        <div class="ms-Dialog-actionsRight">
          <button class="ms-Dialog-action ms-Button ms-Button--primary" id="settings-done" disabled>
            <span class="ms-Button-label">Done</span>
          </button>
        </div>
      </div>
    </section>
  </main>
  <script type="text/javascript" src="../node_modules/core-js/client/core.js"></script>
  <script type="text/javascript" src="../node_modules/jquery/dist/jquery.js"></script>
  <script type="text/javascript" src="../node_modules/office-ui-fabric-js/dist/js/fabric.js"></script>

  <script type="text/javascript" src="../helpers/gist-api.js"></script>
  <script type="text/javascript" src="dialog.js"></script>
</body>

</html>

Dies ist ein sehr einfaches Formular mit einer Texteingabe für einen GitHub-Benutzernamen und einer leeren Liste für Gists, die über JavaScript aufgefüllt werden. Beachten Sie, dass wir Office Fabric für Schriftarten und Formatvorlagen verwenden.

Fügen Sie nun dialog.css in demselben Ordner hinzu, und fügen Sie dann den folgenden Code hinzu.

section {
  margin: 10px 20px;
}

.not-configured-warning {
  display: none;
}

.error-display {
  display: none;
}

.gist-list-container {
  margin: 10px -8px;
  display: none;
}

.list-title {
  border-bottom: 1px solid #a6a6a6;
  padding-bottom: 5px;
}

ul {
  margin-top: 10px;
}

Nun haben wir uns um die Benutzeroberfläche des Dialogfelds gekümmert, jetzt müssen wir aber Code hinzufügen, dass auch tatsächlich eine Aktion ausgeführt wird. Wir verwenden jQuery, um Ereignisse zu verknüpfen, und die messageParent-Funktion, um die Auswahl des Benutzers zurück an den Aufrufer zu senden. Erstellen Sie eine Datei im Ordner settings mit dem Namen dialog.js, und fügen Sie den folgenden Code hinzu.

(function(){
  'use strict';

  // The Office initialize function must be run each time a new page is loaded
  Office.initialize = function(reason){
    jQuery(document).ready(function(){
      if (window.location.search) {
        // Check if warning should be displayed
        var warn = getParameterByName('warn');
        if (warn) {
          $('.not-configured-warning').show();
        } else {
          // See if the config values were passed
          // If so, pre-populate the values
          var user = getParameterByName('gitHubUserName');
          var gistId = getParameterByName('defaultGistId');

          $('#github-user').val(user);
          loadGists(user, function(success){
            if (success) {
              $('input:hidden').filter(function() {
                return this.value === gistId;
              }).parent().addClass('is-selected');
              $('#settings-done').removeAttr('disabled');
            }
          });
        }
      }

      // When the GitHub username changes,
      // try to load Gists
      $('#github-user').on('change', function(){
        $('#gist-list').empty();
        var ghUser = $('#github-user').val();
        if (ghUser.length > 0) {
          loadGists(ghUser);
        }
      });

      // When the Done button is clicked, send the
      // values back to the caller as a serialized
      // object.
      $('#settings-done').on('click', function() {
        var settings = {};

        settings.gitHubUserName = $('#github-user').val();

        var selectedGist = $('li.is-selected');
        if (selectedGist) {
          settings.defaultGistId = selectedGist.children('.gist-id').val();

          sendMessage(JSON.stringify(settings));
        }
      });
    });
  };

  // Load gists for the user using the GitHub API
  // and build the list 
  function loadGists(user, callback) {
    getUserGists(user, function(gists, error){
      if (error) {
        $('.gist-list-container').hide();
        $('#error-text').text(JSON.stringify(error, null, 2));
        $('.error-display').show();
        if (callback) callback(false);
      } else {
        $('.error-display').hide();
        buildGistList($('#gist-list'), gists, onGistSelected);
        $('.gist-list-container').show();
        if (callback) callback(true);
      }
    });
  }

  function onGistSelected() {
    $('.ms-ListItem').removeClass('is-selected');
    $(this).addClass('is-selected');
    $('.not-configured-warning').hide();
    $('#settings-done').removeAttr('disabled');
  }

  function sendMessage(message) {
    Office.context.ui.messageParent(message);
  }

  function getParameterByName(name, url) {
    if (!url) {
      url = window.location.href;
    }
    name = name.replace(/[\[\]]/g, "\\$&");
    var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
      results = regex.exec(url);
    if (!results) return null;
    if (!results[2]) return '';
    return decodeURIComponent(results[2].replace(/\+/g, " "));
  }
})();

Beachten Sie, dass das change-Ereignis für das Feld des GitHub-Benutzernamens so festgelegt ist, dass die Gists des Benutzers geladen werden. Wir müssen die GitHub-Gist-API implementieren. Wir platzieren diese in einer separaten Datei, damit sie einfacher wiederverwendet werden kann.

Erstellen Sie einen Ordner im Stamm des Projekts namens helpers. Erstellen Sie in diesem Ordner eine Datei mit dem Namen gist-api.js, und fügen Sie den folgenden Code hinzu.

function getUserGists(user, callback) {
  var requestUrl = 'https://api.github.com/users/' + user + '/gists';

  $.ajax({
    url: requestUrl,
    dataType: 'json'
  }).done(function(gists){
    callback(gists);
  }).fail(function(error){
    callback(null, error);
  });
}

function buildGistList(parent, gists, clickFunc) {
  gists.forEach(function(gist, index) {

    var listItem = $('<li/>')
      .addClass('ms-ListItem')
      .addClass('is-selectable')
      .attr('tabindex', index)
      .appendTo(parent);

    var desc = $('<span/>')
      .addClass('ms-ListItem-primaryText')
      .text(gist.description)
      .appendTo(listItem);

    var desc = $('<span/>')
      .addClass('ms-ListItem-secondaryText')
      .text(buildFileList(gist.files))
      .appendTo(listItem);

    var updated = new Date(gist.updated_at);

    var desc = $('<span/>')
      .addClass('ms-ListItem-tertiaryText')
      .text('Last updated ' + updated.toLocaleString())
      .appendTo(listItem);

    var selTarget = $('<div/>')
      .addClass('ms-ListItem-selectionTarget')
      .appendTo(listItem);

    var id = $('<input/>')
      .addClass('gist-id')
      .attr('type', 'hidden')
      .val(gist.id)
      .appendTo(listItem);
  });

  $('.ms-ListItem').on('click', clickFunc);
}

function buildFileList(files) {

  var fileList = '';

  for (var file in files) {
    if (files.hasOwnProperty(file)) {
      if (fileList.length > 0) {
        fileList = fileList + ', ';
      }

      fileList = fileList + files[file].filename + ' (' + files[file].language + ')';
    }
  }

  return fileList;
}

Dadurch wird das Einstellungsdialogfeld vollständig implementiert. Nun ist die Frage, wie wir dieses aufrufen können. Sie haben vielleicht bemerkt, dass wir keine Schaltfläche für Einstellungen zum Menüband hinzugefügt haben. Stattdessen überprüft das Add-In, dass diese konfiguriert wurde. Wenn dies nicht der Fall ist, wird der Benutzer beim Aufrufen des Add-Ins aufgefordert, diese zu konfigurieren, bevor der Vorgang fortgesetzt wird. Da der Benutzer zuerst auf eine der beiden Schaltflächen klicken könnte, werden wir diese Überprüfung in beiden Fällen durchführen.

Implementieren einer Schaltfläche ohne Benutzeroberfläche

Wir beginnen mit der Schaltfläche Insert Default Gist. Diese Schaltfläche führt einfach eine JavaScript-Funktion in der Funktionsdatei, aus anstatt einen Aufgabenbereich zu öffnen. Diese Art der Schaltfläche wird als Schaltfläche ohne Benutzeroberfläche bezeichnet.

Das Ziel für diese Schaltfläche besteht darin, zu überprüfen, ob das Add-In bereits konfiguriert wurde. Ist dies der Fall, wird der Inhalt des Gists geladen, den der Benutzer als Standard ausgewählt hat, und in den Textkörper geladen. Ist dies nicht der Fall, wird das Einstellungsdialogfeld angezeigt. Es ist jedoch etwas merkwürdig, dem Benutzer nur das Einstellungsdialogfeld ohne jegliche Erläutert anzuzeigen. In diesem Fall werden wir daher die Meldungsleiste anzeigen, die in der HTML des Dialogfelds enthalten ist, damit der Benutzer weiß, warum das Dialogfeld angezeigt wird.

Beginnen wir mit der Erstellung der Funktionsdatei. Alle Funktionen, die von einer Schaltfläche ohne Benutzeroberfläche aufgerufen werden, müssen in der Funktionsdatei definiert werden, die vom FunctionFile-Element im Manifest für den entsprechenden Formfaktor angegeben ist. In unserem Manifest ist dies als https://localhost:3000/function-file/function-file.html definiert. Öffnen Sie die Datei ./function-file/function-file.html, und aktualisieren Sie den Code so, dass er folgendermaßen aussieht.

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=Edge" />

    <!-- Office JavaScript API -->
    <script type="text/javascript" src="https://appsforoffice.microsoft.com/lib/1.1/hosted/office.debug.js"></script>

    <script type="text/javascript" src="../node_modules/jquery/dist/jquery.js"></script>
    <script type="text/javascript" src="../node_modules/showdown/dist/showdown.min.js"></script>
    <script type="text/javascript" src="../node_modules/urijs/src/URI.min.js"></script>
    <script type="text/javascript" src="../helpers/addin-config.js"></script>
    <script type="text/javascript" src="../helpers/gist-api.js"></script>
    <script type="text/javascript" src="function-file.js"></script>
</head>

<body>
  <!-- NOTE: The body is empty on purpose. Since function in function-file.js are
       invoked via a button, there is no UI to render. -->
</body>

</html>

Beachten Sie, dass eines der Skripttags auf Showdown verweist, was später zum Konvertieren von Markdown in HTML verwendet wird. Eine anderes Tag verweist auf URI.js, was zum Erstellen relativer URLs verwendet wird. Wir müssen diese Bibliotheken installieren. Öffnen Sie die Eingabeaufforderung/Shell im Stammordner des Projekts, und führen Sie den folgenden Befehl aus.

npm install showdown urijs --save

Wir haben auch auf addin-config.js verwiesen, was noch nicht vorhanden ist. Erstellen Sie die Datei im Ordner helpers, und fügen Sie den folgenden Code hinzu.

function getConfig() {
  var config = {};

  config.gitHubUserName = Office.context.roamingSettings.get('gitHubUserName');
  config.defaultGistId = Office.context.roamingSettings.get('defaultGistId');

  return config;
}

function setConfig(config, callback) {
  Office.context.roamingSettings.set('gitHubUserName', config.gitHubUserName);
  Office.context.roamingSettings.set('defaultGistId', config.defaultGistId);

  Office.context.roamingSettings.saveAsync(callback);
}

Dabei wird das RoamingSettingsObjekt verwendet, um die Konfigurationswerte abzurufen oder festzulegen.

Öffnen Sie nun die Datei function-file.js im Ordner function-file, und ersetzen Sie den Inhalt durch den folgenden Code:

var config;
var btnEvent;

// The initialize function must be run each time a new page is loaded
Office.initialize = function (reason) {
  config = getConfig();
};

// Add any ui-less function here
function showError(error) {
  Office.context.mailbox.item.notificationMessages.replaceAsync('github-error', {
    type: 'errorMessage',
    message: error
  }, function(result){
  });
}

var settingsDialog;

function insertDefaultGist(event) {

  // Check if the add-in has been configured
  if (config && config.defaultGistId) {
    // Get the default Gist content and insert
    try {
      getGist(config.defaultGistId, function(gist, error) {
        if (gist) {
          buildBodyContent(gist, function (content, error) {
            if (content) {
              Office.context.mailbox.item.body.setSelectedDataAsync(content,
                {coercionType: Office.CoercionType.Html}, function(result) {
                  event.completed();
              });
            } else {
              showError(error);
              event.completed();
            } 
          });
        } else {
          showError(error);
          event.completed();
        }
      });
    } catch (err) {
      showError(err);
      event.completed();
    }

  } else {
    // Save the event object so we can finish up later
    btnEvent = event;
    // Not configured yet, display settings dialog with
    // warn=1 to display warning.
    var url = new URI('../settings/dialog.html?warn=1').absoluteTo(window.location).toString();
    var dialogOptions = { width: 20, height: 40, displayInIframe: true };

    Office.context.ui.displayDialogAsync(url, dialogOptions, function(result) {
      settingsDialog = result.value;
      settingsDialog.addEventHandler(Microsoft.Office.WebExtension.EventType.DialogMessageReceived, receiveMessage);
      settingsDialog.addEventHandler(Microsoft.Office.WebExtension.EventType.DialogEventReceived, dialogClosed);
    });
  }
}

function receiveMessage(message) {
  config = JSON.parse(message.message);
  setConfig(config, function(result) {
    settingsDialog.close();
    settingsDialog = null;
    btnEvent.completed();
    btnEvent = null;
  });
}

function dialogClosed(message) {
  settingsDialog = null;
  btnEvent.completed();
  btnEvent = null;
}

Beachten Sie, dass der Parameter ?warn=1 der Dialog-URL in der insertDefaultGist-Funktion hinzugefügt wurde. Das Einschließen dieses Parameters bewirkt, dass in dem Dialogfeld die Meldungsleiste angezeigt wird.

Bevor wir die Schaltfläche ohne Benutzeroberfläche testen, müssen wir eine Reihe von Funktionen zur Datei gist-api.js hinzufügen, um die Inhalte eines Gists abzurufen und diese in HTML konvertieren, damit sie in den Nachrichtentext eingefügt werden können. Fügen Sie die folgenden Funktionen hinzu:

function getGist(gistId, callback) {
  var requestUrl = 'https://api.github.com/gists/' + gistId;

  $.ajax({
    url: requestUrl,
    dataType: 'json'
  }).done(function(gist){
    callback(gist);
  }).fail(function(error){
    callback(null, error);
  });
}

function buildBodyContent(gist, callback) {
  // Find the first non-truncated file in the gist
  // and use it.
  for (var filename in gist.files) {
    if (gist.files.hasOwnProperty(filename)) {
      var file = gist.files[filename];
      if (!file.truncated) {
        // We have a winner
        switch (file.language) {
          case 'HTML':
            // Insert as-is
            callback(file.content);
            break;
          case 'Markdown':
            // Convert Markdown to HTML
            var converter = new showdown.Converter();
            var html = converter.makeHtml(file.content);
            callback(html);
            break;
          default:
            // Insert contents as a <code> block
            var codeBlock = '<pre><code>';
            codeBlock = codeBlock + file.content;
            codeBlock = codeBlock + '</code></pre>';
            callback(codeBlock);
        }
        return;
      }
    }
  }
  callback(null, 'No suitable file found in the gist');
}

Wenn der Gist HTML enthält, wird dieser unverändert in den Text eingefügt. Wenn der Gist Markdown ist, wird das Add-In mithilfe von Showdown konvertiert, und die resultierende HTML wird eingefügt. In allen anderen Fällen wird er unverändert als Codeausschnitt eingefügt.

Testen der Schaltfläche

Die Schaltfläche Insert Default Gist sollte nun funktionieren. Speichern Sie alle Änderungen und führen Sie npm start aus, wenn der Server nicht bereits ausgeführt wird. Öffnen Sie Outlook, und verfassen Sie eine neue Nachricht. Wenn Sie auf die Schaltfläche Insert Default Gist klicken, werden Sie aufgefordert, das Add-In zu konfigurieren.

Screenshot der Aufforderung zum Konfigurieren des Add-Ins

Geben Sie Ihren GitHub-Benutzernamen ein. Drücken Sie die Tabulatortaste, um das change-Ereignis aufzurufen, sodass die Liste von Gists geladen wird. Wählen Sie einen Gist als Standard aus, und klicken Sie auf Fertig.

Hinweis

Wenn Sie keine Gists auf GitHub haben, erstellen Sie welche.

Screenshot des Einstellungsdialogfelds des Add-Ins

Klicken Sie nun erneut auf die Schaltfläche Insert Default Gist. Dieses Mal sollten Sie den Inhalt des Gists sehen, der in den Textkörper der E-Mail eingefügt wurde.

Implementieren eines Aufgabenbereichs

Nun können wir an der Schaltfläche Insert Gist arbeiten. Für diese Schaltfläche öffnen wir einen Aufgabenbereich und zeigen alle Gists des Benutzers an. Der Benutzer kann einen Gist auswählen und einfügen. Wenn der Benutzer das Add-In noch nicht konfiguriert hat, wird eine Meldung angezeigt, in der er dazu aufgefordert wird.

Erstellen Sie einen Ordner im Stamm des Projekts namens msg-compose. Erstellen Sie in diesem Ordner insert-gist.hml, und fügen Sie das folgende Markup hinzu.

Hinweis

Das Markup für den Aufgabenbereich sieht dem Entwurfsmuster der Zielseite sehr ähnlich, das unter UX-Entwurfsmustervorlagen für Office-Add-Ins beschrieben wird.

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=Edge" />
  <title>Landing Page</title>

  <!-- Office JavaScript API -->
  <script type="text/javascript" src="https://appsforoffice.microsoft.com/lib/1.1/hosted/office.debug.js"></script>

  <!-- LOCAL -->
  <link rel="stylesheet" href="../node_modules/office-ui-fabric-js/dist/css/fabric.min.css" />
  <link rel="stylesheet" href="../node_modules/office-ui-fabric-js/dist/css/fabric.components.css" />

  <!-- Template styles -->

  <link href="insert-gist.css" rel="stylesheet" type="text/css" />
</head>

<body class="ms-font-l ms-landing-page">
  <main class="ms-landing-page__main">
    <section class="ms-landing-page__content ms-font-m ms-fontColor-neutralPrimary">
      <div id="not-configured" style="display: none;">
        <div class="centered ms-font-xxl ms-u-textAlignCenter">Welcome!</div>
        <div class="ms-font-xl" id="settings-prompt">Please click the <strong>Settings</strong> icon at the bottom of this window to configure this add-in.</div>
      </div>
      <div id="gist-list-container" style="display: none;">
        <ul id="gist-list" class="ms-List">
        </ul>
      </div>
      <div id="error-display" style="display: none;" class="ms-u-borderBase ms-fontColor-error ms-font-m ms-bgColor-error ms-borderColor-error">
      </div>
    </section>
    <button class="ms-Button ms-Button--primary" id="insert-button" disabled>
      <span class="ms-Button-label">Insert</span>
    </button>
  </main>
  <footer class="ms-landing-page__footer ms-bgColor-themePrimary">
    <div class="ms-landing-page__footer--left">
      <img src="../assets/logo-filled.png" />
      <h1 class="ms-font-xl ms-fontWeight-semilight ms-fontColor-white">Git the Gist</h1>
    </div>
    <div id="settings-icon" class="ms-landing-page__footer--right">
      <i class="ms-Icon enlarge ms-Icon--Settings ms-fontColor-white"></i>
    </div>
  </footer>
  <script type="text/javascript" src="../node_modules/jquery/dist/jquery.js"></script>
  <script type="text/javascript" src="../node_modules/showdown/dist/showdown.min.js"></script>
  <script type="text/javascript" src="../node_modules/urijs/src/URI.min.js"></script>
  <script type="text/javascript" src="../helpers/addin-config.js"></script>
  <script type="text/javascript" src="../helpers/gist-api.js"></script>
  <script type="text/javascript" src="insert-gist.js"></script>
</body>

</html>

Erstellen Sie eine Datei mit dem Namen insert-gist.css im Ordner msg-compose, und fügen Sie den folgenden Code hinzu.

/* Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See full license in root of repo. */
html, body {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  overflow: auto; }

body {
  position: relative;
  font-size: 16px; }

main {
  height: 100%;
  overflow-y: auto; }

footer {
  width: 100%;
  position: relative;
  bottom: 0; 
  margin-top: 10px;}

p, h1, h2, h3, h4, h5, h6 {
  margin: 0;
  padding: 0; }

ul {
  padding: 0; }

#settings-prompt {
  margin: 10px 0;
}

#error-display {
  padding: 10px;
}

#insert-button {
  margin: 0 10px;
}

.clearfix {
  display: block;
  clear: both;
  height: 0; }

.pointerCursor {
  cursor: pointer; }

.invisible {
  visibility: hidden; }

.undisplayed {
  display: none; }

.ms-Icon.enlarge {
  position: relative;
  font-size: 20px;
  top: 4px; }

.ms-landing-page {
  display: -webkit-flex;
  display: flex;
  -webkit-flex-direction: column;
          flex-direction: column;
  -webkit-flex-wrap: nowrap;
          flex-wrap: nowrap;
  height: 100%; }
  .ms-landing-page__main {
    display: -webkit-flex;
    display: flex;
    -webkit-flex-direction: column;
            flex-direction: column;
    -webkit-flex-wrap: nowrap;
            flex-wrap: nowrap;
    -webkit-flex: 1 1 0;
            flex: 1 1 0;
    height: 100%; }

  .ms-landing-page__content {
    display: -webkit-flex;
    display: flex;
    -webkit-flex-direction: column;
            flex-direction: column;
    -webkit-flex-wrap: nowrap;
            flex-wrap: nowrap;
    height: 100%;
    -webkit-flex: 1 1 0;
            flex: 1 1 0;
    padding: 20px; }
    .ms-landing-page__content h2 {
      margin-bottom: 20px; }
  .ms-landing-page__footer {
    display: -webkit-inline-flex;
    display: inline-flex;
    -webkit-justify-content: center;
            justify-content: center;
    -webkit-align-items: center;
            align-items: center; }
    .ms-landing-page__footer--left {
      transition: background ease 0.1s, color ease 0.1s;
      display: -webkit-inline-flex;
      display: inline-flex;
      -webkit-justify-content: flex-start;
              justify-content: flex-start;
      -webkit-align-items: center;
              align-items: center;
      -webkit-flex: 1 0 0px;
              flex: 1 0 0px;
      padding: 20px; }
      .ms-landing-page__footer--left:active, .ms-landing-page__footer--left:hover {
        background: #005ca4;
        cursor: pointer; }
      .ms-landing-page__footer--left:active {
        background: #005ca4; }
      .ms-landing-page__footer--left--disabled {
        opacity: 0.6;
        pointer-events: none;
        cursor: not-allowed; }
        .ms-landing-page__footer--left--disabled:active, .ms-landing-page__footer--left--disabled:hover {
          background: transparent; }
      .ms-landing-page__footer--left img {
        width: 40px;
        height: 40px; }
      .ms-landing-page__footer--left h1 {
        -webkit-flex: 1 0 0px;
                flex: 1 0 0px;
        margin-left: 15px;
        text-align: left;
        width: auto;
        max-width: auto;
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis; }
    .ms-landing-page__footer--right {
      transition: background ease 0.1s, color ease 0.1s;
      padding: 29px 20px; }
      .ms-landing-page__footer--right:active, .ms-landing-page__footer--right:hover {
        background: #005ca4;
        cursor: pointer; }
      .ms-landing-page__footer--right:active {
        background: #005ca4; }
      .ms-landing-page__footer--right--disabled {
        opacity: 0.6;
        pointer-events: none;
        cursor: not-allowed; }
        .ms-landing-page__footer--right--disabled:active, .ms-landing-page__footer--right--disabled:hover {
          background: transparent; }

Da die Benutzeroberfläche nun implementiert ist, können wir den Code dahinter hinzufügen. Erstellen Sie eine Datei mit dem Namen insert-gist.js im Ordner msg-compose, und fügen Sie den folgenden Code hinzu.

(function(){
  'use strict';

  var config;
  var settingsDialog;

  Office.initialize = function(reason){
    config = getConfig();

    jQuery(document).ready(function(){
      // Check if add-in is configured
      if (config && config.gitHubUserName) {
        // If configured load the gist list
        loadGists(config.gitHubUserName);
      } else {
        // Not configured yet
        $('#not-configured').show();
      }

      // When insert button is clicked, build the content
      // and insert into the body.
      $('#insert-button').on('click', function(){
        var gistId = $('.ms-ListItem.is-selected').children('.gist-id').val();
        getGist(gistId, function(gist, error) {
          if (gist) {
            buildBodyContent(gist, function (content, error) {
              if (content) {
                Office.context.mailbox.item.body.setSelectedDataAsync(content,
                  {coercionType: Office.CoercionType.Html}, function(result) {
                    if (result.status == 'failed') {
                      showError('Could not insert Gist: ' + result.error.message);
                    }
                });
              } else {
                showError('Could not create insertable content: ' + error);
              } 
            });
          } else {
            showError('Could not retreive Gist: ' + error);
          }
        });
      });

      // When the settings icon is clicked, open the settings dialog
      $('#settings-icon').on('click', function(){
        // Display settings dialog
        var url = new URI('../settings/dialog.html').absoluteTo(window.location).toString();
        if (config) {
          // If the add-in has already been configured, pass the existing values
          // to the dialog
          url = url + '?gitHubUserName=' + config.gitHubUserName + '&defaultGistId=' + config.defaultGistId;
        }

        var dialogOptions = { width: 20, height: 40, displayInIframe: true };

        Office.context.ui.displayDialogAsync(url, dialogOptions, function(result) {
          settingsDialog = result.value;
          settingsDialog.addEventHandler(Microsoft.Office.WebExtension.EventType.DialogMessageReceived, receiveMessage);
          settingsDialog.addEventHandler(Microsoft.Office.WebExtension.EventType.DialogEventReceived, dialogClosed);
        });
      })
    });
  };

  function loadGists(user) {
    $('#error-display').hide();
    $('#not-configured').hide();
    $('#gist-list-container').show();

    getUserGists(user, function(gists, error) {
      if (error) {

      } else {
        buildGistList($('#gist-list'), gists, onGistSelected);
      }
    });
  }

  function onGistSelected() {
    $('.ms-ListItem').removeClass('is-selected');
    $(this).addClass('is-selected');
    $('#insert-button').removeAttr('disabled');
  }

  function showError(error) {
    $('#not-configured').hide();
    $('#gist-list-container').hide();
    $('#error-display').text(error);
    $('#error-display').show();
  }

  function receiveMessage(message) {
    config = JSON.parse(message.message);
    setConfig(config, function(result) {
      settingsDialog.close();
      settingsDialog = null;
      loadGists(config.gitHubUserName);
    });
  }

  function dialogClosed(message) {
    settingsDialog = null;
  }
})();

Speichern Sie alle Änderungen und führen Sie npm start aus, wenn der Server nicht bereits ausgeführt wird. Öffnen Sie Outlook, und verfassen Sie eine neue Nachricht. Wenn Sie auf die Schaltfläche Insert Gist klicken, wird auf der rechten Seite ein Aufgabenbereich geöffnet. Wenn Sie einen Gist auswählen und auf Einfügen klicken, wird dieser in den Textkörper eingefügt.

Screenshot des Aufgabenbereichs des Add-Ins