Сегодня я хочу рассказать о «внутренностях» скриптов и ключевых возможностях, предоставляемых объектной моделью Снегопата. Я познакомлю читателя с порядком создания скрипта для Снегопата «с нуля» на примере разработки скрипта «Авторские комментарии».
Специалисты, имеющие опыт программирования в 1С:Предприятии 7.7 и знакомые с ОпенКонфом наверняка знают о существовании одноименного скрипта для него. Мы будем делать именно его аналог, но для 1С:Предприятия 8.
Перед тем, как заняться реализацией примера из этой статьи, советую предварительно познакомиться с рекомендациями для начинающих по разработке скриптов для Снегопата.
Исходный код скрипта доступен в репозитории скриптов для проекта Снегопат на официальном сайте проекта.
Важно! Скрипт работает в демо-версии Снегопата. Для того, чтобы его запустить, необходимо:
- Установить демо-версию Снегопата. О порядке установки демо-версии написано здесь.
- Скачать и разместить в подкаталоге scripts\Libs каталога Снегопата скрипт-библиотеку TextWindow.js
- Скачать сам скрипт author.js и его форму настройки author.ssf (разместить в любом удобном для вас каталоге).
- Найти в корневом каталоге Снегопата файл addins.ini (если не существует - создать такой файл в любом текстовом редакторе) и добавить в конец файла строку:
script:
- (Пере)запустить конфигуратор под Снегопатом (используя starter.exe, идущий в поставке Снегопата).
Содержание
Статическое подключение других скриптов
Динамическое подключение других скриптов
Использование объектов 1С: метод v8New()
Работа с активным текстовым окном: библиотека TextWindow.js
Реализация алгоритма установки маркеров
Метод parseTemplateString() – программный парсинг шаблонов 1С
Реализация механизма подстановки параметров форматной строки
Программное открытие формы скрипта
Обработчики событий формы и элементов управления
Работа с параметрами обработчиков событий формы и элементов управления
Использование методов глобального контекста 1С
Хранение и чтение настроек скрипта
Описание задачи
«Авторский комментарий» - это распространенный среди 1С:Специалистов способ пометки изменений кода непосредственно в модуле при помощи комментариев, обозначающих начало и конец изменений, характер изменений (добавление кода, изменение кода или удаление кода), а также подпись автора (имя или ник) и дату внесения изменений.
Различают два типа авторских комментариев: для блоков и «однострочники».
Для ясности приведу примеры:
1. Маркер для добавленного блока кода:
//Добавлено: Admin 2012-02-10
Движение = Движения.ЦеныПоставщиков.Добавить();
Движение.Период = Дата;
Движение.Товар = ТекСтрокаТовары.Товар;
Движение.Поставщик = Поставщик;
Движение.Цена = ТекСтрокаТовары.Цена;
/// Admin 2012-02-10
(Маркер «Добавлено:», имя пользователя ИБ, дата изменений в формате гггг-ММ-дд).
2. Маркер для удаленного блока кода:
//Удалено: Admin 2012-02-10
//Движение = Движения.ЦеныПоставщиков.Добавить();
//Движение.Период = Дата;
//Движение.Товар = ТекСтрокаТовары.Товар;
//Движение.Поставщик = Поставщик;
//Движение.Цена = ТекСтрокаТовары.Цена;
/// Admin 2012-02-10
Как видите, под «удалением» подразумевается комментирование выделенного блока кода с установкой соответствующих маркеров.
3. Маркер для измененного блока кода:
//Изменено: Admin 2012-02-10
//Движение = Движения.ЦеныПоставщиков.Добавить();
//Движение.Период = Дата;
//Движение.Товар = ТекСтрокаТовары.Товар;
//Движение.Поставщик = Поставщик;
//Движение.Цена = ТекСтрокаТовары.Цена;
//---- Заменено на: ----
Движение = Движения.ЦеныПоставщиков.Добавить();
Движение.Период = Дата;
Движение.Товар = тзТовары.Товар;
Движение.Поставщик = Поставщик;
Движение.Цена = тзТовары.Цена;
/// Admin 2012-02-10
Установка маркера «Изменено» сопровождается сохранением копии оригинального кода (опционально).
В случае применения маркеров добавления и удаления в текущей строке, используется сокращенный вариант маркеров, которые будем называть «однострочниками». Маркер при этом устанавливается в конец строки.
Однострочник для маркера «Добавлено»:
Движение.Регистратор = Ссылка;//Добавлено: Admin 2012-02-10
Однострочник для маркера «Удалено»:
//Движение.Регистратор = Ссылка;//Удалено: Admin 2012-02-10
Маркер «Изменено», примененный к одной строке работает так же, как если бы его применили к блоку, т.к. в общем случае необходимо оставлять копию исходной строки:
//Изменено: Admin 2012-02-10
//Движение.Цена = ТекСтрокаТовары.Цена;
//---- Заменено на: ----
Движение.Цена = ТекСтрокаТовары.Цена;
/// Admin 2012-02-10
Формат подписи автора (сигнатура) задается при помощи строки, в которой используются параметры, заменяемые при применении маркеров, на конкретные (вычисляемые) значения.
Мы реализуем поддержку следующих параметров:
- %ИмяПользователя% - вставляет имя текущего пользователя информационной базы;
- %ПолноеИмяПользователя% - вставляет полное имя текущего пользователя ИБ;
- %ИмяПользователяХранилищаКонфигурации% - вставляет имя текущего пользователя хранилища конфигурации (под которым установлено текущее подключение к хранилищу конфигурации);
- %ИмяПользователяОС% - имя текущего пользователя операционной системы
- %ДатаВремя#<ФорматнаяСтрока>% - текущее дата и время, отформатированное при помощи форматной строки, синтаксис которой аналогичен синтаксису форматной строки, используемой в методе Формат() глобального контекста 1С:Предприятия 8.
Для примеров маркеров, приведенных выше, форматная строка имеет вид:
%ИмяПользователяОС% %ДатаВремя#ДФ=yyyy-MM-dd%
Основные свойства скрипта
Для создания скрипта можно использовать любой текстовый редактор, в том числе и ретактор текстов 1С:Предприятия 8. Но лучше, конечно же, использовать специализированный редактор кода с возможностью подсветки синтаксических конструкций JavaScript и другими полезными в разработке инструментами. Я в своей практике использую Notepad++ (точнее, его портативную версию).
Файл скрипта должен быть создан в кодировке UTF-8, рекомендуется использовать UTF-8 с меткой BOM (UTF-8 with BOM).
Добавим в начало скрипта следующие директивы:
$engine JScript
$uname author
$dname Авторский комментарий
Первая из них ($engine) указывает тип скриптового движка, который Снегопат должен использовать для выполнения кода скрипта.
При помощи двух других директив указываются уникальное имя ($uname) и название скрипта ($dname) – по сути аналоги свойств «Имя» и «Синоним» объектов метаданных 1С.
Ни одна из названных директив не является обязательной. Если они не указаны, то Снегопат тип движка попытается определить по расширению файла, а в качестве имени и названия скрипта будут использовано имя файла (включая расширение), но указание названных директив считается хорошим тоном.
Статическое подключение других скриптов
Кроме самостоятельной части кода наш скрипт будет использовать функционал других скриптов, импортируя реализованные в них методы и объекты.
Снегопат поддерживает два способа повторного использования кода, реализованного в других скриптах: статическое подключение скрипта по уникальному имени и динамическое подключение скрипта по его полному пути.
Первый способ реализуется при помощи директивы $addin, в качестве первого обязательного параметра которой должно быть указано уникальное имя скрипта, который мы хотим импортировать. Вторым параметром можно указать идентификатор, под которым мы хотим «знать» объект подключаемого скрипта в глобальной области видимости нашего скрипта. Если второй параметр не указан, объект подключаемого скрипта будет добавлен в наш скрипт под своим уникальным именем (указанным в директиве $uname).
Мы в нашем скрипте подключим таким образом два скрипта, входящих в состав ядра Снегопата: «Стандартную библиотеку» stdlib (3_std.js) и библиотеку «Подключение глобальных контекстов» global(0_global_context.js). О назначении и использовании этих библиотек мы поговорим далее в статье, когда нам потребуется их функционал, а пока добавим в наш код директивы для их подключения:
$addin global
$addin stdlib
Динамическое подключение других скриптов
Статическое подключение скрипта требует, чтобы подключаемый скрипт на момент загрузки нашего скрипта уже был загружен Снегопатом (поэтому этот способ подключения я и называю статическим). Это не удобно, когда нам требуется в своем скрипте использовать не скрипт ядра Снегопата (эти-то скрипты загружаются раньше других), а такой же «рядовой» скрипт, как и наш.
Для таких случаев существует возможность динамического подключения скриптов, реализованная в библиотеке stdlib, а именно, метод require(). В качестве первого параметра он принимает имя файла скрипта-библиотеки (скрипта из подкаталога Libs каталога scripts) или полный путь к файлу скрипта, если он расположен не в каталоге Libs.
С помощью этого метода мы подключим к нашему скрипту библиотеку по работе с содержимым активного окна редактора текстов Конфигуратора:
stdlib.require("TextWindow.js",SelfScript);
Если подключаемый скрипт еще не загружен, то require() осуществит его загрузку, если скрипт уже был загружен, то не будет предпринимать никаких других действий, а просто вернет объект подключаемого скрипта.
В качестве второго параметра скрипта в метод require можно передать объект нашего скрипта, доступный через глобальное свойство SelfScript. В этом случае, в глобальную область видимости нашего скрипта будут добавлены все публичные методы и объекты подключаемого скрипта.
Макросы: способы объявления
Макросы - это команды скрипта, которые могут быть вызваны пользователем скрипта. С точки зрения реализации макросы скрипта – это функции скрипта, которые не имеют параметров и имена которых начинаются с префикса «macros».
Наш скрипт будет содержать три макроса, реализующих собственно функционал скрипта: макросы «Маркер «Добавлено», «Маркер «Изменено» и «Маркер «Удалено», а также макрос «Настройка», который будет открывать форму настройки скрипта, в которой пользователь сможет указать свой формат подписи и т.п.
Таким образом, макрос мы могли бы объявить следующим образом:
function macrosМаркерДобавлено() {
// ... программный код макроса ...
}
JavaScript разрешает использовать в качестве имен идентификаторов кириллицу, и мы этим воспользовались.
В списке макросов наш макрос будет выглядеть следующим образом:
Волшебные особенности JavaScript, а именно то, что все в этом языке является объектами, которыми можно манипулировать как ассоциативными массивами, добавляя им новые свойства явно, позволяют нам объявить макрос таким образом, что для пользователя его представление будет еще более дружественным:
SelfScript.self['macrosМаркер "Добавлено"']=function(){
// ... программный код макроса ...
}
Прокомментирую приведенный код. Свойство self объекта-скрипта (SelfScript) ссылается на объект, представляющий глобальную область видимости скрипта в виде ассоциативного массива. Для тех, кто не знаком с понятием «ассоциативный массив», отмечу, что в объектной модели 1С наиболее близким аналогом для него будет являться объект «Соответствие».
Ключами этого ассоциативного массива (они же – имена свойств) являются идентификаторы функций и переменных глобальной области видимости, значениями – функции или значения переменных.
Теперь для пользователя наши макросы будут выглядеть так:
Реализация макросов скрипта
Первые три макроса, реализующие основной функционал скрипта, будут выполнять одинаковый алгоритм:
- Проанализировать наличие и состояние активного текстового окна (вызывается ли макрос в текстовом окне и выделен ли в нем текст или нет);
- На основе результатов анализа принимать решение, будем ли вставлять маркер блока или «однострочник»;
- Добавлять маркеры указанного типа.
Таким образом, все эти действия можно локализовать в одной функции, в качестве параметра передавая тип маркера. Сами типы маркеров определим в виде отдельного объекта MarkerTypes:
var MarkerTypes = {
ADDED: "МаркерДобавлено",
REMOVED: "МаркерУдалено",
CHANGED: "МаркерИзменено"
};
function addMarker(markerType) {
// ... программныйкод функции ...
}
Заглушка функции готова и мы можем добавить ее вызов в наши макросы:
SelfScript.self['macrosМаркер "Добавлено"'] = function() {
addMarker(MarkerTypes.ADDED);
}
Перед тем, как начать реализацию этой функции, определимся с глобальными настройками скрипта, которые должны влиять на формирование маркеров, а значит и на работу этой функции.
Использование объектов 1С: метод v8New()
Итак, согласно исходным требованиям, наш скрипт должен позволять пользователю указывать индивидуальные настройки подписи.
Для их хранения мы объявим в нашем скрипте глобальную переменную Settings и объявим функцию, которая будет считывать настройки, сохраненные пользователем.
Снегопат позволяет использовать в скриптах многие универсальные объекты глобального контекста 1С:Предприятия 8, такие как «Структура», «Таблица значений», «Соответствие» и т.д., создавая их при помощи специального метода v8New(). Первым параметром метод принимает название объекта (строкой), вторым и последующими параметрами – значения, которые должны быть переданы в конструктор объекта. Мы для хранения настроек скрипта будем использовать объект 1С:Предприятия 8 «Структура»:
function getSettings() {
var s = v8New("Структура");
// Настройки по умолчанию.
s.Вставить("ФорматПодписи","%ИмяПользователяОС% %ДатаВремя#ДФ=dd.MM.yyyy%");
s.Вставить("МаркерДобавлено","Добавлено:");
s.Вставить("МаркерУдалено","Удалено:");
s.Вставить("МаркерИзменено","Изменено:");
s.Вставить("ЗакрывающийМаркерБлока","/");
s.Вставить("РазделительКодаПриЗамене","---- Заменено на: ----");
// Дополнительные настройки:
s.Вставить("НеОставлятьКопиюКодаПриЗамене",false);
s.Вставить("НеДобавлятьСигнатуруПослеЗакрывающегоМаркера",false);
return s;
}
Как видите, можно легко использовать русскоязычные имена методов (хотя я лично предпочитаю в скриптах использовать их англоязычные имена).
Вызов метода для инициализации глобальной переменной, в которой будут храниться настройки, добавим в конец скрипта:
var Settings = getSettings();
Пока в методе getSettings() реализована только инициализация настроек значениями по умолчанию. Этого нам достаточно, чтобы приступить к реализации основного функционала. Организацией чтения и сохранения настроек пользователя займемся позже, когда основная часть скрипта будет готова.
Работа с активным текстовым окном: библиотека TextWindow.js
Наша главная функция скрипта addMarker() будет работать с содержимым окна редактора кода 1С, в котором был вызыван макрос.
На сегодня объектная модель Снегопата для этих целей предоставляет объект ITextWindow, который реализует свойства и методы, позволяющие читать и изменять содержимое активного текстового окна. Объект можно получить при помощи метода activeTextWindow(), доступное через глобальное свойство скрипта snegopat.
К сожалению, такая модель позволяет изменять содержимое окна только при помощи работы с выделением текста. Т.е., чтобы изменить какую-либо часть редактируемого в окне текста, необходимо установить выделение на изменяемую часть текста, прочитать содержимое выделения, программно изменить текст и, наконец, установить измененный текст выделения обратно.
На время, пока Александр Орефков трудится над продвинутой реализацией объектной модели Снегопата, для организации традиционного способа работы с текстом был реализован скрипт TextWindow.js - обертка над объектом ITextWindow, реализующая методы, аналогичные методам объекта «Текстовый документ» 1С:Предприятия 8 и несколько собственных методов.
Этот объект избавляет разработчика скриптов от необходимости однообразных манипуляций с выделением текста и делает скрипт независимым от такой модели работы с текстом, а значит, облегчит его портирование на новую модель работы с текстом, которую в скором времени реализует Александр.
Саму библиотеку, как мы помним, мы уже подключили динамически в начале нашего скрипта:
stdlib.require("TextWindow.js",SelfScript);
Библиотека реализует один публичный метод GetTextWindow(), который получает и возвращает объект, представляющий активное текстовое окно. Если активного текстового окна нет, метод вернет значение null.
Итак, вернемся к написанию функции addMarker(). Напомню общий алгоритм работы метода:
- Проанализировать наличие и состояние активного текстового окна (вызывается ли макрос в текстовом окне и выделен ли в нем текст или нет);
- На основе результатов анализа принимать решение, будем ли вставлять маркер блока или «однострочник»;
- Добавлять маркеры указанного типа.
Реализуем алгоритм по шагам. Сначала попытаемся получить активное текстовое окно, и если его нет, прекратим работу скрипта:
var w = GetTextWindow();
if (!w) return;
Для того, чтобы проанализировать, будем ли вставлять маркер в конце строки («однострочник») или же обрамлять им выделенный блок, необходимо получить информацию об установленном в активном окне выделении. Для этих целей у объекта TextWindowWrapper есть метод GetSelection(), который возвращает объект ISelection, имеющий четыре свойства, содержащие координаты выделения:
- beginRow и beginCol – номер строки и колонки начала выделенного текста;
- endRow и endCol – номер строки и колонки окончания выделенного текста.
Нумерация строк и колонок начинается с 1. Это важно всегда иметь в виду, т.к. в JavaScript индексация символов в строке начинается с 0. Не забывайте об этом отличии при выполнении операций получения и установки текста активного окна!
Если в активном окне текст не выделен, то для полученного экземпляра объекта ISelection будут выполняться условия: beginRow == endRow, beginCol == endCol, и значения в свойствах будут указывать на текущую позицию курсора.
Таким образом, если будет выполняться условие beginRow == endRow, то маркер будем размещать в конце строки, в которой находится курсора, в противном случае – будем обрамлять маркерами выделенный текст:
var sel=w.GetSelection();
if (sel.beginRow == sel.endRow)
{
// Однострочник.
}
else
{
// Блок кода.
}
Алгоритм каждой ветки условной конструкции тривиален:
- Получить текст (строки в позиции курсора или выделенного блока);
- Добавить в текст маркеры;
- Установить измененный фрагмент текста.
Для получения одной строки текста, заданной номером, воспользуемся методом GetLine() (можно использовать русскоязычный синоним ПолучитьСтроку()). Для замены строки в тексте, соответственно, предназначен метод ReplaceLine() (ЗаменитьСтроку()). Для формирования маркера используем вызов метода markLine() (его объявим в тексте скрипта как заглушку):
var line = w.GetLine(sel.beginRow);
var code = markLine(markerType,line);
w.ReplaceLine(sel.beginRow,code);
Как видите, работа с содержимым окна редактора в данном случае аналогична программной работе с объектом ТекстовыйДокумент объектной модели 1С:Предприятия 8.
Для работы с блоком текста, заданным его координатами, объект TextWindowWrapper реализует метод Range(). Координаты текста передаются в качестве параметров метода. Возвращает этот метод объект, представляющий методы для чтения (GetText()) и для установки текста блока (SetText()). Воспользуемся ими для установки маркеров выделенного блока. Для формирования маркера блока будем использовать вызов метода markBlock() (его, как и метод markLine() надо объявить в скрипте пока как заглушку):
var endRow = sel.endCol > 1 ? sel.endRow : sel.endRow - 1;
var block = w.Range(sel.beginRow, 1, endRow).GetText();
var code = markBlock(markerType,block);
w.Range(sel.beginRow, 1, endRow).SetText(code);
В данном фрагменте кода надо пояснить еще один момент: определение координаты последней строки выделения и координат колонок. В случае установки маркеров для блока будем открывающий маркер ставить перед первой строкой, а закрывающий – после последней строки блока. Т.е. всегда фактические значения номеров колонок начала и конца выделения учитывать не будем. Первую и последнюю строки будем получать целиком, даже если выделение установлено частично.
Но в ситуации, когда блок выделен целиком (от первой колонки первой строки до последней колонки последней строки), координата последней строки – это координата следующей за выделенным текстом строки. Такой случай проиллюстрирован на скриншоте (стрелка указывает на позицию курсора):
Реальная координата строки, являющейся последней строкой выделения, в этом случае равна
sel.endRow- 1
Приступим теперь к реализации методов markLine() и markBlock().
Реализация алгоритма установки маркеров
Реализацию алгоритмов добавления маркеров рассмотрим на примере реализации метода markLine(). Метод markBlock() реализуется абсолютно аналогичным образом. Я не буду в этот раз приводить описание алгоритма на «человеческом» языке, потому что он достаточно прозрачен, а сразу приведу исходный код и прокомментирую некоторые его части.
function markLine(markerType,line) {
// Удалим концевые пробелы в строке.
var code=line.replace(/(.+?)\s*$/,"$1");
switch(markerType)
{
case MarkerTypes.ADDED:
// Добавляемвхвостподпись.
code = code + getStartComment(markerType);
break;
case MarkerTypes.REMOVED:
// Закомментируем строку и в хвост добавим подпись.
code = commentLine(code) + getStartComment(markerType);
break;
case MarkerTypes.CHANGED:
// Маркер "Изменено" для однострочника такой же как и для блока.
var indent = StringUtils.getIndent(code);
code = indent + getStartComment(markerType) + "\n";
code += prepareChangedBlock(line,indent) + "\n";
code += indent + getEndComment() + "\n";
break;
}
return code;
}
Вызываемые функции commentLine() и commentBlock() добавляют в начало строки и строк блока соответственно символы комментария («//») и возвращают закомментированный таким образом текст.
Объект StringUtils – объект библиотеки TextWindow.js. Он реализует вспомогательные функции по работе с текстом. Мы используем его метод getIndent(), который возвращает отступ в тексте, переданном в качестве параметра. Отступ – это пробельные символы с начала строки и до первого непробельного символа.
Текст маркеров формируется в функциях getStartComment() и getEndComment(). Их реализация также проста и очевидна:
function getStartComment(markerType) {
return "//"+Settings[markerType]+" "+getSignature();
}
function getEndComment() {
var endComment="//"+Settings["ЗакрывающийМаркерБлока"];
if (!Settings["НеДобавлятьСигнатуруПослеЗакрывающегоМаркера"])
endComment+=" "+getSignature();
return endComment;
}
Функция getSignature() – возвращает подпись, сформированную на основе форматной строки, задаваемой в настройках (настройка «ФорматПодписи»).
Метод parseTemplateString() – программный парсинг шаблонов 1С
Теперь настала пора поговорить о способе обработки параметров форматной строки для подписи. Для вычисления значений параметров подстановки используется возможность программного парсинга шаблонов кода 1С, которая предоставляется Снегопатом, а именно, метод parseTemplateString() объекта ISnegopat (объект в скрипте доступен через глобальное свойство snegopat).
Метод в качестве параметра принимает строку с исходным текстом шаблона и вызывает штатный механизм 1С, выполняющий парсинг шаблона. Возвращает строку, полученную в результате выполненения замен управляющих конструкций в шаблоне.
Имена параметров подстановки в нашей форматной строке полностью совпадают с управляющими конструкциями механизма шаблонов. Этим мы и пользуемся в реализации функции parseTpl():
function parseTpl(){
var a=[];
for (var i=0; i<arguments.length; i++)
a.push(arguments[i]);
return snegopat.parseTemplateString('+a.join(',')+'>');
}
Реализация механизма подстановки параметров форматной строки
В целом реализация механизма подстановки параметров форматной строки подписи выглядит следующим образом:
1. Для каждого параметра создается анонимная функция, возвращающая значение этого параметра. Пары имя «параметра – функция» хранятся в виде анонимного объекта в глобальной переменной MarkerFormatStringParameters (аналогично, как бы мы это сделали в 1С при помощи объекта Структура: имя параметра – ключ элемента структуры, функция параметра – значение элемента структуры).
2. MarkerFormatStringParameters пополняется набором предопределенных параметров при помощи метода addFormatStringParam():
addFormatStringParam("ИмяПользователя","parseTpl(name)")
addFormatStringParam("ПолноеИмяПользователя","parseTpl(name)")
addFormatStringParam("ИмяПользователяХранилищаКонфигурации","parseTpl(name)")
Вторым параметром метода передается строка кода на JavaScript, которая должна вычислять и возвращать значение параметра. В методе мы создаем анонимную функцию, вычисляющую переданную строку кода при помощи оператора eval() и добавляем пару «Имя параметра – Функция» в MarkerFormatStringParameters:
function addFormatStringParam(name,code) {
var paramGetter = function(p) {
return eval(code);
}
MarkerFormatStringParameters[name] = paramGetter;
}
3. В функции getSignature() при помощи метода replace()выполняется поиск параметров, вычисление их значения
и подстановка вычисленного значения в форматную строку вместо самого параметра.
function getSignature() {
var fmt=Settings['ФорматПодписи'];
var ptn=/%(.+?)(?:#(.+)){0,1}%/ig;
return fmt.replace(ptn,function(match,p1,p2,offset,s) {
// p1 - имя управляющей конструкции.
// p2 - параметр управляющей конструкции (для ДатаВремя).
return MarkerFormatStringParameters[p1].call(null,p2);
});
}
В качестве второго аргумента метода replace() передается анонимая функция, в которой мы по имени параметра (p1) получаем функцию для вычисления его значения и вызываем ее (о методе call() в языке JavaScript см. здесь).
У параметра «ДатаВремя» есть еще аргумент – форматная строка даты. Ее мы и передаем в качестве второго параметра (p2) в вызов функции получения значения параметра.
Разработка формы скрипта
Итак, основной функционал готов. Приступим к разработке формы настройки скрипта. Для реализации диалоговых форм Снегопат использует механизм обычных (не управляемых) форм 1С:Предприятия 8.
Чтобы создать новую форму скрипта, необходимо воспользоваться командой «Новая форма скрипта» подменю «Действия» командной панели окна Снегопата или вызвать одноименный макрос:
Будет создана и открыта в редакторе форм чистая форма, без единого элемента управления. Редактирование формы осуществляется штатными средствами Редактора форм Конфигуратора 1С:Предприятия 8, хорошо знакомого читателю.
Я реализовал форму следующего вида:
(При нажатии на надпись-гиперссылку с именами параметров в поле ввода "Формат подписи" будет добавляться соответствующий параметр.)
Для удобства сопоставления реквизитов формы с элементами структуры настроек скрипта имена реквизитов я задал такими же, как и соответствующие имена настроек (ключи структуры Settings).
При сохранении формы в качестве расширения имени файла формы обязательно явным образом следует указать расширение *.ssf! В противном случае конфигуратор может «упасть», потеряв созданную и настроенную форму. Это текущая особенность механизма, которую следует учитывать.
При разработке форм скриптов рекомендую использовать следующее соглашение по именованию файлов форм: если скрипт использует только одну форму, то задавать файлу формы же имя, какое имеет файл скрипта.
Программное открытие формы скрипта
Теперь у нас есть форма настроек, и мы можем приступить к реализации макроса «Настройка». При вызове этого макроса мы должны будем открывать форму настроек скрипта.
В объектной модели 1С:Предприятия 8, перед использованием форму надо получить при помощи метода ПолучитьФорму(). В скриптах для Снегопата форму необходимо загрузить из файла. Для этого предназначен метод loadScriptForm(). Первым параметром в метод необходимо передать путь к файлу формы, вторым – объект, в котором будут реализованы обработчики событий формы и ее элементов управления. В нашем случае таким объектом будет сам скрипт (т.е. обработчики мы реализуем непосредственно в глобальной области видимости скрипта):
SelfScript.self['macrosНастройка'] = function() {
// form - неявно определяемая глобальная переменная.
form=loadScriptForm(SelfScript.fullPath.replace(/js$/,'ssf'),SelfScript.self);
form.DoModal();
form=null;
}
Для получения полного пути к форме скрипта мы воспользовались соглашением, о котором говорили в предыдущем параграфе: поскольку имя файла формы и имя файла скрипта у нас отличаются только расширением, то достаточно заменить в полном пути скрипта расширение, чтобы получить полный путь к файлу формы.
Метод loadScriptForm() возвращает объект «Форма» 1С:Предприятия 8, хорошо знакомый 1С:Специалистам.
Обработчики событий формы и элементов управления
Для назначения обработчиков событий их сначала надо создать традиционным для форм 1С образом. В модуль формы будут добавлены объявления:
Процедура ПриОткрытии()
// Вставить содержимое обработчика.
КонецПроцедуры
Процедура КнопкаОкНажатие(Элемент)
// Вставить содержимое обработчика.
КонецПроцедуры
Соблазн написать код обработчиков на языке 1С непосредственно в модуле формы и на языке 1С велик, но его необходимо подавить: реализацию обработчиков необходимо будет написать в файле скрипта.
Т.е. для сгенерированных в модуле формы обработчиков в скрипте необходимо реализовать одноименные функции. Т.е. для приведенных выше обработчиков в скрипт необходимо добавить функции:
function ПриОткрытии(){
// Вставить содержимое обработчика.
}
function КнопкаОкНажатие(Элемент){
// Вставить содержимое обработчика.
}
Автоматически сгенерировать код функций-заглушек на основании кода обработчиков из модуля формы можно при помощи скрипта «Разработка скриптов». Необходимый макрос называется «СформироватьКодОбработчиковФормыНаJavaScript». Перед вызовом макроса необходимо выделить код обработчиков, для которых надо сформировать код для скрипта. Скрипт запросит имя объекта, в котором реализованы методы – обработчики событий. Если эти методы будут реализованы как функции скрипта (как раз наш случай), поле для ввода имени объекта необходимо оставить пустым.
Сгенерированный код будет выведен в окно сообщений, откуда его следует скопировать в файл скрипта.
Удалять пустые обработчики из модуля формы не следует, т.к. при их отсутствии в случае сохранения формы редактор форм может очистить обработчики у элементов управления.
Работа с параметрами обработчиков событий формы и элементов управления
При реализации кода обработчиков событий элементов управления формы и самой формы есть специфичный момент, о котором важно знать. В JavaScript примитивные типы данных (строки, числа и булевы значения) всегда передаются по значению, в то время как объекты передаются по ссылке. Поэтому изменение значения, например, параметра СтандартнаяОбработка в обработчике события «НачалоВыбора» поля ввода не даст ожидаемого эффекта.
Для обхода этой проблемы Снегопат передает в качестве аргументов обработчиков событий не конкретные значения, а объект-обертку этих значений. Этот объект имеет единственное публичное свойство «val», предоставляющее доступ к значению параметра. В этот объект "оборачиваются" не только значения примитивных типов, но и все значения, передаваемые в качестве параметров в функции-обработчики событий.
Проиллюстрируем эту особенность на примере реализации обработчиков события «Нажатие» надписей-гиперссылок, при нажатии на которые в поле ввода «Формат подписи» должен вставляться соответствующий параметр для подстановки:
function НадписьИмяПользователяНажатие(Элемент) {
addToSignatureFormat(form,Элемент.val.Заголовок);
}
Сам метод addToSignature() очень прост и мы не будем описывать его отдельно. Обратите внимание, как в приведенном листинге происходит обращение к свойству «Заголовок»: аргумент Элемент содержит объект-обертку. Реальное значение (элемент управления, в нашем случае – надпись) доступно через его свойство val.
Использование методов глобального контекста 1С
При открытии формы настроек скрипта ее реквизиты необходимо заполнить текущими значениями настройки скрипта из структуры Settings, и наоборот, при сохранении настроек, необходимо обновить значения элементов структуры Settings, используя значения реквизитов формы.
В 1С:Предпрятии 8 существует метод глобального контекста ЗаполнитьЗначенияСвойств(), который мы очень любим использовать для таких задач.
Снегопат позволяет нам использовать в коде скриптов не только объекты общего назначения 1С:Предприятия, но и многие методы глобального контекста 1С:Предприятия, в том числе и метод ЗаполнитьЗначенияСвойств().
Для этих целей мы и подключили в самом начале нашей статьи скрипт global. Чтобы добавить методы глобального контекста в глобальную область видимости нашего скрипта, необходимо в нашем скрипте вызвать метод connectGlobals() скрипта global, передав ему в качестве парметра объект нашего скрипта:
global.connectGlobals(SelfScript);
Теперь нам доступно использования многих методов глобального контекста 1С, в том числе и ЗаполнитьЗначенияСвойств(). Вызовем этот метод для передачи текущих настроек из структуры настроек в реквизиты формы и чтения измененных в форме настроек обратно в структуру:
function ПриОткрытии() {
ЗаполнитьЗначенияСвойств(form,Settings);
}
function КнопкаОкНажатие(Элемент) {
ЗаполнитьЗначенияСвойств(Settings,form);
// ... Здесь будет код сохранения настроек ...
form.Close();
}
Хранение и чтение настроек скрипта
Мы реализовали механизм редактирования настроек. Теперь необходимо научить скрипт сохранять и восстанавливать настройки пользователей между сеансами запуска Конфигуратора.
Для таких целей Снегопат предоставляет специальное свойство profileRoot объекта IDesigner, который реализует методы для работы с хранилищем настроек.
Нас в первую очередь интересуют его методы createValue() – создает значение для хранения настроек в хранилище настроек. setValue() – устанавливает значение настройки и getValue() – получает значение.
Доработаем ранее реализованную функцию getSettings() таким образом, чтобы она считывала и возвращала сохраненные пользователем настройки (напомню, сейчас она всегда возвращает настройки по умолчанию). Для этого необходимо добавить в текущую реализацию всего две строки кода (перед строкой возврата значения функции):
profileRoot.createValue(pflAuthorJs,s,pflSnegopat);
s=profileRoot.getValue(pflAuthorJs);
Первая строчка создает значение настройки, если оно не создано. Первым аргументом передается путь к значению – строка вида «Авторский комментарий/Настройки», мы ее зададим в глобальной переменной, объявление которой добавим где-нибудь в начале скрипта:
var pflAuthorJs='Авторский комментарий/Настройки';
Второй аргумент – значение по умолчанию. Оно будет записано при создании настройки, если ее не существовало. Мы в качестве значения по умолчанию передаем структуру, в которой сохранены настройки скрипта по умолчанию.
Третий аргумент – тип хранилища настроек. В качестве значения аргумента необходимо передать значение перечисляемого типа ProfileStoreType, доступное в глобальной области видимости скрипта. Настройки могут быть сохранены для конкретной информационной базы (значение pflBase), для конкретного пользователя ИБ (pflBaseUser), для информационной базы на конкретном компьютере (pflCompBase), для пользователя информационной базы на конкретном компьютере (pflCompBaseUser), для конкретного компьютера (pflComputer), для сеанса (pflSeanse) и, наконец, для Снегопата (pflSnegopat).
Мы используем последний вариант, подразумевая, что пользователь будет использовать одинаковые настройки подписи для всех информационных баз.
Метод createValue() рекомендуется вызывать всегда перед чтением значения настройки. Если на момент вызова настройка существовала, метод ее не изменит и следующий вызов getValue() получит значение настройки. Если же сохраненной настройки не существовало (например, скрипт запускается впервые), то она будет создана и проинициализирована значением по умолчанию. Таким образом мы гарантируем наличие необходимой настройки всегда.
Заключение
Мы рассмотрели все шаги по созданию скрипта и познакомились с основными возможностями, предоставляемыми объектной моделью Снегопата. Этих знаний вполне достаточно для реализации других скриптов, которые могут помочь вам в выполнении вашей работы.
Конечно же, статья не раскрывает все особенности объектной модели Снегопата, поэтому для полного понимания того, как устроен Снегопат, какие еще возможности он предоставляет и как их можно использовать, следует использовать и другие источники информации.
В первую очередь, это исходные коды существующих на сегодня скриптов. Они содержат действительно наиболее актуальную информацию о приемах реализации тех или иных возможностей при помощи объектной модели Снегопата. Скрипты доступны в публичном официальном репозитории скриптов проекта Снегопат.
Во-вторых, это официальный форум проекта Снегопат, где обсуждаются многие тонкие моменты разработки и использования Снегопата и скриптов.
В-третьих, полезная техническая информация и обсуждения ведутся в комментариях к задачам в багтрекере Снегопата и в багтрекере скриптов.
Возможности, предоставляемые Снегопатом уже сегодня позволяют автоматизировать многие рутинные задачи разработчика. Уже сейчас ведутся работы над созданием альтернативного редактора кода модулей, написан прототип скрипта, реализующего работу с альтернативными системами контроля версий, обсуждаются возможности интеграции конфигуратора и багтрекеров, ведется разработка скрипта для автоматизации выполнения рефакторинга кода 1С.
Есть куча еще не реализованных пожеланий и идей как для новых скриптов, так и для существующих.
Если вам интересно написать скрипт самому или есть идеи или пожелания по функционалу – присоединяйтесь!