Picker#
Výběr vazby místo rozbalovacího seznamu: textové pole s našeptávačem a modální okno
s plným výpisem. Kód: app/UI/Admin/System/Components/PickerComponent/.
Kdy ho použít a kdy ne#
| Situace | Čím to řešit |
|---|---|
| do ~50 položek, seznam se nemění | addSelect() |
| stovky a víc, nebo je potřeba hledat | Picker |
| výběr víc hodnot naráz | addCheckboxList(), nebo Picker v Multiplieru |
Rozbalovací seznam s deseti tisíci položkami se nenačte, nedá se v něm hledat a při každém otevření formuláře vytáhne celou tabulku.
Jak je poskládaný#
| Část | Role |
|---|---|
PickerConfig |
hodnotový objekt — co se vybírá a odkud |
picker traita (UserPickerTrait, CategoryPickerTrait, …) |
postaví PickerConfig vlastním managerem |
PickerHostTrait |
hostitel v presenteru — vyrobí createComponentPicker() |
Picker + PickerFactory |
samotná komponenta |
picker.js |
našeptávač, modál a doplnění polí |
Obsah modálu je DataGrid cílového presenteru v režimu výběru — žádná druhá implementace výpisu neexistuje.
Picker nezná manager
Data si bere přes callbacky v PickerConfig. Proto funguje v administraci
i na frontu a proto se dá tentýž picker použít nad různými zdroji.
Postup: nahrazuji select pickerem#
- Napiš picker traitu do
Presenters/Traits/Pickers/— dvě metody: našeptávání a překlad id na popisek.
protected function userPicker(string $formName, string $targetField): PickerConfig
{
return new PickerConfig(
suggestCallback: fn(string $q): array => $this->suggestUsers($q),
formName: $formName,
targetField: $targetField,
labelResolver: fn(int $id): ?string => $this->resolveUserLabel($id),
targetPresenter: ':Admin:System:User',
);
}
- Našeptávač vrací
id+label, volitelnědatapro doplnění dalších polí. Vždy slimit, jinak se našeptávač zadusí. labelResolverje povinný všude, kde se edituje — bez něj se při načtení editace nemá co zobrazit.- Presenter
use-nePickerHostTraita vpickers()vrátí pole konfigurací klíčované logickým názvem. - Ve formuláři nahraď select skrytým polem téhož jména, jaké je v
targetField. - V šabloně vykresli
{control picker-parentId}a popisek pole si napiš ručně — skryté pole žádný nemá. - V cílovém presenteru zapni režim výběru v továrně gridu (viz Admin presenter).
- Prokliknij: našeptávání, výběr z modálu, uložení, znovuotevření editace.
pickers() musí být čisté a idempotentní
Volá se i při AJAX signálu našeptávače, kdy formulář ještě nemusí být postavený.
Cokoli, co v té metodě sáhne na $form, spadne — a spadne to jen při psaní do
našeptávače, ne při načtení stránky.
Popisek musí být naplněný při načtení editace
Když labelResolver chybí nebo vrátí null, pole vypadá prázdné, přestože vazba
existuje — a protože prázdné pole je platná hodnota, uložení tu vazbu zruší.
Uživatel přitom jen otevřel formulář a klikl na Uložit.
Na editaci se předvyplňuje přes setDefaults()
Ne přímým nastavením hodnoty prvku — viz Formuláře.
Doplnění dalších polí naráz#
fieldMap mapuje datový klíč z našeptávače na název dalšího pole formuláře:
Našeptávač k položce vrátí data: ['email' => '…'] a JavaScript po výběru doplní
i tato pole. Z modálu tatáž data dodá setPickerData() na gridu.
Picker v Multiplieru#
| Parametr | Kdy je potřeba |
|---|---|
targetContainer |
pole leží uvnitř multiplier kontejneru (addresses) |
nestedContainer |
multiplier uvnitř multiplieru (skupiny → parametry) |
displayField |
textový control pro popisek, sourozenec skrytého pole |
Instance se pak jmenuje picker-<klíč>_<index> (u vnořeného picker-<klíč>_<i0>_<i1>).
Oddělovač je podtržítko, ne pomlčka — pomlčka je v Nette separátor cesty komponent.
Bez displayField popisek nepřežije AJAX překreslení řádku
Bez něj picker vyrenderuje obyčejný neodesílaný input, který se při překreslení vrátí prázdný. Uživatel vidí, že se mu vybraná hodnota „ztratila“ — přitom skryté pole hodnotu pořád má.
Klíč konfigurace nesmí končit _<číslo>
Jméno instance se rozkládá regulárním výrazem na klíč a indexy řádků. Klíč
address_2 by se rozpadl na address + index 2.
Sebe-reference (nadřazená kategorie, rodičovská stránka)#
Picker na tutéž entitu musí vyloučit sám sebe i svůj podstrom — na to jsou
persistentní parametry pickerExclude a dvojice pickerExcludeSubtreeLft/Rgt
(Admin presenter).
Cyklus ve stromu se projeví až při přepočtu
Kategorie nastavená jako vlastní rodič se uloží bez chyby. Rozbije až přepočet
lft/rgt celého stromu — a to se stane někdy později, při úplně jiné operaci.
Překreslení závislých polí po výběru#
submitButton říká, které submit tlačítko má picker po úspěšném výběru „kliknout“,
aby se dorenderovala pole závislá na vybrané hodnotě (třeba volby vybraného parametru).
To tlačítko musí mít prázdný validační rozsah
setValidationScope([]) + onClick → redrawControl. Jinak by překreslení blokovala
validace polí, které uživatel ještě nevyplnil. Ukládat nebude — admin base ukládá
jen přes save/update (Admin presenter).
Bez tlačítka jde na výběr navázat vlastní chování
picker.js vždycky vystřelí DOM událost picker:success; preventDefault() na ní
potlačí automatické překreslení.
Zakládání nové položky z pickeru#
Picker umí založit novou položku bez opuštění formuláře.
Nově založená položka se musí vrátit i s popiskem
Jinak se do pole propíše jen id a uživatel vidí prázdno — přestože se záznam opravdu založil.
Skryté pole a formulářový kontext#
Picker uvnitř komponenty s vlastním kontextem umí vyrobit DRUHÉ skryté pole téhož jména
Odešle se to špatné a vazba se uloží prázdná. Řeší se to n:snippetArea kolem
překreslované části, ne {formContext}.
Kam sáhnout#
| Chci | Kde |
|---|---|
| všechny volby konfigurace | app/UI/Admin/System/Components/PickerComponent/PickerConfig.php |
| hostitele v presenteru | app/UI/Admin/System/Presenters/Traits/Pickers/PickerHostTrait.php |
| hostitele v komponentě | …/PickerComponent/ComponentPickerHostTrait.php |
| kanonický vzor traity | app/UI/Admin/System/Presenters/Traits/Pickers/UserPickerTrait.php |
| režim výběru v gridu | app/UI/Admin/System/Components/DataGridComponent/DataGrid.php |
Navazující kapitoly: Práce s tabulkami · Admin presenter · Formuláře