Office Add-Ins: Working with Tables in Word. Part 4: Getting a table in a Content Control

In this article we will see how to use a ContentControl to quickly and accurately access a table in a Word document.

Introduction

In previous articles we have seen how to get a Table using a search or cycling through allTables in the document. While these are two perfectly valid methods they come with some potential issues and considerations for architecting the Word document itself. In this article we will see how to control access to the right table at the right time without the concerns or issues from the previous article.

Content Controls

Rich Text Content controls are the only content controls supported in Office.js (as of version 1.3). The documentation for the Content control API documentation shows us that we can get a content control via a “tag” which is assigned to the Content Control in Developer Mode within the word document.

Create a Content Control on your word document and give it a Tag (Birds in this case)

In normal mode we can then insert a table

Using the following code we can access the content control, get the first (only) table inside there and manipulate it by adding a new row.

async function runContent() {
    await Word.run(async (context) => {

        // Create a proxy object for the content controls collection that contains a specific tag.
        var contentBirds = context.document.contentControls.getByTag('Birds');
        // Queue a command to load the tag property for all of content controls. 
        context.load(contentBirds, 'tag');
        await context.sync()

        // Queue a commmand to load the results.
        const tableCollection = contentBirds.items[0].tables;
        context.load(tableCollection);
        await context.sync()
        var theTable = tableCollection.items[0];
        context.load(theTable, '');
        await context.sync();
        let numRows = theTable.rowCount.toString()
        theTable.addRows("End", 1, [[numRows, "Phoenix"]])
        
    });
}

The inserted row can be seen, and in a similar manner to the previous article we can log the amount of time taken to execute the function. In this case it is slightly slower (100ms) than searching (80ms) (there is more context loading) but it is consistent and does not come with any of the disadvantages of having to search for a specific term in a table.

Note

The obvious downside to this method is if a user of the word document takes the table out of the content control (either on purpose or by mistake). Checks should be put int he code to determine that if the table cannot be found then call the elgant error handling.

Conclusion

This is the most reliable manner for accessing a table quickly and accurately within a Word document, but does come with a small amount of additional overhead and the extra construct of having to have a Content Control within the Word document.

Office Add-Ins: Working with Tables in Word. Part 3: Two methods for getting existing tables

In this article I will demonstrate a method for getting to a Table using the prescribed methods in the API context.body.

Background

In previous articles we jhave seen how to create a table from scratch using the API or using OOXML. In this article we will look at how to reference an existing table and get a handle on it. We are going to look at two methods, one direct and one indirect, both have advantages and disadvantages.

Getting a table via the API directly

Within the the API model you can access all tables through the context.document.body.tables

The Methods (at leave in v1.3) are rather limiting and you cannot get a Table directly by name or id.

        const tableCollection = context.document.body.tables;

and from there you can reference each table individually through the index.

            theTable = tableCollection.items[i]

What this does not however facilitate is getting the table you want, directly. Let’s say I want to get the Insects table from the image below – how do I get that?

Well if you KNOW it is the nth table in the document then you are fine – but the chances are you don’t or at best can’t be sure.

Testing each table

What you can do is cycle through all the tables in the collection and test the first cell. I have specifically created these tables in such a manner that the first cell in the table is testable. Cycling through all the tables allows me to do a simple test of the first cell and if it is Insects then I know I am in the right table.

In this example we get all tables and then once we have the Insects table I insert a row:

async function runAllTables() {
    await Word.run(async (context) => {

        const tableCollection = context.document.body.tables;
        // Queue a commmand to load the results.
        context.load(tableCollection);
        await context.sync()
        //cycle through the tbale collection and test the first cell of each table looking for insects
        for (var i = 0; i < tableCollection.items.length; i++) {
            var theTable = null;
            theTable = tableCollection.items[i];
            var cell1 = theTable.values[0][0];
            if (cell1 == "Insects") {
                //once found, load the table in memory and add a row
                context.load(theTable, '');
                await context.sync();
                let numRows = theTable.rowCount.toString()
                theTable.addRows("End", 1, [[numRows, "Lightning Bug"]])
            }
        }
    });
}

As the number of tables in the document increase this gets slower and slower (understandably). There is also a limitation on the fact that you might not be able to identify the Table with a value in the first cell as easily (depends on requirements). As you can see from this example in the console.log I added the length of time to complete the addRow and it is 77ms on three tables. If I copy and past the first two tables and make a lot more of them you can see the speed gets slower (124ms) which might not seem like a lot, but in a large document with lots of large tables it can become significant to the end user.

Searching for a word in the document, and then getting the table parent

If you are able to identify a string in your table, you can search for that string and then get the parentTable. That method for finding your table is much more efficient and faster than getting all tables in a collection as per the previous method.

In this example we search for Insects, get the parentTable and then simply insert a row with a new value

async function runSearch() {
    await Word.run(async (context) => {
        const body = context.document.body 
        const insectRangeCollection = body.search("Insects");
        // Queue a commmand to load the results.
        context.load(insectRangeCollection);
        await context.sync()
        //get the parent table from the search result range
        const table1 = insectRangeCollection.getFirst().parentTable
        context.load(table1, '');
        await context.sync()
        let numRows2 = table1.rowCount.toString()
        table1.addRows("End", 1, [[numRows2, "Butterfly"]])
    });
}

As with the previous example – if I copy and paste the first two tables and make a lot more of them, you will see that this search and add row process remains approx the same speed as before.

 

Conclusion

While neither of these methods are ideal, if you know what is possible before you start to create your solution you can work in your options. Not having an obvious getTableById method is somewhat limiting.

Office Add-Ins: Working with Tables in Word. Part 2: Creation from OOXML

In this long overdue article I will discuss how to create a custom table, get the OOXML from it, clean it and then be able to insert the table anywhere into a Word document.

Background

In the previous article we looked at how to create a table from  scratch using range.insert table which is nice but limiting in that the formatting of the created table leaves a lot to be desired. It would be much easier to create the table we want first, get the OOXML and then be able to re-insert it pre-formmated so to speak.

Getting the OOXML of a table

One of the examples posted in the Add-in API documention uses the range.getOOXML to show how to extract the OOXML from a highlighted range in a word document. Using the Add-in Script Lab we can easily simulate this in a word document. I took the basic API call example and inserted the code from the API documentation.

 

  1. I created a custom table with hashed borders (formatting) and highlighted it
  2. Click the run code button to execute the sample code
  3. The OOXML was generated in the console below

Inserting OOXML

Using this process in reverse you can then copy the OOXML and using the range.insertOOXML method you can click anywhere in a word document and click a button to insert the preformatted table.

 

Conclusion

While we can format tables manually with code – as we get more complicated with out tables this may not be an option. Inserting as OOXML will guarantee the formatting we want and then allow us to manipulate as a table as we will see next

Create custom functions in Excel

As of MS Build 2018 custom functions in Excel are available for all developers to use and implement.

https://dev.office.com/blogs/azure-machine-learning-javascript-custom-functions-and-power-bi-custom-visuals-further-expand-developers-capabilities-with-excel

For more information on how to create a custom function in Excel using the JavaScript Add-In model check out this link.

https://aka.ms/customfunctions

“Custom functions (similar to user-defined functions, or UDFs), enable developers to add any JavaScript function to Excel using an add-in. Users can then access custom functions like any other native function in Excel (such as =SUM()).”

This is actually a huge deal because one of the limitations of the Add-In model in the past is this ability to create a custom function without having to add it to the tool bar as an icon which has to be clicked. With this release it means that develoipers can create their own functions in excel in an analogous manner to VBA. The simple example below adds the two numbers and for no good reason adds 42 as well. But you get the idea.

custom functions

 

 

 

Outlook Add-ins community call

Back in March the Outlook Add-In development community had it’s quarterly meeting. The notes from the meeting were blogged and released here: https://dev.office.com/blogs/outlook-community-call-march-21-2018.

These calls are really helpful to spread the new on the progress being made in the Outlook Add-In area. Some really interesting points of notes at this call were the upcoming preview requirements for API 1.6

https://dev.office.com/reference/add-ins/outlook/preview/index?product=outlook&version=preview

I have a particular selfish interest in the “Office.context.auth.getAccessTokenAsync – Added access to getAccessTokenAsync, which allows add-ins to get an access token for the Microsoft Graph API.”

I think I am going to play with that 🙂

Next call will be at 8:00AM PST on June 20, 2018. https://aka.ms/outlookcommunitycall

Office Add-Ins: Working with Tables in Word. Part 1: Creation

In this article I will show how the Word JavaScript API can be utilized to add tables to your word document using an Office Add-In. This will be a multi part blog post as there are a lot of nuances and interesting ways in which you can play with tables in Word.

Introduction

In previous articles I have written about how to interact with a Word document to search and replace and even save the word document to Salesforce. Going back to a more basic level we are going to look at how to build tables in word.

We will be using the new Script Lab  which is a new Playground Add-In which can be used for development and general tinkering with Add-Ins. This and other articles on the topic are for demonstration purposes and are not hardened for production use.

The reference

For more information on Tables and how what methods/properties are available check out the documentation page – Table Object (JavaScript API for Word)

Creating a table 

At the most basic level a table is created by instantiating the table object and adding values to it.

insertTable(rowCount: number, columnCount: number, insertLocation: string, values: string[][])

This can be done from a number of different parents:

  • The body
  • A range
  • A contentControl
  • A paragraph

The method requires the following:

  • rowCount – a number
  • columnCount – a number
  • insertLocation – Depends on parent – either (body: Start/End/Replace) or (range: Before/After)
  • values: 2 dimentional Array – [[“This is”, “a table”], [“this is”, “a new row”]]

Using these parameters we can create a table from within the Script lab using the following code:

$("#run").click(run);

async function run() {
    try {
        await Word.run(async (context) => {

            var body = context.document.body;
            var range = context.document.getSelection();
            var myArray = [["a", "b"], ["c", "d"]];
            var table = range.insertTable(2, 2, "Before", myArray);            

            // Synchronize the document state by executing the queued commands,
            // and return a promise to indicate task completion.
                await context.sync().then(function () {
                    console.log('Table added before the start of the range.');
                });;
        });
    }
    catch (error) {
        OfficeHelpers.UI.notify(error);
        OfficeHelpers.Utilities.log(error);
    }
}

 

 

Conclusion

Using the Script Lab we have seen how we can easily insert a table into a Word document using the Office Add-In API. In future articles we will look at looking for the table we want to modify and then manipulating tables.

 

 

19 Jan – Speaking at Northwest Chicago JavaScript meetup – Office.js

We have been hosting the NWCJS meetup here at PSC Group for the past few months. We have had some great speakers talking about React Native, How Chrome works under the covers, ES6, Web Components and others. This time it is my turn to speak.

I am going to be talking about Office.js the framework library which is the underpinning of Office Add-Ins. I will be talking about how the framework works, how it’s intended to be used and present lots of examples of how you can use JavaScript alone to automate functionality within the Office suite of products (Word, Excel, Outlook etc). The Office.js library is intended to replace the main functionality of VBA but in a way that works not only on Windows machines, but also on the web and on Mac clients.

For more information on time and place check it out 🙂

Office.js

Thursday, Jan 19, 2017, 6:00 PM

PSC Group, LLC
1051 Perimeter Drive Suite 500 Schaumburg, IL

21 Javascripters Attending

Office.js – Using JavaScript to build functionality into Microsoft Office by Marky RodenMarky Roden is Principal Architect for the PSC Group LLC and Head of the PSC Labs emerging technologies group, tasked with using cutting edge technologies to transform solutions offerings for customers.**************Schedule 6:00 – begin arriving and sociali…

Check out this Meetup →

Building Office Add-ins using Office.js – the book, the website

I wanted to show some love for the new website and book by Michael Zlatkovsky. Michael is a Software Developer and Program Manager in the Microsoft Office Extensibility team. I met Michael at the MS MVP Summit back in October and we had a great conversation or two about Office Addins and their future development.

So Michael’s new leanpub book is called Building Office Add-Ins using Office.js and can be purchased through leanpub. The new supporting website http://buildingofficeaddins.com/ provides some of the key information from the book and gives a nice overview thereof while introducing the reader to the more technical aspects of using Office.js.

Michael’s a great guy and this is a very valuable resource for Office Add-In developers. I heartily recommend it.

Saving a word document directly from an Office Add-In into Salesforce.com

In this article I will demonstrate and discuss how to programmatically save a Word document directly into Salesforce.

Introduction

In previous articles we have looked at how to programmatically interact with a Word document from within an Office Add-In. In this case we will use the Office JavaScript API to access the file as a binary string and then convert it into a format compatible with Salesforce.com.

Turning your word document into a binary

This article in the Office dev center details how to Get the whole document from an add-in for PowerPoint or Word. We will use the bulk of that article up to the point where we send the slice. We are going to look at a modified sendSlice() function. The dev.office.com article details how you can access the binary contents of the word document, but falls short when it comes to getting the base64 encoded version of the file to be submitted to an external site. This is the complicated part !

Getting the right format for Salesforce

Unfortunately the salesforce documentation is not based around using JavaScript to access the REST APIs. It is based around using curl commands in UNIX. The sample page for Inserting or updating Blob Data explains how to upload a multipart message which includes both the meta-data values and the binary data for the file. For the record they also neglect to provide any guidance on how to get the binary of the file….!

Using the code posted on this stackoverflow post  we are able to turn a binary string into the necessary format to send the file to salesforce.

The final sendSlice() function is shown below. The significant thing to note is that the initial file must be sent to the ContentVersion sobject. Because this is a new document a parentId is not necessary and a ContentDocumentId is created. In a future article we will look at how to then associate this new file attachment is an existing object.

function sendSlice(slice, state) {
  updateStatus("In sendSlice");
  var data = slice.data;

  // If the slice contains data, create an HTTP request.
  if (data) {

    // Encode the slice data, a byte array, as a Base64 string.
    // NOTE: The implementation of myEncodeBase64(input) function isn't
    // included with this example. For information about Base64 encoding with
    // JavaScript, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding.

    var u8 = new Uint8Array(data);
    var b64encoded = btoa(String.fromCharCode.apply(null, u8));

    var objectData = {
      "Description": "Demo Upload Directly from Word",
      "Title": "sample.docx",
      "ReasonForChange": "Initial Upload",
      "PathOnClient": "sample.docx"
    }

    var boundary = 'boundary_string_' + Date.now().toString();
    var attachmentContentType = 'application/octet-stream';

    // Serialize the object, excluding the body, which will be placed in the second partition of the multipart/form-data request
    var serializedObject = JSON.stringify(objectData);

    var requestBodyBeginning = '--' + boundary
        + '\r\n'
        + 'Content-Disposition: form-data; name="entity_attachment";'
        + '\r\n'
        + 'Content-Type: application/json'
        + '\r\n\r\n'
        + serializedObject
        + '\r\n\r\n' +
        '--' + boundary
        + '\r\n'
        + 'Content-Type: ' + attachmentContentType
        + '\r\n'
        + 'Content-Disposition: form-data; name="VersionData"; filename="filler"'
        + '\r\n\r\n';

    var requestBodyEnd =
        '\r\n\r\n'
        + '--' + boundary + '--';

    // The atob function will decode a base64-encoded string into a new string with a character for each byte of the binary data.
    var byteCharacters = window.atob(b64encoded);

    // Each character's code point (charCode) will be the value of the byte.
    // We can create an array of byte values by applying .charCodeAt for each character in the string.
    var byteNumbers = new Array(byteCharacters.length);

    for (var i = 0; i < byteCharacters.length; i++) {
      byteNumbers[i] = byteCharacters.charCodeAt(i);
    }

    // Convert into a real typed byte array. (Represents an array of 8-bit unsigned integers)
    var byteArray = new Uint8Array(byteNumbers);

    var totalRequestSize = requestBodyBeginning.length + byteArray.byteLength + requestBodyEnd.length;

    var uint8array = new Uint8Array(totalRequestSize);
    var i;

    // Append the beginning of the request
    for (i = 0; i < requestBodyBeginning.length; i++) {
      uint8array[i] = requestBodyBeginning.charCodeAt(i) & 0xff;
    }

    // Append the binary attachment
    for (var j = 0; j < byteArray.byteLength; i++, j++) {
      uint8array[i] = byteArray[j];
    }

    // Append the end of the request
    for (var j = 0; j < requestBodyEnd.length; i++, j++) {
      uint8array[i] = requestBodyEnd.charCodeAt(j) & 0xff;
    }

    //Create the new file in Salesforce
    var url = "https://yourdomain.my.salesforce.com/services/data/v37.0/sobjects/ContentVersion/"

    return $.ajax({
      method: "POST",
      url: url,
      processData: false,
      data: uint8array.buffer,
      headers: {
        "Content-Type": 'multipart/form-data' + "; boundary=\"" + boundary + "\"",
        "Authorization": "Bearer " + app.getCookie(app.addinName)
      }
    }).error(function (data) {
      updateStatus("FAIL: " + JSON.stringify(data))
    }).success(function (data) {
      updateStatus("Success creating ContentVersion: " + data.id)
      closeFile(state)
    });

  }
}

 

Conclusion

In this article we have seen an example of how we can extend the functionality of an Office Add-In to access the binary data of a word document and then save it to a cloud provider (in this case Salesforce).

 

How to create Dynamic Workflow functionality in Outlook Add-Ins based on unique keys

In this article I will demonstrate how to use a regular expression to provide dynamic contexual assignment of an Outlook add-in. Using that regular expression we will be able to identify the necessary information to link the Add-In to some external dynamic workflow functionality.

Introduction

During the Collab365 presentation I demonstrated a workflow application which was a simple vacation request approval. In that demonstration I showed how we are able to detect a unique key inside of an email and then use that value to open the workflow within the add in (https://youtu.be/t6kRFV09TYs?t=1950).

In previous articles I have demonstrated how to create a contextual add-in and in this article we will look at how we can take that up a notch and make the context dynamic and useful.

I am going to demonstrate the capability in the context of a napacloud add-in so that you are able to work along.

Messing with RegEx

Regular Expressions are a weird an wonderfully powerful capability, generally reserved for people way smarter than me to figure out. Fortunately there are many example sites which can be used to help you figure out how you should build your regular expression.

I used http://scriptular.com/ which allowed me to test my regular expression until I made it work.

We need to create a unique key within out email so that it can be picked up by Outlook. We cannot use an existing URL though because there is already functionality associated with that.

r2

Using regular expressions in an Office Add-in

Inside of the napacloud tooling we are able to play with adding a regular expression to the context of the Add-In.

r1

So I insert my regular expression into the box, save and run. In this case (for demonstration I make the number of characters fixed to be 6)

r3

Test Email

I created a simple test email with a few expressions which COULD match……and when the Add-In runs – only only of the links is highlighted

r4

So now what?

Well what we want to happen is when the user clock on that Workflow Id: Ac9sXP link we want to take them to the right page within our Application (surfaced through the Add-In).

Within the Office.initialize we are able to determine functionality which happens when the user selects the Add-In. I used the same regular expression to match the desired Workflow Id

Office.initialize = function (reason) {
        $(document).ready(function () {
            Office.context.mailbox.item.body.getAsync(
		  "text",
		  { asyncContext:"This is passed to the callback" },
		  function callback(result) {
		    // Do something with the result
			var str = result.value; 
			var res = str.match(/Workflow\sId:\s[0-9|a-z|A-Z]{6}/g).toString();      				
			$("#content-main").html("The RegEx matched is---> "+res)
		  });
        });
    };

When you run the sample in the browser you get this

r5

More advanced

Once you have this string in JavaScript you can do a number of things with it, the two most obvious to me are:

  • Create an iFrame within the application and add the WorkflowID to the Query String
  • If you have an angular application you can then route the user to the correct document using the WorkflowID

Conclusion

In this article I showed how to create a contextual addin using a semi-complex Regular Expression. Then using that exposed value from the email we can surface dynamic functionality for the user.

The Code

I have posted the code from this sample napacloud out on GitHub for anyone who wants to download it an play with it

You can find it here:

https://github.com/markyroden/RegExInOutlookAddIn