Использование контекстных проверок в сложных диалогах

12 0

При разработке больших диалогов (с количеством полей ~35-37) требуется учитывать человеческий фактор. Важно предусмотреть все вероятные ошибки заполнения полей и обеспечить удобство использования диалога.

В качестве примера рассмотрим диалог настройки модуля «Финансовый архив» версии DIRECTUM 5.6.

Подготовка к разработке

Перед тем как приступить к разработке, рекомендуется:

  1. Сгруппировать поля по смыслу: распределить по вкладкам и/или добавить группы с заголовками. Во-первых, психологически проще работать с небольшим количеством полей. Во-вторых, проще найти нужное поле для точечного изменения.
  2. Определить список обязательных для заполнения полей.
  3. Определить, какие проверки нужно сделать для заполнения полей, сколько и где их применять.

Дополнительно определить список компонент системы, связанных с диалогом. В диалоге настройки модуля «Финансовый архив» мы не только запрашиваем значения констант, ролей, групп, но и проверим корректность заполнения связанных с константами справочников, записей пользователей и прочее.

Типы проверок по заполнению полей

Проверки делятся на ошибки и предупреждения.

  • К ошибкам относится некорректное заполнение значения поля, которое может привести к дальнейшей (пусть локальной) неработоспособности системы. Например, если не указать ответственного за финансовый архив, то при возникновении ошибок в модуле не удастся их найти и, как следствие, оперативно решить. Часть возможностей системы заблокируется этой ошибкой.
  • К предупреждениям относятся некритичные ошибки или уведомления, которые можно просто принять к сведению или временно отложить. Например, в диалоге настройки «Финансовый архив» по регламенту необязательно сразу указывать записи справочников Настройки согласования входящих/исходящих первичных учетных документов. Это может сделать инициатор задания на согласование. Система же о регламенте не знает, поэтому предупредить пользователя об отсутствии настройки стоит.

Еще предупреждения используются на вкладке с настройками интеграции с 1С, так как интеграция с 1С может не использоваться в компании. Однако после того, как выбрана интегрированная система, связанные поля будут проверяться на корректность.

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

Предупреждения для пользователя, особенность хинтов

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

Как можно предупреждать пользователя?

  1. Добавить кнопку, по которой можно запустить проверку и отобразить список ошибок.
  2. Раскрасить цветом обязательные поля, которые были не заполнены, или поля, которые были заполнены некорректно.
  3. Вывести текст ошибки InplaceHint на форме:

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

Как работают проверки в веб-контролах

В нашем примере на вкладку диалога добавлен веб-контрол, который содержит текст ошибки, совершенной на конкретной вкладке:

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

Если ошибки при проверке не выявлены, в веб-контроле на зеленом фоне появляется надпись «Настройка выполнена успешна».

Если ошибок много, размер веб-контрола может вырасти на неопределенную высоту. Поэтому в веб-контроле диалога мы выводим только одну ошибку, а с остальными ошибками предлагаем ознакомиться по ссылке:

Полный список ошибок открывается в отдельном окне (немодальном, чтобы удобнее было исправлять):

Для возможности проверки всех вкладок сделали «Общую» вкладку, на которой наличие ошибок отображается иконкой:

Таким образом администратору не потребуется лишний раз переключаться по всем вкладкам, чтобы найти ошибку. Плюс общая вкладка служит для того, чтобы построить некоторый «алгоритм», по которому следует идти при первоначальной настройке: настроить связанные модули, потом пойти последовательно по вкладкам.

Как это реализовано

Все реализовано через методы. Общая схема выглядит так:

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

Вычисление действия Общая проверка выглядит так:

  IsError = FALSE
  ResDoc: IList = Object.OnExecute_CheckDoc
  ResIntegr: IList = Object.OnExecute_CheckIntegration(FALSE)
  ResRoute: IList = Object.OnExecute_CheckProcesses
  ResRole: IList = Object.OnExecute_CheckRole     
     
  Object.Form.InplaceHint.Visible = FALSE
  
  CheckResult = CreateList()
  // Проверка вкладки "Документы"
  ErrorList: IStringList = ResDoc.ValueByName("error")
  CheckDoc = IfThen(ErrorList.Count > 0; 'no'; 'yes')
  CheckResult.Add("CheckDoc"; CheckDoc)
  // Проверка вкладки "Группы и роли"
  ErrorList: IStringList = ResRole.ValueByName("error")
  CheckRole = IfThen(ErrorList.Count > 0; 'no'; 'yes')                                 
  CheckResult.Add("CheckRole"; CheckRole)
  // Проверка вкладки "Деловые процессы"
  ErrorList: IStringList = ResRoute.ValueByName("error")
  CheckProcesses = IfThen(ErrorList.Count > 0; 'no'; 'yes')
  CheckResult.Add("CheckProcesses"; CheckProcesses)
  // Проверка вкладки "Интеграция с УС"
  ErrorList: IStringList = ResIntegr.ValueByName("error")
  CheckIntegr = IfThen(ErrorList.Count > 0; 'no'; 'yes')
  CheckResult.Add("CheckIntegration"; CheckIntegr)  
  Index = 0
  CountCheck =  CheckResult.Count
  while Index < CountCheck
    if CheckResult.Values(Index) == 'no'
      IsError = TRUE
      Index = CheckResult.Count 
    endif
    Index = Index + 1
  endwhile
  Result = IsError
        
  Object.CreateWebControlMain(CheckResult)

Метод CreateWebControlMain изменяет веб-контрол на главной вкладке для того, чтобы обновить иконки.

Если на главной вкладке отмечена вкладка с ошибками, то проще всего на нее перейти с веб-контрола. Для этого сделали функцию Change:

           function Change(SLTabSheetName){
              var ob = window.external.Form.View.Component;
              var NewTab = ob.Form.Controls.FindControl(SLTabSheetName);
              NewTab.Activate();     
            }

В xml-файле этот блок имеет вид:

<point
name = "Документы"
description = "Соответствие видов документов DIRECTUM и систем обмена"
state = "yes"
ref = "javascript:Change(&quot;SLTabSheet1&quot;);"
/>

Таким образом с главной вкладки можно легко перейти на другие.

Сами проверки и отображение контекстных хинтов реализованы в методах, разных для вкладок. Например, код в методе вкладки Документы выглядит так:

  DELIMITER = ';'
  ERROR_CONST = "error"
  WARNING_CONST = "warning"  
  DOCKIND_CONTROL_PREFIX = 'DocKind%sT'
  WEB_BROWSER_CONTROL_NAME = 'WebBrowserCheckDoc'
  
  Controls = Object.Form.Controls
  
  ResultListDoc = CreateList()
  // Список предупреждение
  WarningList = CreateStringList()
  WarningList.Delimiter = DELIMITER
  // Список ошибок
  ErrorList = CreateStringList()
  ErrorList.Delimiter = DELIMITER
  // Результат контекстной проверки 
  ResultListDoc.Add(ERROR_CONST; ErrorList)
  ResultListDoc.Add(WARNING_CONST; WarningList)
  // Список реквизитов с ошибками
  ReqError = CreateStringList()
  ReqError.Delimiter = DELIMITER
  
  // Проверка на заполненность обязательных полей
  Index = 0
  while Index < 10
    RecIndex = IfThen(Index = 0; ''; Index)
    ReqName = Format(DOCKIND_CONTROL_PREFIX; RecIndex)
    Control = Controls.FindControl(ReqName)
    NotCheckReq = Not (ReqName == CheckReqName) 
    if (not Assigned(Control.Text) and NotCheckReq) or ReqName == AnnulateReqName
      TextErr = Object.ReplaceHTMLSpecialSymbols(LoadStringFmt('DIR67E5EDE0_FBCB_479F_86C7_FCF21CC25821'; 'DFA'; Control.Title))
      ErrorList.Add(TextErr)
      ReqError.Add(ReqName)    
    endif
    Index = Index + 1
  endwhile
  
  ReqTypeCard = CreateStringList()
  ReqTypeCard.Delimiter = DELIMITER
  // Поля для заполнения типов карточек
  ReqTypeCard.Add('DocType1')
  ReqTypeCard.Add('DocType2')
  ReqTypeCard.Add('DocType3')
  ReqTypeCard.Add('DocType4')
  ReqTypeCard.Add('DocType5')
  Object.SetColorAndTooltip(ReqTypeCard; FALSE)
  // Проверка на заполненность обязательных полей
  Index = 0  
  while Index < ReqTypeCard.Count
    ReqName = ReqTypeCard.Values(Index)
    Requisite = Object.FindRequisite(ReqName)
    if Assigned(Requisite) 
      if not Assigned(Requisite.Value)
        TextErr = Object.ReplaceHTMLSpecialSymbols(LoadStringFmt('DIR67E5EDE0_FBCB_479F_86C7_FCF21CC25821'; 'DFA'; Requisite.Title))
        ErrorList.Add(TextErr)
        ReqError.Add(ReqName)
      endif
    endif
    Index = Index + 1
  endwhile     

  // Соответствие видов документов и типов карточек
  Constants = CreateList()
    // Акт
  Constants.Add('DocType1' ;'DocKindT|DocKind1T')
  // Накладная
  Constants.Add('DocType2';'DocKind2T|DocKind3T')
  // Счет-фактура
  Constants.Add('DocType3';'DocKind4T|DocKind5T')
  // УПД  
  Constants.Add('DocType4';'DocKind6T|DocKind7T')
  // УПД со счетом-фактурой  
  Constants.Add('DocType5';'DocKind8T|DocKind9T')
  
  // Проверить соответствие типов карточек и видов документов
  TypeCardRef = References.SYSREF_EDOCUMENT_CARDS
  DocKindRef = References.SYSREF_EDOCUMENT_KINDS
  
  DocKindList = CreateStringList()
  DocKindList.Delimiter = '|'
  
  ReqNames = CreateStringList()
  ReqNames.Delimiter = '|'
  Index = 0
  IsErrorFillTypeCard = FALSE
  while Index < Constants.Count  
    DocKindList.DelimitedText = Constants.Values(Index) 
    Requisite = Object.FindRequisite(Constants.Names(Index))
    if Assigned(Requisite) 
      DocTypeCode = Trim(Requisite.AsString)
      Controls.FindControlByRequisite(Requisite).Color.Reset 

      TextHint = CreateStringList()
      TextHint.Delimiter = '; '
      
      foreach DocKind in DocKindList    
        DataSet = Object.DetailDataSet(1)
        I = 1  
        DataSet.First
        while I <= DataSet.ActualRecordCount      
          Requisite = DataSet.FindRequisite(DocKind)
          Controls.FindControlByRequisite(Requisite).Color.Reset 
          if Assigned(Requisite) 
            DocKindCode = Trim(Requisite.AsString)
            if Assigned(DocKindCode) and Assigned(DocTypeCode) 
              if not DFACheckIfKindConformsToType(;; DocKindCode; DocTypeCode)
                ReqError.Add(DocKind)
                TextErr = LoadStringFmt('DIR11E21F92_5A3F_48C7_BA3B_426D687E5F91'; 'DFA'; ArrayOf(DocKindRef.ObjectInfoByCode(DocKindCode).Name; TypeCardRef.ObjectInfoByCode(DocTypeCode).Name))
                TextErr = Object.ReplaceHTMLSpecialSymbols(TextErr)
                ErrorList.Add(TextErr)
                IsErrorFillTypeCard = TRUE
              endif  
            endif
          endif
          DataSet.Next
          I = I+ 1
        endwhile   
      endforeach 
      if IsErrorFillTypeCard 
        ReqError.Add(Constants.Names(Index))
        IsErrorFillTypeCard = FALSE
      endif      
      Index = Index + 1
    endif
  endwhile   
      
  if ReqError.Count > 0
    Object.SetColorAndTooltip(ReqError)            
  endif      

  Constants = nil
  
  Result = ResultListDoc
  
  AllErr = ErrorList.Count + WarningList.Count
  // Проверить количество всех ошибок и предупреждений. Если ошибка только одна, текст ошибки отобразить в хинте
  if AllErr == 1
    if ErrorList.Count = 1
      Object.CreateWebControlHint(WEB_BROWSER_CONTROL_NAME; ErrorList.Values(0); 'error';; FALSE)
    else
      Object.CreateWebControlHint(WEB_BROWSER_CONTROL_NAME; WarningList.Values(0); 'warning';; FALSE)
    endif      
  else
    if ErrorList.Count > 0
      Object.CreateWebControlHint(WEB_BROWSER_CONTROL_NAME; LoadString('DIR63766F90_C203_44A7_B820_731397F734A9'; 'DFA'); 'error'; AllErr)
      Object.CreateWebControlDFAError(ResultListDoc; WEB_BROWSER_CONTROL_NAME)
    else
      if WarningList.Count > 0
        Object.CreateWebControlHint(WEB_BROWSER_CONTROL_NAME; LoadString('DIRA4D9413A_46E9_462D_8F7B_F87969AD3147'; 'DFA'); 'warning'; WarningList.Count)
        Object.CreateWebControlDFAError(ResultListDoc; WEB_BROWSER_CONTROL_NAME)
      else
        Object.CreateWebControlHint(WEB_BROWSER_CONTROL_NAME; LoadString('DIR15E77C9B_A599_4B39_800B_A31556314B16'; 'DFA'); 'success')
      endif
    endif
  endif

В вычислениях формируется два разных списка: с ошибками и с предупреждениями. Для отображения хинта используются методы:

· CreateWebControlHint. Для отображения хинта на вкладке.

· CreateWebControlDFAError. Для подготовки информации о полном списке ошибок. Если ошибок несколько, отображает список в отдельном диалоге.

Содержимое метода CreateWebControlHint:

  Controls = Object.Form.Controls     
   
  TempPath = Format("%sDFADialog\"; GetTempFolder())
  CreateFile(TempPath; 'D') 

  XMLDesc = CreateList()
  if ShowDialog
    HintArr = ArrayOf(Hint; CheckResult; LoadString('DIRBF1D520B_3D27_4A48_8163_9328059CECA3'; 'DFA'); Format("javascript:DialogShow(%s, &quot;%s&quot;);"; ArrayOf(CountError; WebBrowserControlName)))
  else
    HintArr = ArrayOf(Hint; CheckResult; ''; '')
  endif
  XMLDesc.Add('point'; HintArr)
  FilePath = Object.CreateXMLForWebControl(XMLDesc; "Hint";; WebBrowserControlName)   

  WebBrowserControl = Controls.FindControl(WebBrowserControlName)
  if not VarIsNull(WebBrowserControl) 
    WebBrowserControl.Navigate(FilePath) 
  endif
  XMLDesc = nil

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

  MAIN_MODE = "Main"
  HINT_MODE = "Hint"
  XMLDesc: IList = List
  if not Assigned(Path) 
    Path = GetTempFolder() & "DFADialog" & "\"  
  endif
  CreateFile(Path; 'D')
  
  Result = ''
    
  ConstructorDesc = CreateList()
  FileName = ''
  if Mode == MAIN_MODE
    ConstructorDesc.Add('title'; ArrayOf('name'; 'ref'; 'url'))
    ConstructorDesc.Add('browser'; ArrayOf('name'; 'ref'; 'url')) 
    ConstructorDesc.Add('point'; ArrayOf('name'; 'description'; 'state'; 'ref'))
    ConstructorDesc.Add('default'; ArrayOf('name'))
    FileName = Path & "ReportDFADialog" & ".xml"
  else
    if Mode == HINT_MODE  
      ConstructorDesc.Add('point'; ArrayOf('hint';'state'; 'ref'; 'url'))
      FileName = Path & "ReportDFADialogHintFor" & WebBrowserControlName & ".xml"
    else
      ConstructorDesc.Add('point'; ArrayOf('hint';'state'))
      FileName = Path & "BrowserError" & WebBrowserControlName & ".xml"
    endif
  endif
  Createfile(FileName)
  
  XMLStringList = CreateStringList()
  XMLStringList.Delimiter = CR
  XMLStringList.Add('<?xml version="1.0" encoding="windows-1251"?>')
  XMLStringList.Add(Format("<?xml-stylesheet type='text/xsl' href='DFADialog%s.xsl'?>"; Mode))
  XMLStringList.Add("<Instruction>")
  Index = 0
  while Index < XMLDesc.Count
    NameElement = XMLDesc.Names(Index)
    Arr = XMLDesc.Values(Index)
    XMLStringList.Add(Format("<%s"; NameElement))
    Constructor = ConstructorDesc.ValueByName(NameElement)
    Ind = 0
    foreach Element in CArrayElement(Constructor)
      XMLStringList.Add(Format('%s = "%s"'; ArrayOf(Element; Arr[Ind])))
      Ind = Ind + 1
    endforeach
    XMLStringList.Add("/>")
    Index = Index + 1
  endwhile
  XMLStringList.Add("</Instruction>")
  
  XMLDesc = nil
  ConstructorDesc = nil
  
  WriteFile(FileName;; XMLStringList.DelimitedText)  
  Object.CreateXSLForWebControl(Mode; Path)
  Object.CreateCSSForWebControl(Mode; Path)
  Result = FileName

CreateXSLForWebControl и CreateCSSForWebControl – это методы, которые формируют css- и xsl-файлы для использования в веб-контроле. Аналогично, но через функции, реализовано создание файлов в отчете по состоянию поручений. В данном случае использование методов предпочтительнее, т.к. разработка сконцентрирована в одной компоненте, что позволяет проще перенести функционал в другую базу.

Пока комментариев нет.

Авторизуйтесь, чтобы написать комментарий