Всем привет, я – Виталий Квитковский, руководитель программистов в небольшой государственной компании. В этом туториале я расскажу про базовые возможности написания макросов в программе "Р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
и GetRo
w. Что характерно, они возвращают номера столбца и строки, как если бы они нумеровались с единицы. Поэтому для получения текущей ячейки из них надо будет вычесть 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 офис". Вот еще несколько полезных ссылок:
Очень важная ссылка: содержит возможные значения некоторых совершенно неочевидных свойств. https://support.r7-office.ru/using-api-document-builder/global-2/
Официальный (но более скудный) туториал можно найти здесь https://api.onlyoffice.com/docs/plugin-and-macros/macros/getting-started/ (на английском языке) и здесь https://support.r7-office.ru/category/desktop_editors/plugins-and-macros/macros-plugins-and-macros/ (на русском).
Для тех, кто хочет знать больше, есть документация: https://api.onlyoffice.com/docs/office-api/usage-api/spreadsheet-api/api/ (на английском языке) и https://support.r7-office.ru/category/using-api-document-builder/api-tables/ (на русском).
Если вам и этого недостаточно, всегда можно скопировать исходный код с https://github.com/ONLYOFFICE/sdkjs (в этом репозитории я нашел все, что связано с Javascript API, возможно, вам пригодятся и другие репозитории Onlyoffice) и посмотреть все, что вам нужно.
Спасибо за внимание!
Комментарии (5)
EvgeniyRasyuk
02.12.2024 15:20День добрый
еще можно для изучения материала воспользоваться бесплатными курсами
integralik Автор
02.12.2024 15:20Обнаружился стандартный способ запускать макрос по клику, добавил в статью :)
Le_Grand
Спасибо автору, очень полезная информация!
Хотелось бы получить ответ или увидеть отдельную статью с подробным описанием по работе с фигурами. Практически нет никакой информации.
По своей работе столкнулся с необходимостью написания такого макроса. Но, к примеру, не знаю как очищать лист от уже имеющихся фигур, или вносить текст в фигуры, объединять фигу и менять их параметры. Может напишите пару таких функций для примера?
integralik Автор
Я с ходу не вижу поиска по имеющимся фигурам, но, судя по паре примеров, они привязаны к листу. Можно просто делать копию всех данных ячеек на листе на новый лист, а потом удалять старый лист и переименовывать новый. И вуаля, старые удалены.
А дальше можно создавать с нужными параметрами примерно аналогично вот этой статье: 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, их можно синхронно изгибать.
Но в целом я скорее не знаком с матчастью, без приличного опыта где-нибудь в другом месте с аналогичными задачами (а у меня нет) надо довольно долго копаться, вряд ли возьмусь.
slavius
При копировании данных ячеек можно потерять части оформления и привязки, а потом долго думать - в какой момент всё на листе поплыло, хотя лист тот-же, да не тот.