Lekce 2 - Blazor - Rozšíření Todo aplikace v .NET Core SPA
V minulé lekci, Blazor - .NET Core SPA s C# .NET i na straně klienta, jsme si uvedli technologii Blazor a vytvořili jednoduchou Todo aplikaci.
V dnešní lekci si rozšíříme úvodní příklad s Todo aplikací o
další komponentu TodoList
. Ukážeme si také, jak reagovat na
události.
Model úkolu - Todo
Nová komponenta TodoList
bude zobrazovat seznam úkolů a jako
zdroj dat tedy bude mít kolekci úkolů. Pro úkol, který v našem případě
obsahuje pouze text a informaci o splnění, ještě nemáme vytvořenou
modelovou třídu. Pojďme to nyní napravit.
V Solution Exploreru vytvořme novou složku Model/
a v ní
novou třídu s názvem Todo
. Do nové třídy přidáme dvě
vlastnosti:
public class Todo { public string Text { get; set; } public bool Done { get; set; } }
Nový jmenný prostor Model
také "zaregistrujme" v souboru
_Imports.razor
. To nám usnadní práci s naší novou třídou
uvnitř komponent:
@using BlazorITnetwork.Model
Váš jmenný prostor, tedy část BlazorITnetwork
,
se samozřejmě může jmenovat jinak v závislosti na názvu projektu.
Komponenta TodoList
Ve složce Shared/
vytvořme novou komponentu
TodoList
. Jak už jsem zmínil výše, tato komponenta bude
zobrazovat seznam úkolů, proto jí přidejme parametr typu
IList<Todo>
. Seznam úkolů zobrazíme s pomocí cyklu
foreach
a s využitím Bootstrapu
jej můžeme obalit třeba do .card
:
<div class="card"> <div class="card-header"> <h4 class="card-title">Seznam úkolů</h4> </div> <div class="card-body"> @if (Todos.Count == 0) { <span>Seznam je prázdný ...</span> } else { foreach (var todo in Todos) { <TodoItem Text="@todo.Text" Done="@todo.Done" /> } } </div> </div>
@code { [Parameter] public IList<Todo> Todos { get; set; } = new List<Todo>(); }
Pokud vám Visual Studio
neochvějně tvrdí, že třídu Todo
nezná, zkuste
rebuild aplikace. Případně zkontrolujte výše popsanou
úpravu v souboru _Imports.razor
.
V souboru Index.razor
nyní zobrazíme novou komponentu
TodoList
. Jako parametr ji předáme seznam úkolů k zobrazení.
Ten bychom v reálné aplikaci pravděpodobně načetli z databáze, ale my si jej zde jednoduše vytvoříme
přímo v kódu:
<TodoList Todos="@todos"/>
@code{ IList<Todo> todos = new List<Todo>() { new Todo() {Text = "Naučit se Blazor na ITnetwork"}, new Todo() {Text = "Vytvořit vlastní Blazor aplikaci"}, new Todo() {Text = "Pochopit práci s parametry komponenty", Done = true} }; }
Jak vypadá naše aplikace po spuštění je vidět na následujícím obrázku:
Přidání nového úkolu
Rozšíříme naší aplikaci o další funkcionalitu. Umožníme uživateli
přidat nový úkol. K tomuto účelu vytvoříme komponentu
NewItemEntry.razor
. HTML část komponenty se bude skládat z:
- popisku,
- vstupního pole pro text a
- tlačítka pro spuštění akce.
Pokud bude vstupní pole prázdné, tlačítko pro přidání úkolu bude neaktivní. Komponentu opět můžeme naformátovat s pomocí Bootstrapu. Část s kódem bude zatím obsahovat textovou vlastnost pro navázání na vstupní pole a vlastnost jen pro čtení, která znepřístupní tlačítko, pokud bude vstupní pole prázdné:
<div class="input-group input-group-lg"> <div class="input-group-prepend"> <span class="input-group-text">Nový úkol</span> </div> <input type="text" class="form-control" @bind="Text"/> <div class="input-group-append"> <button class="btn btn-secondary" disabled=@buttonDisabled>Přidat</button> </div> </div>
@code { public string Text { get; set; } string buttonDisabled => string.IsNullOrEmpty(Text) ? "disabled" : null; }
Komponentu NewItemEntry
si vložíme do komponenty
TodoList
pod seznam úkolů:
...
<NewItemEntry/>
...
Výsledek vypadá nějak takto:
Neaktivní pole
Než přidáme obsluhu stisknutí tlačítka, pojďme odstranit na první pohled nezjistitelnou chybu. Naše tlačítko má být neaktivní, protože vstupní pole je prázdné, to je v pořádku. Ovšem pokud do vstupního pole napíšeme nějaký text, tlačítko zůstává stále neaktivní a to už v pořádku není. Použili jsme obousměrný binding, tak kde je problém?
Tlačítko se aktivuje až ve chvíli, kdy neprázdné pole opustíme
(ztratí focus), například kliknutím na jinou komponentu. Důvodem je, že
binding defaultně reaguje na událost onchange
, která se vyvolá
až při opuštění vstupního pole. My bychom potřebovali reagovat hned po
stisku klávesy, takže upravíme událost bindingu na oninput
:
... <input type="text" class="form-control" @bind="Text" @bind:event="oninput"/> ...
Po spuštění se tlačítko aktivuje hned po napsání prvního znaku.
Obsluha kliknutí na tlačítko
V komponentě NewItemEntry
nemůžeme při kliknutí na
tlačítko přidat položku do seznamu, protože tato komponenta seznam úkolů
nemá. Seznam je v nadřazené komponentě TodoList
, proto
komponenta NewItemEntry
pouze "oznámí" nadřazené komponentě,
že došlo ke stisku tlačítka a předá jí text ze vstupního pole.
Výsledné zpracování této "události" tedy proběhne v komponentě
TodoList
.
Do komponenty NewItemEntry
přidáme parametr
OnNewItem
typu EventCallback<string>
a tuto
událost vyvoláme v nové metodě NewItem()
, kterou zavoláme na
stisk tlačítka:
... <button class="btn btn-secondary @buttonDisabled" @onclick="NewItem">Přidat</button> ...
@code { ... [Parameter] public EventCallback<string> OnNewItem { get; set; } void NewItem() { OnNewItem.InvokeAsync(Text); Text = string.Empty; } }
Místo typu EventCallback<>
bychom mohli
použít i klíčová slova event
a delegate
nebo typy
Action<>
, resp. Func<>
.
EventCallback
je ovšem typ delegáta používaný k vystavení
událostí napříč komponentami. A nadřazená komponenta může samozřejmě
přiřadit obsluhu události EventCallback
podřízené komponenty.
Při použití události EventCallback
tímto způsobem jsou
nadřazené a podřízené komponenty automaticky překresleny při vyvolání
události. U ostatních výše zmíněných způsobů by bylo nutné pro
správné překreslení volat metodu StateHasChanged()
.
Do komponenty TodoList
přidáme metodu OnNewItem()
s parametrem typu string
. Tuto metodu použijeme pro obsloužení
naší nové události na komponentě NewItemEntry
. Jen v ní
přidáme nový úkol s předaným textem do seznamu úkolů:
... <NewItemEntry OnNewItem="@OnNewItem"/> ...
@code { ... void OnNewItem(string text) { Todos.Add(new Todo() { Text = text }); } }
Vše funguje, jak má :
Reakce na Enter
Komponentu NewItemEntry
můžeme upravit tak, aby kromě stisku
tlačítka vyvolala akci přidání úkolu také po stisku klávesy
Enter. Klidně si to zkuste nejdříve sami
Jedno z možných řešení je níže:
... <input type="text" class="form-control" @bind="Text" @bind:event="oninput" @onkeypress="KeyPress"/> ...
@code { ... void KeyPress(KeyboardEventArgs e) { if (e.Key == "Enter" && !string.IsNullOrEmpty(Text)) NewItem(); } }
To je pro dnešní lekci vše. Pokud si chcete procvičovat, zkuste třeba
upravit komponentu TodoList
tak, aby se po splnění všech úkolů
zbarvilo záhlaví zelenou barvou.
V další lekci, Blazor - Binding, se podíváme trochu hlouběji na one-way a two-way binding.