Совмещаем выбор из справочника со вводом вручную
Совмещаем выбор из справочника со вводом вручную¶
Important
Начиная с Tessa 1.18.2, данный механизм не требуется. Для создания полей, в которых пользователь может как выбрать элемент из справочника, так и ввести вручную, используйте свойства “Разрешить ручной ввод” и “Поле для ручного ввода” элемента управления “Ссылка\Reference”. Добавив к этому простое расширение, которое при сохранении карточки автоматически создает элемент справочника, если ввод был осуществлен вручную, и подставляет его идентификатор в карточку, вы получите удобное и простое заполнение справочника без переключений контекстов ввода. Данный пример оставлен в руководстве для общего развития.
Пусть надо разработать карточку SomePartnerCard, в которой пользователь либо даёт ссылку на существующего контрагента из справочника, либо вводит все данные контрагента вручную, после чего контрагент позже заносится в справочник. Сложность в том, что контрагент содержит не только ID и имя, но также и другие поля, такие как полное имя FullName, адрес Address и телефон Phone.
Справочник контрагентов расположен в таблице Partners. Для простоты это физическая таблица, хотя она может быть и виртуальной, если контрагенты расположены во внешней подсистеме, такой как 1С.
Представление Partners¶
Поля из этой таблицы выбираются через представление Partners, которая предоставляет ссылку Partner на контрагента со всеми полями в ней (не только с ID и Name). Также представление предоставляет параметр Name для фильтрации по имени контрагента.
Метаинформация:
Метаданные представления в формате JSON. Замените алиас представления и группу, если в вашем представлении они другие.
Example
{
"Alias": "Partners",
"Appearance": null,
"Appearances": null,
"AutoWidthRowLimit": null,
"Caption": "Partners",
"Columns": [
{
"Alias": "PartnerId",
"Appearance": null,
"Caption": null,
"Condition": null,
"DisableGrouping": false,
"HasTag": false,
"Hidden": true,
"Localizable": false,
"MaxLength": null,
"SortBy": null,
"TreatValueAsUtc": false,
"Type": "Guid Not Null"
},
{
"Alias": "PartnerName",
"Appearance": null,
"Caption": "Контрагент",
"Condition": null,
"DisableGrouping": false,
"HasTag": false,
"Hidden": false,
"Localizable": false,
"MaxLength": null,
"SortBy": "p.Name",
"TreatValueAsUtc": false,
"Type": "String(Max) Null"
},
{
"Alias": "PartnerFullName",
"Appearance": null,
"Caption": "Полное название",
"Condition": null,
"DisableGrouping": false,
"HasTag": false,
"Hidden": false,
"Localizable": false,
"MaxLength": null,
"SortBy": "p.FullName",
"TreatValueAsUtc": false,
"Type": "String(Max) Null"
},
{
"Alias": "PartnerAddress",
"Appearance": null,
"Caption": "Адрес",
"Condition": null,
"DisableGrouping": false,
"HasTag": false,
"Hidden": false,
"Localizable": false,
"MaxLength": null,
"SortBy": null,
"TreatValueAsUtc": false,
"Type": "String(Max) Null"
},
{
"Alias": "PartnerPhone",
"Appearance": null,
"Caption": "Телефон",
"Condition": null,
"DisableGrouping": false,
"HasTag": false,
"Hidden": false,
"Localizable": false,
"MaxLength": null,
"SortBy": null,
"TreatValueAsUtc": false,
"Type": "String(Max) Null"
}
],
"ConnectionAlias": null,
"DefaultSortColumns": null,
"EnableAutoWidth": false,
"ExportDataPageLimit": null,
"Extensions": null,
"GroupingColumn": null,
"MultiSelect": false,
"Overrides": null,
"PageLimit": null,
"Paging": "No",
"Parameters": [
{
"Alias": "Name",
"AllowedOperands": [
{
"::single_type": "str"
}
],
"AutoCompleteInfo": null,
"Caption": "Краткое название",
"Condition": null,
"DisallowedOperands": [
{
"::single_type": "str"
}
],
"DropDownInfo": null,
"Hidden": false,
"HideAutoCompleteButton": false,
"Multiple": true,
"RefSection": null,
"SourceViews": null,
"TreatValueAsUtc": false,
"Type": "String(Max) Null"
}
],
"QuickSearchParam": null,
"References": null,
"RowCounterVisible": false,
"RowCountSubset": null,
"SelectionMode": "Row",
"Subsets": null,
"TreatAsSingleQuery": false,
"TreeGroup": null,
"TreeGroupDisplayValue": null,
"TreeGroupId": null,
"TreeGroupParentId": null,
"TreeId": null,
"TreeParentId": null
}
Запрос
select
Id as PartnerId,
Name as PartnerName,
FullName as PartnerFullName,
Address as PartnerAddress,
Phone as PartnerPhone
from Partners p with(nolock)
where 1 = 1
#param_expr(Name, p.Name)
Таблицы SomePartnerMainInfo и SomePartnerVirtual¶
Чтобы был возможен как ввод из справочника через представление, так и ввод вручную, в карточке должно быть две таблицы (строковые секции). Таблица SomePartnerMainInfo физически сохраняется в базе данных и отдельно (без комплексной колонки) содержит все вводимые вручную данные, плюс идентификатор контрагента и признак “контрагент введён вручную”. Виртуальная таблица SomePartnerVirtual содержит комплексную колонку, ссылающуюся на таблицу Partners и содержащую все ссылочные колонки из тех, что предоставляет представление.
Колонки в таблице SomePartnerMainInfo (Cards, Entries):
-
SomePartner, Reference(Typified) Null, ссылается на таблицу Partners: ссылка на контрагента из справочника.
- SomePartnerID, Guid Null - идентификатор контрагента из справочника или null, если контрагент введён вручную или ещё не введён.
-
SomePartnerManualMode, Boolean Not Null, Default value = False: признак того, что контрагент вводится вручную (True) или выбран из справочника (False). Если указать значение по умолчанию True вместо False, то при создании карточки будет активироваться режим ручного ввода, а для переключения на выбор из справочника пользователь должен будет нажать флажок.
-
SomePartnerName, String(255) Null: имя контрагента. Название колонки выбирается как префикс SomePartner + название соответствующей колонки в таблице Partners. Тип колонки выбирается в соответствии с типом в таблице Partners, но ему явно указывается Null (чтобы можно было сохранить нашу карточку SomePartnerCard без контрагента). Для всех колонок ниже действуют эти же правила.
-
SomePartnerFullName, String(512) Null: полное имя контрагента.
-
SomePartnerAddress, String(512) Null: адрес контрагента.
-
SomePartnerPhone, String(128) Null: телефон контрагента.
Колонки в таблице SomePartnerVirtual (Cards, Entries, флажок “Is virtual”):
-
SomePartner, Reference(Typified) Null, ссылается на таблицу Partners: ссылка на контрагента из справочника. Содержит все ссылочные колонки, которые есть в представление Partners и аналоги которых есть с таким же именем в таблице SomePartnerMainInfo.
-
SomePartnerID, Guid Null
-
SomePartnerName, String(255) Null
-
SomePartnerFullName, String(512) Null
-
SomePartnerAddress, String(512) Null
-
SomePartnerPhone, String(128) Null
-
Тип карточки SomePartnerCard¶
Теперь создадим тип карточки SomePartnerCard, в который включим все созданные колонки в обеих таблицах.
На вкладку карточки добавим колоночный блок с алиасом SomePartner, выводом в 2 колонки и следующими контролами в заданном порядке:
-
“Контрагент из справочника”, ссылочное поле SomePartnerVirtual: SomePartner, алиас SomePartnerID.Auto. Указаны алиас представления Partners и алиас параметра Name. Если требуется заблокировать автодополнение с клавиатуры и выбирать только через троеточие, то последние два поля остаются пустыми. Также при желании можно настроить автокомплит в режиме комбобокса (при этом можно написать представление типа “мои последние контрагенты” для быстрого выбора).
-
“Имя контрагента”, строковое поле SomePartnerMainInfo: SomePartnerName, алиас SomePartnerName.Manual
-
“Ввести вручную”, логическое значение SomePartnerMainInfo: SomePartnerManualMode, без алиаса.
-
“Полное имя”, строковое поле SomePartnerMainInfo: SomePartnerFullName, алиас SomePartnerFullName.ReadOnly.
-
“Адрес”, строковое поле SomePartnerMainInfo: SomePartnerAddress, алиас SomePartnerAddress.ReadOnly.
-
“Телефон”, строковое поле SomePartnerMainInfo: SomePartnerPhone, алиас SomePartnerPhone.Manual
Как видим, добавлены:
-
автокомплит (ссылочное поле) для выбора из справочника, который привязывается к виртуальной таблице SomePartnerVirtual;
-
поле с именем уже из основной таблицы SomePartnerMainInfo прямо под автокомплитом, чтобы одно при нажатии переключателя заменяло автокомплит;
-
переключатель для выбора режима “из справочника / ручной ввод”, ссылается на булевское поле из основной таблицы;
-
прочие контролы (в данном случае строковые) для вывода значений из комплексной колонки, причём контролы ссылаются на основную таблицу; среди них контрол для ввода телефона, видимый только в режиме ручного ввода.
Алиасы у контролов указаны неспроста, они подсказывают нам логику их использования, а также упрощают написание расширения, которое управляет их видимостью и поведением. Значения суффиксов в алиасах:
-
.Auto
- контрол доступен в режиме выбора из справочника и скрыт в режиме ручного выбора. -
.Manual
- контрол скрыт в режиме выбора из справочника и доступен в режиме ручного выбора. -
.ReadOnly
- контрол отображается только для чтения в режиме выбора из справочника и полностью доступен в режиме ручного выбора.
Видимость и состояние контролов без суффиксов не меняется.
Это позволяет легко скрывать некоторые контролы для различных режимов ввода, а не просто делать их доступными только для чтения. Т.е. в режиме ручного ввода могут быть доступны контролы, которые не показываются в режиме выбора из справочника. Таким контролам в алиасе нужно заменить суффикс .ReadOnly
на .Manual
. Обратное также верно.
Расширение SomePartnerUIExtension¶
Для корректного отображения и функционирования типа карточки надо написать расширение типа CardUIExtension. Оно выполнится на клиенте сразу после открытия вкладки с карточкой, подготовит её данные и подпишется на события, которые позволят корректно реагировать на ввод контрагента, а также на изменение способа ввода с ручного на выбор из справочника, и наоборот.
Класс расширения будет выглядеть следующим образом:
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using Tessa.Cards;
using Tessa.Platform.Storage;
using Tessa.UI.Cards;
namespace Tessa.Extensions.Client.UI
{
public sealed class SomePartnerUIExtension : CardUIExtension
{
// методы расширения
}
}
Метод SetupControls поможет устанавливать свойства контролов в соответствии с режимом ввод. Параметр метода block содержит ссылку на модель блока карточки со всеми контролами ввода, а параметр manualMode равен true для ручного ввода и false для ввода из справочника.
-
Метод устанавливает или сбрасывает режим “только для чтения” для контролов, алиас которых заканчивается на
.ReadOnly
-
Метод показывает или скрывает контролы, алиасы которых заканчиваются на
.Auto
или.Manual
. -
Метод вызывает block.Rearrange(), чтобы перераспределить пространство для скрытых/показанных контролов блока. Вызов Rearrange необходим, т.к. блок многоколоночный и не способен динамически скрывать контролы без перерисовки.
private static void SetupControls(IBlockViewModel block, bool manualMode)
{
foreach (IControlViewModel control in block.Controls)
{
if (control.Name != null)
{
if (control.Name.EndsWith(".ReadOnly", StringComparison.Ordinal))
{
control.IsReadOnly = !manualMode;
}
else if (control.Name.EndsWith(".Auto", StringComparison.Ordinal))
{
control.ControlVisibility = manualMode ? Visibility.Collapsed : Visibility.Visible;
}
else if (control.Name.EndsWith(".Manual", StringComparison.Ordinal))
{
control.ControlVisibility = manualMode ? Visibility.Visible : Visibility.Collapsed;
}
}
}
block.Rearrange();
}
Метод Initialized
- основной для расширения, он вызывается платформой при открытии вкладки с карточкой (новой или существующей) или при обновлении карточки (после сохранения или нажатия плитки “Обновить”). Регистрация расширения укажет, что это именно вкладка с карточкой нужного типа (см. ниже). Алгоритм метода указан в комментариях.
Метод написан таким образом, чтобы его можно было легко адаптировать:
-
для дополнительных полей в ссылке (новое поле
Partners.SAPCode
), при этом исправлять код не придётся, только корректно дать имена колонкам и алиасы контролам; -
для изменённых типов полей или новых полей с типами, отличными от строкового, при этом исправлять код не придётся;
-
для нескольких ссылок из одной карточки (несколько контрагентов), в таком случае меняется лишь проверка префикса полей SomePartner;
-
для других типов ссылок (не на контрагента, а, например, на пользователя), может потребоваться только поменять префикс полей SomePartner.
public override Task Initialized(ICardUIExtensionContext context)
{
CardSection mainTable = context.Model.Card.Sections["SomePartnerMainInfo"];
CardSection virtualTable = context.Model.Card.Sections["SomePartnerVirtual"];
// копируем поля в виртуальную секцию, чтобы отобразить в автокомплите
foreach (KeyValuePair<string, object> field in mainTable.Fields)
{
if (field.Key.StartsWith("SomePartner") && !field.Key.EndsWith("ManualMode"))
{
virtualTable.Fields[field.Key] = field.Value;
}
}
// начальная видимость контролов
IBlockViewModel block = context.Model.Blocks["SomePartner"];
SetupControls(block, mainTable.Fields.Get<bool>("SomePartnerManualMode"));
// изменение видимости контролов при нажатии на checkbox
mainTable.FieldChanged += (s, e) =>
{
if (e.FieldName.EndsWith("ManualMode"))
{
// изменение видимости контролов
SetupControls(block, (bool) e.FieldValue);
// очистка введённых в предыдущем режиме данных
foreach (string fieldName in mainTable.Fields.Keys.ToArray())
{
if (fieldName.StartsWith("SomePartner") && fieldName != e.FieldName)
{
mainTable.Fields[fieldName] = null;
virtualTable.Fields[fieldName] = null;
}
}
}
};
// копируем поля в физическую секцию, чтобы отобразить в строковых контролах
virtualTable.FieldChanged += (s, e) =>
{
if (e.FieldName.StartsWith("SomePartner"))
{
mainTable.Fields[e.FieldName] = e.FieldValue;
}
};
return Task.CompletedTask;
}
Расширение регистрируется на клиенте для нашего типа карточки SomePartnerCard:
extensionContainer
.RegisterExtension<ICardUIExtension, SomePartnerUIExtension>(x => x
.WithOrder(ExtensionStage.AfterPlatform)
.WithSingleton<SomePartnerUIExtension>()
.WhenCardTypes("SomePartnerCard"));
Карточка в TessaClient¶
Так выглядит наша карточка при создании:
Пользователь вводит контрагента в автокомплите или выбирает через троеточие. При этом в остальных полях, доступных только для чтения, отображается информация о выбранном контрагенте.
Пользователь передумал и ставит флажок “Ввести вручную”. При этом все поля очищаются, автокомплит “превращается” в строковое поле с другим заголовком, появляется поле для ввода телефона, а поля, ранее доступные только для чтения, становятся редактируемыми.
Пользователь вводит всю инфу о контрагенте вручную. Если он опять сбросит галку “Ввести вручную”, то опять появится автокомплит и очистятся значения всех полей.
В любой момент пользователь может сохранить карточку. После загрузки все её поля и контролы корректно восстановятся.