Когда-то очень давно появилась в разделе идей вот эта запись: http://club.directum.ru/idea/Prosmotr-i-dobavlenie-zapisi-tablicy-v-vide-formy.aspx. В комментариях я предложил альтернативную схему реализации (которую подсмотрел у нашей самописной программы-редактора бюджетных заявок) в виде представления каждой строки таблицы маленькой формой. Денис Баранов предложил тогда реализовать такую схему в веб-браузере на форме. Понадобилось без малого три года на то, чтобы вместе сложились время, место, обстоятельства и возможности, чтобы появилась в свет такая реализация. Технология не прошла еще боевого крещения на пользователях, но получен первый положительный от владельца бизнес-процесса, поэтому спешу представить вашему вниманию вариант реализации. Может быть в процессе обсуждения всплывут какие-то полезные вещи и что-то можно будет исправить до процесса широкой эксплуатации.
Итак, получается примерно вот такая картина:
Сразу приведу плюсы и минусы технологии. Преимущества:
Недостатки:
Переходим к технической части. Стандартный дисклеймер: я не являюсь гуру верстки и дизайна, а JS первый раз использовал где-то в начале этого года. Поэтому код может содержать неточности, ошибки и подводные камни, особенно с учетом того, что одну и ту же задачу на html + js можно решить иногда десятью разными способами. Мне в быстром изучении html, css и js очень помог ресурс http://learn.javascript.ru - мало воды, много практических примеров, многое оттуда перенесено напрямую сюда.
Техническая часть из себя будет представлять простыни кода с необходимыми пояснениями. Разработка велась в PyCharm 3.1 (он у меня уже был установлен), где есть сворачивание блоков кода, подсветка, автокомплит и так далее. Рекомендую пользоваться системой контроля версий, благо есть возможность. В дальнейшем речь будет идти о справочнике, хотя все это справедливо и для форм электронных документов.
Делаем заготовку html-файла в каком-нибудь веб-узле на IIS или просто на диске в файле (для работы пользователей в DIRECTUM предлагается разместить конечный html-файл в самом DIRECTUM и выгружать его во временную папку - так будет обойдена проблема возможной недоступности веб-ресурса):
Элемент input нужен для пересоздания таблицы без полной перезагрузки страницы, будем по нему виртуально щелкать. Такая полная перезагрузка, почему-то, стабильно роняет DIRECTUM в сочетании Win7 + IE11. В других сочетаниях может тоже падает, не проверял. Блок style размещаем для удобства прямо в html-файле, чтобы не возиться с несколькими файлами, при желании можно вынести его в отдельный css. Элемент div у нас будет содержать создаваемую табличную часть, а script - код на Javascript (тоже не выносим в отдельный файл).
В событие "Форма" - "Показ" справочника помещаем код, который будет нам загружать нашу страницу:
filePath = "http://servername/filename.html"
WebBrowserControl = Object.Form.Controls.FindControl("STWebBrowserControl1")
if Assigned(WebBrowserControl)
if Assigned(WebBrowserControl.Document)
WebBrowserControl.Document.getElementById('refreshButton').click()
else
WebBrowserControl.Navigate(FilePath)
endif
endif
Есть небольшая хитрость. Если у пользователя проблемы с отображением веб-контрола или всей страницы, то можно ему показать старую табличную часть. Один из вариантов - можно завести роль, в которой у нас будет список пользователей, для которых отображается старая табличная часть. Тогда размещаем веб-контрол и таблицу в одном месте и регулируем их скрытие/появление (вот только установка Visible в 4.9.1 для веб-контрола не работает, поэтому сдвигаем его за пределы видимости, используя свойство Left).
В событии "Запись" - "Отмена после" размещаем код, который будет обновлять таблицу, если пользователь отменил изменения:
WebBrowserControl = Object.Form.Controls.FindControl("STWebBrowserControl1")
if not VarIsNull(WebBrowserControl)
WebBrowserControl.Document.getElementById('refreshButton').click()
endif
Начинаем заполнять html-файл. Первой создаем "входную функцию", с которой начнется построение таблицы, и сразу ее вызываем. Эта функция делает три вещи. Во-первых, она определяем необходимые для работы данные: датасет, набор доступных для редактирования реквизитов и флаг доступности таблицы для вставки/удаления строк (это параметры для моего случая; для других случаев возможно уменьшение или увеличение параметров для работы функции, неизменным остается только датасет). Если html-файл открыт на форме справочника, то данные берутся из DIRECTUM, в противном случае, мы создаем тестовые заглушки, чтобы наш файл без потери функциональности можно было открывать в браузере. Это дает нам простоту тестирования без привязки к DIRECTUM и консоль/отладку по клавише F12 (выводить информацию в консоль можно, написав в js вызов console.log(<текст>); или можно показывать сообщение, аналогичное ShowMessage(), с помощью alert(<текст>)). Во-вторых, наша стартовая функция формирует таблицу. Ну и в-третьих, добавляется (в случае возможности для текущего пользователя редактировать таким образом таблицу) ссылка "Добавить строку". Текст функции (функции в секцию script можно вставлять в любом порядке - браузер сначала ищет определения и только потом начинает исполнение кода):
function createHTML(){
if (window.external.Form){ //if directum
var ref = window.external.Form.View.Component;
var dds = ref.DetailDataset(1);
var editableControlsList = ref.Params.FindItem('EditableControlsList');
var allowEditTable = (ref.Params.FindItem("AllowEditTable") == 'Yes');
var recordOpened = ref.RecordOpened;
}
else { //for testing
var dds = new getDDSForTesting();
var editableControlsList = new getEditableControlsList();
var allowEditTable = true;
var ref = {};
var recordOpened = true;
}
if (recordOpened) {
dds.First();
var firstDiv = document.getElementById('firstdiv');
for (var i = 0; i < dds.RecordCount; i++){
createNewHtmlRow(firstDiv, dds, true, editableControlsList, allowEditTable);
dds.Next();
}
if (allowEditTable) {
var link = document.createElement('A');
link.onclick = addNewRowToHTMLTable;
link.directumDataset = dds;
link.editableControlsList = editableControlsList;
link.allowEditTable = allowEditTable;
link.innerHTML = 'Добавить новую строку';
link.className = 'addNewRowBtn';
link.id = 'linkAddNewRow';
firstDiv.appendChild(link);
}
}
}
Идем дальше. Функция создания заглушки датасета getDDSForTesting(). Объяснять тут в общем-то нечего, она эмулирует IReference и добавляет три строки в виртуальную таблицу для тестирования.
function getDDSForTesting() {
this.currentRowNum = -1;
this.RecordCount = 0;
this.dataset = [];
//IReference.Next()
this.Next = function() {
this.currentRowNum++;
}
//IReference.Requisites()
this.Requisites = function (requisiteName) {
if (this.RecordCount == 0) {
alert('Набор данных пуст. Установка реквизита ' + requisiteName + ' невозможнa');
return undefined;
}
//gets current table row from dataset
var tableRow = this.dataset[this.currentRowNum];
//gets data from table row by requisite name
if (requisiteName in tableRow) {
return tableRow[requisiteName];
}
else {
var directumRequisite = {};
directumRequisite.AsString = "";
directumRequisite.Value = "";
directumRequisite.Dataset = this;
tableRow[requisiteName] = directumRequisite;
return tableRow[requisiteName];
}
}
//IReference.Locate()
this.Locate = function (requisiteName, requisiteValue) {
var tableRow;
var reqValue;
for (var i = 0; i < this.RecordCount; i++) {
tableRow = this.dataset[i];
reqValue = tableRow[requisiteName].AsString;
if (reqValue === requisiteValue) {
this.currentRowNum = i;
return true;
}
}
return false;
}
//IReference.Delete()
this.Delete = function () {
var iterSteps = this.RecordCount - 1;
for (var i = this.currentRowNum; i < iterSteps; i++) {
this.dataset[i] = this.dataset[i+1];
}
this.RecordCount--;
this.dataset.length--;
if (this.RecordCount == this.currentRowNum) {
this.currentRowNum--;
}
};
//IReference.Insert()
this.Insert = function() {
var newIndex;
var rowNum;
if (this.RecordCount == 0) {
newIndex = 0;
rowNum = '0';
}
else {
newIndex = this.dataset.length;
rowNum = this.dataset[newIndex - 1]['НомСтр'].AsString;
}
this.dataset[newIndex] = {};
//adds one requisite with number of row
this.dataset[newIndex]['НомСтр'] = {};
this.dataset[newIndex]['НомСтр'].AsString = +rowNum + 1;
this.dataset[newIndex]['НомСтр'].Value = +rowNum + 1;
this.dataset[newIndex]['НомСтр'].Dataset = this;
//------
this.currentRowNum = newIndex;
this.RecordCount++;
};
//IReference.First()
this.First = function() {
this.currentRowNum = 0;
};
//adds 3 elements to dataset
this.Insert();
this.Requisites('SIN_StringT').AsString = 'Первый';
this.Requisites('SIN_String2T').AsString = 'Первый 2Т';
this.Insert();
this.Requisites('SIN_StringT').AsString = 'Второй';
this.Requisites("SIN_YesNoT").AsString = 'Нет';
this.Insert();
this.Requisites("SIN_StringT").AsString = 'Третий';
this.Requisites('SIN_YesNoT').AsString = 'Да';
}
Функция получается несколько тяжелая для понимая с учетом того, что IReference содержит в себе и итератор, и данные (в результате нижеприведенный пример выглядит странно), но можно в ней не разбираться, главное, что она работает.
foreach rec in reference
showmessage(reference.Наименование)
endforeach
Следующей будет небольшая функция, которая заменит нам IStringList в простейшем варианте, который разрешает редактировать все контролы:
function getEditableControlsList() {
this.IndexOf = function(requisiteName) {
return 0;
}
}
Можно сделать эту заглушку более интеллектуальной, в зависимости от requisiteName возвращая -1 (элемент недоступен для редактирования) или любое число большее или равное нулю (хотя сам индекс нам не нужен, но функции Find в IStringList, к сожалению, нет). В DIRECTUM этот список можно формировать в событии "Форма" - "Показ", добавляя имена реквизитов, которые разрешено редактировать.
Пропущу пока функцию createNewHtmlRow() и перейду к ссылке/кнопке "Добавить новую строку". На ее событие onclick вешается несложная функция addNewRowToHTMLTable(), которая вызывает ту же самую createNewHtmlRow():
function addNewRowToHTMLTable(evt) {
var dds = this.directumDataset;
var editableControlsList = this.editableControlsList;
var allowEditTable = this.allowEditTable;
dds.Insert();
var firstDiv = document.getElementById('firstdiv');
createNewHtmlRow(firstDiv, dds, false, editableControlsList, allowEditTable);
window.scrollTo(0,document.body.scrollHeight);
}
В последней строке происходит прокрутка экрана до конца страницы, чтобы новая добавленная строка было полностью видима.
Функция createNewHtmlRow() формирует нам строку таблицы. Именно там задается расположение полей и управляющие кнопки/ссылки для строки. Понятно, что набор полей будет уникальным в каждом конкретном случае, поэтому дам общие пояснения.
Текст функции вместе со вспомогательными функциями раздует статью еще в два раза, поэтому приводить его здесь не буду. Они есть в приложенном файле.
Функция rebuildTable(), которая указана для input в файле-заготовке. Это функция просто очищает доступным способом содержимое первого элемента div и вызывает новое построение таблицы:
function rebuildTable() {
var firstDiv = document.getElementById('firstdiv');
try {
firstDiv.innerHTML = '';
} catch(e) {
while(firstDiv.firstChild) {
firstDiv.removeChild(firstDiv.firstChild);
}
}
createHTML();
}
Остался вопрос связи табличной части с html-формой.
if Assigned(Object.View)
WebBrowserControl = Object.Form.Controls.FindControl("STWebBrowserControl1")
if Assigned(WebBrowserControl)
if Assigned(Sender.Value)
value = Sender.DisplayText
else
value = ''
endif
WebBrowserControl.Document.getElementById('orgElem' & Object.DetailDataset(1).НомСтр).value = value
endif
endif
Т.е. при построении таблицы в html, для элемента задается id, состоящий из условного наименования и номера строки. Тогда мы можем в нужный момент найти этот элемент в html и записать туда нужное нам значение.
Приложенный файл открывается в браузере и служит демонстрационной моделью. Для использования его в DIRECTUM нужно исправить функцию createNewHtmlRow(), задав нужный порядок полей и их коды.
От администрации DIRECTUM Club. Материал подан как заявка на DIRECTUM Awards - поддерживайте участника.
Андрей, это офигенно круто!
Спасибо :)
Круто, но ведь нереально трудоемко :(
Авторизуйтесь, чтобы написать комментарий