How to Fix IntelliSense in VSCode for OfficeJS

I spent the better part of this morning trying to figure out why a new Yeoman generated Outlook add-in was not working with IntelliSense in VSCode. I would type “Office.” and nothing would appear. I would press CTRL+{SPACE} and still nothing.

After a lot of digging and comparing projects that did work to those that did not, I found that the @types were missing. So I then installed @types/office-js:

npm install @types/office-js

Once I did this, viola! IntelliSense started working like a charm. From my experience this has happened to me a few different times, in these given scenarios:

  • Moving an OfficeJS project from VS2017 to VSCode
  • Moving a Script Lab project to VSCode
  • Creating a blank OfficeJS project from scratch with just two files (manifest and webpage).

In my continued research I found that the way VSCode handles types is through NPM and you need to have data type definition files to get it all to work, that is why you need to install both NPM and the OfficeJS @types to get this to work. Per this page:  https://code.visualstudio.com/Docs/languages/javascript

Automatic Type Acquisition (ATA) uses npm, the Node.js package manager, to install and manage Type Declaration (typings) files. To ensure that Automatic Type Acquisition works properly, first ensure that you have npm installed on your machine.

Secondly to that, for OfficeJS development, make sure you have @types/office-js.

Getting Started as an OfficeJS Developer

At the MVP Summit 2018, I have had a number of conversations with various MVP’s that there really should be more documentation on how to get started with OfficeJS from scratch. Like web developer – from scratch.

So, this blog post is my attempt to get you started as if you have little to no web developer experience. The hope is that once you have read this and studied the linked content you will be able to:

  • Write HTML and JavaScript
  • Understand what JQuery is and how to use it
  • Setup your VSCode environment
  • Identify the key parts of an add-in (source code files)
  • Begin writing, and debugging your own code.

To get started, if you are completely new to web development (or even a little rusty and need a refresher), you might want to read the following articles/tutorials. If you already know this stuff, then you can move on:

 

NOTE: From these links you need to come away with an understanding of these basic fundamental concepts that are used heavily in OfficeJS:

  1. How JavaScript and HTML work together
  2. The HTML DOM model
  3. How to write HTML and define controls (buttons, text boxes, etc.) with an id
  4. How to use JQuery(*) to get a reference to a control on the HTML page and then manipulate it with JavaScript.

(*) JQuery is simply a library that, for simplicity sake, makes it easy for you to access and manipulate HTML controls via the DOM. You access it with a “$” character. This is why you might see so many “$”‘s in the sample/example code out there.

Once you know the basics of creating a web page, and manipulating it, you can then begin by setting up VSCode and learning how to code OfficeJS solutions. As for development environments, there are many out there, and many are nearly the same, but I suggest VSCode because it is simply a better environment to learn “webby” development. Especially if you are on a Mac. With that said, you will want to:

  1. Setup and configure VSCode
  2. Start learning Office from the new quick start and tutorials on docs.office.com.
  3. You can also stick to the basics (no development environment needed) if you install and use Script Lab.

NOTE: A fundamental concept you need to come away with after working through the tutorials is to understand the “parts” of an add-in. I think the best way to understand the PARTS of an add-in:

  • All you really need for an OfficeJS solution are two files. A manifest and an HTML file.

To understand the anatomy of an add-in, you can read this post where I describe the most basic add-in you can create. Here is a simplified explanation to get your started:

  • The Manifest – an XML file that defines to Office your add-in. It defines things like the name, a unique id (GUID), the platform and app it supports, and most importantly your Ribbon (text, icons, etc), and what happens when you click those buttons. Specifically, it tell Office where to navigate when that button is clicked. Then this XML file is what gets “installed” in your Office app. Office reads it, places the button(s) on the Ribbon which loads the defined web page when clicked.
  • The HTML file – this contains “script” tags which point to all your code, supporting libraries (like JQuery) which similar to VBA/VSTO references. The HTML page also contains your Task Pane user interface (buttons, etc.).
  • The JavaScript file – This is your code, which will contain an Office.initialize() function that Office locates and executes when the HTML page is loaded. This can be a separate file or like the most basic add-in an inline script tag in your HTML file.

The entire load and run process for an add-in works like this:

  • The Manifest is loaded by Office and adds a button to the ribbon.
  • When the button is clicked, the HTML file specified in the manifest is loaded in the task pane.
  • When the HTML file is loaded, all the script tags (references and your own JS file) are loaded.
  • Once all the scripts are loaded, Office locates the Office.initialize() function and executes it.
  • It is in this code block that you can initialize your controls, like buttons on the HTML page, and what happens when the user clicks them.

Once you have your VSCode development environment setup for Office, and you are ready to start your first project, follow these steps I outlined for using the Yeoman generator.

I hope this is enough to get even the most novice non-web developer going. If you have any questions, please post them below. If there is any additional content you would like to see, please let me know.

The last thing I will leave you with is to answer an interesting question that arose this week:

Why would I learn to develop in OfficeJS? I am not interested in just making a web widget for Excel.

I think this is a common misconception. Office 365 Web Add-ins (OfficeJS) are at all not like Windows Vista Sidebar Gadgets for Office. They are actually quite powerful and there are many useful scenarios. Here are a few:

  • Integration with internal applications like CRM
  • Document tagging, or placing metadata on parts of the document, possibly linked with internal systems, controls or processes
  • Document building, either interactively through a task pane or dialog or from predefined rules
  • Advanced content editing, identifying and replacing specific content based on more complex rules than simple search/replace
  • Sending data from the document for more complex analysis and/or manipulation via back-end services and then updating the document with the results.

Once you delve a bit more into the object model and the capabilities, you will learn that you do not always have to open a task pane. You can open a dialog instead. Or, you can have no user interface at all, just have a button click on the ribbon perform work against the document like a macro used to do.

And here is another thought: Office is used by one-billion people world-wide across each of the supported platforms of Windows, Mac, web, iOS, and Android. The goal of these add-ins is to support the same code base (mostly) across all of these different platforms. You really can write-once/run-anywhere. You can build an add-in and then place it in the Microsoft Store and have it reach a huge audience. Do you need more of a reason to get started?

Happy coding.

Start Developing in OfficeJS Today with Script Lab

Recently, I have been working with a tool from Microsoft Garage which allows you to quickly develop OfficeJS projects in Word, Excel and PowerPoint. It is called Script Lab. You can find more information about it here:

Script Lab

It is also in GitHub project. You can read about it here:

https://github.com/OfficeDev/script-lab

To install Script Lab, open Word, Excel or PowerPoint and on the Insert tab, click Store.

insert

Type “script lab in” the search box and hit enter. Then select Script Lab from the list and click Add.

store

Once added, you will see a new Script Lab tab. From there, you click the Code button to open the task pane.

scriptlab

This tool comes with a lot of options:

  • If you click the hamburger menu you will see an option to Sign Into GitHub. This will allow you to create Gist’s and access and save code snippets to GitHub.
  • You can import Samples from GitHub (with a URL) and access Snippets.
  • There are options to change the color in the editor at the bottom from Dark to Light.
  • And once in your solution, you can change the template (the html page), the CSS Style and any references libraries.

NOTE:  You are dealing with a subset of the whole solution here. You cannot create Ribbon buttons or dialogs. You can only develop task pane add-ins. You can work with Script Labs in Word, Excel and PowerPoint, but not Outlook (yet).

Script Lab allows you to test things in a quick fashion. Here is what the code pane looks like:

code.PNG

NOTE: Script Lab defaults to TypeScript. It emits code in JavaScript on Export. There is no option to switch to only JavaScript. If you want to program in JavaScript that does not matter because JavaScript will work just fine as well. But in a shout out to Michael Zlatkovsky – who says Office of TypeScript are a match made in heaven – I look at this as an opportunity to get better at TypeScript.

One of the more difficult part of working in OfficeJS is to be able to quickly test how some feature of function or a block of code will work. In the VSTO and VBA days, I would pop open the Visual Basic Editor in Word, Excel or PowerPoint (ALT+F11), then open the Immediate Window (CTRL+G) and then start typing away to test specific object model items. There was nothing similar to this with OfficeJS, until now.

Script Lab is just that, a laboratory for you to test your ideas, your snippets, your work in progress. It is a way to test something before you put it into your full blown solution. You can then save code snippets as Gist (on GitHub) and export the solution.

And once you have written your code and are ready to test it, you very easily run it in the Taskpane or (my favorite) you can run side-by-side. From the Ribbon click the Run button and then you can still edit your code/html and then refresh and reload it in the side-by-side Run window. This makes developing a solution or just testing ideas VERY easy:

side-by-side.PNG

Once you are running you (kind of) have debugging tools, can see the DOM and see console messages. And my favorite part, is that once you have hacked out a basic add-in concept with buttons and text in the HTML and code in the script file, you can export your solution (Export for publishing). This will create a ZIP which will contain the XML manifest and HTML (with script inline), which is very similar to the minimalist add-in I outlined in a previous blog post:

OfficeJS: Create a (VERY) Basic Add-in for Excel

NOTE: If you do write async/await code the exported result will be very ugly and hard to follow. This is why you might want to export to GitHub if you use async/awat code (TypeScript) so you can keep the original source and then transpile to ES5 later.

However, I really like VS Code and the power of NPM and Browser-Sync, so I worked on converting a Script Lab project into an NPM project. This took quite a lot of work and that will be a topic for a future blog post, but the gist of it is to create a new Office Yeoman (yo office) project for the same application and then gut and Frankenstein it with the Script Lab code.

Bottom line, I love Script LabwlEmoticon-openmouthedsmile.png If you are working on an OfficeJS project and/or planning on creating an OfficeJS project and/or want to learn how to develop in OfficeJS, this is definitely THE tool to have.

 

Did you miss the Message… box?

Several customers have asked me if OfficeJS has something similar to a Visual Basic or C# MessageBox.Show() function. The answer is no. And for a long time there was not even a dialog option. With the latest releases of the OfficeJS libraries comes a new dialog feature. Yet to get a standard MessageBox, you will still need to create it from scratch. Or, at least until this blog post you did. I have created a helper library the consists of two files:

  • dialogs.js
  • dialogs.html

To reference this library you can do any of the following:

If you used NPM, you can reference then with a script tag like this:

<script type="language/javascript" src="./node_modules/officejs.dialogs/dialogs.js">

NOTE: This assumes your page is in the root of your project. The key point is that it is added to your node_modules when you use NPM and this is how you will reference it.

Once referenced you can then call a MessageBox like this:

MessageBox.Reset();
MessageBox.Show("This is a test with a lot of text that simply has not point but to show you what " +
                "happens when you place a lot of text in a MessageBox for any point other than to " +
                "place a whole lot of text in the MessageBox.",
                "This is the Caption",
                MessageBoxButtons.AbortRetryCancel,
                MessageBoxIcons.Stop,
                true, "Check this if you want me to stop nagging you.",
                function(buttonPressed, checked) {
                  console.log("The button pressed was: " + buttonPressed);
                  console.log("The checkbox was checked? " + checked);
                });

Here is what this will look like:

msg

You can call up an InputBox like this:

InputBox.Reset();
InputBox.Show("What value do you want to enter?",
              "InputBox caption",
              "Default value", function(result) {
                var msg = "";
                if(result.length == 0) {
                  msg = "The user pressed cancel.";
                } else {
                  msg = "The user entered: " + result;
                }
                console.log(msg);
              });

Here is what the above code looks like:

input

You can show a custom form of your own design like this:

  Form.Reset();
  Form.Show("/test.html",10,20,false,function(result){
    var yourJSON = JSON.parse(result).Result;
    // if you placed false in the 4th param you must
    // handle the close of the form
    Form.DialogClose();
  });

Here is an example of what the above code looks like:

form-ex

It is important to note that like everything else in the OfficeJS world this is an async dialog. This also means it is non-blocking. This means any code you do not have in your callback method will continue to run. And if you are wanting to display multiple message boxes at the same time – you cannot. The last one you try to display wins, the others will be gone. Most everything in this dialog is just like you will remember from the good ol’ Visual Basic/C# MessageBox and InputBox. Even the constants for MessageBoxButtons and MessageBoxIcons are the same. But, I added a little flare and it probably helps with the best practices in OfficeJS to not nag the user with dialogs, and that is the ability to add a check box to the form so you can ask the user if they do not want to be bothered by your code anymore.

For the MessageBox, the withcheckbox and checkboxtext are there to give you that ability. Additionally, you see the callback method (asyncResult) that will return once the use dismissed the dialog. It will return with two pieces of information:

  • The button the user clicked in string format. So “Yes” or “Cancel” will be what you see here.
  • A Boolean representing whether the check box was checked or not.

For the InputBox, the callback method (asyncResult), will return one piece of information. If will return the text the user entered, or it will return nothing (an empty string), if the user pressed cancel.

The Form method will return a JSON object:

Error: { },                  // Error object
Result: { },                 // JSON from form
Cancelled: false       // boolean if formed cancelled with X

The Result object will be the JSON from your own form. In your code you will need to call back to the parent like this:

Office.initialize = function(reason) {
    $(document).ready(function () {
        $("#okButton").click(function() {
            // notify the parent
            Office.context.ui.messageParent("My custom message.");
        });
    });
};

You will also see in the examples above, I call .Reset() before I issue a new dialog request. This is because the objects are global and this is a way to be certain to clean up anything in memory that might be associated with a previous dialog. In my testing, I never really had problems like this, but I added it as an extra precaution.

Also, note, I have only tested this in Outlook OWA, I have not had a chance to test it in Excel, Word, PowerPoint or even in the full Outlook client. So, if you encounter issues in those other clients, please let me know.

Finally, I want to call out the OfficeJS Helpers. This library provides a lot of help with authorization dialogs, but also has a simple method for displaying messages using OfficeHelpers.ui.notify(). You can install it into your project using NPM:

npm install –save @microsoft/office-js-helpers

Node Package Manager

As I delve ever deeper into the world of Node, I have found the ability to install packages with NPM quite handy. I do this from VS Code using the Terminal window. I just type:

npm install <packagename>

As I have been developing different packages for my customers, I have found the need to install code that I have been reusing over and over again. Most importantly, easyEws. So, I created a npmjs account (davecra) and I published easyEws. But what is even better and what I was after, is I can now install the latest version of easyEws by going into the Terminal window in VS Code and typing this:

npm install easyEws

 

Testing Office.JS: The Power of Choice

github

You know, sometimes the way you want to do things is not necessarily the right way. And even then the right way makes life so hard you want to cry. With Office.JS I recently blogged about “a way” you can test multiple sprints of your add-in in your development environment: Testing a Web Add-in with Multiple Manifest. In this article I explain a method of creating multiple manifests to point to each “version or sprint” of your add-in. Recently, I found that one of my customers threw a wrinkle into that idea. Not that creating multiple manifests was impossible, but they were needing two conflating factors:

  1. Each sprint points to a different folder of HTML/JS.
  2. And they needed to use query string parameters (like this post) to load one of maybe 20 different configuration settings.

With up to 20 configurations per sprint and lets say 4 sprints, you end up with 80 manifest files. So, this is untenable. You need more choices, then enter necessity… wlEmoticon-hotsmile.png

So, lets say you need to test multiple configurations and multiple sprints. Here is how you will set this up:

  • You will publish each sprint of your Web Site Project  into a different folder in IIS. Like this:
folder
Different sprints in separate folders
  • Then, you will create a new HTML page. I called mine Launcher.html. This page will contain a submit button and two drop-down select controls:
    • One for sprints
    • One for configs
  • Next, you will modify your manifest. You can add a new button called “Sprint Testing” for example and have it launch: Launcher.html.
  • In the Launcher.JS, populate those dropdowns. You can populate these hard coded into the HTML, from your client side JS or call your service controller. In my example, I call my service controller.
  • When the user clicks the submit button after choosing the desired settings, you will build the URL to the proper sprint folder and then append the config values to the current query string (that last item is critical so that your sprint Compose/Read page will get the important Office related query string values passed to it). You will see I do this below by replacing my “launcher.html”, replacing it will the new path, then append the config setting:
url = window.location.href.replace("launcher.html", sprint + "/ComposeMessage.html");
url += "&Config=" + config;
  • Once done your code will perform a location.replace() on the url and you will have your proper sprint and configuration loaded. You will only have a single manifest file and you will be able to test/regress test as much as needed with different sprints and configurations.

Here is what the HTML page I created looks like when loaded:

launcherpane

 

Here is the HTML:

<!DOCTYPE html>
<html>
<head>
    <title>Select Which Sprint to Launch</title>
    <meta charset="utf-8" />
    <script src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js" type="text/javascript"></script>
    <script src="Scripts/jquery-3.1.1.js" type="text/javascript"></script>
    <script src="launcher.js" type="text/javascript"></script>
</head>
<body>
<h3>Please select the sprint you wish to launch:</h3>
<select id="selectSprintList">
</select>
<h3>Please select the configuration:</h3>
<select id="selectConfigList">
</select>
    <button id="launchSprintButton">Submit</button>
</body>
</html>

Here is the code behind for the page:

/// <reference path="Scripts/jquery-3.1.1.js" />
(function () {
    'use strict';

    Office.initialize = function (reason) {
        $(document).ready(function (reason) {
            /// Upon load connect to the server and request
            /// the sprints so that we can fill the select
            /// field on the form.
            makeAjaxCall("GetSprints", null, function (response) {
                /** @type {String} */
                var data = response.Message.toString();
                /** @type {String[]} */
                var results = data.split(",");
                $.each(results, function (index, value) {
                    $("#selectSprintList").append("
<option>" + value + "</option>");
                })
            }, function (error) {
                $(document).append("

" + error + "

");
            });

            makeAjaxCall("GetConfigs", null, function (response) {
                /** @type {String} */
                var data = response.Message.toString();
                /** @type {String[]} */
                var results = data.split(",");
                $.each(results, function (index, value) {
                    $("#selectConfigList").append("
<option>" + value + "</option>");
                })
            });

            $("#launchSprintButton").click(function (event) {
                /// The user clicked the submit button. Build the URL
                /// from the selection in the select control and
                /// change the location
                var sprint = $("#selectSprintList").val();
                var config = $("#selectConfigList").val();
                var url = "";
                if (sprint != null && sprint != "" &&
                    config != null && sprint != "") {
                    // replace the launcher page with the proper sprint folder location,
                    // but be sure to keep all the query string values - to pass on
                    // to our OfficeJS page. However, add our one config setting to the end
                    url = window.location.href.replace("launcher.html", sprint + "/ComposeMessage.html");
                    url += "&Config=" + config;
                    // replace the url
                    location.replace(url);
                }
            });

        });
    }
})();

// Helper function to call the web service controller
makeAjaxCall = function (command, params, callback, error) {
    var dataToPassToService = {
        Command: command,
        Params: params
    };
    $.ajax({
        url: 'api/Default',
        type: 'POST',
        data: JSON.stringify(dataToPassToService),
        contentType: 'application/json;charset=utf-8',
        headers: { 'Access-Control-Allow-Origin': '*' },
        crossDomain: true
    }).done(function (data) {
        callback(data);
    }).fail(function (status) {
        error(status.statusText);
    })
};

And here is the code for the controller, now, I cheated and just returned a string value for each call, but this at least help you get the idea of how to build your own list for sprints and configs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;

namespace OutlookLauncherDemoWeb.Controllers
{
    public class DefaultController : ApiController
    {
        ///
<summary>
        /// Service Request - incoming
        /// </summary>

        public class WebServiceRequest
        {
            public string Command { get; set; }
            public string[] Params { get; set; }
        }

        ///
<summary>
        /// Service Response - outgoing
        /// </summary>

        public class WebServiceResponse
        {
            public string Status { get; set; }
            public string Message { get; set; }
        }

        [HttpPost()]
        public WebServiceResponse Values(WebServiceRequest request)
        {
            WebServiceResponse response = null;
            switch (request.Command)
            {
                case "GetSprints":
                    return getSprints();
                case "GetConfigs":
                    return getConfigs();
            }

            response = new WebServiceResponse();
            response.Message = "Unknown command";
            return response;
        }

        private WebServiceResponse getConfigs()
        {
            WebServiceResponse response = new WebServiceResponse();
            response.Message = "Config1,Config2,Config3,Config4,Config5,Config6,Config7,Config8";
            return response;
        }

        private WebServiceResponse getSprints()
        {
            WebServiceResponse response = new WebServiceResponse();
            response.Message = "sprint1,sprint2,sprint3,sprint4";
            return response;
        }
    }
}

The idea here is that this add-in is a baseline for yours. You can build these components into a debug build of your add-in and it will call into each separate folder/sprint where you have posted previous versions.

I have published the full source up on GitHub: https://github.com/davecra/OutlookLauncherDemo