Работа с JavaScript в ELMA

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

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

JS работает с веб-страницей через интерфейс DOM (объектная модель документа). Согласно этому интерфейсу – страница представляется в виде дерева объектов, обладающих различными свойствами, через которые и осуществляется управление содержимым страницы и выполнение различных интерактивных сценариев.

Разберем примеры применения JS для реализации различного функционала в ELMA. В качестве дополнительных инструментов будем использовать инструменты разработчика, встроенные в браузер Google Chrome.

Переход на нужную вкладку задачи документооборота и скрытие лишних вкладок

Базовые задачи документооборота ("Ознакомление с документом", "Согласование документа") не позволяют создать форму задачи через Конструктор форм. Поэтому всегда по умолчанию в ней содержатся вкладки: Ознакомление - где приведена информация о документе и вынесенном в задачу контексте, Предпросмотр, О задаче, История, Документ.

Со стороны заказчика часто возникает требование максимально минимизировать интерфейс и уменьшить количество действий пользователя. При открытии задачи ознакомления логично сразу переходить на вкладку Предпросмотр и скрывать "лишние" по смыслу (для обычного пользователя) вкладки.

Откроем задачу и вызовем инструменты разработчика (кнопка F12). Первым делом реализуем переход на вкладку "Предпросмотр" при открытиии страницы. Основные задачи:

  1. Найти нужный элемент в объектной модели страницы.
  2. "Сымитировать" клик по элементу.

Для гарантированного поиска элемента необходимо определить какие-либо ключевые параметры элемент. Нажмем кнопку выбора элемента на странице и кликнем по заголовку вкладки Предпросмотр. В дереве элементов вкладки Elements панели разработчика будет отображен HTML-код этого элемента (рис. 1).

 

Рис. 1. Выбор элемента на странице и его отображение в дереве объектов страницы

У данного элемента есть различные атрибуты: class, href. Достаточно "уникальным" идентификатором элемента можно считать часть строки-значения атрибута href - PreviewTab. Чтобы убедиться, что на странице нет других элементов, у которых в значении атрибута href присутствует подобная подстрока, можно воспользоваться поиском данного текста в исходном коде страницы (сочетание клавиш Ctrl+U).

Используя данную "уникальную" строку, мы можем найти элемент в DOM страницы при помощи jQuery - набора специальных функций для упрощения взаимодействия JavaScript и веб-страницы. Поиск элементов осуществляется при помощи так называемых "селекторов". Будем использовать селектор поиска элемента по значению атрибута ([attribute$=value]). В нашем случае необходимо найти элемент по части значения атрибута href. Поэтому придется использовать нестрогий поиск. Итоговая конструкция будет представлять из себя следующее: 

$('a[href*="PreviewTab"]')

Символ * указывает, что мы ищем элементы по части строки. Другие примеры использования функций и параметры можно найти в интернете. Результатом выполнения этой функции будет являться коллекция. Протестировать функцию можно на вкладке Console инструментов разработчика браузера Google Chrome (рис. 2).

Рис. 2. Отладка выполнения функций JS в браузере

После выполнения функции будет отображен результат ее выполнения и, в данном случае, найденная коллекция элементов. Так как нам нужен сам элемент, необходимо обратиться к нему через индекс. Рекомендуется также реализовать проверку наличия элементов в коллекции (иначе при ошибке это может привести к невыполнению остальных функций JS на странице). Собственно для перехода на вкладку используется метод элемента страницы click(), который имитирует щелчок мышью по нужному элементу.

Итоговый сценарий на языке JS для поиска элемента и клика:

var prevTabList = $('a[href*="PreviewTab"]');
if (prevTabList.length>0)
{
prevTabList[0].click();
}

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

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

Рис. 3. Элементы DOM страницы, связанные с вкладками

Также реализуем необходимые проверки работы каждого селектора. Пример кода JS для скрытия всех вкладок:

var listTaskInfo = $('a[href = "#2-3"]');
if (listTaskInfo.length>0)
{
	listTaskInfo[0].style.display = 'none';
}  
var listHistory = $('a[href*="Common/EntityHistory/"]');
if (listHistory.length>0)
{
	listHistory[0].style.display = 'none';
}
var listDoc =  $('a[href*="/Documents/Document/TabView"]');
if (listDoc.length>0)
{
	listDoc[0].style.display = 'none';
}
var listOpenDoc = $('a[href*="/Documents/Document/View"]');
if (listOpenDoc.length>0)
{
	listOpenDoc[0].style.display = 'none';
}

Чтобы данный код (переход на нужную вкладку и скрытие "лишних") выполнялся автоматически при загрузке страницы задачи ознакомления, его необходимо встроить в код этой страницы. Для этого, в Дизайнере ELMA открываем настройку задачи "Ознакомление с документом". На вкладке Контекст выносим любую переменную и переходим к редактированию ее свойств. На вкладке Системные нажимаем кнопку Редактировать в поле Форма Razor (рис. 4).

 

Рис. 4. Привязка Razor-формы к основной форме задачи

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

<script type="text/javascript">
//чтобы код срабатывал при открытии страницы - его надо связать с событием загрузки страницы (load)
$(window).load(function()
{ 
	//ищем и "кликаем" по вкладке предпросмотр
	var prevTabList = $('a[href*="PreviewTab"]');
	if (prevTabList.length>0)
	{
	prevTabList[0].click();
	}
	//ищем и скрываем вкладку "О задаче"
	var listTaskInfo = $('a[href = "#2-3"]');
	if (listTaskInfo.length>0)
	{
	listTaskInfo[0].style.display = 'none';
	}  
	//ищем и скрываем вкладку "История"
	var listHistory = $('a[href*="Common/EntityHistory/"]');
	if (listHistory.length>0)
	{
	listHistory[0].style.display = 'none';
	}
	//ищем и скрываем вкладку "Документ"
	var listDoc =  $('a[href*="/Documents/Document/TabView"]');
	if (listDoc.length>0)
	{
	listDoc[0].style.display = 'none';
	}
	//ищем и скрываем ссылку "открыть в новом окне"
	var listOpenDoc = $('a[href*="/Documents/Document/View"]');
	if (listOpenDoc.length>0)
	{
	listOpenDoc[0].style.display = 'none';
	}
	//позиционируем страницу в начало
	window.scrollTo(0, 0);   
}
);
</script>

На рисунке 5 представлена структура кода в Razor-форме элемента с пояснениями.

Рис. 5. Диалог редактирования Razor

После сохранения и публикации процесса форма задачи ознакомления в браузере выглядит так, как показано на рисунке 6.

Рис. 6. Результаты работы сценария на JS

Сохранение и обработка контекста процесса по кнопке с формы задачи

Рассмотрим пример, когда нам необходимо изменить контекст процесса по нажатию кнопки на форме задачи и сохранить его. При этом, задача не должна завершаться (кнопка не должна являться кнопкой перехода с задачи процесса).

При обычном использовании сценария, в котором мы поменяем значение атрибута контекста – контекст будет изменен на текущей форме, однако сохранится в экземпляре только после совершения перехода по процессу или же нажатия кнопки Действия Сохранить.

Таким образом, решение задачи сводится к трем шагам:

  1. Изменить контекст процесса на форме.
  2. Отследить событие изменения контекста.
  3. "Сымитировать" нажатие кнопки Действия - Сохранить.

Для примера выполним изменение переменной с типом Пользователь с пустого значения, на "Администратор" по кнопке, расположенной рядом со значением переменной контекста.

В контексте процесса добавим переменную "Изменяемый пользователь", имя свойства "ChangedUser". Также добавим строковую переменную "NeedSaveContext", ее назначение будет пояснено позже. Воспользуемся конструктором форм задачи и разместим элементы на форме (рис. 7).

Рис. 7. Пример формы задачи с кнопкой изменения контекста

На кнопку установим сценарий, изменяющий значение основной переменной контекста и нашей вспомогательной переменной "NeedSaveContext".

public virtual void ChangeUser (Context context, EleWise.ELMA.Model.Views.FormViewBuilder<Context> form)
{
    //изменение вспомогательной пременной
    context.NeedSaveContext = "yes";
    //изменение основной переменной контекста: тут может быть изменение какого угодно количества переменных и вызов любых других сценариев
    context.ChangedUser = EntityManager<User>.Instance.LoadOrNull(1L);
}

Поле со служебной переменной обязательно должно быть доступно для редактирования! Однако, для удобства пользователей его лучше скрыть. Это можно сделать в сценарии на загрузке формы.

public virtual void OnFormLoad (Context context, EleWise.ELMA.Model.Views.FormViewBuilder<Context> form)
{
    form.For(c => c.NeedSaveContext).Visible(false);
}

Чтобы сымитировать нажатие кнопки Действия - Сохранить надо узнать, какие действия осуществляются при ее нажатии. Переходим в форму задачи процесса в браузере, открываем инструменты разработчика и выделяем нужный элемент в меню тулбара. Нас интересует обработка события нажатия мыши "onclick" (Рис. 8).

 

Рис. 8. Инспектирование кнопки "Действия - Сохранить"

По событию "onclick" выполняется код JS: 

$('#SelectedConnectorUid').val('00000000-0000-0000-0000-000000000000');submit_ignore_validate($('form:first'));

Данный код нам нужно повторить при нажатии нашей кнопки Изменить пользователя

Далее необходимо на форму добавить панель, в настройках указываем "Без стиля" и пустой заголовок. Переходим в редактирование Razor разметки панели. Аналогично тому, как это было описано ранее (рис. 4).

В коде разметки необходимо добавить код:

<script type="text/javascript">
//ajaxSuccess - это событие получения любой информации с сервера. Оно выполняется после загрузки формы, при срабатывании любой динамики на форме и т.п.
$(document).ajaxSuccess(function(event, xhr, setting)
{ 
	//Все поля на форме сопоставлены с контекстом процесса и имеют id = "Entity_<НазваниеПеременнойКонтекста>. 
	//Однако к значению атрибутов можно обратиться только у полей, у которых доступно редактирование (даже если они скрыты на форме при помощи сценария)
//Сравниваем значение атрибута с тем, которое мы установили по сценарию, привязанному к кнопке 
	if ($('#Entity_NeedSaveContext ').val()== 'yes')
	{
		//перед перезагрузкой страницы - "сбрасываем" флаг того, что нужно сохранить контекст на пустую строку, это позволит не исполнять данный код JS во время загрузки страницы после сохранения контекста (так как там тоже будет выполнено событие ajaxSuccess
		$('#Entity_NeedSaveContext ').val('');
		//выполняем имитацию нажатия кнопки меню "Действия" "Сохранить"
		$('#SelectedConnectorUid').val('00000000-0000-0000-0000-000000000000');
		submit_ignore_validate($('form:first'));    
	}
}
);
</script>

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

 

Рис. 9. Результат работы кнопки

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

Простое добавление нужных функций позиционирования страницы и имитации клика по вкладке в событие onLoad не подходит – так как они будут выполняться при любом открытии задачи, а не только после нажатия на кнопку.

Для подобного решения можно использовать установку cookie ограниченного времени действия.

У объекта document существует строковый атрибут cookie, который можно прочитать вызвав на вкладке "Console" инструментов разработчика функцию document.cookie.

Для установки cookie в тело описанной выше функции (выполняемой по событию ajaxSuccess) добавить вначале.

//вычисляем срок жизни cookie, в данном случае 2 секунды от текущего момента. Время зависит от быстродействия загрузки страницы
var date = new Date(new Date().getTime() + 2 * 1000);
//устанавливаем cookie со значением «ButtonWasPress» и заданным ранее сроком действия
document.cookie = "ButtonWasPress; path=/; expires=" + date.toUTCString();

Таким образом, после перезагрузки страницы мы можем проверить наличие установленной cookie, реализовав в теле функции, привязанной к событию загрузки страницы.

$(window).load(function()
{
	//проверяем наличие установленной ранее cookie
	if (document.cookie.indexOf("ButtonWasPress")!=-1)
	{
		//"скроллим" страницу в браузере на 100 пикселей вниз
		window.scrollTo(0, 100);
	}
}
);

Вместо "скроллинга" страницы можно использовать и переход на нужную вкладку.

Стоит отметить, что в данном примере, cookie сохраняется для всех страниц данного сайта, и, в течении этих 2 секунд cookie будет доступно на любой странице сервера ELMA. Если в иных формах будут использованы подобные проверки и такое же название cookie, это также вызовет срабатывание сценария JS. Данный момент можно решить – сделав проверку заголовка страницы или задав разные имена cookie для разных форм задач.

Рассмотренные примеры позволяют решить множество небольших доработок интерфейса, которые по различным причинам невозможно выполнить при помощи сценариев C# в дизайнере ELMA.

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