Когда-то очень давно появилась в разделе идей вот эта запись: 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 - поддерживайте участника.
Андрей, это офигенно круто!
Спасибо :)
Круто, но ведь нереально трудоемко :(
Авторизуйтесь, чтобы написать комментарий