В прошлой статье я рассказал, какие возможности скрывает в себе поле HTML-документа, выведенное на форму 1С. Сегодня подробнее остановлюсь на возможностях языков CSS (язык стилей и некоторых элементов поведения) и JavaScript (язык программирования, используемый в веб-разработке), которые могут пригодиться в работе с 1С-интерфейсом.

Демонстрационный пример

Работу с JS и CSS рассмотрим на примере разработки вот такого индикатора, который будет показывать для текстового поля, сколько символов еще можно напечатать.

Пример я буду реализовывать на демоконфигурации «Управляемое приложение», платформа 8.3.25.

Подготовка элемента формы и базовое наполнение

Подключим наш индикатор к полю с дополнительной информацией на форме элемента контрагента.

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

<html>
  <head>
    <meta http-equiv="content-type" content="text/html;charset=utf-8">
    <link rel="stylesheet" type="text/css" href="__STYLE__">
    <style type="text/css">
      /* Styles */
    </style>
  </head>
  <body>
    <!-- HTML code -->
    <script>
      // Script
    </script>
  </body>
</html>

В тексте HTML-документа используется сразу три языка:

  • язык разметки HTML,

  • язык стилей CSS,

  • язык программирования поведения JavaScript.

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

Инициализацию поля HTML-документа выполним в процедуре, вызываемой из обработчика ПриСозданииНаСервере(), а отдельные смысловые части вынесем в соответствующие функции:

&НаСервере
Процедура ИнициализироватьHTMLИндикатор()
	
	ТекстHTMLДокумента = 
	"<html>
	|  <head>
	|    <meta http-equiv=""content-type"" content=""text/html;charset=utf-8"">
	|    <link rel=""stylesheet"" type=""text/css"" href=""__STYLE__"">
	|    <style type=""text/css"">
	|      /* Styles */
	|      &amp;ТекстСтилей&amp;
	|    </style>
	|  </head>
	|  <body>
	|    <!-- HTML code -->
	|    &amp;ТекстHTML&amp;
	|    <script>
	|      // Script
	|      &amp;ТекстJS&amp;
	|    </script>
	|  </body>
	|</html>";
	
	ТекстHTMLДокумента = СтрЗаменить(ТекстHTMLДокумента, "&amp;ТекстHTML&amp;", ТекстHTML());
	ТекстHTMLДокумента = СтрЗаменить(ТекстHTMLДокумента, "&amp;ТекстСтилей&amp;", ТекстCSS());
	ТекстHTMLДокумента = СтрЗаменить(ТекстHTMLДокумента, "&amp;ТекстJS&amp;", ТекстJavaScript());
	
	HTMLИндикатор = ТекстHTMLДокумента;
	
КонецПроцедуры

Функция ТекстHTML()
	
	Возврат 
	"<div class=""progressbar"">
	|  <div class=""fillbar"">
	|  </div>
	|  <p class=""info"">
	|    145
	|  </p>
	|</div>";
	
КонецФункции

Функция ТекстCSS()
	
	Возврат 
	".progressbar {
	|  position: relative;
	|  width: 104px;
	|  height: 27px;
	|  border: 1px solid #0aa;
	|  border-radius: 4px;
	|}

	|.fillbar {
	|  position: absolute;
	|  top: 2px;
	|  left: 2px;
	|  width: 45px;
	|  height: 21px;
	|  border: 1px solid #aa0;
	|  border-radius: 3px;
	|  background-color: #aa0;
	|  z-index: 2;
	|}

	|.info {
	|  position: absolute;
	|  right: 5px;
	|  top: 8px;
	|  margin: 0;
	|  padding: 0;
	|  font-size: 10px;
	|  z-index: 3;
	|}";
	
КонецФункции

Функция ТекстJavaScript()
	
	Возврат "";
	
КонецФункции

В результате при открытии формы, получим статичный элемент индикатора.

Теперь наша задача — вдохнуть в него жизнь и наделить определенным поведением:

  • по мере редактирования поля комментария индикатор должен показывать число оставшихся символов от максимальной длины текста;

  • длина заливки цветом должна соответствовать процентной доле объема текста;

  • цвет заливки будет меняться в зависимости от заполненности поля: зеленый при заполнении до 70%, желтый при 71% – 90%, красный при заполнении более 90% от допустимой максимальной длины.

Анатомия HTML-документа

Прежде чем начать манипуляции с HTML-полем, немного остановимся на том, как оно устроено изнутри.

Язык HTML

HTML-текст нашего элемента буквально рассказывает, какие элементы будут присутствовать в разметке. У нас их всего 3:

  • сам элемент-индикатор (тег div), обведенный рамкой;

  • вложенные в него цветовой ползунок (вложенный div);

  • элемент «параграф» (тег «p»), содержащий строковое представление числа символов.

Язык CSS

Сама по себе HTML-разметка не представляет большого интереса с точки зрения визуального результата. За взаимное расположение элементов и их внешний вид полностью отвечает язык описания стилей CSS.

Чтобы «сопоставить» элемент разметки при описании его стилей, в языке CSS выполняется позиционирование на элементе по так называемому Селектору. Типов селекторов существует с десяток, основные из них:

  • Позиционирование по имени тега. Например: «div {}»  — стили будут применены ко всем элементам с тегом «div». Хотя для некоторых задач это бывает удобно, но в целом такой подход не очень гибкий, поскольку мы жестко привязаны к семантике расстановки HTML-тегов, а их разнообразие не так велико.

  • Позиционирование по ID элемента: «#myid {}» — применить к элементу с идентификатором myid. Сложность применения такого селектора в том, что идентификаторы должны быть уникальными в рамках всего HTML-документа, и за этим нужно дополнительно следить. К тому же этот подход не рекомендуется Стандартами. 

  • Позиционирование по имени класса: «.info {}» (начиная с точки). Такой метод наиболее предпочтителен, и именно он используется в современной веб-индустрии. Управлять классами можно очень гибко, а задавать классы отдельным элементам разметки можно прямо в HTML-тексте в виде отдельного параметра тега class, что и сделано в нашем примере.

Открываем портал в HTML и управляем контекстом элемента

Динамически изменять число символов на индикаторе будем из кода на языке JavaScript, а инициализировать событие обновления будет обработчик текстового элемента формы ИзменениеТекстаРедактирования(). Его единственная задача — вызвать нашу процедуру ОбновитьHTMLИндикатор() (см. ниже).

Заполним текст модуля JS, который возвращает функция ТекстJavaScript():

Функция ТекстJavaScript()
	
	Возврат "
	|var maxLenght = 255; // Переменная, в которой будем хранить максимальную длину текста
	|
	|function updateText(num) {  // Объявляем функцию изменения текста параграфа
	|  var item = document.querySelector('.info'); // Находим интересующий элемент разметки по селектору
	|  item.textContent = (maxLenght - num); // Заменяем текстовый контекст элемента
	|}";
	
КонецФункции

А теперь хорошая новость. Все, что объявлено в коде JavaScript, сразу становится доступным «снаружи», для этого не нужно специальным образом объявлять переменные или функции экспортными. Однако нужно заранее позаботиться о наличии «портала» для проникновения в код документа. Таким промежуточным звеном выступает сущность, которую условно назовем «контекст окна HTML». Хранить его будем в отдельной переменной модуля, а инициализировать в обработчике события ДокументСформирован() элемента «Поле HTML-документа».

&НаКлиенте
Перем КонтекстДокументаHTML;

&НаКлиенте
Перем КонтекстОкнаHTML;

&НаКлиенте
Процедура HTMLИндикаторДокументСформирован(Элемент)
	
	Если НЕ HTMLЭлементыИнициализированы Тогда // Атрибут формы для предотвращения повторного запуска
		HTMLЭлементыИнициализированы = Истина;
		
		КонтекстДокументаHTML = Элемент.Документ;
		КонтекстОкнаHTML 	= КонтекстДокументаHTML.parentWindow;
		Если КонтекстОкнаHTML = Неопределено Тогда
			КонтекстОкнаHTML = КонтекстДокументаHTML.defaultView;	
		КонецЕсли;
		
		КонтекстОкнаHTML.maxLenght = 100; // Инициируем переменную, объявленную в модуле JS
		
	КонецЕсли;
	
	ОбновитьHTMLИндикатор();
	
КонецПроцедуры

Вызываем функцию JavaScript из кода 1С

Теперь у нас все готово для манипуляции HTML-документом из кода 1С. Задействуем контекст окна HTML, чтобы вызвать функцию updateText(), объявленную в модуле JavaScript:

&НаКлиенте
Процедура ОбновитьHTMLИндикатор()
	
	Если Не HTMLЭлементыИнициализированы Тогда
		Возврат;
	КонецЕсли;
	
	ТекДлина = СтрДлина(Элементы.ДополнительнаяИнформация.ТекстРедактирования);
	КонтекстОкнаHTML.updateText(ТекДлина);
	
КонецПроцедуры

Изменяем стили на лету

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

Так, стили можно непосредственно задать в HTML-элементе в виде атрибута тега style. Причем такие стили будут иметь более высокий приоритет, чем те, что находятся в подключенном модуле. А нам это как раз на руку.

Среди стилей нашего класса fillbar нас интересуют два: width (ширина) и background-color (цвет заливки). Если их прописать непосредственно в коде HTML, то определение тега будет выглядеть так:

<div class="fillbar" style="width: 45px; background-color: #aa0">

Для работы со стилями из JS-кода у элемента есть служебное свойство style, от которого через точку можно задать значение любого CSS-свойства. С одной оговоркой: если имя свойства составное (как background-color), то в языке JavaScript оно преобразуется в camel-case нотацию и превращается в backgroundColor. Полный текст JS-функции, который управляет цветовым индикатором, будет выглядеть следующим образом:

&НаСервере
Функция ТекстJavaScript()
	
	Возврат "
	|// ...
	|
	|function fillColor(num) {
	|  // получаем элемент цветового индикатора
	|  var item = document.querySelector('.fillbar');
	|  // рассчитываем параметры
	|  var count = 100 - Math.round((maxLenght - num) / maxLenght * 100);
	|  var color = ""#0f0"";
	|  if (count > 70) {color = ""#ff0""}
	|  if (count > 90) {color = ""#f00""}
	|  // задаем новые стили заливки
	|  item.style.width = count + ""px"";
	|  item.style.backgroundColor = color;
	|}";
	
КонецФункции

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

&НаКлиенте
Процедура ОбновитьHTMLИндикатор()
	
	//...
	КонтекстОкнаHTML.updateText(ТекДлина);
	КонтекстОкнаHTML.fillColor(ТекДлина);
	
КонецПроцедуры

Еще один элемент

Наконец, добавим еще один элемент к нашему индикатору, заодно познакомимся с несколькими дополнительными приемами JS и CSS.

Теперь под нашим индикатором появится вот такой спидометр, стрелка которого будет указывать, насколько мы близки к пределу.

Фон спидометра будет украшать приятная глазу градиентная заливка, а края - отбрасывать тень, придавая элементу объем. Стрелка будет не просто перемещаться, но еще плавно менять цвет от зеленого к красному, в зависимости от положения.

Для этого дополним код HTML новыми элементами, а в CSS опишем стили для новых классов:

Функция ТекстHTML()
	
	Возврат 
	"
	|  ...
	|  <div class=""arrow_wrap""> // подложка спидометра
	|    <div class=""arrow""> // из этого сделаем стрелку
	|    </div>
	|  </div>
	|</div>";
	
КонецФункции

Функция ТекстCSS()
	
	Возврат 
	"...	
	|.arrow_wrap {
	|  position: relative;
	|  margin-top: 50px;
	|  width: 100px;
	|  height: 50px;
	|  min-width: 104px;
	|  min-height: 27px;
	|  border: 1px solid #aaa;
	|  border-radius: 50% 50% 0 0;
	|  box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.2), // тень
	|              5px 5px 10px rgba(0, 0, 0, 0.1);
	|  background: linear-gradient(217deg, // градиент
	|      rgba(255, 150, 150, 0.8),
	|      rgba(255, 150, 150, 0) 70.71%),
	|      linear-gradient(127deg,
	|      rgba(150, 255, 150, 0.8),
	|      rgba(150, 255, 150, 0) 70.71%),
	|      linear-gradient(336deg,
	|      rgba(150, 150, 255, 0.8),
	|      rgba(150, 150, 255, 0) 70.71%);
	|}

	|.arrow {
	|  position: absolute;
	|  width: 30px;
	|  height: 5px;
	|  border-radius: 10px;
	|  top: 45px;
	|  left: 20px;
	|  transform-origin: right center 0;
	|  
	|  background-color: rgb(0, 200, 0); // эти два свойства будем менять из кода
	|  transform: rotate(90deg);
	|}";
	
КонецФункции

Суть движения стрелки — в использование CSS-трансформаций:

  • transform-origin задает опорную точку элемента, относительно которой он будет двигаться;

  • transform указывает функцию трансформации, в нашем случае это вращение с указанием значения угла.

Осталось добавить код на JS, который будет «двигать» стрелку:

Функция ТекстJavaScript()
	
	Возврат "
	| ...
	|function duePersent(num) {
	|  return Math.round((maxLenght - num) / maxLenght * 100);
	|}
	|function updateArrow(num) {
	|  var item = document.querySelector('.arrow');
	|  
	|  var due = duePersent(num);
	|  var deg = 180 - Math.round(due*180/100);
	|  var rr = 200 - Math.round(due*200/100);
	|  var gg = Math.round(due*200/100);
	|  
	|  var transformValue = `rotate(${deg}deg)`;
	|  var bgcValue = `rgb(${rr}, ${gg}, 0)`;
	|  
	|  item.style.backgroundColor = bgcValue;
	|  item.style.transform = transformValue;
	|}
	|function updateAll(num) {
	|  var count = num;
	|  if (num > maxLenght) {
	|    count = maxLenght;
	|  }
	|  
	|  updateText(count);
	|  fillColor(count);
	|  updateArrow(count);
	|}";
	
КонецФункции

Поведением стрелки управляет функция updateArrow(). Цвет и угол наклона устанавливаются из рассчитанных значений CSS-свойств через style. А сложное представление самих значений формируется путем шаблонизации строк. Такое возможно, если заключить строки не в кавычки, а в обратные бэктики (клавиша «Ё» на клавиатуре).

Наконец, все функции управления поведением мы объединили в одну updateAll(), в которой дополнительно выполняем контроль переполнения значения.

&НаКлиенте
Процедура ОбновитьHTMLИндикатор()
	
	Если Не HTMLЭлементыИнициализированы Тогда
		Возврат;
	КонецЕсли;
	
	ТекДлина = СтрДлина(Элементы.ДополнительнаяИнформация.ТекстРедактирования);
	КонтекстОкнаHTML.updateAll(ТекДлина);
	
КонецПроцедуры

CSS-анимации

В заключении познакомимся с анимацией. Существует несколько способов заставить что-то двигаться с помощью CSS и/или JS. Мы выбрали CSS-анимации с использованием keyframes. Этот метод довольно прост, но в то же время достаточно гибкий, т. к. позволяет задать произвольное значение трансформации в произвольные отметки времени («кадры» — отсюда и название).

Добавим два дополнительных класса CSS, отвечающих за анимацию:

Функция ТекстCSS()
	
	Возврат 
	"...
	|@keyframes bounce {
	|    0% {
	|      transform: translateY(-2px);
	|    }
	|  
	|    20% {
	|      transform: translateY(2px);
	|    }
	|  
	|    40% {
	|      transform: translateY(-1px);
	|    }
	|  
	|    60% {
	|      transform: translateY(1px);
	|    }
	|  
	|    80% {
	|      transform: translateY(-1px);
	|    }
	|  
	|    100% {
	|      transform: translateY(0);
	|    }
	|}
	|
	|.modal-error {
	|    animation: bounce 0.3s; // общая продолжительность сценария анимации
	|}";
	
КонецФункции

Теперь в JS-коде, в функции контроля переполнения присвоим класс modal-error классу-основанию спидометра, чтобы заставить его немного потрястись. Это дополнительно привлечет внимание пользования при переполнении.

Функция ТекстJavaScript()
	
	Возврат "
	|var arrowBody = document.querySelector("".arrow_wrap""); // переменная модуля для доступа к элементу
	|
	| ...
	|function updateAll(num) {
	|  
	|  ...
	|  if (num > maxLenght) {
	|    arrowBody.classList.remove(""modal-error""); // удаляем класс, если он был присвоен ранее
	|    arrowBody.offsetWidth = arrowBody.offsetWidth; // делаем что-то с элементом, чтобы браузер его обновил
	|    arrowBody.classList.add(""modal-error""); // снова присваиваем класс, чтобы возбудить анимацию
	|  }
	|}";
	
КонецФункции

Результат:

Заключение

Конечно, в рамках двух статей невозможно описать все тонкости веб-технологий для поля HTML-документа. Ибо они практически безграничны! Моя цель: на простом рабочем примере показать, что из себя представляют новые для 1С-ника языки HTML, CSS и JavaScript. И как их применить при разработке новых интерфейсных элементов, если возможностей 1С недостаточно.

PS. Весь представленный в статье веб-код можно посмотреть и опробовать по ссылке: https://codepen.io/stasganiev/pen/RwzqOpW.

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


  1. yarkov
    21.11.2024 08:57

    Какая жесть этот ваш 1С ))


    1. MaximRV
      21.11.2024 08:57

      чойта