Это маленькое расширение - фрагмент гораздо более серьёзной системы, но его достаточно, чтобы быстро и удобно организовать запись и последующий просмотр логов, протоколов, описаний действий системы. Ничего нового - размещённые в нужных местах кода вызовы функции накопления лога, как строки с разметкой html, и затем её просмотр по завершении операции. Может применяться в фоновых заданиях и потом возвращаться на вызывающую сторону, может использоваться на сервере, на клиенте, во внешнем соединении.
Механизмы не используют объекты, связанные с html как объектом 1С, всё очень примитивно, конкатенация и строковые теги. Нет проверки правильности записываемых в строку данных (валидации html), это на совести разработчика. Зато и разобраться, и доработать под свои нужны совсем просто. Можно использовать как пример, образец, шаблон и основу своих разработок.
Совершенно никакие модули БСП для работы этого расширения не нужны.
На скриншоте показаны результаты одного из вариантов оформления - с таблицей параметров под спойлером и ошибкой, выделенной красным. Читабельнее, чем сообщения внизу окна)
Есть много солидных серьёзных систем, а это скорее мини-заготовка. Если кто качать не хочет, то вот исходники основной механики, она совсем простая:
#Область ВедениеЛога
// Инициализирует лог - строку, в которую при логировании добавляются новые сообщения с разметкой.
// Всегда вписывает имя текущего пользователя ИБ, может вписывать дату и ИБ инициализации.
// Возвращает строку с начальной разметкой HTML.
//
// Параметры:
// БезШапки - булево; необязательный (по умолчанию Ложь); если Истина - то текущая дата сеанса и строка соединения ИБ не будут вписаны в лог.
//
Функция ИнициироватьЛог(БезШапки=Ложь) Экспорт
рЛог="<html><head></head><body>#ТекстЛога#</body></html>";
ВнестиВЛог(рЛог,"Текущий пользователь: "+ИмяПользователя());
Если не БезШапки Тогда
ВнестиВЛог(рЛог,"Дата инициации: "+Строка(ТекущаяДатаСеанса()));
ВнестиВЛог(рЛог,"База инициации: "+СтрокаСоединенияИнформационнойБазы());
КонецЕсли;
Возврат рЛог;
КонецФункции
// Добавляет в конец тела лог-строки HTML указанные строковые сведения с нужной разметкой.
// Основная рабочая процедура логирования, вызываемая при фиксации записей.
//
// Параметры:
// Лог - строка; собственно дописываемый лог действий;
// Сведения - строка; дописываемые произвольные сведения (могут содержать свои HTML-теги, не противоречащие общим принципам разметки);
// КакПараграф - булево; необязательный (по умолчанию Ложь); если Истина - то вставляемые сведения будут обрамлены тегами параграфа <P>;
// ПодСпойлер - булево; необязательный (по умолчанию Ложь); если Истина - то вставляемые сведения будут обрамлены тегом спойлера <details>;
// ЭтоОшибка - булево; необязательный (по умолчанию Ложь); если Истина - то вставляемые сведения будут даны красным цветом текста.
//
Процедура ВнестиВЛог(Лог, Сведения, КакПараграф=Ложь, ПодСпойлер=Ложь, ЭтоОшибка=Ложь) Экспорт
Попытка
Если ТипЗнч(Лог)<>Тип("Строка") Тогда Возврат КонецЕсли;
Если КакПараграф Тогда
рВставка="<P></P>"+Сведения+"<P></P>";
Иначе
рВставка=Сведения+"<br>";
КонецЕсли;
Если ЭтоОшибка Тогда
рВставка="<font color=""#ff0000"">"+рВставка+"</font>";
КонецЕсли;
Если ПодСпойлер Тогда
рВставка="<details>"+рВставка+"</details>";
КонецЕсли;
рВставка=СтрЗаменить(рВставка,Символы.ПС,"<br>");
Лог=СтрЗаменить(Лог,"#ТекстЛога#",рВставка+"#ТекстЛога#");
Исключение
КонецПопытки;
КонецПроцедуры
// Добавляет в конец тела лог-строки HTML тег разделителя <hr>
//
// Параметры:
// Лог - строка; собственно дописываемый лог действий.
//
Процедура ВывестиРазделитель(Лог) Экспорт
ВнестиВЛог(Лог, Символы.ПС+"<hr>");
КонецПроцедуры
// Фактически, "перегрузка" типового сообщения пользователю, с возможностью при необходимости доработать.
// В текущей версии может включать, например, вызов ВнестиВЛог, где лог ведётся в некоей переменной кэша.
// Также может применяться в фоновых заданиях.
//
// Параметры:
// Сведения - произвольные выводимые пользователю сведения;
// СтатусСообщения - неиспользуемый параметр для совместимости с Сообщить при их глобальной замене на Сообщать.
//
Процедура Сообщать(Сведения, СтатусСообщения=Неопределено) Экспорт
сооб=Новый СообщениеПользователю;
сооб.Текст=Сведения;
сооб.Сообщить();
КонецПроцедуры
#КонецОбласти
#Область РаботаСHTML
Функция ПолучитьHTMLизТаблицыЗначений(рТаблица, Знач рПараметры="", докВладелец=Неопределено) Экспорт
// распознаем параметры
Если ТипЗнч(рПараметры)<>Тип("Структура") Тогда рПараметры=Новый Структура КонецЕсли;
//рКодировка=?(рПараметры.Свойство("Кодировка"),рПараметры.Кодировка,"UTF-8");
рШиринаРамки=?(рПараметры.Свойство("ШиринаРамки"),рПараметры.ШиринаРамки,0); // в пикселях
Если рШиринаРамки<>0 Тогда // имеет смысл
рЦветРамки=?(рПараметры.Свойство("ЦветРамки"),рПараметры.ЦветРамки,"");
рЦветРамкиЯркий=?(рПараметры.Свойство("ЦветРамкиЯркий"),рПараметры.ЦветРамкиЯркий,"");
рЦветРамкиТёмный=?(рПараметры.Свойство("ЦветРамкиТемный"),рПараметры.ЦветРамкиТемный,"");
рТипРамки=?(рПараметры.Свойство("ТипРамки"),рПараметры.ТипРамки,"hsides");
рЛинии=?(рПараметры.Свойство("Линии"),рПараметры.Линии,"all");
КонецЕсли;
рМеждуЯчейками=?(рПараметры.Свойство("МеждуЯчейками"),рПараметры.МеждуЯчейками,0);
рОтступДоЯчейки=?(рПараметры.Свойство("ОтступДоЯчейки"),рПараметры.ОтступДоЯчейки,1);
рШирина=?(рПараметры.Свойство("Ширина"),рПараметры.Ширина,100); // макс.ширина, пиксели или проценты
рВысота=?(рПараметры.Свойство("Высота"),рПараметры.Высота,1);
рЦветФона=?(рПараметры.Свойство("ЦветФона"),рПараметры.ЦветФона,"");
рЦветФонаЗаголовка=?(рПараметры.Свойство("ЦветФонаЗаголовка"),рПараметры.ЦветФонаЗаголовка,"");
рВыравнивание=?(рПараметры.Свойство("Выравнивание"),рПараметры.Выравнивание,"center");
рВыравниваниеГор=?(рПараметры.Свойство("ВыравниваниеГоризонтальное"),рПараметры.ВыравниваниеГоризонтальное,"middle");
рВыравниваниеВерт=?(рПараметры.Свойство("ВыравниваниеВертикальное"),рПараметры.ВыравниваниеВертикальное,"center");
// определимся с документом
Если докВладелец=Неопределено Тогда
док=Новый ДокументHTML("");
телдок=док.СоздатьЭлемент("BODY");
док.Тело=телдок;
Иначе
док=докВладелец;
телдок=док.Тело;
КонецЕсли;
// собственно делаем таблицу
таб=док.СоздатьЭлемент("TABLE");
// ставим параметры таблицы в целом, имеющие свойства в объектной модели 1С
таб.Выравнивание=рВыравнивание; // align
таб.Рамка=СокрЛП(рШиринаРамки); // border
таб.ОтступДоЯчейки=СокрЛП(рОтступДоЯчейки); // cellPadding
таб.РасстояниеМеждуЯчейками=СокрЛП(рМеждуЯчейками); // cellSpacing
таб.ЦветФона=СокрЛП(рЦветФона); // bgColor
таб.Ширина=СокрЛП(рШирина); // width
Если рШиринаРамки<>0 Тогда // имеет смысл
таб.Линии=СокрЛП(рЛинии); // rules (допустимо: all, groups, cols, none, rows)
КонецЕсли;
// ставим параметры таблицы в целом, НЕ имеющие свойств в объектной модели 1С
// ставим их в атрибуты именно к таб, а не ко всему Телу документа, и не к Телу таблицы
атр=док.СоздатьАтрибут("height");
атр.Значение=СокрЛП(рВысота);
таб.Атрибуты.УстановитьИменованныйЭлемент(атр);
//
атр=док.СоздатьАтрибут("cols"); // общее объявление
атр.Значение=СокрЛП(рТаблица.Колонки.Количество());
таб.Атрибуты.УстановитьИменованныйЭлемент(атр);
//
//атр=док.СоздатьАтрибут("nowrap"); // запрет переносов текста (кому понадобится, раскомментите)
//атр.Значение=Истина;
//таб.Атрибуты.УстановитьИменованныйЭлемент(атр);
// аналогичным образом можно устанавливать прочие свойства таблицы в целом, её строк и ячеек
Если рШиринаРамки<>0 Тогда // имеет смысл
атр=док.СоздатьАтрибут("borderColor");
атр.Значение=СокрЛП(рЦветРамки);
таб.Атрибуты.УстановитьИменованныйЭлемент(атр);
атр=док.СоздатьАтрибут("borderColorLight");
атр.Значение=СокрЛП(рЦветРамкиЯркий);
таб.Атрибуты.УстановитьИменованныйЭлемент(атр);
атр=док.СоздатьАтрибут("borderColorDark");
атр.Значение=СокрЛП(рЦветРамкиТёмный);
таб.Атрибуты.УстановитьИменованныйЭлемент(атр);
// доступные типы рамок: void, above, below, lhs, rhs, hsides, vsides, box
атр=док.СоздатьАтрибут("frame");
атр.Значение=СокрЛП(рТипРамки);
таб.Атрибуты.УстановитьИменованныйЭлемент(атр);
КонецЕсли;
// добавляем таблицу и её тело
телдок.ДобавитьДочерний(таб);
телтаб=док.СоздатьЭлемент("TBODY");
таб.ДобавитьДочерний(телтаб);
// делаем заголовок
стро=таб.ВставитьСтроку(1);
стро.ВертикальноеПоложение=рВыравниваниеВерт;
стро.Выравнивание=рВыравниваниеГор;
стро.ЦветФона=рЦветФонаЗаголовка;
//
телтаб.ДобавитьДочерний(стро);
Для каждого кол Из рТаблица.Колонки Цикл
яч=док.СоздатьЭлемент("TD");
яч.ТекстовоеСодержимое=СокрЛП(?(ПустаяСтрока(кол.Заголовок),кол.Имя,кол.Заголовок));
стро.ДобавитьДочерний(яч);
КонецЦикла;
// заполняем таблицу
Для каждого рСтрока Из рТаблица Цикл
#Если Клиент Тогда
ОбработкаПрерыванияПользователя();
#КонецЕсли
стро=таб.ВставитьСтроку(1); // вопреки документации, Индекс это обязательный параметр
// хотя, строку можно добавлять и так: стро=док.СоздатьЭлемент("TR"), но ИндексСтроки поменять будет нельзя!
стро.ВертикальноеПоложение=рВыравниваниеВерт;
стро.Выравнивание=рВыравниваниеГор;
стро.ЦветФона=рЦветФона;
телтаб.ДобавитьДочерний(стро);
// коллекция Ячейки - для чтения, а нам надо добавлять
Для каждого кол Из рТаблица.Колонки Цикл
#Если Клиент Тогда
ОбработкаПрерыванияПользователя();
#КонецЕсли
//яч=стро.ВставитьЯчейку(1); // а вот это почему-то вообще не заработало ни при каких...
яч=док.СоздатьЭлемент("TD"); // поэтому делаем так
рЗначение=рСтрока[кол.Имя];
Если ТипЗнч(рЗначение)=Тип("ТаблицаЗначений") Тогда
яч.ДобавитьДочерний(ПолучитьHTMLизТаблицыЗначений(рЗначение,рПараметры,док));
ИначеЕсли ТипЗнч(рЗначение)=Тип("Массив") Тогда
Для каждого знч Из рЗначение Цикл
#Если Клиент Тогда
ОбработкаПрерыванияПользователя();
#КонецЕсли
Если ТипЗнч(знч)=Тип("ТаблицаЗначений") Тогда
яч.ДобавитьДочерний(ПолучитьHTMLизТаблицыЗначений(знч,рПараметры,док));
Иначе // вложенные массивы поддерживать не будем, но при желании можно сделать
тек=док.СоздатьЭлемент("P"); // можно, например, так
тек.ТекстовоеСодержимое=СокрЛП(Строка(знч));
яч.ДобавитьДочерний(тек);
КонецЕсли;
КонецЦикла;
Иначе // записываем строковое представление
яч.ТекстовоеСодержимое=СокрЛП(Строка(рЗначение));
КонецЕсли;
стро.ДобавитьДочерний(яч);
КонецЦикла;
КонецЦикла;
Если докВладелец<>Неопределено Тогда
Возврат таб; // нужна таблица как ЭлементHTML
Иначе
// нужен полноценный текст результата, записываем получившееся
зап1=Новый ЗаписьHTML;
зап1.УстановитьСтроку(); // вопреки документации, параметр Кодировка не допускается
зап2=Новый ЗаписьDOM; // обойдёмся без явного указания конфигурации записи DOM
зап2.Записать(док,зап1);
Возврат зап1.Закрыть();
КонецЕсли;
КонецФункции
// Строит и возвращает HTML-строку, содержащую оформленную таблицу по данным переданной таблицы значений.
// При ошибке возвращает пустой тег.
//
// Параметры:
// тЗначений - таблица значений с произвольным форматом, чьи данные надо преобразовать в HTML-строку;
// ПодСпойлер - булево; необязательный (по умолчанию Ложь); если Истина - то результатная строка будет обрамлена тегом спойлера <details>.
//
Функция ПостроитьТаблицуHTMLизТаблицыЗначений(тЗначений, ПодСпойлер=Ложь) Экспорт
Попытка
Если ТипЗнч(тЗначений)<>Тип("ТаблицаЗначений") Тогда Возврат "<>" КонецЕсли;
//
рПараметры=Новый Структура;
рПараметры.Вставить("ШиринаРамки",2);
рПараметры.Вставить("ТипРамки","box");
рПараметры.Вставить("ЦветРамки","#99cc00");
рПараметры.Вставить("ЦветРамкиЯркий","#00ff00");
рПараметры.Вставить("ЦветРамкиТемный","#339966");
рПараметры.Вставить("МеждуЯчейками",0);
рПараметры.Вставить("ОтступДоЯчейки",1);
рПараметры.Вставить("Ширина","70%");
рПараметры.Вставить("Высота",1);
рПараметры.Вставить("ЦветФона","#fffacd");
рПараметры.Вставить("ЦветФонаЗаголовка","#ccc085");
рПараметры.Вставить("Выравнивание","center"); // всей таблицы
рПараметры.Вставить("ВыравниваниеГоризонтальное","middle"); // текста в ячейках
рПараметры.Вставить("ВыравниваниеВертикальное","center"); // текста в ячейках
//
ХТМЛ=ПолучитьHTMLизТаблицыЗначений(тЗначений,рПараметры);
рез=ПолучитьСодержимоеТела(ХТМЛ); // т.к. базовая функция обрамляет
//
Если ПодСпойлер Тогда
рез="<details>"+рез+"</details>";
КонецЕсли;
//
Возврат рез;
Исключение
Возврат "<>";
КонецПопытки;
КонецФункции
// Строит и возвращает HTML-строку, содержащую оформленную таблицу по данным переданной структуры или соответствия.
// При ошибке возвращает пустой тег.
//
// Параметры:
// КоллекцияДанных - структура или соответствие с произвольным содержимым, чьи данные надо преобразовать в HTML-строку;
// ПодСпойлер - булево; необязательный (по умолчанию Ложь); если Истина - то результатная строка будет обрамлена тегом спойлера <details>.
//
Функция ПостроитьТаблицуHTMLизКоллекции(КоллекцияДанных, ПодСпойлер=Ложь) Экспорт
Попытка
Если ТипЗнч(КоллекцияДанных)<>Тип("Структура") и ТипЗнч(КоллекцияДанных)<>Тип("Соответствие") Тогда Возврат "<>" КонецЕсли;
//
рТаблица=Новый ТаблицаЗначений;
рТаблица.Колонки.Добавить("Ключ",Новый ОписаниеТипов("Строка",,Новый КвалификаторыСтроки(250)));
рТаблица.Колонки.Добавить("Значение");
рТаблица.Колонки.Добавить("ТипЗначенияКлюча",,"Тип значения");
//
Для каждого киз Из КоллекцияДанных Цикл
строТаблицы=рТаблица.Добавить();
ЗаполнитьЗначенияСвойств(строТаблицы,киз);
строТаблицы.ТипЗначенияКлюча=Строка(ТипЗнч(киз.Значение));
КонецЦикла;
рТаблица.Сортировать("Ключ ВОЗР");
//
рез=ПостроитьТаблицуHTMLизТаблицыЗначений(рТаблица);
//
Если ПодСпойлер Тогда
рез="<details>"+рез+"</details>";
КонецЕсли;
//
Возврат рез;
Исключение
Возврат "<>";
КонецПопытки;
КонецФункции
// Возвращает фрагмент HTML, откуда убраны все теги и оставлено только текстовое содержимое.
// При ошибке возвращает пустую строку.
//
// Параметры:
// ХТМЛ - обрабатываемая исходная строка с тегами;
// ЗаменятьПереносы - булево; необязательный (по умолчанию Истина), если Истина - параграфы и разрывы строк будут заменены на &BR
//
Функция ПолучитьТекстБезТегов(Знач ХТМЛ, ЗаменятьПереносы=Истина) Экспорт
Попытка
Если ЗаменятьПереносы Тогда
ХТМЛ=СтрЗаменить(СтрЗаменить(ХТМЛ,"<br>","&BR"),"<P>","&BR");
КонецЕсли;
//
чтен=Новый ЧтениеHTML;
чтен.УстановитьСтроку(ХТМЛ);
постр=Новый ПостроительDOM;
докДОМ=постр.Прочитать(чтен);
стро=докДОМ.Тело.ТекстовоеСодержимое;
стро=СтрЗаменить(стро,"&BR",Символы.ПС);
//
Возврат стро;
Исключение
Возврат "";
КонецПопытки;
КонецФункции
// Возвращает тело HTML, содержащееся между тегами BODY, включая все его остальные теги.
// При ошибке возвращает пустую строку.
//
// Параметры:
// ХТМЛ - обрабатываемая исходная строка с тегами. Если тегов тела нет, возвращается исходная строка.
//
Функция ПолучитьСодержимоеТела(Знач ХТМЛ) Экспорт
Попытка
Если ПустаяСтрока(ХТМЛ) Тогда Возврат "" КонецЕсли;
//
стро=СтрЗаменить(СтрЗаменить(ХТМЛ,"<BODY>","<body>"),"</BODY>","</body>");
//
пози=СтрНайти(стро,"<body>");
Если пози<>0 Тогда стро=Сред(стро,пози+6) КонецЕсли;
пози=СтрНайти(стро,"</body>",НаправлениеПоиска.СКонца);
Если пози<>0 Тогда стро=Лев(стро,пози-1) КонецЕсли;
//
Возврат стро;
Исключение
Возврат "";
КонецПопытки;
КонецФункции
#КонецОбласти
// Инициирует лог аналогично серверной (только без шапки)
// Возвращает строку с начальной разметкой HTML.
//
Функция ИнициироватьЛог() Экспорт
рЛог="<html><head></head><body>#ТекстЛога#</body></html>";
Возврат рЛог;
КонецФункции
// Открывает форму просмотра лога как HTML-документа; выполняет финальную обработку переданной строки.
//
// Параметры:
// Лог - строка; собственно выводимый лог, должен иметь правильную HTML-разметку;
// ВходПараметры - структура; необязательный (по умолчанию пустая структура), допустимы ключи:
// Заголовок - строка; если указан, то устанавливается как заголовок формы просмотра лога;
// Владелец - форма клиентского приложения; если указана, то ставится как владелец формы просмотра.
//
Процедура ОткрытьПросмотрЛога(Знач Лог, ВходПараметры=Неопределено) Экспорт
Если ТипЗнч(Лог)<>Тип("Строка") Тогда
Логирование.Сообщать("Лог имеет неверный тип ("+Строка(ТипЗнч(Лог))+"), его просмотр не имеет смысла!"); Возврат;
КонецЕсли;
Если ПустаяСтрока(Лог) Тогда
Логирование.Сообщать("Лог пуст, его просмотр не имеет смысла!"); Возврат;
КонецЕсли;
//
Лог=СтрЗаменить(Лог,"#ТекстЛога#","");
парф=Новый Структура("ДокументХТМЛ",Лог);
Если ТипЗнч(ВходПараметры)<>Тип("Структура") Тогда ВходПараметры=Новый Структура КонецЕсли;
Если ВходПараметры.Свойство("Заголовок") Тогда парф.Вставить("Заголовок",ВходПараметры.Заголовок) КонецЕсли;
Если ВходПараметры.Свойство("Владелец") и ТипЗнч(ВходПараметры.Владелец)=Тип("ФормаКлиентскогоПриложения") Тогда
рВладелец=ВходПараметры.Владелец;
Иначе
рВладелец=Неопределено;
КонецЕсли;
//
ОткрытьФорму("ОбщаяФорма.ПросмотрДокументаHTML",парф,рВладелец,Истина);
КонецПроцедуры
// Собственно выводит сообщение пользователю, но перед этим позволяет его обработать произвольным образом,
// используется по всей конфигурации вместо "Сообщить".
//
Процедура Сообщать(Сведения) Экспорт
сооб=Новый СообщениеПользователю;
сооб.Текст=Сведения;
сооб.Сообщить();
КонецПроцедуры
И ещё там есть простенькая общая форма просмотра html, где размещено одно строковое поле с видом "Поле html-документа" и процедурой:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
ПолеХТМЛ=Параметры.ДокументХТМЛ;
//
Если Параметры.Свойство("Заголовок") Тогда
ЭтотОбъект.Заголовок=Параметры.Заголовок;
КонецЕсли;
КонецПроцедуры
Назначение расширения - "Дополнение", есть основная роль и не-интерфейсная подсистема для всех объектов расширения.
Тестировалось под 8.3.18.1289 и 8.3.18.1363, но заработает на любом релизе выше 8.3.14 (где появился WebKit).
Кому пригодится - будет хорошо.