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


Microsoft Office

Исследование JavaScript API for Office: связывание с данными и собственные XML-фрагменты. Часть 3

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

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

JavaScript API for Office

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

  • сценарии для использования связывания с данными в приложении для Office;
  • применение объекта Binding и набора Bindings;
  • файловый формат Office Open XML;
  • использование элементов управления контентом с собственными XML-фрагментами;
  • сопоставление элементов управления контентом с элементом в XML;
  • использование JavaScript с собственными XML-фрагментами;
  • основы объекта CustomXmlParts;
  • применение методов CustomXmlParts.

Исходный код можно скачать по ссылке.

Эта статья — третья часть в серии, посвященной глубокому рассмотрению JavaScript API for Office. В ней мы продолжаем изучение ключевых аспектов этого API, уделяя основное внимание связыванию с данными и поддержке работы с собственными XML-фрагментами (custom XML parts). В первой части «Exploring the New JavaScript API for Office» (msdn.microsoft.com/magazine/jj891051) был дан широкий обзор объектной модели. Во второй части «Exploring the JavaScript API for Office: Data Access and Events» (msdn.microsoft.com/magazine/jj991976) рассматривались важные концепции, относящиеся к получению содержимого файлов, а также подробно обсуждалась модель событий (Event model). В четвертой части, которая последует за этой статьей, мы сосредоточимся исключительно на третьем типе приложений для Office: на почтовых программах.

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

Связывание с данными в приложении для Office

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

После создания привязка сохраняется, даже если область перемещается по страницам (в Word) или копируется в другой рабочий лист (в Excel). Например, привязка к таблице сохраняется и в том случае, когда пользователь ее переименовывает.

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

Привязки и «представление» приложения Связывание с данными в приложении для Office дает ему прямой доступ к набору данных в файле Office, что упрощает анализ этих данных в приложении, не полагаясь на прямую команду от пользователя. Тем не менее, связывание с данными позволяет делать больше, чем просто получать доступ к определенным данным: вы можете включать сам файл Office как настраиваемый и неотъемлемый компонент приложения.

Многие приложения для Office предоставляют пользователям интерфейс, содержащийся исключительно в рамках UI приложения области задач или контента, — и в этом нет ничего неправильного. Однако в каком-то смысле данные и их представление в файле Office сами являются «представлением» приложения. Пользователи взаимодействуют со своими данными в файле Office. Они вводят новые данные, изменяют существующие и удаляют ненужные в содержимом документа. Приложения Office создают представление данных, известное и понятное пользователям.

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

Конечно, верно и обратное. Вы можете использовать файл Office как источник данных, храня в нем содержимое модели данных. После этого вы можете предлагать представление данных через свое приложение. Гибкость привязок позволяет применять шаблон Model-View-Controller (MVC) к приложению и файлу Office так, как это нужно вам.

Сценарии использования привязок Не накладывая жестких ограничений на творчество разработчиков, приложения позволяют использовать привязки в любой комбинации тремя универсальными способами:

  • приложение реагирует на изменение пользователем данных в области;
  • приложение получает данные из области, анализирует их и представляет пользователю с возможностью моделирования или передачи данных;
  • приложение передает данные из внешнего источника в связанную область.

Возьмите, к примеру, простое приложение для получения биржевых сводок, вставленное в рабочую книгу Excel, где один столбец содержит биржевые символы, а другой — текущие котировки акций. С помощью механизма связывания с данными это приложение могло бы выполнить привязку к полю биржевых символов и получать эти символы из соответствующего столбца. Затем приложение могло бы подписаться на изменения в котировках акций через некий веб-сервис и анализировать результаты, получаемые от сервиса. Наконец, приложение могло бы выполнить привязку к полю котировок в рабочем листе и обновлять их в реальном времени.

В следующем разделе, где будет рассматриваться объект Binding, мы так и сделаем: создадим рабочую книгу биржевых сводок.

Применение объекта Binding Вся магия связывания с данными скрыта в наборе Bindings и объекте Binding.

  • Набор Bindings представляет все привязки, созданные между файлом Office и приложением для Office. Приложение не имеет доступа к привязкам, созданным другими приложениями.
  • Объект Binding представляет одну именованную привязку между областью в файле Office и приложением. Он предоставляет несколько членов для получения, чтение и присваивания данных, а также для реакции на изменения в связанной области.

Мы рассмотрим эти объекты подробнее, когда будем создавать приложение для получения биржевых сводок.

Прежде чем идти дальше, давайте взглянем на данные. На рис. 1 показано, как выглядит представление этого приложения. Как видите, мы используем вымышленные биржевые символы для демонстрационных целей.

Таблица Stocks в рабочей книге Excel с формулами и примененным условным форматированием
Рис. 1. Таблица Stocks в рабочей книге Excel с формулами и примененным условным форматированием

Кроме того, мы уже добавили кое-какой «интеллект» в эту рабочую книгу. Область данных, с которой нам требуется связывание, отформатирована как таблица и называется Stocks. К значениям в поле на правой стороне была добавлена собственная формула для их сравнения с другими значениями в таблице. Мы также применили к таблице условное форматирование, чтобы в правом столбце появлялись наборы значков.

Стоит отметить, что мы добавили эту рабочую книгу в наше решение в Visual Studio 2012, чтобы вам не пришлось заново создавать нашу таблицу при каждой отладке приложения. Чтобы добавить рабочую книгу в решение, щелкните правой кнопкой мыши проект приложения в решении (первый проект, перечисленный в Solution Explorer при использовании шаблона по умолчанию), выберите Add Existing Item и укажите рабочую книгу. Затем в свойствах проекта приложения укажите Start Action для файла вашей рабочей книги. При отладке вам понадобится вручную вставлять приложение в рабочую книгу (вкладка Insert | кнопка Apps for Office).

После инициализации прикладной логике приложения нужно установить привязку, а затем добавить обработчик для события привязки — Office.EventType.BindingDataChanged. Этот исходный код показан на рис. 2. Заметьте, что мы инкапсулировали наш код в автоматически вызываемую анонимную функцию, хранящуюся в переменной StockTicker. Имя таблицы в рабочем листе, имя привязки и сама привязка — все они хранятся в полях внутри «класса» StockTicker. «Класс» StockTicker предоставляет доступ только к одному члену: initializeBinding.

Рис. 2. Создание привязки к рабочей книге Excel и добавление обработчика для события изменения данных в привязке

var StockTicker = (function () {
  var tableName = "Sheet1!Stocks",
      bindingName = "Stocks",
      binding;
  // Создаем привязку к таблице в рабочем листе
  function initializeBinding() {
    Office.context.document.bindings.addFromNamedItemAsync(
      tableName,
      Office.BindingType.Table,
      { id: bindingName },
      function (results) {
        binding = results.value;
        addBindingsHandler(function () { refreshData(); });
    });
  }
  // Обработчик событий для обновления таблицы
  // при изменении табличных данных
  var onBindingDataChanged = function (result) {
    refreshData();
  }
  // Добавляем обработчик для события
  // BindingDataChanged привязки
  function addBindingsHandler(callback) {
    Office.select("bindings#" + bindingName).addHandlerAsync(
      Office.EventType.BindingDataChanged,
      onBindingDataChanged,
      function () {
        if (callback) { callback(); }
    });
  }
  // Другие методы-члены этого "класса"...
  return {
    initializeBinding: initializeBinding
  };
})();

Чтобы установить привязку между приложением и таблицей в рабочем листе, мы можем задействовать один из нескольких методов класса Document в JavaScript API, в том числе addFromNamedItemAsync, addFromPromptAsync и addFromSelectionAsync. (Заметьте, что addFromPromptAsync доступен только в Excel и Excel Web App.)

Поскольку нам известно имя связываемой области (это таблица Stocks в Sheet1), для установления привязки мы вызвали метод addFromNamedItemAsync. При этом имя таблицы передается с учетом нотации диапазонов в Excel (Sheet1!Stocks). Этот метод возвращает, в том числе, ссылку на саму привязку, что позволяет сохранить ее в нашей переменной binding (поле класса).

В нашем коде мы передали значение Office.BindingType.Table через параметр bindingType метода. Это указывает на то, что мы хотим создать привязку к нашим данным типа «Table», хотя мы также могли бы выбрать привязку текстового или матричного типа. Привязка к области как к таблице дает нам несколько преимуществ. Например, если пользователь добавляет новое поле или строку в таблицу, размер связанной соответственно увеличивается. Это работает и в обратном направлении. Объект TableBinding, лежащий в основе привязки, предоставляет свойства для добавления полей и строк и даже для удаления всех данных в таблице.

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

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

Вы заметите, что функция addBindingsHandler использует метод Office.select для получения привязки, хотя можно было бы вместо этого применить метод Bindings.getByIdAsync. Главное различие между двумя методами — уровень доступа к данным, возвращаемым в результатах. Метод Office.select возвращает вызвавшему коду promise объекта Binding. Если метод выполнен успешно, в объекте Binding возвращается лишь ограниченное количество членов, доступных для использования. Выбрав привязку через Office.select, мы можем сразу же вызывать члены из объекта Binding. Тем самым мы избавляемся от необходимости добавлять обратных вызов в функцию, которая получает привязку для добавления к ней обработчика.

(Наверное, вы подумали, что мы могли бы просто использовать локальную переменную binding, которая захватывает ссылку на привязку. И вы правы: могли бы. Мы писали код именно так для демонстрационных целей.)

На рис. 3 показаны функции refreshData и getBindingData. Функция refreshData просто начинает цепочку асинхронных вызовов, которые возвращают табличные данные из рабочего листа, вызывая getBindingData. Функция getBindingData содержит вызов метода Binding.getDataAsync и возвращает данные в виде объекта TableData.

Рис. 3. Получение данных от табличной привязки и вызова веб-сервиса

var StockTicker = (function () {
  // Другие члены этого "класса"...
  // Обновляем данные, отображаемые в связанной таблице
  // рабочей книги. Эта функция начинает цепочку
  // асинхронных вызовов, обновляющих связанную таблицу.
  function refreshData() {
    getBindingData();
  }
  // Получаем данные по биржевым символам из связанной таблицы,
  // а затем вызываем сервис биржевых сводок
  function getBindingData() {
    binding.getDataAsync(
      {
        startRow: 0,
        startColumn: 0,
        columnCount: 1
      },
      function (results) {
        var bindingData = results.value,
            stockSymbols = [];
        for (var i = 0; i < bindingData.rows.length; i++) {
          stockSymbols.push(bindingData.rows[i][0]);
        }
        getStockQuotes(stockSymbols);
    });
  }
  return {
    // Предоставляемые члены "класса"
  };
})();

В вызове getDataAsync, показанном на рис. 3, мы могли бы явным образом указать тип извлекаемых данных (или сменить тип данных), передав анонимный объект {coercionType: Office.CoercionType.Table} в параметре options. Так как мы не указали тип извлекаемых данных, вызов getDataAsync возвращает данные привязки с их исходным типом (как объект TableData).

Объект TableData, как обсуждалось во второй статье, обеспечивает более высокую степень структуризации данных, с которыми мы работаем, а именно: свойства header и rows, с помощью которых можно выбирать данные из таблицы. В этом примере нам нужно лишь получить биржевые символы из первого поля таблицы. Как вы, вероятно, помните, свойство rows хранит данные в таблице как массив массивов, где каждый элемент первого массива соответствует строке в таблице.

Работая с привязкой к объекту TableData, мы можем указать подмножество строк и полей, извлекаемых через привязку, и использовать для этого параметры startRow и startColumn. Оба параметра задают начальные точки (с отсчетом от 0) выборки данных из таблицы, где началом координат является верхний левый угол таблицы. (Заметьте, что параметры startRow и startColumn нужно использовать совместно, иначе вы получите исключение.) Так как нас интересует только первое поле таблицы, мы также передаем в параметре columnCount значение 1.

Получив этот столбец данных, мы записываем каждое значение в одномерный массив. На рис. 3 видно, что мы вызываем функцию getStockQuotes, которая принимает массив биржевых символов как аргумент. На рис. 4 мы используем функцию getStockQuotes для получения данных от веб-сервиса биржевых котировок. (В демонстрационных целях мы выбросили код для веб-сервиса.) После разбора результатов от этого сервиса вызывается локально определенный метод removeHandler.

Рис. 4. Вызов веб-сервиса и удаление обработчика событий BindingDataChanged

var StockTicker = (function () {
  // Другие члены этого "класса"...
  // Вызываем веб-сервис для получения новых биржевых котировок
  function getStockQuotes(stockSymbols) {
    var stockValues = [];
    // Вызываем веб-сервис и разбираем результаты. Они хранятся
    // в переменной stockValues, которая содержит массив
    // массивов, включающий биржевые символы
    // с текущими значениями.
    removeHandler(function () {
      updateTable(stockValues);
    });
  }
  // Отключаем обработчик событий BindingDataChanged
  // на время обновления таблицы
  function removeHandler(callback) {
    binding.removeHandlerAsync(
      Office.EventType.BindingDataChanged,
      { handler: onBindingDataChanged },
      function (results) {
        if (results.status == Office.AsyncResultStatus.Succeeded) {
           if (callback) { callback(); }
        }
    });
  }
  return {
    // Предоставляемые члены "класса"
  };
})();

Функцию removeHandler вызывает метод binding.removeHandlerAsync, который удаляет обработчик событий BindingDataChanged. Если бы мы оставили этот обработчик, подключенным к данному событию, оно было бы сгенерировано при обновлении таблицы. А это привело бы к повторному вызову обработчика и обновлению таблицы, в результате чего мы попали бы в бесконечный цикл. После обновления таблицы мы вновь подключим обработчик к этому событию.

(Конечно, мы также могли бы создать различные привязки для разделения полей в таблице, используя приведение к матричному типу. Тогда можно было бы подключаться к событиям только в тех столбцах, которые могут редактироваться пользователями.)

Метод removeHandlerAsync принимает параметр handler, который указывает имя удаляемого обработчика. Это рекомендованный способ удаления обработчиков событий привязки.

На рис. 5 мы обновим таблицу новыми биржевыми котировками, вызвав локально определенную функцию updateTable.

Рис. 5. Получение данных от табличной привязки и вызов веб-сервиса

var StockTicker = (function () {
  // Другие члены этого "класса"...
  // Обновляем объект TableData, на который ссылается привязка,
  // а затем обрановляем данные в таблице на рабочем листе
  function updateTable(stockValues) {
    var stockData = new Office.TableData(),
        newValues = [];
    for (var i = 0; i < stockValues.length; i++) {
      var stockSymbol = stockValues[i],
          newValue = [stockSymbol[1]];
      newValues.push(newValue);
    }
    stockData.rows = newValues;
    binding.setDataAsync(
      stockData,
      {
        coercionType: Office.CoercionType.Table,
        startColumn: 3,
        startRow: 0
      },
      function (results) {
        if (results.status == Office.AsyncResultStatus.Succeeded) {
          addBindingsHandler();
        }
    });  
  }
  return {
    // Предоставляемые члены "класса"
  };
})();

Функция updateTable принимает данные, передаваемые веб-сервисом, и записывает их в связанную таблицу. В этом примере параметр stockValues содержит еще один массив массивов, где каждый элемент в первом массиве является массивом, хранящим биржевой символ и его текущую котировку. Чтобы поместить эти данные обратно в связанную таблицу, мы создаем новый объект TableData и вставляем в него данные по биржевым котировкам.

Будьте осторожны. Данные, помещаемые в свойство TableData.rows, должны соответствовать форме данных, которые вставляются в привязку. Если бездумно поместить совершенно новый объект TableData в связанную таблицу, возникает риск потери каких-либо данных в этой таблице, например формул. На рис. 5 мы добавили данные в объект TableData как один столбец данных (массив массивов, где каждый подмассив содержит один элемент). Вставляя эти данные обратно в связанную таблицу, мы должны поместить обновленный столбец данных в соответствующий столбец.

И здесь мы вновь используем свойства startRow и startColumn. Функция updateTable вызывает binding.setDataAsync, который записывает TableData обратно в таблицу в рабочем листе; при этом указываются параметры startColumn и startRow. Параметр startColumn присваивается значению 3, т. е. объект TableData вставит свои данные, начиная с четвертого столбца в таблице. В обратном вызове для метода setDataAsync мы снова вызываем функцию addBindingsHandler, чтобы заново подключить обработчик к событию.

Когда метод binding.setDataAsync завершается успешно, в связанную область помещаются новые табличные данные и сразу же показываются на экране. С точки зрения пользователя, все происходит совершенно бесшовно. Пользователь вводит данные в ячейку таблицы, нажимает Enter, и столбец Value таблицы обновляется автоматически.

Собственные XML-фрагменты

Особого внимания заслуживает поддержка в JavaScript API for Office возможности создания собственных XML-фрагментов (custom XML parts) и работы с ними в Word. Чтобы оценить глубокий потенциал JavaScript API for Office в этой области, полезны некоторые познания — главным образом вы должны понимать, как можно комбинировать файловый формат Office Open XML (OOXML, или OpenXML), собственные XML-фрагменты, элементы управления контентом и XML-сопоставления для создания по-настоящему эффективных решений, а именно: решений, включающих создание динамических документов Word.

Форматы OOXML В Office 2007 был введен новый файловый формат OOXML для документов Office, который стал форматом по умолчанию в Office 2010 и Office 2013. (Вы можете определить, какие документы Office имеют файловый формат OOXML просто по расширениям этих документов, которые теперь являются четырехбуквенными, и многие из них заканчиваются на «x», например «.docx» для документов Word, «.xlsx» для таблиц Excel или «.pptx» для документов PowerPoint.)

Документы Office в формате OOXML фактически представляют собой ZIP-файлы. Каждый ZIP-файл содержит набор XML-файлов, называемых фрагментами (parts), которые все вместе и образуют документ Office. Если вы переименуете документ Office, скажем, документ Word с расширением .docx в .zip, а потом проанализируете файлы внутри него, то увидите, что документ на самом деле состоит из набора отдельных XML-файлов, организованных в папки, и все они помещены в ZIP-пакет, как показано на рис. 6.

Структура файлов в документе Office с форматом Open XML
Рис. 6. Структура файлов в документе Office с форматом Open XML

Основы собственных XML-фрагментов Хотя существуют стандартные XML-фрагменты, которые приложения Office всегда создают для каждого нового документа Office в формате OOXML (например, один из встроенных XML-фрагментов описывает базовые свойства документа), любопытно, что вы можете добавить свои XML-фрагменты в документ Word, рабочую книгу Excel или презентацию PowerPoint. Собственных XML-фрагменты добавляются в набор XML-файлов внутри ZIP-пакета, образующего документ Office. Собственный XML-фрагмент хранится в файловой структуре документа, но не отображается конечному пользователю. Это позволяет вставлять бизнес-данные, которые передаются с определенным экземпляром документа Office и скрыты в файловой структуре. С таким XML вы можете работать в своем приложении, и это именно то, что поддерживается JavaScript API for Office.

Элементы управления контентом Наряду с форматом OOXML и его файловой структурой, позволяющей включать собственный XML в документ, в Word 2007 добавлены элементы управления контентом (content controls) — функциональность, которая очень удачно дополняет собственные XML-фрагменты.

Эти элементы дают возможность определять фиксированные области в документе Word, где хранятся конкретные виды данных, такие как чистый текст, форматированный текст, картинки, даты и даже повторяющиеся данные (repeating data). Ключевой аспект элементов управления контентом, дополняющих собственные XML-фрагменты, — связывание с данными через XML-сопоставление (XML mapping).

XML-сопоставление Элемент управления контентом можно связать, или сопоставить, с XML-элементом в XML-фрагменте, который содержится в документе. Например, предприятие может встроить бизнес-данные из серверной системы как собственный XML-фрагмент в документ Word, в котором этому фрагменту сопоставляются элементы управления контентом. Последние связываются с определенными узлами в собственном XML-фрагменте, так чтобы после открытия документа конечным пользователем эти элементы управления контентом, сопоставленные с XML, автоматически заполнялись данными из этого XML-фрагмента. Или противоположный сценарий. Предприятие могло бы использовать тот же документ Word с сопоставленными элементами управления контентом, но для того, чтобы конечный пользователь вводит данные в эти элементы. При сохранении документа данные из сопоставленных элементов управления контентом записываются обратно в XML-файл. А приложение потом могло бы извлекать эти данные из собственного XML-фрагмента в сохраненном документе и передавать их серверной системе. JavaScript API for Office обеспечивает богатую поддержку для разработки таких приложений.

Применение JavaScript API for Office для работы с собственными XML-фрагментами Лучший способ изучить некоторые из более важных частей API собственных XML-фрагментов в приложениях для Office JavaScript Object Model — разобрать их на примере. В этом разделе мы используем пример «invoice manager» (bit.ly/YRdlwt) из области Samples на портале разработчиков приложений для Office и SharePoint, так что вы сможете делать все вместе с нами. Образец «invoice manager» (диспетчер счетов) — это пример сценария с динамическими документами, где предприятию нужно генерировать документы, извлекающие данные из серверной системы, и в итоге создавать счета. В этом случае данными являются имя клиента, адрес поставки и связанный список товаров, закупленных этим клиентом.

Пример включает документ-шаблон, используемый для создания новых счетов. В документе-шаблоне есть разметка с именем клиента, адресом и таблицей товаров, приобретенных клиентом. Каждый из разделов этого документа (имя клиента, адрес и список товаров) является элементом управления контентом. А каждый такой элемент сопоставлен с узлом в схеме, созданной для хранения данных по счетам для клиентов, как показано на рис. 7.

Элементы управления контентом в документе, сопоставленные с собственным XML-фрагментом
Рис. 7. Элементы управления контентом в документе, сопоставленные с собственным XML-фрагментом

UI в приложении-примере «invoice manager» весьма прост, что видно на рис. 8.

UI для приложения-примера «invoice manager»
Рис. 8. UI для приложения-примера «invoice manager»

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

UI, заполняемый данными из собственного XML-фрагмента
Рис. 9. UI, заполняемый данными из собственного XML-фрагмента

Когда пользователь выбирает кнопку Populate, приложение записывает отображаемые данные как собственный XML-фрагмент в документ. Поскольку элементы управления контентом сопоставлены с узлами в этом XML-фрагменте, то после его передачи в документ элементы управления контентом сразу же отображают данные для каждого XML-узла, с которым они сопоставлены. Вы можете заменять собственный XML-фрагмент «на лету» (как это делали мы), но элементы управления контентом будут отображать сопоставленные данных, только если фрагмент соответствует схеме, с которой сопоставлены эти элементы. На рис. 10 показана форма Packing Slip диспетчера счетов, когда элементы управления контентом сопоставлены с собственным XML-фрагментом в документе.

Элементы управления контентом, сопоставленные с узлами в собственном XML-фрагменте и показывающие связанные данные
Рис. 10. Элементы управления контентом, сопоставленные с узлами в собственном XML-фрагменте и показывающие связанные данные

Объект CustomXmlParts

CustomXmlParts.addAsync Первый шаг в работе с собственными XML-фрагментами — научиться добавлять их в документ с помощью JavaScript API for Office. Единственный способ сделать это — применение метода customXmlParts.addAsync. Как и подразумевает его имя, метод customXmlParts.addAsync добавляет ваш XML-фрагмент асинхронно и имеет следующую сигнатуру:

Office.context.document.customXmlParts.addAsync(xml [, options], callback);

Заметьте, что обязательным первым параметром функции является XML-строка. Это XML для собственного XML-фрагмента. Как уже упоминалось, диспетчер счетов использует собственные XML-фрагменты, сопоставленные с элементами управления контентом в документе, но сначала он должен получить данные по клиентам, чтобы вставить их как XML. В файле InvoiceManager.js, где хранится логика всего приложения, программа имитирует получение данных по клиентам из серверной системы с помощью пользовательской функции setupMyOrders. Эта функция создает массив из трех объектов, представляющих заказы трех клиентов. Конечно, можно вообразить любое количество способов, с помощью которых организация могла бы хранить и получать историю покупок клиента, например базу данных SQL, но простоты ради мы создаем три «зашитых» в код заказа прямо в приложении.

После создания объектов заказов представляемые ими данные должны быть преобразованы в XML, чтобы их можно было использовать в вызове customXmlParts.addAsync. Именно это и происходит в функции initializeOrders, которая также настраивает UI приложения и подключает обработчики событий к элементам управления в UI. Важная часть находится в jQuery-коде, который подключает обработчик к событию click кнопки Populate, как показано на рис. 11.

Рис. 11. Подключение обработчика к событию click кнопки Populate

$("#populate").click(function () {
  var selectedOrderID = parseInt($("#orders option:selected").val());
  _document.customXmlParts.getByNamespaceAsync("", function (result) {
    if (result.value.length > 0) {
      for (var i = 0; i < result.value.length; i++) {
        result.value[i].deleteAsync(function () {
        });
      }
    }
  });
  var xml = $.json2xml(findOrder(myOrders, selectedOrderID));
  _document.customXmlParts.addAsync(xml, function (result) { });
});

По сути, анонимная функция, действующая как обработчик событий click кнопки Populate, преобразует объект заказа (созданный с помощью функции setupMyOrders) в XML-строку, а затем вызывает метод customXmlParts.addAsync и передает XML-строку, содержащую информацию о заказе, как обязательный первый параметр.

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

_document.customXmlParts.addAsync(xml,
  function (result) { });

Как и в случае всех обратных вызовов в JavaScript API for Office, объект AsyncResult является единственным аргументом функции обратного вызова. В случае customXmlParts.addAsync и всех функций customXmlParts объект AsyncResult можно использовать для получения:

  • ссылки на только что созданный собственный XML-фрагмент через свойство AsyncResult.value;
  • результата запроса через свойство AsyncResult.status;
  • информации об ошибке (если таковая была) через свойство AsyncResult.error;
  • данных состояния (если вы включали таковые в вызов customXmlParts.addAsync) через свойство AsyncResult.asyncContext.

В случае последнего варианта помните, что еще одним параметром метода customXmlParts.addAsync был не обязательный объект options:

Office.context.document.customXmlParts.addAsync(
  xml [, options], callback);

Объект options предоставляется как способ передачи вашего пользовательского объекта в вызове функции обратного вызова.

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

CustomXmlParts.getByNamespaceAsync Другая ключевая часть JavaScript API for Office для работы с собственными XML-фрагментами, которая демонстрируется в примере «invoice manager», — метод customXmlParts.getByNamespaceAsync, который вы можете увидеть в коде обработчика события click для кнопки Populate. Сигнатура customXmlParts.getByNamespaceAsync такова:

Office.context.document.customXmlParts.getByNamespaceAsync(
  ns [, options], callback);

Обязательный первый параметр, ns, — это строка, указывающая пространство имен собственных XML-фрагментов, которые вы хотите получить. Поэтому customXmlParts.getByNamespaceAsync возвращает массив собственных XML-фрагменте в документе, которые находятся в указанном вами пространстве имен. Так как собственные XML-фрагменты, создаваемые в примере с диспетчером счетов, не используют пространства имен, в вызове customXmlParts.getByNamespaceAsync передается пустая строка вместо параметра ns, как показано на рис. 12.

Рис. 12. Использование метода CustomXmlParts.getByNamespaceAsync

$("#populate").click(function () {
  var selectedOrderID = parseInt($("#orders option:selected").val());
  _document.customXmlParts.getByNamespaceAsync("", function (result) {
    if (result.value.length > 0) {
      for (var i = 0; i < result.value.length; i++) {
        result.value[i].deleteAsync(function () {
        });
      }
                    }
     });
     var xml = $.json2xml(findOrder(myOrders, selectedOrderID));
     _document.customXmlParts.addAsync(xml, function (result) { });
   });
   var selOrder = $("#orders option:selected");
   popOrder(selOrder.val());

Как и все асинхронные функции в API, customXmlParts.getByNamespaceAsync имеет не обязательный параметр options и параметр callback.

CustomXmlParts.getByIdAsync Последний программный способ получить собственный XML-фрагмент в документе — customXmlParts.getByIdAsync. Сигнатура этой функции:

Office.context.document.customXmlParts.getByIdAsync(id [, options], callback);

Эта функция получает один собственный XML-фрагмент, используя GUID этого фрагмента. Вы найдете GUID собственного XML-фрагмента в файле itemPropsn.xml внутри пакета документа. Кроме того, вы можете  получить GUID через свойство id объекта customXmlPart. Здесь главное в том, что строка GUID должна содержать фигурные скобки («{}») вокруг GUID.

В примере не используется функция customXmlParts.getByIdAsync, но следующий код демонстрирует ее достаточно четко:

function showXMLPartBuiltId() {
  Office.context.document.customXmlParts.getByIdAsync(
    "{3BC85265-09D6-4205-B665-8EB239A8B9A1}", function (result) {
    var xmlPart = result.value;
    write(xmlPart.id);
  });
}
// Функция, записывающая в div с id='message' в странице
function write(message){
  document.getElementById('message').innerText += message;
}

В дополнение к параметру id, как в customXmlParts.addAsync и customXmlParts.getByNamespaceAsync, метод customXmlParts.getByIdAsync также имеет не обязательный параметр options и обязательный параметр callback, и они используются так же, как и в других функциях.

Объект CustomXmlPart Представляет один собственный XML-фрагмент. Получив ссылку на customXmlPart с помощью методов объекта customXmlParts, вы можете обращаться к нескольким свойствам, перечисленным в табл. 1.

Табл. 1. Свойства CustomXmlPart

Имя Описание
builtIn Получает значение, указывающее, встроен ли customXmlPart
id Получает GUID объекта customXmlPart
namespaceManager Получает набор сопоставлений префиксов пространства имен (customXmlPrefixMappings), применяемых в текущем customXmlPart

CustomXmlPart также имеет события, перечисленные в табл. 2.

Табл. 2. События CustomXmlPart

Имя Описание
nodeDeleted Возникает при удалении узла
nodeInserted Возникает при вставке узла
nodeReplaced Возникает при замене узла

Но для целей этой статьи мы сосредоточимся на нескольких ключевых методах объекта customXmlPart, которые часто используются разработчиками. Они показаны в табл. 3.

Табл. 3. Методы CustomXmlPart

Имя Описание
addHandlerAsync Асинхронно добавляет обработчик для события объекта customXmlPart
deleteAsync Асинхронно удаляет данный собственный XML-фрагмент из набора
getNodesAsync Асинхронно получает любые customXmlNodes в данном собственном XML-фрагменте, которые совпадают с указанным XPath
getXmlAsync Асинхронно получает XML из данного собственного XML-фрагмента

CustomXMLPart.addHandlerAsync Этот метод является ключевым для подключения обработчиков событий, реагирующих на изменения в собственном XML-фрагменте. Сигнатура метода customXmlPart.addHanderAsync выглядит следующим образом:

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

Заметьте, что первый обязательный параметр является перечислением Office.EventType, которое указывает, какого вида событие в приложениях для объектной модели Office вы хотите обрабатывать. Следующий обязательный параметр, handler, — это обработчик события. Здесь важно следующее: когда обработчик вызывается, JavaScript API for Office передает параметр аргументов события, специфичный для данного вида обрабатываемых событий (NodeDeletedEventArgs, NodeInsertedEventArgs или NodeReplacedEventArgs). Затем, как и во всех асинхронных функциях в API, имеются не обязательный параметр options и параметр callback.

Рассмотрим сценарий, где документ используется как форма для ввода данных. Форма содержит элемент управления контентом Repeating Section, поэтому всякий раз, когда пользователь вводит повторяющийся элемент, в нижележащий собственный XML-фрагмент добавляется новый узел. При каждом добавлении или вставке узла срабатывает событие NodeInserted, и вы можете отреагировать на это событие (и все события customXmlPart), используя customXmlPart.addHandlerAsync.

На рис. 13 показано, как вы могли бы отреагировать на событие NodeInserted.

Рис. 13. Подключение обработчика для события CustomXmlPart.NodeInserted

function addNodeInsertedEvent() {
  Office.context.document.customXmlParts.getByIdAsync(
    "{3BC85265-09D6-4205-B665-8EB239A8B9A1}", function (result) {
    var xmlPart = result.value;
    xmlPart.addHandlerAsync(Office.EventType.NodeInserted,
      function (eventArgs) {
        write("A node has been inserted.");
    });
  });
}
// Функция, записывающая в div с id='message' в странице
function write(message){
  document.getElementById('message').innerText += message;
}

CustomXMLPart.deleteAsync Конечно, наряду со знанием того, как добавить собственный XML-фрагмент, важно понимать, как его можно удалить. Метод customXmlPart.deleteAsync предоставляет такую функциональность. CustomXmlPart.deleteAsync — асинхронная функция со следующей сигнатурой:

customXmlPart.deleteAsync([options ,] callback);

Если вернуться к примеру «invoice manager», то вы можете увидеть демонстрацию customXMLPart.deleteAsync:

$("#populate").click(function () {
  var selectedOrderID = parseInt($("#orders option:selected").val());
  _document.customXmlParts.getByNamespaceAsync("", function (result) {
    if (result.value.length > 0) {
      for (var i = 0; i < result.value.length; i++) {
        result.value[i].deleteAsync(function () {
        });
      }
    }
});

В обработчике события click для кнопки Populate логика программы проверяет, существуют ли какие-нибудь собственные XML-фрагменты с «пустыми» пространствами имен. Если да, она удаляет их с помощью метода customXmlPart.deleteAsync.

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

В следующий раз: почтовые приложения

В этой (третьей) статье из серии мы рассмотрели некоторые «продвинутые» методики работы с данными в приложениях для Office. Мы показали, как добавить дополнительный «интеллект» в таблицу Excel, используя привязки данных. Мы также изучили, как использовать собственные XML-фрагменты в приложении для Word, которые способствуют автоматизации создания документов.

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


Стивен Оливер (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.

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