Работа с выборками#

На странице описана работа с экземплярами выборок: связи между ними, поиск, передача параметров и режимы открытия. Отдельные разделы посвящены жизненному циклу формы, дополнительным панелям и пользовательской блокировке объектов.

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

К основным элементам выборки относятся:

  • отображения;

  • компоновка формы;

  • фреймы;

  • атрибуты;

  • операции;

  • фильтры.

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

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

Отношения между экземплярами выборок#

Экземпляры выборок, открытые в приложении, связаны отношением «мастер–деталь». Мастер-выборка является родительской для детальной выборки. Детальная выборка имеет доступ к данным и параметрам мастер-выборки.

Корнем дерева выборок приложения является выборка главного меню. Все остальные выборки приложения являются подчинёнными по отношению к ней.

Выборка, являющаяся корнем отдельной формы, имеет доступ к данным выборки главного меню. Экземпляры выборок внутри формы также образуют иерархию «мастер–деталь» в соответствии с логикой отображения данных.

Иерархия выборок используется для передачи параметров и обновления связанных данных.

Поиск открытых выборок#

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

Поиск выборки на форме#

Метод findSelection() ищет выборку в пределах текущей формы:

selection.form.findSelection(name, representation)

Параметры метода:

  • name — системное имя выборки;

  • representation — имя отображения выборки, например List, Card или Panel.

Для поиска рекомендуется передавать точные значения selection.name и selection.representation. Если вместо имени отображения передать пустую строку, поиск выполняется только по системному имени выборки.

Метод возвращает первую найденную выборку или null, если подходящая выборка отсутствует.

Поиск начинается не от текущей выборки и не ограничивается её дочерними элементами. Метод обходит всё дерево формы сверху вниз, поэтому может найти главную, детальную или соседнюю выборку, если она открыта на той же форме.

Пример поиска по системному имени выборки и имени отображения:

val contactPersonSelection = selection.form.findSelection(
  "gtk-ru.bitec.app.bs.contactPerson.Bs_ContactPerson",
  "Card_Body"
)

if (contactPersonSelection != null) {
  contactPersonSelection.refresh()
}

Выборку также можно найти по классу отображения:

selection.form.findSelection(repClass)

Параметр repClass — экземпляр класса отображения искомой выборки.

Пример:

val datasetWidgetSelection =
  selection.form.findSelection(
    Bts_DatasetWidgetAvi.list_ByWidgetSetting()
  )

if (
  datasetWidgetSelection != null &&
  jChangedQurModels.contains(
    datasetWidgetSelection.getSelfVar("idQurModel").asNLong.ns
  )
) {
  datasetWidgetSelection.refreshDetails()
}

В этом варианте системное имя выборки и имя отображения определяются по переданному классу.

Поиск всех подходящих выборок#

Метод findSelections() используется, если на форме может находиться несколько подходящих экземпляров выборки:

selection.form.findSelections(name, representation)

Параметры метода:

  • name — системное имя выборки;

  • representation — имя отображения выборки.

Метод возвращает все найденные выборки или пустой массив, если совпадений нет.

Пример:

selection.form.findSelections(
  "gtk-ru.bitec.app.bs.contactPerson.Bs_ContactPerson",
  "Card_Body"
).foreach(_.refresh())

Поиск также можно выполнить по классу отображения:

selection.form.findSelections(repClass)

Параметр repClass — экземпляр класса отображения искомых выборок.

Пример:

selection.form.findSelections(
  Bts_DatasetWidgetAvi.list_ByWidgetSetting()
).foreach(sel => {
  if (jChangedQurModels.contains(sel.getSelfVar("idQurModel").asNLong.ns)) {
    sel.refreshDetails()
  }
})

Если на форме находится несколько выборок с одинаковыми системным именем и отображением, findSelection() возвращает первую выборку в порядке обхода дерева формы. Для получения всех совпадений используется findSelections().

Поиск выборки по идентификатору экземпляра#

Метод application.findSelectionById() ищет конкретный экземпляр выборки по его runtime-идентификатору:

application.findSelectionById(id, onFound)

Параметры метода:

  • id — runtime-идентификатор экземпляра выборки, полученный из selection.id;

  • onFound — функция, которая выполняется для найденной выборки.

Пример:

val selectionId = selection.id

application.findSelectionById(
  selectionId,
  foundSelection => foundSelection.refresh()
)

Значение selection.id идентифицирует конкретный открытый экземпляр выборки. Оно не является системным именем выборки или идентификатором записи в базе данных.

Идентификатор действует только в рамках текущего сеанса приложения. Если выборка была закрыта или создана заново, прежний идентификатор больше не позволяет её найти.

Если выборка отсутствует, findSelectionById() не выполняет переданную функцию.

Для отдельной обработки этого случая используется метод findSelectionByIdOrElse():

application.findSelectionByIdOrElse(
  id,
  onFound,
  onNotFound
)

Параметры метода:

  • id — runtime-идентификатор экземпляра выборки;

  • onFound — функция, выполняемая для найденной выборки;

  • onNotFound — функция, выполняемая, если выборка не найдена.

Пример:

application.findSelectionByIdOrElse(
  selectionId,
  foundSelection => foundSelection.refresh(),
  () => {
    // Выборка закрыта или пересоздана
  }
)

Поиск по идентификатору используется, когда требуется обратиться именно к ранее сохранённому экземпляру выборки. Например, если на форме открыто несколько выборок с одинаковыми системным именем и отображением.

Параметры выборок#

Параметрами экземпляра выборки могут быть:

  • атрибуты датасета;

  • дополнительно созданные параметры;

  • специальные параметры фрейма.

Иерархия «мастер–деталь» позволяет передавать параметры между экземплярами выборок и автоматически обновлять данные при изменении используемых параметров.

Получение значений параметров#

Для получения значений параметров используются методы getVar() и getSelfVar().

Характеристика

getVar()

getSelfVar()

Область поиска

Текущая выборка и дерево мастер-выборок

Только текущая выборка

Результат поиска

Ближайший параметр с указанным именем

Параметр с указанным именем в текущей выборке

Использование

Получение локальных параметров и параметров мастер-выборок

Получение параметров, которые не должны зависеть от мастер-выборок

Оба метода возвращают значение типа Variant. Для типизированной обработки, в том числе при значении null, результат преобразуется к соответствующему N-типу.

Преобразование значений

Для преобразования Variant используются методы RichVariant.

Метод

Результат

asNLong

NLong

asNDate

NDate

asNNumber

NNumber

asNString

NString

asNBytes

NBytes

asNGid

NGid

Пример преобразования значения к строковому типу:

getSelfVar(sAttr).asNString

Доступ к параметрам мастер-выборки#

Метод getVar() позволяет получать параметры текущей и мастер-выборок.

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

Для обращения непосредственно к параметрам мастер-выборки к имени параметра добавляется префикс super$. В этом случае поиск в текущей выборке не выполняется.

Пример получения строкового параметра непосредственной мастер-выборки:

getVar("super$sCaption").asNString

Для обращения к параметрам мастер-выборки, которая является родительской для непосредственного мастера, используется префикс super$super$.

getVar("super$super$sCaption").asNString

Если параметр отсутствует на указанном уровне, система продолжает поиск выше по дереву отношений «мастер–деталь».

Главная выборка находится на верхнем уровне иерархии и может содержать общие параметры, используемые другими выборками приложения.

Для общих параметров главной выборки используются уникальные имена. Это позволяет обращаться к ним из разных частей приложения с помощью одного префикса super$ и не получать значение параметра с таким же именем из ближайшей мастер-выборки.

Установка значений параметров#

Для установки значения параметра используется метод setVar().

Через setVar() передаются:

  • объекты Java;

  • массивы JVM;

  • типы-значения Scala, наследующие AnyVal, например Int и Boolean;

  • Nullable-типы платформы.

Созданный в Scala объект Array представлен как массив JVM и может использоваться в качестве значения параметра.

Nullable-типы существуют как отдельные обертки на уровне Scala-кода, но во время выполнения представлены соответствующими Java-типами. Например, значение NNumber представлено объектом java.math.BigDecimal.

Внимание

Объекты ссылочных типов Scala, включая scala.List, через setVar() не передаются. При пересоздании загрузчика классов такой объект может стать недоступен открытой выборке.

Передача параметров при открытии выборки#

При открытии выборки в нее можно передать карту дополнительных параметров с помощью метода params().

Пример:

Bs_GoodsAvi.defCard
  .newForm()
  .params(
    Map(
      CardRep.IdItemSharp -> idvGds,
      CardRep.EditingType -> EditingType.edit
    )
  )
  .open()

Переданные параметры добавляются в открываемую выборку.

Получение переданных параметров#

Для получения переданного параметра используется метод getSelfVar().

Если для типа переданного значения отсутствует отдельный метод преобразования RichVariant, метод get() извлекает исходный Object. После этого объект приводится к требуемому типу с помощью asInstanceOf.

Пример получения массива значений Int:

getSelfVar(sAttr).get().asInstanceOf[Array[Int]]

Без вызова get() попытка привести Variant к типу переданного объекта завершится ошибкой преобразования.

Открытие выборки#

Для создания формы используется метод newForm(). После создания форму можно открыть в обычном, модальном режиме или в режиме выбора значения.

Режимы открытия#

Для открытия выборки используются методы:

  • newForm().open() — открывает форму в обычном режиме;

  • newForm().openModal() — открывает модальную форму;

  • newForm().openLookup() — открывает форму в режиме выбора значения.

При открытии любым из этих способов родительской для новой выборки становится выборка главного меню ***_MainMenu. Открытая выборка становится главной выборкой формы, для которой selection.isMainOnForm = true.

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

Параметры, явно переданные через params(), доступны в самой открытой выборке, но не создают ожидаемое отношение с мастер-выборкой.

Открытие выборки для выбора значения#

Метод openLookup() открывает форму, в которой пользователь может выбрать объект и вернуть его данные в вызывающую выборку.

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

Пример открытия формы и обработки выбранного значения:

val data = Btk_ClassAvi
  .listForChoose()
  .newForm()
  .results("id" :: "sCaption" :: Nil)
  .openLookup()

if (data.getLookupResult eq LookupResult.ok) {
  val id = data.getData(1, 0)
  val sCaption = data.getData(1, 1)
}

Метод getLookupResult возвращает результат закрытия формы. Значение LookupResult.ok означает, что пользователь подтвердил выбор.

Метод getData() используется для получения значений полей, указанных в results().

Выбор нескольких значений#

Для выбора нескольких строк в форме, открытой через openLookup(), используется опция useMultiSelect.

Пример:

val data = Btk_ClassAvi
  .listForChoose()
  .newForm()
  .results("id" :: "sCaption" :: Nil)
  .useMultiSelect
  .openLookup()

if (data.getLookupResult eq LookupResult.ok) {
  for (i <- 1 to data.size) {
    val id = data.getData(i, 0)
    val sCaption = data.getData(i, 1)
  }
}

После подтверждения выбора свойство data.size содержит количество выбранных строк. Значения каждой строки можно получить методом getData().

Возможность выделения нескольких строк непосредственно в списке или дереве настраивается отдельно в разметке выборки.

Жизненный цикл формы#

Подробнее о настройке и просмотре аудита открытия форм и выполнения операций см. в разделе «Аудит открытия форм и выполнения операций».

Дополнительные панели#

Дополнительная панель — элемент интерфейса текущей формы, предназначенный для размещения отдельной выборки.

Выборка, открытая внутри панели, становится детальной относительно выборки, из которой была создана панель. Отношение «мастер–деталь» устанавливается автоматически.

Создание и настройка панели#

Для создания панели используется метод createPanelBuilder().

Метод можно вызвать без параметров:

selection.createPanelBuilder()

В этом случае создаётся builder без предварительного указания отображения выборки.

В метод также можно передать класс отображения выборки:

selection.createPanelBuilder(repClass)

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

Пример:

selection.createPanelBuilder(
  Mct_TechProcNormAvi.panel_OperationInfo()
)

Метод возвращает CoreCreatePanelBuilder, с помощью которого последовательно задаются параметры панели:

  • name() — задаёт имя панели, используемое для её идентификации. Имя должно быть уникальным в пределах формы и не заменяет системное имя выборки или имя её отображения;

  • align() — задаёт расположение панели относительно контейнера;

  • width() — задаёт ширину панели в пикселях;

  • params() — передаёт начальные параметры в открываемую выборку;

  • toggle() — отображает или скрывает панель.

Метод align() поддерживает значения:

  • Bottom — размещение по нижней границе;

  • Client — размещение во всём свободном пространстве компоновщика;

  • Left — размещение по левой границе;

  • Right — размещение по правой границе;

  • Top — размещение по верхней границе.

Пример создания, настройки и отображения панели:

selection
  .createPanelBuilder(
    Mct_TechProcNormAvi.panel_OperationInfo()
  )
  .name("Operation Info")
  .align(Align.Right)
  .width(500)
  .params(
    Map[String, Object](
      "param1" -> "value1"
    )
  )
  .toggle()

Методы настройки можно использовать по отдельности или объединять в одну цепочку.

Параметры, заданные через params(), применяются при первом создании панели. При последующих вызовах toggle() их значения не обновляются. Для передачи новых начальных значений панель необходимо создать заново.

При скрытии панели размещённая в ней выборка деактивируется, но не уничтожается. При повторном отображении используется тот же экземпляр выборки.

Для повторного переключения видимости панели достаточно вызвать ту же операцию с createPanelBuilder() и toggle(). Повторно передавать начальные параметры через params() не требуется.

selection
  .createPanelBuilder(
    classOf[
      resmpreport.Pro_ResMPReportChartAvi.Card_ResMPReportChartResourceFilter
    ]
  )
  .toggle()

Связь с мастер-выборкой#

Выборка, открытая через createPanelBuilder(), становится детальной относительно мастер-выборки, из которой был вызван метод.

Для обращения из выборки, размещённой в панели, к мастер-выборке используется метод selection.master():

val masterSelection = selection.master()

Через полученный экземпляр можно вызывать операции мастер-выборки и обновлять её данные.

Для обращения из мастер-выборки к выборке, размещённой в панели, используется метод selection.form.findSelection():

val panelSelection = selection.form.findSelection(
  "PanelSelection",
  "Card"
)

if (panelSelection != null) {
  panelSelection.refresh()
}

Поиск выполняется по системному имени выборки и имени её отображения. Имя панели, заданное методом name(), при поиске не используется.

Подробнее о параметрах и результате метода см. в разделе Поиск выборки на форме.

Параметры мастер-выборки доступны в выборке, размещённой в панели, через префикс super$:

val parentId = getVar("super$PARENT_ID").asJLong()

В примере PARENT_ID — имя параметра мастер-выборки.

Обновление содержимого панели#

При смене текущей записи мастер-выборки содержимое выборки в панели автоматически не обновляется.

Для загрузки данных, соответствующих новой записи мастера, необходимо явно вызвать refresh() у выборки, размещённой в панели:

val panelSelection = selection.form.findSelection(
  "PanelSelection",
  "Card"
)

if (panelSelection != null) {
  panelSelection.refresh()
}

Если панель скрыта, размещённая в ней выборка деактивируется при обновлении. После повторного вызова toggle() отображается тот же экземпляр выборки.

Пользовательская блокировка#

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

Пока бизнес-объект заблокирован одним пользовательским сеансом, изменить его из другого пользовательского сеанса нельзя.

После снятия пользовательской блокировки все объекты, заблокированные этим сеансом, снова становятся доступны для блокирования.

Встроенная пользовательская блокировка#

Пользовательская блокировка устанавливается при начале редактирования объекта в методе Dvi#beforeEdit, сформированном кодогенератором.

override def beforeEdit(): Unit = {
  ...
  val rop = defaultRep.thisRop()

  if (
    rop != null &&
    Set(ReadRopMode, UpdateRopMode).contains(rop.ropMode)
  ) {
    defaultRep.tryUserLock()
  }
  ...
}

Метод lockObject#

Метод lockObject() устанавливает пользовательскую блокировку для одного объекта:

Btk_FormSessionApi().lockObject(gid)

Параметр gid содержит глобальный идентификатор блокируемого объекта.

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

Пользовательский сеанс представлен записью в таблице Btk_FormSession.

Если объект уже заблокирован другим пользовательским сеансом, метод вызывает исключение с описанием блокировки.

При редактировании коллекции заблокированного объекта метод beforeEdit() пытается установить пользовательскую блокировку для мастер-объекта. Поэтому блокировка распространяется на бизнес-объект вместе с его коллекциями.

Метод lockObjectMulti#

Метод lockObjectMulti() устанавливает пользовательскую блокировку для нескольких объектов:

Btk_FormSessionApi().lockObjectMulti(
  Seq[NGid],
  riseError = true
)

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

Параметр riseError определяет обработку объектов, уже заблокированных другим пользовательским сеансом:

  • true — вызвать исключение, если заблокирован хотя бы один из переданных объектов;

  • false — пропустить объекты, уже заблокированные другим сеансом.

Если параметр riseError не передан, используется значение true.

Примеры:

// Вызовет исключение при наличии заблокированных объектов
Btk_FormSessionApi().lockObjectMulti(
  Seq[NGid]
)

// Пропустит объекты, заблокированные другим сеансом
Btk_FormSessionApi().lockObjectMulti(
  Seq[NGid],
  riseError = false
)

Метод lockObjectMultiWithInfErr#

Метод lockObjectMultiWithInfErr() устанавливает пользовательскую блокировку для нескольких объектов и возвращает сведения об объектах, которые уже заблокированы другим сеансом:

Btk_FormSessionApi().lockObjectMultiWithInfErr(
  Seq[NGid]
)

Метод не вызывает исключение и возвращает кортеж:

(Seq[NGid], NString)

Возвращаемые значения:

  • Seq[NGid] — глобальные идентификаторы записей, уже заблокированных другим пользовательским сеансом;

  • NString — сформированное сообщение об ошибке.

Результат метода можно использовать для реализации прикладной обработки конфликтов блокировки.