Поделиться через


Microsoft Office

Исследование JavaScript API for Office: доступ к данным и события

Стивен Оливер
Эрик Шмидт

Продукты и технологии:

JavaScript API for Office

В статье рассматриваются:

  • доступ к содержимому файла Office;
  • получение и задание выделенных данных;
  • получение всего содержимого файла;
  • получение различных типов данных из приложения Project;
  • события в приложении для Office;
  • основные сценарии в модели событий;
  • выделение на уровне документа и события, вызываемые изменениями в данных;
  • установка событий, связанных с изменениями.

Это вторая в серии статей, посвященных глубокому изучению JavaScript API for Office. В первой статье (msdn.microsoft.com/magazine/jj891051) был дан широкий обзор объектной модели. Эта статья продолжается с того места, где мы остановились в прошлый раз, — с детального рассмотрения того, как обращаться к содержимому файла, и обзора модели событий.

На протяжении всей серии мы часто ссылаемся на справочную документацию по JavaScript API for Office. Вы можете найти официальную документацию, образцы кода и ресурсы сообщества в разделе Apps for Office and SharePoint Developer Center на сайте MSDN (dev.office.com).

Доступ к содержимому файла Office из другого приложения для Office

JavaScript API for Office предоставляет несколько основных способов доступа к данным в файле Office: можно либо получать/задавать текущие выделенные данные, либо получать весь файл. Этот уровень доступа к данным может показаться простым и действительно обе методики несложны в использовании. Однако каждая методика обеспечивает высокую гибкость и адаптацию, что дает массу возможностей вашим приложениям.

Помимо доступа к выделенным данным или ко всему файлу, JavaScript API for Office также обеспечивает связывание с данными или манипулирование собственными XML-фрагментами в документе. Давайте подробнее рассмотрим эти методики работы с контентом Office.

Получение и задание выделенных данных

Как упоминалось ранее, объект Document дает приложению доступ к данным в файле. В случае приложений области задач и контента мы можем получать или задавать выделенный контент в файле Office, используя методы Document.getSelectedDataAsync и Document.setSelectedDataAsync.

Эти два метода позволяют манипулировать несколькими типами форматов данных. Оба принимают параметр coercionType, в котором передается одна из констант из перечисления Office.CoercionType. Параметр coercionType указывает формат данных, в котором нужно получить или установить контент. В зависимости от его значения можно выбирать в качестве данных чистый текст, таблицу, матрицу, HTML или даже «исходный» формат Office Open XML (OOXML). (Заметьте, что на момент публикации этой статьи получение и задание текста в виде HTML или OOXML поддерживается только Word 2013.)

При использовании getSelectedDataAsync и setSelectedDataAsync не всегда обязательно указывать coercionType. По возможности он распознается логически из контекста. Например, если вы передаете строковый литерал в вызове setSelectedDataAsync, то coercionType по умолчанию будет «text». Если бы вы передали те же данные в виде массива массивов, coercionType по умолчанию был бы «matrix».

Мы дадим некоторые примеры того, насколько мощными могут быть эти простые методы, но в основном будем использовать метод setSelectedDataAsync. Начнем с кода, который вставляет простой текст в документ Word:

// Определяем какие-либо данные для вставки в документ
var booksToRead = "Anabasis by Xenophon; \n" +
  "Socrates' Apology by Plato; \n" +
  "The Illiad by Homer.";
// Задаем эти данные как простой текст
Office.context.document.setSelectedDataAsync(
  booksToRead,
  { coercionType: Office.CoercionType.Text },
  function (result) {
    // Обращаемся к результатам при необходимости
});

Результат показан на рис. 1.

Результаты вставки данных как простого текста
Рис. 1. Результаты вставки данных как простого текста

Теперь мы изменим пример так, чтобы вставить текст как матрицу. Матрица — это массив массивов, вставляемых как простой диапазон ячеек (Excel) или простая таблица (Word).

При вставке в Word код включает неформатированную таблицу без шапки и с двумя полями. Каждый элемент в массиве первого уровня представляет строку в конечной таблице, а каждый элемент в подмассиве содержит данные для ячейки в этой строке:

// Определяем матрицу данных для записи в документ
var booksToRead = [["Xenophon", "Anabasis"],
  ["Plato", "Socrates' Apology"],
  ["Homer", "The Illiad"]];
// Задаем эти данные как неформатированную таблицу
Office.context.document.setSelectedDataAsync(
  booksToRead,
  { coercionType: Office.CoercionType.Matrix },
  function (result) {
    // Обращаемся к результатам при необходимости
});

Результат показан на рис. 2.

Результат вставки данных как матрицы
Рис. 2. Результат вставки данных как матрицы

Помимо типа приведения (coercion type) к матрице, можно получать или задавать данные как таблицу, используя объект TableData. Это позволяет немного расширить возможности форматирования конечного результата — в данном конкретном случае можно создать шапку таблицы (header row). Для обращения к этой строке шапки и содержимому объекта TableData используются свойства headers и rows соответственно.

Кроме того, с помощью объекта TableData можно указывать подмножество данных для вставки, используя параметры startRow и startColumn. Это позволяет, например, помещать данные в один столбец существующей таблицы с пятью полями. Мы подробнее рассмотрим параметры startRow и startColumn в следующей статье из этой серии.

Примечание: Если выделением (selection) в документе является таблица, форма выделения должна соответствовать вставляемым данным (если только вы не указываете параметры startRow и startColumn). То есть если вставляемые данные являются таблицей 2 × 2, а выделение в документе представляет собой ячейки 3 × 2 в таблице, этот метод завершится неудачей. То же самое относится к вставке данных как матрице.

Как и тип приведения «матрица», свойства headers и rows возвращают массив массивов, где каждый элемент в первом массиве — строка данных, а каждый элемент в подмассиве содержит одну ячейку данных в таблице, как показано на рис. 3.

Рис. 3. Вставка данных в документ как таблицы

// Определяем некие табличные данные для вставки в документ,
// включая строку заголовка (шапку)
var booksToRead = new Office.TableData();
booksToRead.headers = [["Author", "Title"]];
booksToRead.rows = [["Xenophon", "Anabasis"],
  ["Plato", "Socrates' Apology"],
  ["Homer", "The Illiad"]];
// Присваиваем эти данные документу как таблицу с шапкой
Office.context.document.setSelectedDataAsync(
  booksToRead,
  { coercionType: Office.CoercionType.Table },
  function (result) {
    // Обращаемся к результатам при необходимости
});

Результат выполнения кода с рис. 3. представлен на рис. 4.

Результаты вставки данных как таблицы
Рис. 4. Результаты вставки данных как таблицы

В следующем примере мы вставим те же данные, но на этот раз отформатированные как HTML и воспользуемся приведением Office.CoercionType.HTML. Теперь мы можем включить дополнительное форматирование во вставленные данные, такие как CSS-стили (рис. 5).

Рис. 5. Вставка данных в документ как HTML

// Определяем некие HTML-данные для вставки в документ, включая
// строку заголовка, форматирование текста и CSS-стили
var booksToRead =
  "<table style='font-family:Segoe UI'>" +
    "<thead style='background-color:#283E75;color:white'>" +
      "<tr><th>Authors</th><th>Books</th></tr>" +
    "</thead>" +
    "<tbody>" +
      "<tr><td>Xenophon</td><td><u>Anabasis</u></td></tr>" +
      "<tr><td>Plato</td><td><u>Socrates' Apology</u></td></tr>" +
      "<tr><td>Homer</td><td><u>The Iliad</u></td></tr>" +
    "</tbody>" +
  "</table>";
// Вставляем эти данные в документ как таблицу
// с примененными стилями
Office.context.document.setSelectedDataAsync(
  booksToRead,
  { coercionType: Office.CoercionType.Html },
    function (result) {
    // Обращаемся к результатам при необходимости
});

Результаты выполнения кода с рис. 5 показаны на рис. 6.

Результаты вставки данных как HTML
Рис. 6. Результаты вставки данных как HTML

Наконец, можно вставить текст в документ и как OOXML, что открывает широкие возможности в оформлении данных и позволяет использовать гораздо более сложные типы контента в Word (например, SmartArt или подставляемые картинки [inline pictures]).

Таблица данных, с которой мы работали, представив ее в виде OOXML и сохранив как строковый литерал, показана на рис. 7 (заметьте, что для краткости здесь дана лишь часть этой таблицы).

Рис. 7. Фрагмент OOXML, представляющий таблицу Word, сохраненную как строковый литерал JavaScript

var newTable = "<w:tbl>" +
  "<w:tblPr>" +
    "<w:tblStyle w:val=\"TableGrid\"/>" +
    "<w:tblW w:w=\"0\" w:type=\"auto\"/>" +
    "<w:tblBorders>" +
      "<w:top w:val=\"single\" w:sz=\"4\" w:space=\"0\"" +
        "w:color=\"283E75\"/>" +
      "<w:left w:val=\"single\" w:sz=\"4\" w:space=\"0\"" +
        "w:color=\"283E75\"/>" +
      "<w:bottom w:val=\"single\" w:sz=\"4\" w:space=\"0\"" +
        "w:color=\"283E75\"/>" +
      "<w:right w:val=\"single\" w:sz=\"4\" w:space=\"0\"" +
        "w:color=\"283E75\"/>" +
      "<w:insideH w:val=\"single\" w:sz=\"4\" w:space=\"0\"" +
        "w:color=\"283E75\"/>" +
      "<w:insideV w:val=\"single\" w:sz=\"4\" w:space=\"0\"" +
        "w:color=\"283E75\"/>" +
      "</w:tblBorders>" +
    "<w:tblLook w:val=\"04A0\" w:firstRow=\"1\" w:lastRow=\"0\"" +
      "w:firstColumn=\"1\" w:lastColumn=\"0\""  +
      "w:noHBand=\"0\" w:noVBand=\"1\"/>" +
  "</w:tblPr>" +
  "<w:tblGrid>" +
    "<w:gridCol w:w=\"4675\"/>" +
    "<w:gridCol w:w=\"4675\"/>" +
  "</w:tblGrid>" +
  "<w:tr w:rsidR=\"00431544\" w:rsidTr=\"00620187\">" +
    "<w:tc>" +
      "<w:tcPr>" +
        "<w:tcW w:w=\"4675\" w:type=\"dxa\"/>" +
        "<w:shd w:val=\"clear\" w:color=\"auto\" w:fill=\"283E75\"/>" +
      "</w:tcPr>" +
      "<w:p w:rsidR=\"00431544\" w:rsidRPr=\"00236B94\""  +
        "w:rsidRDefault=\"00431544\" w:rsidP=\"00620187\">" +
        "<w:pPr>" +
          "<w:rPr>" +
            "<w:b/>" +
            "<w:color w:val=\"FEFEFE\"/>" +
          "</w:rPr>" +
        "</w:pPr>" +
        "<w:r w:rsidRPr=\"00236B94\">" +
          "<w:rPr>" +
            "<w:b/>" +
            "<w:color w:val=\"FEFEFE\"/>" +
          "</w:rPr>" +
          "<w:t>Authors</w:t>" +
        "</w:r>" +
      "</w:p>" +
    "</w:tc>" +
    "<w:tc>" +
      "<w:tcPr>" +
        "<w:tcW w:w=\"4675\" w:type=\"dxa\"/>" +
        "<w:shd w:val=\"clear\" w:color=\"auto\" w:fill=\"283E75\"/>" +
      "</w:tcPr>" +
      "<w:p w:rsidR=\"00431544\" w:rsidRPr=\"00236B94\"" +
        "w:rsidRDefault=\"00431544\" w:rsidP=\"00620187\">" +
        "<w:pPr>" +
          "<w:rPr>" +
            "<w:b/>" +
            "<w:color w:val=\"FEFEFE\"/>" +
          "</w:rPr>" +
        "</w:pPr>" +
        "<w:r w:rsidRPr=\"00236B94\">" +
          "<w:rPr>" +
            "<w:b/>" +
            "<w:color w:val=\"FEFEFE\"/>" +
          "</w:rPr>" +
          "<w:t>Books</w:t>" +
        "</w:r>" +
      "</w:p>" +
    "</w:tc>" +
  "</w:tr>" +
  "<w:tr w:rsidR=\"00431544\" w:rsidTr=\"00620187\">" +
    "<w:tc>" +
      "<w:tcPr>" +
        "<w:tcW w:w=\"4675\" w:type=\"dxa\"/>" +
      "</w:tcPr>" +
      "<w:p w:rsidR=\"00431544\" w:rsidRDefault=\"00431544\"" +
        "w:rsidP=\"00620187\">" +
        "<w:r>" +
          "<w:t>Xenophon</w:t>" +
        "</w:r>" +
      "</w:p>" +
    "</w:tc>" +
    "<w:tc>" +
      "<w:tcPr>" +
        "<w:tcW w:w=\"4675\" w:type=\"dxa\"/>" +
      "</w:tcPr>" +
      "<w:p w:rsidR=\"00431544\" w:rsidRDefault=\"00431544\"" +
        "w:rsidP=\"00620187\">" +
        "<w:r>" +
          "<w:t>Anabasis</w:t>" +
        "</w:r>" +
      "</w:p>" +
    "</w:tc>" +
  "</w:tr>" +
// Остальной код опущен для краткости
"</w:tbl>";

Эта методика требует также высокой квалификации в области XML и, в частности, структур, описываемых стандартом OOXML (ECMA-376). При вставке OOXML в документ данные должны храниться в виде строки (HTML-объекты Document вставлять нельзя), которая содержит всю необходимую информацию, в том числе взаимосвязи и связанные части документа в пакете файлового формата (file format package). Таким образом, при вставке в Word более сложного типа контента, использующего OOXML, вы должны манипулировать OOXML-данными в соответствии с правилами и рекомендациями по применению OOXML и Open Packaging Conventions.

На рис. 8 мы обошли эту проблему, сначала получив данные как OOXML, затем сцепив наши данные с этим OOXML из документа (обрабатывая полученные и новые данные как строки), а потом вставив OOXML обратно в документ. (Признаемся, что отчасти этот код работает потому, что мы не добавляли никакого контента, требующего введения или удаления каких-либо зависимостей или частей документа в файле.)

Рис. 8. Вставка данных в документ как таблицы с использованием OOXML

// Получаем OOXML для данных в точке вставки и добавляем
// таблицу в начало выделения
Office.context.document.getSelectedDataAsync(
  Office.CoercionType.Ooxml,
  {
    valueFormat: Office.ValueFormat.Formatted,
    filterType: Office.FilterType.All
  },
  function (result) {
    if (result.status == "succeeded") {
      // Получаем OOXML, возвращаемый getSelectedDataAsync
      var selectedData = result.value.toString();
    // Определяем новую таблицу в OOXML
      var newTable = "<!--Details omitted for brevity.-->";
           // Находим тег '<w:body>' в возвращенных данных;
      // этот тег представляет тело контента в выделении,
      // содержащемся в основной части пакета документа
      // (/word/document.xml), а затем вставляем новую таблицу
      // в OOXML в этой точке
      var newString = selectedData.replace(
        "<w:body>",
        "<w:body>" + newTable,
        "gi");
              // Вставляем данные обратно в документ
        // с добавленной таблицей
        Office.context.document.setSelectedDataAsync(
          newString,
          { coercionType: Office.CoercionType.Ooxml },
          function () {
        });
    }
});

Результаты выполнения кода с рис. 8 показаны на рис. 9.

Результаты вставки данных как OOXML
Рис. 9. Результаты вставки данных как OOXML

Примечание: Один из хороших способов научиться тому, как манипулировать OOXML из какого-либо приложения — добавить контент, с которым вам нужно работать, через UI (например, вставив SmartArt выбором Insert | Illustrations | SmartArt), получить OOXML для этого контента с помощью getSelectedDataAsync, а затем изучить результаты. Подробнее на эту тему см. публикацию в блоге «Inserting images with apps for Office» по ссылке bit.ly/SeU3MS.

Получение всего содержимого из файла

Получение или установка данных в точке выделения работает нормально, но бывают ситуации, где нужно получать все содержимое из файла. Так, приложению может понадобиться весь контент в документе в виде текста, его разбор и последующее представление в виде схемы с кружками и стрелками (bubble chart). Другой пример: приложению требует отправить весь контент из файла удаленному веб-сервису для удаленной печати или рассылки по факсу.

JavaScript API for Office предоставляет функциональность и для таких сценариев. С помощью JavaScript API приложение может создать копию файла, разбить ее на фиксированные порции данных (размером до 4 Мб), а затем читать данные в этих фрагментах.

Процесс получения всего содержимого файла, по сути, сводится к следующим трем этапам.

  1. Приложение, вставляемое в Word или PowerPoint, вызывает метод Document.getFileAsync, который возвращает объект File, соответствующий копии файла.
  2. Как только приложение получает ссылку на файл, оно может вызывать метод File.getSliceAsync, чтобы обращаться к конкретным порциям файла, передавая индекс нужной порции. Если это делается в цикле for, будьте внимательны к тому, как вызывающий код обрабатывает замыкания (closures).
  3. Наконец, приложение должно закрывать объект File по окончании работы с ним, вызывая метод File.closeAsync. В любом момент в памяти могут оставаться лишь два файла; попытка открыть третий файл с помощью Document.getFileAsync вызовет ошибку «An internal error has occurred».

Один из хороших способов научиться тому, как манипулировать OOXML из какого-либо приложения — добавить контент, с которым вам нужно работать, через UI.

На рис. 10 мы получаем документ Word порциями по 1 Кб, перебираем весь файл такими порциями и закрываем файл по окончании работы с ним.

Рис. 10. Получение всего содержимого из файла в виде текста и перебор порций

// Получаем все содержимое документа Word
// порциями по 1 Кб в виде текста
function getFileData() {
  Office.context.document.getFileAsync(
  Office.FileType.Text,
  {
    sliceSize: 1000
  },
  function (asyncResult) {
    if (asyncResult.status === 'succeeded') {
      var myFile = asyncResult.value,
        state = {
          file: myFile,
          counter: 0,
          sliceCount: myFile.sliceCount
        };
      getSliceData(state);
    }
  });
}
// Получаем порцию файла, как указывается счетчиком,
// содержащимся в параметре state
function getSliceData(state) {
  state.file.getSliceAsync(
    state.counter,
    function (result) {
    var slice = result.value,
      data = slice.data;
    state.counter++;
      // Что-то делаем с данными
      // Проверяем, не достигнута ли последняя порция в файле;
     // если нет, получаем следующую порцию, а если да,
    // закрываем файл
    if (state.counter < state.sliceCount) {
      getSliceData(state);
    }
    else {
      closeFile(state);
    }
  });
}
// Закрываем файл по окончании работы с ним
function closeFile(state) {
  state.file.closeAsync(
    function (results) {
      // Уведомляем пользователя о завершении процесса
  });
}

Подробнее о том, как получить все содержимое файла из приложения для Office, см. страницу документации «How to: Get the whole document from an app for PowerPoint» по ссылке bit.ly/12Asi4x.

Получение из Project данных задачи, представления и ресурса

В случае приложений области задач (task pane apps), вставляемых в Project, JavaScript API for Office предлагает дополнительные методы, позволяющие считывать данные для активного проекта и выбранной задачи, ресурса или представления. Скрипт project-15.js расширяет office.js и, кроме того, добавляет события смены выбора для задач, ресурсов и представлений. Например, когда пользователь выбирает задачу в представлении Team Planner, приложение может интегрировать и отображать в одном месте оставшуюся работу, запланированную для этой задачи, список сотрудников, способных выполнить эту работу, и связанные проекты в других списках задач SharePoint или Project Server, которые могут повлиять на график работ.

Приложение области задач, вставленное в проект, имеет доступ к содержимому проекта только для чтения.

Приложение области задач, вставленное в проект, имеет доступ к содержимому проекта только для чтения. Но, поскольку это приложение лежит в основе веб-страницы, оно может читать и записывать данные во внешние приложения, используя JavaScript и такие протоколы, как Representational State Transfer (REST). Например, в документацию на Apps for Office and SharePoint включено приложение-пример для Project Professional, которое использует jQuery в сочетании с OData-сервисом подготовки отчетов в Project для сравнения данных по общим затратам и объемам работы по активному проекту со средними показателями для всех проектов в Project Web App (рис. 11).

Приложение области задач, использующее jQuery в сочетании с OData-сервисом подготовки отчетов
Рис. 11. Приложение области задач, использующее jQuery в сочетании с OData-сервисом подготовки отчетов

Подробнее на эту тему см. страницу документации «How to: Create a Project app that uses REST with an on-premises Project Server OData service» по ссылке bit.ly/T80W2H.

Поскольку ProjectDocument расширяет объект Document, объект Office.context.document захватывает ссылку на активный проект — по аналогии с приложениями, вставляемыми в другие хост-приложения. Асинхронные методы, доступные в Project, имеют сигнатуры, похожие на сигнатуры других методов в JavaScript API for Office. Например, у метода getProjectFieldAsync три параметра:

  • fieldId указывает в объекте поле, возвращаемое для параметра callback. Перечисление Office.ProjectProjectFields включает 12 полей, такие как GUID проекта, дата начала, дата окончания и (если таковые есть) URL для Project Server или URL списка задач в SharePoint;
  • asyncContext (необязательный) — любой пользовательский тип, возвращаемый в объекте asyncResult;
  • callback содержит ссылку на функцию, которая выполняется при возврате управления этим методом и содержит код для обработки успешного или неудачного выполнения метода.

Как видно на рис. 12, методы, специфичные для приложений в Project, используются примерно так же, как в приложениях, размещаемых в других хост-приложениях. Во фрагменте скрипта локально определенная функция вызывает процедуру, которая отображает в приложении сообщение об ошибке. В этом скрипте не используется параметр asyncContext.

Рис. 12. Получение GUID поля из области задач, вставленной в Project

var _projectUid = "";
// Получаем GUID активного проекта
function getProjectGuid() {
  Office.context.document.getProjectFieldAsync(
    Office.ProjectProjectFields.GUID,
    function (asyncResult) {
      if (asyncResult.status == Office.AsyncResultStatus.Succeeded) {
        _projectUid = asyncResult.value.fieldValue;
        }
      else {
       // Отображаем пользователю сообщение об ошибке
      }
    }
  );
}

Хотя метод getProjectFieldAsync может получать только 12 полей для универсального проекта, метод getTaskFieldAsync способен получить любое из 282 полей для задачи, используя перечисление ProjectTaskFields, а метод getResourceFieldAsync — любое из 200 полей для ресурса, используя перечисление ProjectResourceFields. К более универсальным методам в объекте ProjectDocument относятся getSelectedDataAsync, который возвращает выделенные текстовые данные в любом поддерживаемом представлении, и getTaskAsync, который возвращает несколько элементов универсальных данных для выбранной задачи. Приложения области задач могут работать с 16-ю разными представлениями в Project.

Кроме того, приложения области задач в Project могут добавлять или удалять обработчики событий, когда пользователь изменяет представление, выбирает задачу или ресурс.

События в приложении для Office

JavaScript API for Office позволяет создавать за счет использования событий более «отзывчивые» приложения. Модель событий для этого API поддерживает четыре ключевых сценария, фундаментальных в разработке приложений для Office (об этом — позже). Понимание этих четырех сценариев даст вам четкое представление об этой модели событий.

Модель событий полностью согласованная, поэтому, изучив общий шаблон обработки событий, вы полностью поймете эту важную концепцию.

В отношении общего шаблона событий в приложениях для Office API следующие объекты имеют связанные с ними события:

  • Binding;
  • CustomXMLPart;
  • Document;
  • RoamingSettings (почтовые приложения);
  • Settings.

Помимо событий, сопоставленных с ними, каждый из перечисленных объектов имеет два метода для работы с его событиями:

  • addHandlerAsync;
  • removeHandlerAsync.

Так как метод removeHandlerAsync просто отменяет подписку обработчика на некое событие и поскольку его сигнатура почти одинакова таковой у addHandlerAsync, в следующем разделе мы сосредоточимся только на addHandlerAsync.

Примечание: Между методами removeHandlerAsync и addHandlerAsync есть одно очень важное различие. Параметр handler не обязателен для removeHandlerAsync. Если он не указан, удаляются все обработчики событий данного типа.

Метод AddHandlerAsync

Метод addHandlerAsync подключает обработчик событий к определенному событию и имеет одинаковую сигнатуру в каждом объекте, который его реализует:

objectName.addHandlerAsync(eventType, handler [, options], callback);

Давайте обсудим параметры этого метода.

Параметр eventType Обязательный параметр. Принимает перечисление EventType, которое сообщает методу, к какому типу события требуется подключение.

Параметр handler Этот параметр может быть либо именованной функцией, либо анонимной, подставляемой в код функцией. Заметьте: как и в модели событий большинства языков программирования, исполняющая среда приложений для Office вызывает обработчик и передает ему объект события как единственный параметр. Кроме того, если вы используете подставляемую в код анонимную функцию в качестве параметра handler, единственный способ удалить данный обработчик — убрать все обработчики этого события вызовом removeHandlerAsync без параметра handler.

Параметр options Как и во всех асинхронных функциях в приложениях для Office API, вы можете указать объект, содержащий необязательные параметры, но в методах addHandlerAsync единственный необязательный параметр — asyncContext. Он позволяет передавать любые данные через асинхронный метод, которые вы можете получать в обратном вызове.

Параметр callback Этот параметр действует точно так же, как и везде в Office API, с одним важным исключением: вы не можете использовать свойство value объекта AsyncResult. Как обсуждалось ранее в этой статье, когда исполняющая среда запускает обратный вызов, она передает объект AsyncResult и с помощью его свойства value вы получаете значение, возвращаемое асинхронным вызовом. В случае обратных вызовов в методе addHandlerAsync значение объекта AsyncResult всегда является неопределенным.

На рис. 13 демонстрируется, как написать метод addHandlerAsync для события DocumentSelectionChanged (в коде предполагается, что у вас есть элемент <div> с атрибутом id, значение которого — «message»).

Рис. 13. Подключение обработчика для события DocumentSelectionChanged через метод Document.addHandlerAsync

Office.initialize = function (reason) {
  $(document).ready(function () {       
    Office.context.document.addHandlerAsync(
      Office.EventType.DocumentSelectionChanged, onDocSelectionChanged,
        addHandlerCallback);
      Office.context.document.addHandlerAsync(
        Office.EventType.DocumentSelectionChanged, onDocSelectionChanged2,
        addHandlerCallback2);
  });
};
function onDocSelectionChanged(docSelectionChangedArgs) {
  write("onDocSelectionChanged invoked each event.");
}
function onDocSelectionChanged2(docSelectionChangedArgs) {
  write("onDocSelectionChanged2 invoked each event.");
}
function addHandlerCallback(asyncResult) {
  write("addHandlerCallback only called once on app initialize.");
}
function addHandlerCallback2(asyncResult) {
  write("addHandlerCallback2 only called once on app initialize.");
}
function write(message) {$('#message').append(message + "\n");

При инициализации приложения код на рис. 13 подключает функции-обработчики onDocSelectionChanged и onDocSelectionChanged2 к событию DocumentSelectionChanged, показывая, что для одного и того же события может быть не один обработчик. Когда срабатывает событие DocumentSelectionChanged, оба обработчика просто пишут в <div> сообщение.

Вызовы addHandlerAsync также включают обратные вызовы addHandlerCallback и addHandlerCallback2 соответственно. Эти обратные вызовы тоже записывают сообщение в <div>, но вызываются лишь раз, когда addHandlerAsync завершается.

В том же духе вы можете использовать метод addHandlerAsync для подключения обработчиков к любым событиям в JavaScript API for Office.

Ключевые сценарии в модели событий в приложениях для Office

Как упоминалось, в JavaScript API for Office существуют четыре ключевых сценария, вокруг которых группируются события в модели. Все события в этом API подпадают под одну из следующих четырех категорий:

  • события Office.initialize;
  • события смены выделения уровня документа;
  • события изменения данных и выделения уровня привязки;
  • события изменения настроек.

События Office.initialize До сих пор в JavaScript API for Office вы чаще всего встречались с событием Office.initialize. Событие initialize происходит в каждом приложении для Office, которое вы создаете. Фактически это первая часть вашего кода, выполняемая исполняющей средой.

Если вы посмотрите на начальный код, предоставляемый Visual Studio 2012 для любого нового приложения в проекте Office, то заметите, что первые строки начального кода в файле ProjectName.js для вашего приложения обеспечивают подключение обработчика к событию Office.initialize:

// Эта функция выполняется, когда приложение готово
// к взаимодействию с хост-приложением; она гарантирует
// готовность DOM до добавления обработчиков щелчков кнопок
Office.initialize = function (reason) { /* код обработчика */};

Как вы знаете из предыдущей статьи (раздела по иерархии объектной модели), Office является объектом самого верхнего уровня в JavaScript API for Office и представляет экземпляр вашего приложения в период выполнения. Событие Initialize срабатывает, когда исполняющая среда приложения для Office полностью загружена и готова к взаимодействию с вашим приложением. Поэтому обработчик события Initialize, по сути, является средством «установления связи» между вашим приложением и исполняющей средой, и происходить все это должно до того, как начнет выполняться остальная часть вашего кода.

Функция, которую вы должны предоставить в качестве обработчика события Office.initialize, принимает единственный аргумент: перечисление InitializationReason. Это перечисление содержит всего два члена: Inserted и documentOpened:

  • Inserted указывает, что приложение инициализируется, так как оно было только что вставлено в документ;
  • documentOpened означает, что приложение инициализируется, так как только что был открыт документ, в который это приложение уже было вставлено.

Исполняющая среда передаст перечисление InitializationReason как единственный аргумент вашей функции-обработчику. С этого момента вы можете решать, как ваш код будет реагировать на конкретную причину.

Вот пример того, как это могло бы работать:

Office.initialize = function (reason) {
  // Отображаем причину инициализации
  if (reason == "inserted")
  write("The app was just inserted.");
  if (reason == "documentOpened")
  write(
    "The app is already part of the document.");
}
// Функция, которая записывает в div с id='message' на странице
function write(message){
  document.getElementById(
  'message').innerText += message;
}

Примечание: В предыдущем фрагменте кода предполагается, что у вас есть элемент <div> с атрибутом id, значение которого — «message».

Любопытно, что вы не обязаны что-то включать в тело функции, предоставляемой в качестве обработчика, но эта функция в любом случае должна присутствовать в вашем приложении, а иначе при его запуске возникнет ошибка.

Кстати, обработчик события Office.initialize — хорошее место для инициализации других инфраструктур, которые вы, возможно, используете в своем приложении, например jQuery. И вновь в начальном коде, предоставляемом Visual Studio для новых приложений в проектах Office, вы увидите нечто вроде того, что показано на рис. 14.

Рис. 14. Инициализация других инфраструктур в обработчике события Office.initialize

Office.initialize = function (reason) {
  $(document).ready(function () {
    $('#getDataBtn').click(function () { getData('#selectedDataTxt'); });
   // Если метод setSelectedDataAsync поддерживается
    // хост-приложением, setDatabtn подключается к вызову
    // метода, а иначе setDatabtn удаляется
    if (Office.context.document.setSelectedDataAsync) {
        $('#setDataBtn').click(function () { setData('#selectedDataTxt'); });
    }
    else {
      $('#setDataBtn').remove();
    }
  });
};

jQuery-событие .ready обрабатывается внутри обработчика события Office.initialize. Это гарантирует загрузку JavaScript API for Office и его готовность до вызовов из jQuery-кода.

События смены выделения уровня документа Эти события возникают при перемещении в документе с одного выделения на другое. Например, когда пользователь щелкает кнопку мыши в документе Word и переходит из текущего выделения к диапазону текста или объекту, или к любому месту в документе, на уровне документа генерируется событие смены выделения.

Следующий код иллюстрирует, как реагировать на смену текущего выделения:

function addEventHandlerToDocument() {
  Office.context.document.addHandlerAsync(
    Office.EventType.DocumentSelectionChanged,
    MyHandler);
}
function MyHandler(eventArgs) {
  doSomethingWithDocument(eventArgs.document);

События изменения данных и выделения уровня привязки В приложениях для объектной модели Office привязки (bindings) — это способ согласованного доступа к конкретной области документа (или электронной таблицы) за счет установления связи, или связывания, с уникально именованной областью документа. Чтобы работать с привязкой, вы сначала создаете ее, используя один из предоставляемых API методов. Затем вы можете ссылаться на конкретную привязку, созданную вами, по ее уникальному идентификатору.

Привязки также инициируют события, и ваше приложение при необходимости может реагировать на них. В частности, привязки генерируют событие при изменении выделения в связанной области и при изменении данных в ней. Следующие два фрагмента кода показывают, как обрабатывать изменения в выделении и данных в данной привязке (в обоих фрагментах кода предполагается, что у вас есть элемент <div> с атрибутом id, значение которого — «message»).

Реагируем на событие Binding.bindingSelectionChanged:

function addEventHandlerToBinding() {
  Office.select("bindings#MyBinding").addHandlerAsync(
    Office.EventType.BindingSelectionChanged,
    onBindingSelectionChanged);
}
function onBindingSelectionChanged(eventArgs) {
  write(eventArgs.binding.id + " has been selected.");
}
// Функция, которая записывает в div с id='message' на странице
function write(message){
  document.getElementById('message').innerText += message;
}

Реагируем на событие Binding.bindingDataChanged:

function addEventHandlerToBinding() {
  Office.select("bindings#MyBinding").addHandlerAsync(
    Office.EventType.BindingDataChanged, onBindingDataChanged);
}
function onBindingDataChanged(eventArgs) {
  write("Data has changed in binding: " + eventArgs.binding.id);
}
// Функция, которая записывает в div с id='message' на странице
function write(message){
  document.getElementById('message').innerText += message;
}

События изменения настроек Приложения для объектной модели Office позволяют разработчикам сохранять настройки, относящиеся к их приложениям. Объект Settings действует как контейнер свойств, в котором параметры приложения хранятся в виде пар «ключ-значение». С объектом Settings также связано событие Settings.settingsChanged, которое срабатывает, если изменяется сохраненный параметр.

Подробнее о событии Settings.settingsChanged см. документацию MSDN на JavaScript API for Office по ссылке bit.ly/U92Sbe.

Что дальше: более сложная тематика

Во второй статье из этой серии мы обсудили основы получения и установки содержимого файлов Office из приложения для Office. Мы показали, как получить и задать данные выделения и как получить все данные из файла. Вы узнали, как обращаться к данным проекта, задачи, представления и ресурса из приложения для Project. Наконец, были рассмотрены события в JavaScript API for Office и то, как кодировать с использованием событий.

Примечание: Мы хотели бы поблагодарить Джима Корбина (Jim Corbin), технического писателя из Office Division, за помощь в части, касающейся приложений для Project.

В следующей статье мы подробнее рассмотрим некоторую более сложную тематику, связанную с JavaScript API for Office: связывание с данными и собственные XML-фрагменты.


Стивен Оливер (Stephen Oliver) — программист и технический писатель в Office Division, является Microsoft Certified Professional Developer (SharePoint 2010). Занимается написанием документации для разработчиков по Excel Services и Word Automation Services, а также PowerPoint Automation Services. Помогает курировать сайт Excel Mashup на ExcelMashup.com.

Эрик Шмидт (Eric Schmidt) — программист и технический писатель в Office Division. Создал несколько примеров кода для приложений, рассчитанных на Office, в том числе популярный пример кода «Persist custom settings». Кроме того, пишет статьи и создает видеоролики о других продуктах и технологиях, так или иначе связанных с возможностью программирования в Office.

Выражаем благодарность за рецензирование статьи экспертам Марку Брюстеру (Mark Brewster), Шилпе Котари (Shilpa Kothari) и Хуану Балмори Лабра (Juan Balmori Labra).