Модель API для конкретного приложения

В этой статье описывается использование модели API для создания надстроек в Excel, OneNote, PowerPoint, Visio и Word. Здесь представлены основные понятия, лежащие в основе использования API на основе обещаний.

Примечание.

Эта модель не поддерживается клиентами Outlook или Project. Используйте модель Common API для работы с этими приложениями. Полные сведения о доступности платформ см. в статье Доступность клиентских приложений и платформ Office для надстроек Office.

Совет

В примерах на этой странице используются API JavaScript для Excel, но эти понятия также применяются к API JavaScript Для OneNote, PowerPoint, Visio и Word. Полные примеры кода, показывающие, как можно использовать эти и другие понятия в различных приложениях Office, см. в разделе Примеры кода надстройки Office.

Асинхронный характер API на основе обещаний

Надстройки Office — это веб-сайты, которые отображаются в элементе управления webview в приложениях Office, таких как Excel. Этот элемент управления внедряется в приложение Office на классических платформах, таких как Office в Windows, и выполняется в iframe HTML в Office в Интернете. Из-за соображений производительности интерфейсы API Office.js не могут синхронно взаимодействовать с приложениями Office на всех платформах. Таким образом, вызов API sync() в Office.js возвращает обещание, которое разрешается, когда приложение Office выполняет запрошенные действия чтения или записи. Кроме того, вы можете поместить в очередь несколько действий, например действия настройки свойств или вызова методов, а затем запустить их в виде пакета команд в одном вызове метода sync(), а не отправлять отдельные запросы для каждого действия. В разделах ниже описано, как сделать это, используя API run() и sync().

Функция *.run

Excel.run, OneNote.run, PowerPoint.runи Word.run выполняют функцию, которая указывает действия, выполняемые в Excel, Word и OneNote. *.run автоматически создает контекст запроса, который можно использовать для взаимодействия с объектами Office. Когда *.run завершает работу, обещание разрешается, и все объекты, которые были выделены в среде выполнения, будут автоматически разблокированы.

В следующем примере показано, как использовать шаблон Excel.run. Тот же шаблон также используется в OneNote, PowerPoint, Visio и Word.

Excel.run(function (context) {
    // Add your Excel JS API calls here that will be batched and sent to the workbook.
    console.log('Your code goes here.');
}).catch(function (error) {
    // Catch and log any errors that occur within `Excel.run`.
    console.log('error: ' + error);
    if (error instanceof OfficeExtension.Error) {
        console.log('Debug info: ' + JSON.stringify(error.debugInfo));
    }
});

Контекст запроса

Приложение Office и надстройка выполняются в разных процессах. Так как они используют разные среды выполнения, надстройкам требуется объект RequestContext, чтобы можно было подключать надстройку к объектам в Office, например к листам, диапазонам, абзацам и таблицам. Этот объект RequestContext предоставляется в качестве аргумента при вызове *.run.

Прокси-объекты

Объекты JavaScript для Office, объявляемые и используемые с помощью API на основе обещаний, являются прокси-объектами. Все методы, которые вы вызываете, либо свойства, которые вы настраиваете либо загружаете, в прокси-объектах просто добавляются в очередь команд, ожидающих выполнения. Когда вы вызываете метод sync() в контексте запроса (например, context.sync()), команды, помещенные в очередь, передаются в приложение Office и выполняются. По существу, эти API ориентированы на работу с пакетами. Вы можете поместить в очередь любое количество изменений в контексте запроса, а затем вызвать метод sync(), чтобы запустить пакет команд, помещенных в очередь.

Например, во фрагменте кода ниже показано, как объявить локальный объект JavaScript Excel.Range (selectedRange) для ссылки на выделенный диапазон в книге Excel, а затем задать ряд свойств для этого объекта. Объект selectedRange представляет собой прокси-объект, поэтому свойства, заданные в этом объекте, и метод, вызываемый в этом объекте, не будут отображены в документе Excel, пока надстройка не вызовет метод context.sync().

const selectedRange = context.workbook.getSelectedRange();
selectedRange.format.fill.color = "#4472C4";
selectedRange.format.font.color = "white";
selectedRange.format.autofitColumns();

Совет по производительности: минимизируйте количество созданных прокси-объектов

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

// BAD: Repeated calls to .getRange() to create the same proxy object.
worksheet.getRange("A1").format.fill.color = "red";
worksheet.getRange("A1").numberFormat = "0.00%";
worksheet.getRange("A1").values = [[1]];

// GOOD: Create the range proxy object once and assign to a variable.
const range = worksheet.getRange("A1");
range.format.fill.color = "red";
range.numberFormat = "0.00%";
range.values = [[1]];

// ALSO GOOD: Use a "set" method to immediately set all the properties
// without even needing to create a variable!
worksheet.getRange("A1").set({
    numberFormat: [["0.00%"]],
    values: [[1]],
    format: {
        fill: {
            color: "red"
        }
    }
});

sync()

При вызове метода sync() в контексте запроса будет синхронизировано состояние прокси-объектов и объектов в документе Office. Метод sync() запускает любые команды, помещенные в очередь в контексте запроса, и получает значения для любых свойств, которые следует загрузить в прокси-объектах. Метод sync() выполняется асинхронно и возвращает обещание, которое разрешается по завершении работы метода sync().

В примере ниже показана пакетная функция, которая определяет локальный прокси-объект JavaScript (selectedRange), загружает свойство этого объекта, а затем использует шаблон обещаний JavaScript для вызова метода context.sync() и, соответственно, синхронизации состояния прокси-объектов и объектов в документе Excel.

await Excel.run(async (context) => {
    const selectedRange = context.workbook.getSelectedRange();
    selectedRange.load('address');
    await context.sync();
    console.log('The selected range is: ' + selectedRange.address);
});

В предыдущем примере настроен параметр selectedRange, и его свойство address загружается при вызове context.sync().

Так как sync() — это асинхронная операция, всегда следует возвращать объект Promise, чтобы завершить операцию sync(), прежде чем продолжить выполнение сценария. Если вы используете TypeScript или JavaScript ES6+, вы можете await вызов context.sync() вместо возврата обещания.

Совет по производительности: минимизируйте количество вызовов синхронизации

В API JavaScript для Excel sync() является единственной асинхронной операцией и в некоторых обстоятельствах может выполняться медленно, особенно в случае с Excel в Интернете. Для оптимизации производительности минимизируйте количество вызовов sync(), поставив в очередь максимально возможное количество изменений до ее вызова. Дополнительные сведения об оптимизации производительности с помощью sync() см. в статье Избегайте использования метода context.sync в циклах.

load()

Чтобы можно было считывать свойства прокси-объекта, вам необходимо явно загрузить их и заполнить прокси-объект данными из документа Office, а затем вызвать метод context.sync(). Например, вы создали прокси-объект для ссылки на выделенный диапазон, а затем вам потребовалось считать свойство address выделенного диапазона. Прежде чем вы сможете считать свойство address, вам потребуется загрузить его. Чтобы запросить загрузку свойств прокси-объекта, вызовите метод load() в объекте и укажите свойства, которые необходимо загрузить. В следующем примере показана загрузка свойства Range.address для myRange.

await Excel.run(async (context) => {
    const sheetName = 'Sheet1';
    const rangeAddress = 'A1:B2';
    const myRange = context.workbook.worksheets.getItem(sheetName).getRange(rangeAddress);

    myRange.load('address');
    await context.sync();
      
    console.log (myRange.address);   // ok
    //console.log (myRange.values);  // not ok as it was not loaded

    console.log('done');
});

Примечание.

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

Как и запросы на задание свойств или вызов методов в прокси-объектах, запросы на загрузку свойств в прокси-объектах добавляются в очередь ожидающих команд в контексте запроса, которые будут выполняться при следующем вызове sync() метода. Вы можете ставить в очередь столько load() вызовов в контексте запроса, сколько это необходимо.

Скалярные и навигационные свойства

Существует две категории свойств: скалярные и навигационные. К скалярным свойствам относятся назначаемые типы, такие как строки, целые числа и структуры JSON. Свойства навигации — это объекты и коллекции объектов только для чтения, которым назначаются поля вместо прямого назначения свойства. Например, элементы name и position объекта Excel.Worksheet являются скалярными свойствами, а protection и tables — свойствами навигации.

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

someRange.load("format/font/name")

Вы также можете задавать скалярные свойства из свойства навигации по пути к ним. Например, вы можете задать размер шрифта для Excel.Range с помощью команды someRange.format.font.size = 10;. Чтобы задать свойство, необязательно загружать его.

Имейте в виду, что некоторые свойства объекта могут совпадать с именем другого объекта. Например, format — это свойство объекта Excel.Range, но также имеется и объект format. Поэтому если вы, например, вызываете range.load("format"), это эквивалентно range.format.load() (нежелательный пустой оператор load()). Чтобы избежать этого, ваш код должен загружать только "конечные узлы" в дереве объектов.

Загрузка из коллекции

При работе с коллекцией используйте load в коллекции для загрузки свойств для каждого объекта в коллекции. Используйте load точно так же, как и для отдельного объекта в этой коллекции.

В следующем примере кода показано name , что свойство загружается и регистрируется для каждой диаграммы на листе "Пример".

await Excel.run(async (context) => {
    const sheet = context.workbook.worksheets.getItem("Sample");
    const chartCollection = sheet.charts;

    // Load the name property on every chart in the chart collection.
    chartCollection.load("name");
    await context.sync();

    chartCollection.items.forEach((chart) => {
        console.log(chart.name);
    });
});

Свойство коллекции обычно не включается items в load аргументы. Все элементы загружаются при загрузке свойств элемента. Однако если вы будете циклически выполняться над элементами в коллекции, но не нужно загружать какое-либо конкретное свойство элементов, вам потребуется свойство loaditems .

В следующем примере кода показано name свойство, задается для каждой диаграммы на листе "Пример".

await Excel.run(async (context) => {
    const sheet = context.workbook.worksheets.getItem("Sample");
    const chartCollection = sheet.charts;

    // Load the items property from the chart collection to set properties on individual charts.
    chartCollection.load("items");
    await context.sync();

    chartCollection.items.forEach((chart, index) => {
        chart.name = `Sample chart ${index}`;
    });
});

Если вызвать метод load() для объекта (или коллекции), не указывая параметры, будут загружены все скалярные свойства объекта или объектов в коллекции. Загрузка ненужных данных замедлит вашу надстройку. Необходимо всегда явным образом указывать свойства для загрузки.

Важно!

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

  • Excel.Range.numberFormatCategories

ClientResult

Методы в API на основе обещаний, возвращающие примитивные типы, используют шаблон, похожий на парадигму load/sync. Например, Excel.TableCollection.getCount получает количество таблиц в коллекции. getCount возвращает ClientResult<number>. Это означает, что свойство value возвращаемого ClientResult выражено числом. Сценарий не может получить доступ к этому значению, пока не вызовет context.sync().

Следующий код получает общее количество таблиц в книге Excel и записывает его в консоль.

const tableCount = context.workbook.tables.getCount();

// This sync call implicitly loads tableCount.value.
// Any other ClientResult values are loaded too.
await context.sync();

// Trying to log the value before calling sync would throw an error.
console.log (tableCount.value);

set()

Установка свойств объекта с вложенными свойствами навигации может быть трудоемкой задачей. Вместо того чтобы задавать отдельные свойства с помощью путей навигации, как описано выше, вы можете использовать метод object.set(), доступный для объектов в API JavaScript на основе обещаний. С помощью этого метода можно задать сразу несколько свойств объекта, передавая другой объект того же типа Office.js или объект JavaScript со свойствами, сходными по структуре со свойствами объекта, для которого вызывается метод.

Следующий пример кода задает несколько свойств формата диапазона путем вызова set() метода и передачи объекта JavaScript с именами и типами свойств, которые зеркало структуру свойств в объекте Range . В этом примере предполагается, что данные находятся в диапазоне B2:E2.

await Excel.run(async (context) => {
    const sheet = context.workbook.worksheets.getItem("Sample");
    const range = sheet.getRange("B2:E2");
    range.set({
        format: {
            fill: {
                color: '#4472C4'
            },
            font: {
                name: 'Verdana',
                color: 'white'
            }
        }
    });
    range.format.autofitColumns();

    await context.sync();
});

Некоторые свойства невозможно задать напрямую

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

// PageLayout.zoom.scale must be set by assigning PageLayout.zoom to a PageLayoutZoomOptions object.
sheet.pageLayout.zoom = { scale: 200 };

В предыдущем примере вы не сможете напрямую присвоить zoom значение: sheet.pageLayout.zoom.scale = 200;. Этот оператор выдает ошибку, так как zoom не загружен. Даже если zoom загружен, масштабный набор не будет работать. Все контекстные операции происходят в zoom, обновляя прокси-объект в надстройке и переписывая локально установленные значения.

Это поведение отличается от свойств навигации, например Range.format. Свойства можно задать с помощью навигации format по объектам, как показано здесь.

// This will set the font size on the range during the next `content.sync()`.
range.format.font.size = 10;

Вы можете определить свойство, для которого невозможно напрямую задать его вложенные свойства, путем проверки модификатора только для чтения. Для всех свойств, доступных только для чтения, можно напрямую задать их вложенные свойства, использующиеся не только для чтения. Записываемые свойства, например PageLayout.zoom, должны быть заданы на уровне объекта. Сводка:

  • Свойство только для чтения: вложенные свойства можно задать с помощью навигации.
  • Записываемое свойство: вложенные свойства нельзя задать с помощью навигации (необходимо установить их в рамках начального назначения родительского объекта).

*Методы и свойства OrNullObject

Некоторые методы и свойства доступа создают исключение, если нужный объект не существует. Например, если для получения листа Excel указать имя листа, не существующее в книге, метод getItem() создаст исключение ItemNotFound. Библиотеки конкретных приложений позволяют коду проверять наличие сущностей документа, не требуя кода обработки исключений. Это достигается с помощью вариантов методов и свойств *OrNullObject. Эти варианты вместо создания исключения возвращают объект, свойству isNullObject которого присвоено значение true, если указанный элемент не существует.

Например, вы можете вызвать метод getItemOrNullObject() для коллекции, такой как Worksheets, чтобы получить элемент из коллекции. Метод getItemOrNullObject() возвращает указанный элемент, если он существует. В противном случае возвращается объект, свойству isNullObject которого присвоено значение true. Затем код может оценить это свойство, чтобы определить, существует ли объект.

Примечание.

Варианты *OrNullObject никогда не возвращают значение JavaScript null. Они возвращают обычные прокси-объекты Office. Если сущность, представляемая объектом, не существует, свойству isNullObject объекта присваивается значение true. Не проверяйте возвращенный объект на нулевое значение или ложность. Для него никогда не используются значения null, false или undefined.

В следующем примере кода осуществляется попытка получить лист Excel с именем Data с помощью метода getItemOrNullObject(). Если лист с таким именем не существует, создается новый лист. Обратите внимание, что код не загружает свойство isNullObject. Office автоматически загружает это свойство, когда вызывается context.sync, поэтому вам не нужно явным образом загружать его с помощью dataSheet.load('isNullObject').

await Excel.run(async (context) => {
    let dataSheet = context.workbook.worksheets.getItemOrNullObject("Data");
    
    await context.sync();
    
    if (dataSheet.isNullObject) {
        dataSheet = context.workbook.worksheets.add("Data");
    }
    
    // Set `dataSheet` to be the second worksheet in the workbook.
    dataSheet.position = 1;
});

Стек отмены приложения

При обработке API для конкретного приложения стек отмены приложения очищается. Это означает, что вы не можете отменить изменения, внесенные до каких-либо действий, выполненных надстройкой (если только эта надстройка не использует только общие API или не взаимодействует с файлом). То же самое относится и к изменениям, внесенным надстройкой.

См. также