Всем привет, я – Виталий Квитковский, руководитель программистов в небольшой государственной компании. В этом туториале я расскажу про базовые возможности написания макросов в программе "Р7 офис" – российском пакете, являющемся ответвлением Onlyoffice. Я пробовал запускать код в обеих программах, он работает одинаково. Есть некоторые отличия в деталях интерфейса, но в целом базовые функции работают одинаково.

Главная причина, почему мне так нравятся макросы в этих пакетах – я веб-разработчик, а эти макросы пишутся на Javascript :) Они, разумеется, не будут работать в Microsoft Office и других версиях офиса, но макросы чаще создаются для упрощения работы того, кто редактирует файл, а не для тех, кто потом будет смотреть на результат, поэтому для меня этого было вполне достаточно. В этой статье я также ограничусь макросами для таблиц, поскольку большая часть работы в нашей компании происходит именно с Excel-файлами.

В сети уже есть туториал, однако он больше построен по принципу «что можно сделать с данными с помощью нашего API». А я хотел бы построить эту статью по принципу «какие базовые микрозадачи возникают в процессе решения большинства задач и как их решать». Также мы порадуемся доступности большинства функционала классического Javascript на примере работы со строками, объект Math и даже fetch.

Предварительная подготовка

Для того, чтобы нам удобно было разрабатывать макросы, нам понадобится отладчик. Чтобы запускать программу с работающим отладчиком по умолчанию, нужно добавить ключ запуска --ascdesktop-support-debug-info. Например, в Windows это можно сделать в свойствах ярлыка на "Р7 офис", указав во вкладке «Ярлык» в поле «Объект»

"C:\Program Files\R7-Office\Editors\DesktopEditors.exe" --ascdesktop-support-debug-info

Теперь запустите программу, найдите в верхнем меню самую правую вкладку «Плагины», а в ней нажмите кнопку «Макросы». (В Onlyoffice версии 8.2.2, актуальной на 1 декабря 2024 года, кнопка «Макросы» перенесена во вкладку «Вид»). Здесь вы можете создавать макросы и запускать их. К сожалению, окно с кодом загораживает большую часть экрана, для того, чтобы посмотреть, как макрос повлиял на содержимое, зачастую приходится закрыть окно с кодом нажатием кнопки «Ок» и затем снова вернуться в него нажатием на «Макрос».

Тем не менее, отладочную информацию можно получить и без этого. Просто нажав F1 при открытом окне макроса, вы увидите Chrome dev tools! А это значит, что наши любимые console.log и console.dir выведут нам всю полезную отладочную информацию. Именно для работы Chrome dev tools нам и понадобился специальный параметр при запуске.

При создании макроса вам сразу предлагается заготовка для запуска, и остается написать только тело функции. Напишем в консоли Hello world!

(function()
{
    console.log('Hello world!');
})();

Далее его можно выполнить по нажатию кнопки «Выполнить» в "Р7 офис" или кнопки play в Onlyoffice.

Работа с ячейками

Ячейки достать достаточно легко. Если вам нужна ячейка B3 на текущем активном листе, то ее можно получить с помощью

let cell = Api.GetActiveSheet().GetRange("B3");

Однако для целей перебора ячеек нам нужны номера строк и столбцов. Давайте научимся оперировать с ними.

let cell = Api.GetActiveSheet().GetRangeByNumber(2, 1);

Это работа с той же ячейкой. Здесь сначала идет строка, потом столбец, и нумерация начинается с нуля (то есть для ячейки A1 мы бы писали 0, 0). При этом чтобы получить из текущей ячейки ее индекс строки и индекс столбца, есть функции GetCol и GetRow. Что характерно, они возвращают номера столбца и строки, как если бы они нумеровались с единицы. Поэтому для получения текущей ячейки из них надо будет вычесть 1:

let cell = Api.GetActiveSheet().GetRange("B3");
let indexCol = cell.GetCol() - 1;
let indexRow = cell.GetRow() - 1;
let sameCell = Api.GetActiveSheet().GetRangeByNumber(indexCol, indexRow);

Вам это понадобится в тот момент, когда, например, нужно будет, начиная с текущей ячейки, перебрать несколько ячеек, допустим, вниз, и сделать их фон желтым:

let sheet = Api.GetActiveSheet();
let cell = sheet.GetRange("B3");
let indexCol = cell.GetCol() - 1;
let indexRow = cell.GetRow() - 1;
let color = Api.CreateColorFromRGB(255, 255, 200);
for (let i = indexRow; i < indexRow + 5; i++) {
    sheet.GetRangeByNumber(i, indexCol).SetFillColor(color);
}

Если же вам для вставки в формулу нужно получить имя ячейки, то это делается с помощью метода GetAddress:

let cell = Api.GetActiveSheet().GetRangeByNumber(2, 1);
console.log(cell.GetAddress());

даст вам B3. Если вам нужно добавить в формулу символ доллара (фиксация строки/столбца ячейки при протягивании), то нужно указывать первые два параметра как true (доллар нужен) или false (не нужен, по умолчанию). cell.GetAddress(true, false) даст B$3, а cell.GetAddress(false, true) - $B3. Если вам нужен ввод формулы в стиле R1C1, то указывайте третьим параметром 'xlR1C1':

let cell = Api.GetActiveSheet().GetRangeByNumber(2, 1);
console.log(cell.GetAddress(false, false, 'xlR1C1')); //выведет R[2]C[1]

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

let cell = Api.GetActiveSheet().GetRangeByNumber(2, 1);
console.log(cell.GetAddress(false, false, 'xlA1', true)); //выведет
//[Книга1.xlsx]'Лист1'!B3

Формат вывода номера ячейки по умолчанию называется xlA1, но в принципе вы можете вписать в качестве параметра что угодно, кроме xlR1C1 (даже true), это сработает.

Если вам нужно будет только название листа, от названия книги легко избавиться с помощью стандартных функций Javascript.

Если вы оперируете ячейками на другом листе, в выводе команды GetAddress названия листа все равно не будет (если только 4-й параметр не указан как true).

let cell = Api.GetSheet('Лист1').GetRange('B1');
cell.SetValue(cell.GetAddress()); //выведет B1

Работа с содержимым ячеек и стилями

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

let cell = Api.GetActiveSheet().GetRange("B3");

Если вы хотите получить значение ячейки, воспользуйтесь методом GetValue:

console.log(cell.GetValue());

Если вам нужна формула как строка – это метод GetFormula:

console.log(cell.GetFormula());

Если вам нужно сохранить данные в ячейку – воспользуйтесь SetValue. Формулы тоже сохраняются через SetValue, если первым символом значения является =. Названия функций в формуле можно вводить как на английском, так и на русском языках (по крайней мере, если при установке программы был выбран русский язык).

cell.SetValue('234');
cell.SetValue('=MIN(C3:C10)');
cell.SetValue('=МИН(C3:C10)');

Если вы хотите ввести в одну ячейку много строк текста, то их можно разделять через \n, но для того, чтобы отображение стало корректным, нужно встать на эту ячейку и нажать кнопку «Перенос строк», разрешив перенос строк, либо нажать дабл-клик на эту ячейку и сохранить ее. Иначе текст будет отображаться слепленным.

cell.SetValue("2\n\n3\n\n4");

Так произойдет, даже если вы получите значение из другой ячейки с множеством строк с помощью GetValue. При этом с полученным значением можно будет работать, считая, что разрывы строк – это \n, например:

let sheet = Api.GetActiveSheet();
let multilineText = sheet.GetRange("H14").GetValue();
let rowsArray = multilineText.split("\n");
console.log(rowsArray); // будет массивом с набором строк
oWorksheet.GetRange("H15").SetValue(multilineText); //после выполнения макроса
//нужно будет на ячейе нажать «перенос строк», чтобы она отображалась корректно

Если вам нужно внести одинаковое значение во много ячеек, то можно в GetRange указать диапазон. Чаще это используется для одинакового форматирования блока целиком:

let cells = Api.GetSheet('Лист1').GetRange('B1:D40'); //здесь и далее
cells.SetValue(‘123’);

Теперь давайте разберемся с форматированием.

cells.SetBold(); //установить ячейкам жирный шрифт, с параметром false – наоборот, убрать жирный шрифт
cells.SetItalic(false); //аналогично для курсива
cells.SetUnderline(); //и для подчеркнутого текста
cells.SetStrikeout(true); //для зачеркивания нужно указать true
cells.SetNumberFormat("0.00"); //два знака после запятой; больше примеров в документации https://api.onlyoffice.com/docs/office-api/usage-api/spreadsheet-api/apirange/setnumberformat/
cells.SetFontName('Courier New'); //шрифт
cells.SetFontSize(14); //размер шрифта, пунктов
//значения – top | center | bottom
cells.SetAlignVertical('bottom'); //вертикальное выравнивание
//значения – left | center | right | justify
cells.SetAlignHorizontal('justify'); //горизонтальное выравнивание

Если мы хотим работать с высотой строки или шириной столбца, то это лучше делать через лист:

let sheet = Api.GetActiveSheet();
sheet.SetRowHeight(12, 20); //нумерация с нуля, высота в пикселях
sheet.SetColumnWidth(1, 30); //нумерация с нуля, ширина в более крупных единицах, точно установить не удалось

Но можно и через ячейку:

let cell = Api.GetActiveSheet().GetRange("B3");
cell.SetRowHeight(20);
cell.SetColumnWidth(30);

Набор данных как таблица

Если вы хотите работать с блоком данных как с таблицей (строка заголовков, фильтр, сортировка, вам понадобится метод FormatAsTable. При этом рекомендую предварительно отформатировать данные под нужный формат, например:

let sheet = Api.GetSheet('Лист1');
let cells = sheet.GetRange('H7:I40'); // Диапазон с числовыми данными
cells.SetNumberFormat("0.000"); // Форматируем как число с тремя знаками после запятой
Api.GetSheet('Лист1').FormatAsTable('G6:I40'); // В верхней строке у нас заголовки, в столбце G – строки, не требующие специального форматирования

Цвета и границы

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

let color = Api.CreateColorFromRGB(255, 255, 200);

Первый параметр – уровень красного, второй – зеленого, третий – синего (от 0 до 255).

Альтернативный вариант – выбрать из предустановленных цветов. Там, помимо очевидных вроде yellow и cyan, достаточно большой список, который можно найти на странице https://support.r7-office.ru/using-api-document-builder/global-2/, поискав по слову PresetColor.

let color = Api.CreateColorByName("dodgerBlue");

Методы CreateRGBColor, CreatePresetColor и CreateSchemeColor нужны для рисования фигур, для текста и ячеек они не подойдут. Пример можно посмотреть по ссылке https://api.onlyoffice.com/docs/office-api/usage-api/spreadsheet-api/api/createpresetcolor/ , а возможные значения для метода CreateSchemeColor – по предыдущей ссылке, поискав по слову SchemeColorId.

Дальше с помощью цвета можно устанавливать цвет текста ячеек или их фона:

let cells = Api.GetSheet('Лист1').GetRange('B1:D40');
cells.SetFillColor(color); //для фона
cells.SetFontColor(color); //для текста

Установка толщины и цвета границ диапазона производится следующим способом:

let cells = Api.GetSheet('Лист1').GetRange('B1:D40');
let color = Api.CreateColorFromRGB(255, 255, 200);
cells.SetBorders('Left', 'Thin', color);

Варианты значений для первого параметра SetBorders – обратите внимание, что они пишутся с большой буквы, и что Left, Right, Top и Bottom означают только внешние границы диапазона – это, помимо них, еще InsideHorizontal, InsideVertical (все внутренние горизонтальные или вертикальные границы), а также DiagonalDown и DiagonalUp (перечеркивание ячейки снизу вверх или сверху вниз).

Варианты значений для второго параметра SetBorders:
Double | Hair | DashDotDot | DashDot | Dotted | Dashed | Thin | MediumDashDotDot | SlantDashDot | MediumDashDot | MediumDashed | Medium | Thick
Я чаще всего пользуюсь Thick. К сожалению, мне так и не удалось заставить заработать значение None для этого параметра, чтобы удалить границы.

Работа Javascript-функций

Очень многие возможности нативного Javascript работают в полном объеме. Например, объектом Math можно воспользоваться, чтобы сгенерировать случайный цвет, на котором должно быть видно черный текст. Собственно, это практически любой цвет, у которого каждый из параметров RGB больше 155. Давайте напишем функцию генерации такого цвета. Ее можно расположить над кодом макроса:

function rand() {
     return 155 + Math.round(100 * Math.random());
}

function generateColor() {
    return Api.CreateColorFromRGB(rand(), rand(), rand());
}

Далее, если мы хотим получить данные из какого-нибудь внешнего источника, то fetch также прекрасно работает, а если мы получаем оттуда json, то его можно распарсить с помощью JSON.parse. Например, получим курсы валют с сайта, загружающего их с сайта ЦБ:

function getCurrencies() {
    fetch('https://www.cbr-xml-daily.ru/daily_json.js').then(response => {
        let src = JSON.parse(response.value);         
        let start = 2;
        for (let chr in src.Valute) {
            let arCourses = src.Valute[chr];
            getCell('A' + start).SetValue(chr);
            getCell('B' + start).SetValue(arCourses.Value);
            getCell('C' + start).SetValue(arCourses.Previous);
            start++;
        }
    }); 
}

Работа вне окна макроса

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

Реализуется это также с помощью стандартного функционала Javascript:

addEventListener("keyup", function(event) {
    if(event.ctrlKey) {
        let code = event.keyCode;
        if (code == 53) { // Ctrl+5
            someFunction(); //какая-то ваша функция
        }
        if (code == 54) { // Ctrl+6
            someOtherFunction(); //какая-то другая ваша функция
        }     
    } 
});

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

Api.GetActiveSheet().GetName(); //имя активного листа
Api.GetActiveSheet().GetActiveCell().GetAddress(); //имя активной ячейки

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

addEventListener("click", function(event) {
    let cellName = Api.GetActiveSheet().GetActiveCell().GetAddress(false, false, 'xlA1');
    if (cellName === 'B3') {
        someFunction();
    }
    if (cellName === 'F3') {
        someOtherFunction();
    } 
});

Проблема, однако, в том, что хитрый Onlyoffice не обновляет данные, измененные таким образом, пока не будет изменена хотя бы одна ячейка в таблице. Делать редактирование ячейки на каждую комбинацию клавиш выглядит как-то очень глупо, и я нашел пару «лайфхаков», одну для «клавиатурщиков», другую – для «мышечников». Если вы предпочитаете не снимать рук с клавиатуры, то сразу после комбинации клавиш, которую вы нажали, вам нужно нажать F9 – пересчет всех формул на листе, и обновления отобразятся. Если вам больше нравится работать мышкой, то вы можете нажать ту из кнопок выравнивания по вертикали в верхней панели, которая уже и так нажата.

Соответственно, инструктируя тех, кто будет пользоваться вашим макросом, вы можете просто сразу комбинировать эти действия, например, функция someFunction запускается через комбинацию клавиш Ctrl+5 и F9, или же по нажатию на ячейку B3 (позаботьтесь, чтобы там была имитация кнопки с подписью) и потом на кнопку выравнивания текста по вертикали.

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

Апдейт. Обнаружился стандартный способ запускать макрос по клику: нужно вставить на лист любую фигуру (если хотите надпись, то есть такая фигура, на значке буква А и имитация текста), а дальше кликнуть по фигуре правой кнопкой мыши (в случае надписи - по ее границе) и в выпадающем меню выбрать "Назначить макрос", далее выбрать нужный макрос.

Автозапуск

Довольно очевидно, что макрос, запускающий addEventListener, должен запускаться автоматически при открытии файла. За это отвечает кнопка «автозапуск» в редакторе макросов, и автозапускаемые макросы помечены значком (А). При открытии файла Onlyoffice спрашивает, запустить ли автозапускаемые макросы.

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

Если вы поставили галочку «Всегда запускать макросы на входе», а в макросе появилась ошибка, то система не даст вам редактировать макросы. В этом случае отключение автоматического автозапуска макросов находится в пункте меню «Файл – Дополнительные параметры – Настройки макросов» (для возврата к варианту по умолчанию выберите «Показывать уведомления»).

Заключение

Разумеется, этот туториал посвящен только базовым возможностям настройки макросов для таблиц в Onlyoffice и "Р7 офис". Вот еще несколько полезных ссылок:

Спасибо за внимание!

Комментарии (5)


  1. Le_Grand
    02.12.2024 15:20

    Спасибо автору, очень полезная информация!

    Хотелось бы получить ответ или увидеть отдельную статью с подробным описанием по работе с фигурами. Практически нет никакой информации.

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


    1. integralik Автор
      02.12.2024 15:20

      Я с ходу не вижу поиска по имеющимся фигурам, но, судя по паре примеров, они привязаны к листу. Можно просто делать копию всех данных ячеек на листе на новый лист, а потом удалять старый лист и переименовывать новый. И вуаля, старые удалены.

      А дальше можно создавать с нужными параметрами примерно аналогично вот этой статье: https://api.onlyoffice.com/docs/office-api/usage-api/spreadsheet-api/api/createpresetcolor/
      Типы фигур можно посмотреть здесь: https://api.onlyoffice.com/docs/office-api/usage-api/form-api/shapetype/
      С надписями я бы копал в сторону AddWordArt, их можно синхронно изгибать.

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


      1. slavius
        02.12.2024 15:20

        При копировании данных ячеек можно потерять части оформления и привязки, а потом долго думать - в какой момент всё на листе поплыло, хотя лист тот-же, да не тот.


  1. EvgeniyRasyuk
    02.12.2024 15:20

    День добрый
    еще можно для изучения материала воспользоваться бесплатными курсами

    https://r7-consult.ru/km-main


  1. integralik Автор
    02.12.2024 15:20

    Обнаружился стандартный способ запускать макрос по клику, добавил в статью :)