Перейти к содержанию

Совмещаем выбор из справочника со вводом вручную

Совмещаем выбор из справочника со вводом вручную

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 колонки и следующими контролами в заданном порядке:

  1. “Контрагент из справочника”, ссылочное поле SomePartnerVirtual: SomePartner, алиас SomePartnerID.Auto. Указаны алиас представления Partners и алиас параметра Name. Если требуется заблокировать автодополнение с клавиатуры и выбирать только через троеточие, то последние два поля остаются пустыми. Также при желании можно настроить автокомплит в режиме комбобокса (при этом можно написать представление типа “мои последние контрагенты” для быстрого выбора).

  2. “Имя контрагента”, строковое поле SomePartnerMainInfo: SomePartnerName, алиас SomePartnerName.Manual

  3. “Ввести вручную”, логическое значение SomePartnerMainInfo: SomePartnerManualMode, без алиаса.

  4. “Полное имя”, строковое поле SomePartnerMainInfo: SomePartnerFullName, алиас SomePartnerFullName.ReadOnly.

  5. “Адрес”, строковое поле SomePartnerMainInfo: SomePartnerAddress, алиас SomePartnerAddress.ReadOnly.

  6. “Телефон”, строковое поле 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

Так выглядит наша карточка при создании:

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

Пользователь передумал и ставит флажок “Ввести вручную”. При этом все поля очищаются, автокомплит “превращается” в строковое поле с другим заголовком, появляется поле для ввода телефона, а поля, ранее доступные только для чтения, становятся редактируемыми.

Пользователь вводит всю инфу о контрагенте вручную. Если он опять сбросит галку “Ввести вручную”, то опять появится автокомплит и очистятся значения всех полей.

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

Back to top