OfficeHelpers

This is a separate GitHub project
github.com/OfficeDev/office-js-helpers

<link rel="stylesheet" 
      href="https://unpkg.com/@microsoft/office-js-helpers@1.0.2/dist/office.helpers.min.js">

Utilities.log

Makes the errors more readable
If multiple parameters are sent then it just logs them instead.

  static log(exception: Error | CustomError | string, extras?: any, ...args) { 
    if (!(extras == null)) {
      return console.log(exception, extras, ...args);
    }
    if (exception == null) {
      console.error(exception);
    }
    else if (typeof exception === 'string') {
      console.error(exception);
    }
    else {
      console.group(`${exception.name}: ${exception.message}`);
      {
        let innerException = exception;
        if (exception instanceof CustomError) {
          innerException = exception.innerError;
        }
        if ((window as any).OfficeExtension && innerException instanceof OfficeExtension.Error) {
          console.groupCollapsed('Debug Info');
          console.error(innerException.debugInfo);
          console.groupEnd();
        }
        {
          console.groupCollapsed('Stack Trace');
          console.error(exception.stack);
          console.groupEnd();
        }
        {
          console.groupCollapsed('Inner Error');
          console.error(innerException);
          console.groupEnd();
        }
      }
      console.groupEnd();
    }
  }
}


UI.notify

Shows a notification at the top of the page

import { Utilities, PlatformType } from '../helpers/utilities';  
import { stringify } from '../util/stringify';
import html from './message-banner.html';

const DEFAULT_WHITESPACE = 2;

export interface IMessageBannerParams {
  title?: string;
  message?: string;
  type: 'default' | 'success' | 'error' | 'warning' | 'severe-warning';
  details?: string;
}

export class UI {

  static notify(message: string);
  static notify(message: string,
                title: string);
  static notify(message: string,
                title: string,
                type: 'default' | 'success' | 'error' | 'warning' | 'severe-warning');
  static notify(error: Error);
  static notify(error: Error,
                title: string);
  static notify(message: any);
  static notify(message: any,
                title: string);
  static notify(message: any,
                title: string,
                type: 'default' | 'success' | 'error' | 'warning' | 'severe-warning');


  static notify(...args: any[]) {
    const params = _parseNotificationParams(args);

    if (params == null) {
      console.error(new Error('Invalid params. Cannot create a notification'));
      return null;
    }

    const messageBarClasses = {
'success': 'ms-MessageBar--success',
'error': 'ms-MessageBar--error',
'warning': 'ms-MessageBar--warning',
'severe-warning': 'ms-MessageBar--severeWarning'
    };

    const messageBarTypeClass = messageBarClasses[params.type] || '';

    let paddingForPersonalityMenu = '0';
    if (Utilities.platform === PlatformType.PC) {
      paddingForPersonalityMenu = '20px';
    }
    else if (Utilities.platform === PlatformType.MAC) {
      paddingForPersonalityMenu = '40px';
    }

    const messageBannerHtml =
      html.replace('@@CLASS', messageBarTypeClass).replace('\'@@PADDING\'', paddingForPersonalityMenu);

    const existingNotifications = document.getElementsByClassName('office-js-helpers-notification');
    while (existingNotifications[0]) {
      existingNotifications[0].parentNode.removeChild(existingNotifications[0]);
    }

    document.body.insertAdjacentHTML('afterbegin', messageBannerHtml);

    const notificationDiv = document.getElementsByClassName('office-js-helpers-notification')[0];
    const messageTextArea = document.createElement('div');
    notificationDiv.insertAdjacentElement('beforeend', messageTextArea);

    if (params.title) {
      const titleDiv = document.createElement('div');
      titleDiv.textContent = params.title;
      titleDiv.classList.add('ms-fontWeight-semibold');
      messageTextArea.insertAdjacentElement('beforeend', titleDiv);
    }

    params.message.split('\n').forEach(text => {
      const div = document.createElement('div');
      div.textContent = text;
      messageTextArea.insertAdjacentElement('beforeend', div);
    });

    if (params.details) {
      const labelDiv = document.createElement('div');
      messageTextArea.insertAdjacentElement('beforeend', labelDiv);
      const label = document.createElement('a');
      label.setAttribute('href', 'javascript:void(0)');
      label.onclick = () => {
        (document.querySelector('.office-js-helpers-notification pre') as HTMLPreElement)
          .parentElement.style.display = 'block';
        labelDiv.style.display = 'none';
      };
      label.textContent = 'Details';
      labelDiv.insertAdjacentElement('beforeend', label);

      const preDiv = document.createElement('div');
      preDiv.style.display = 'none';
      messageTextArea.insertAdjacentElement('beforeend', preDiv);
      const detailsDiv = document.createElement('pre');
      detailsDiv.textContent = params.details;
      preDiv.insertAdjacentElement('beforeend', detailsDiv);
    }

    (document.querySelector('.office-js-helpers-notification > button') as HTMLButtonElement)
      .onclick = () => notificationDiv.parentNode.removeChild(notificationDiv);
  }
}

ExcelUtilities.forceCreateSheet


static async forceCreateSheet( 
    workbook: Excel.Workbook,
    sheetName: string,
    clearOnly?: boolean
  ): Promise<Excel.Worksheet> {
    if (workbook == null && typeof workbook !== typeof Excel.Workbook) {
      throw new APIError('Invalid workbook parameter.');
    }

    if (sheetName == null || sheetName.trim() === '') {
      throw new APIError('Sheet name cannot be blank.');
    }

    if (sheetName.length > 31) {
      throw new APIError('Sheet name cannot be greater than 31 characters.');
    }

    let sheet: Excel.Worksheet;
    if (clearOnly) {
      sheet = await createOrClear(workbook.context as any, workbook, sheetName);
    }
    else {
      sheet = await recreateFromScratch(workbook.context as any, workbook, sheetName);
    }

    // To work around an issue with Office Online (tracked by the API team), it is
    // currently necessary to do a `context.sync()` before any call to `sheet.activate()`.
    // So to be safe, in case the caller of this helper method decides to immediately
    // turn around and call `sheet.activate()`, call `sync` before returning the sheet.
    await workbook.context.sync();

    return sheet;
  }
}

ExcelUtilities.createOrClear

Called from the forceCreateSheet method
Clears a worksheet or adds a new one

async function createOrClear( 
  context: Excel.RequestContext,
  workbook: Excel.Workbook,
  sheetName: string
): Promise<Excel.Worksheet> {
  if (Office.context.requirements.isSetSupported('ExcelApi', 1.4)) {
    const existingSheet = context.workbook.worksheets.getItemOrNullObject(sheetName);
    await context.sync();

    if (existingSheet.isNullObject) {
      return context.workbook.worksheets.add(sheetName);
    }
    else {
      existingSheet.getRange().clear();
      return existingSheet;
    }
  }
  else {
    // Flush anything already in the queue, so as to scope the error handling logic below.
    await context.sync();

    try {
      const oldSheet = workbook.worksheets.getItem(sheetName);
      oldSheet.getRange().clear();
      await context.sync();
      return oldSheet;
    }
    catch (error) {
      if (error instanceof OfficeExtension.Error && error.code === Excel.ErrorCodes.itemNotFound) {
        // This is an expected case where the sheet didn't exist. Create it now.
        return workbook.worksheets.add(sheetName);
      }
      else {
        throw new APIError('Unexpected error while trying to delete sheet.', error);
      }
    }
  }
}

ExcelUtilities.recreateFromScratch

Called from the forceCreateSheet method
Adds a new worksheet then removes the old and then renames the new worksheet
This code has an assumption that the sheet you want to replace is not the last worksheet in the workbook
Excel workbooks must always have at least one visible worksheet

async function recreateFromScratch( 
  context: Excel.RequestContext,
  workbook: Excel.Workbook,
  sheetName: string
): Promise<Excel.Worksheet> {
  const newSheet = workbook.worksheets.add();

  if (Office.context.requirements.isSetSupported('ExcelApi', 1.4)) {
    context.workbook.worksheets.getItemOrNullObject(sheetName).delete();
  }
  else {
    // Flush anything already in the queue, so as to scope the error handling logic below.
    await context.sync();

    try {
      const oldSheet = workbook.worksheets.getItem(sheetName);
      oldSheet.delete();
      await context.sync();
    }
    catch (error) {
      if (error instanceof OfficeExtension.Error && error.code === Excel.ErrorCodes.itemNotFound) {
        // This is an expected case where the sheet didn't exist. Hence no-op.
      }
      else {
        throw new APIError('Unexpected error while trying to delete sheet.', error);
      }
    }
  }

  newSheet.name = sheetName;
  return newSheet;
}

Errors - API Error

Custom error type to handle API specific errors.

import { CustomError } from './custom.error';  

export class APIError extends CustomError {
  constructor(message: string, public innerError?: Error) {
    super('APIError', message, innerError);
  }
}

Errors - CustomError

export abstract class CustomError extends Error { 
  constructor(public name: string, public message: string, public innerError?: Error) {
    super(message);
    if ((Error as any).captureStackTrace) {
      (Error as any).captureStackTrace(this, this.constructor);
    }
    else {
      let error = new Error();
      if (error.stack) {
        let last_part = error.stack.match(/[^\s]+$/);
        this.stack = `${this.name} at ${last_part}`;
      }
    }
  }
}

Dictionary class


Dialog - DialogError class


Dialog - IDialogSize


Dialog - Dialog class



© 2019 Better Solutions Limited. All Rights Reserved. © 2019 Better Solutions Limited TopPrevNext