Minule jsme začali psát aplikaci pro evidenci hudebních disků, kterou jsme naučili přidávat, upravovat a editovat jednotlivé disky. Dnes přidáme možnost vytvářet a upravovat seznam skladeb na jednotlivých discích.
Přidání seznamu skladeb do editační šablony
Otevřete si projekt z minula a přepněte se na stránku AlbumDetail.aspx do režimu Source. Najděte v komponentě FormView značku EditItemTemplate a smažte v ní první řádek <table>. Místo něj vložte tento kód:
<div style="float: right; width: 430px">
<asp:SqlDataSource ID="SqlDataSource3" runat="server"></asp:SqlDataSource>
<asp:GridView ID="GridView1" runat="server" DataSourceID="SqlDataSource3">
</asp:GridView>
</div>
<table style="width: 450px">
Tím jsme před tabulku vložili oddíl div (zarovnaný doprava) a jak tomuto oddílu, tak i tabulce jsme nastavili pevnou šířku. Komponentě FormView nastavte ještě hodnotu vlastnosti Width na 900px, abychom měli komponenty správně srovnané.
Jak je vidět, dovnitř našeho oddílu jsem přidal komponenty SqlDataSource3 a GridView1, které budou sloužit k zobrazení a úpravě seznamu skladeb. Nyní se přepněte do režimu Design, abychom mohli datový zdroj nastavit. Proklikejte se do editace šablony EditItemTemplate podle obrázků:
V konfiguraci datového zdroje nastavte SQL dotaz takto:
Vyberte tabulku Songs a všechny sloupce kromě AlbumId.
Seřadíme písničky podle jejich čísla vzestupně.
Zobrazíme jenom písničky z alba, které je předáno jako parametr id v adrese URL.
A necháme si vygenerovat i SQL příkazy pro přidávání, úpravy a mazání záznamů.
Proklikejte průvodce až do konce a komponenta GridView se okamžitě přizpůsobí podle zadaného dotazu. Musíme ji samozřejmě trochu upravit, aby vyhovovala našim potřebám. Přepněte se do režimu návrhu a změňte kód komponenty GridView1 takto:
<asp:GridView ID="GridView1" runat="server" DataSourceID="SqlDataSource3" AutoGenerateColumns="False">
<Columns>
<asp:BoundField DataField="Number" HeaderText="Číslo" SortExpression="Number" ItemStyle-Width="60px" />
<asp:BoundField DataField="Title" HeaderText="Název skladby" SortExpression="Title" ItemStyle-Width="160px" />
<asp:BoundField DataField="Length" DataFormatString="{0:m:ss}" HeaderText="Délka" HtmlEncode="False" SortExpression="Length" ItemStyle-Width="80px" />
</Columns>
</asp:GridView>
Upravování záznamů přímo uvnitř GridView
Budeme chtít umožnit úpravu záznamů a protože tyto záznamy jsou jednoduché, můžeme je nechat uživatele upravit přímo v tabulce. GridView je totiž velice chytrá komponenta, která toto všechno zvládne. Stačí přidat sloupec typu CommandField, který umí přepínat stav komponentu z režimu zobrazení do režimu úprav řádku.
Přepněte se tedy opět do režimu Design a proklikejte se až do šablony EditItemTemplate, kde rozbalte podokno úloh komponenty GridView1 a klikněte na položku Edit Columns.... V dialogovém okně přidejte nový sloupec typu CommandField/Edit, Update, Cancel.
Nyní dialog potvrďte a v režimu Source nahraďte kód komponenty GridView tímto (je to rychlejší než kdybych popisoval, které vlastnosti máte nastavit na které hodnoty).
<asp:GridView ID="GridView1" runat="server" DataSourceID="SqlDataSource3" AutoGenerateColumns="False" DataKeyNames="SongId">
<Columns>
<asp:BoundField DataField="Number" HeaderText="Číslo" SortExpression="Number" ItemStyle-Width="60px" ControlStyle-Width="40px" />
<asp:BoundField DataField="Title" HeaderText="Název skladby" SortExpression="Title" ItemStyle-Width="160px" ControlStyle-Width="140px" />
<asp:BoundField DataField="Length" DataFormatString="{0:m:ss}" HeaderText="Délka" HtmlEncode="False" SortExpression="Length" ItemStyle-Width="80px" ControlStyle-Width="60px" />
<asp:CommandField ShowEditButton="True" EditText="Upravit" CancelText="Zrušit" UpdateText="Uložit" ItemStyle-Width="80px" />
</Columns>
</asp:GridView>
Prvním třem sloupcům jsem přidal vlastnosti ControlStyle-Width, které definují šířku TextBoxu, který se zobrazí při editaci sloupce. Vlastnost ItemStyle-Width nastavuje šířku celého sloupce.
Sloupci CommandField jsem nastavil vlastnosti EditText, CancelText a UpdateText tak, aby byly česky (pokud nemáte český .NET framework, standardně by byly anglicky).
Pokud si nyní stránku otevřete v prohlížeči, zobrazí se seznam skladeb. Pokud kliknete na některý odkaz Upravit, změní se texty v řádku na TextBoxy, ve kterých můžete hodnoty rovnou upravit.
Validace na několik způsobů
Jediná věc, na kterou musíme dát pozor, je správnost vložených dat. Pokud uživatel do prvního sloupce zadá nějaký nesmysl, nastane nám chyba, protože databáze očekává číslo a když dostane místo čísla třeba text, asi z toho nebude příliš nadšená. Tato situace se dá řešit několika způsoby, já bych zde zmínil dva:
- změnit dotyčný sloupec na TemplateField a vytvořit mu EditItemTemplate s TextBoxem a validátorem, který už správnost dat ohlídá
- napsat si vlastní třídu EditableNumericBoundField, která se bude chovat úplně stejně jako BoundField s tím rozdílem, že si sama bude kontrolovat, jestli do ní uživatel zadal číslo nebo ne
První způsob je jednodušší a rychlejší, ale pokud bychom v projektu měli tabulek 10 a chtěli například změnit vzhled validátoru, už bychom to museli dělat na více místech, což asi není vhodné. Druhý způsob je o dost těžší a vyžaduje znalosti objektově orientovaného programování, obzvláště pak dědičnost, ale na druhou stranu můžeme náš EditableNumericBoundField, který jsme si sami napsali, použít nejen v této aplikaci kde chceme a kdy chceme, ale lze jej použít i v jiných webových aplikacích.
Vzhledem k tomu, že zde máme sloupce 3 a každý nějakou validaci potřebuje (první musí být číslo, druhý nesmí být prázdný a třetí musí být platný časový údaj nebo prázdná hodnota), napíšeme si vlastní field pro druhý sloupec, pro první a třetí použijeme TemplateField. Přidáme do něj validátor, který nám zkontroluje, jestli je v daném poli číselná hodnota. Přepněte se tedy do režimu Design a proklikejte se až do dialogu pro editaci sloupců komponenty GridView. Označte první sloupec a klikněte na odkaz Convert this field into TemplateField.
V režimu Source změňte kód, který nám vygeneroval průvodce, takto:
<asp:TemplateField HeaderText="Číslo" SortExpression="Number" ItemStyle-Width="60px">
<EditItemTemplate>
<asp:TextBox ID="TextBox1" runat="server" Text='<%# Bind("Number") %>' Width="40px"></asp:TextBox>
<asp:CustomValidator ID="CustomValidator1" runat="server" ErrorMessage="*" ValidateEmptyText="true" ControlToValidate="TextBox1"></asp:CustomValidator>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label1" runat="server" Text='<%# Bind("Number") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
Tím jsme do editační šablony přidali komponentu CustomValidator, což je obecný validátor, ve kterém si přesně můžeme určit kritéria validace. Vlastnost ValidateEmptyText říká, že se má provést kontrola i když jsme nic nezadali. Kritéria pro samotnou validaci musíme již naprogramovat. Nahoře v okně kódu vyberte v prvním rozbalovacím seznamu komponentu CustomValidator a ve druhém její událost ServerValidate. Podle toho, ve kterém programovacím jazyce píšete, nyní vložte dovnitř této procedury tento kód:
' zkontrolovat správnost čísla skladby
If String.IsNullOrEmpty(args.Value) Then
args.IsValid = False
Else
Dim num As Integer
If (Integer.TryParse(args.Value, num)) AndAlso (num > 0) Then
args.IsValid = True
Else
args.IsValid = False
End If
End If
// zkontrolovat správnost čísla skladby
if (string.IsNullOrEmpty(args.Value))
args.IsValid = false;
else
{
int num;
if ((int.TryParse(args.Value, out num)) && (num > 0))
args.IsValid = true;
else
args.IsValid = false;
}
V této proceduře nejprve zkontrolujeme, jestli není args.Value (text, který kontrolujeme) prázdný. Pokud je, ihned výsledek validace args.IsValid na False, čímž říkáme, že údaj není správný. Pro zjištění jsme použili funkci String.IsNullOrEmpty, která je k tomuto účelu určena. Pokud text prázdný není, vytvoříme si proměnnou num typu Integer a zkusíme zavolat metodu Integer.TryParse, která zkusí převést hodnotu na celé číslo. Pokud to nejde, vrátí False, pokud to jde, převedené číslo uloží do této proměnné a pak ještě testujeme, jestli je toto číslo větší než nula.
Ve VB.NET je velmi důležité použít operátor AndAlso (samotný And nestačí), protože chceme druhou podmínku vyhodnocovat pouze pokud je první splněna (pokud hodnota není číslo, testovat nic nechceme). Operátor And vyhodnotí podmínky obě a pak teprve se rozhodne, jestli jsou obě dvě splněny. AndAlso otestuje první a pokud selže, na druhou se vykašle úplně, protože i když by platila, stejně celý výraz už neplatí.
Podobně ověříme i třetí sloupec s časovým údajem. Změňte jeho kód takto:
<asp:TemplateField HeaderText="Délka" SortExpression="Length" ItemStyle-Width="80px">
<EditItemTemplate>
<asp:TextBox ID="TextBox2" runat="server" Text='<%# Bind("Length", "{0:m:ss}") %>' Width="60px"></asp:TextBox>
<asp:CustomValidator ID="CustomValidator2" runat="server" ErrorMessage="*" ValidateEmptyText="false" ControlToValidate="TextBox2" OnServerValidate="CustomValidator2_ServerValidate"></asp:CustomValidator>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label2" runat="server" Text='<%# Bind("Length", "{0:m:ss}") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
Vytvořte proceduru události ServerValidate komponenty CustomValidator2 a vložte do ní tento kód:
' zkontrolovat správnost délky skladby
Dim length As DateTime
args.IsValid = DateTime.TryParseExact(args.Value, "m:ss", Nothing, System.Globalization.DateTimeStyles.None, length)
// zkontrolovat správnost délky skladby
DateTime length;
args.IsValid = DateTime.TryParseExact(args.Value, "m:ss", null, System.Globalization.DateTimeStyles.None, out length);
Pomocí DateTime.TryParseExact zkusíme převést text na časový údaj ve formátu m:ss, čili minuty:sekundy. Pokud se to podaří, args.IsValid bude nastaveno na True.
Nyní můžeme v prohlížeči editaci vyzkoušet, ale má to bohužel jeden háček - úprava časového údaje nebude fungovat správně. Při převodu textu z políčka ASP.NET neví, v jakém formátu data jsou, a pokusí se to uhodnout, ale špatně. Předpokládá totiž čas ve formátu hodiny:minuty a ne minuty:sekundy. Musíme to tedy upravit tak, aby se převod provedl správně. Vytvořte tedy proceduru události RowUpdating komponenty GridView1, která se spustí těsně před uložením upraveného záznamu do databáze. V parametrech této události máme možnost změnit kolekci NewValues, která obsahuje hodnoty, které se po skončení této události předají do parametrů komponenty SqlDataSource1 a spustí se příslušný SQL příkaz. Do této procedury vložte tento kód:
' správně převést čas
If (e.NewValues("Length") IsNot Nothing) AndAlso (Not String.IsNullOrEmpty(e.NewValues("Length").ToString())) Then
e.NewValues("Length") = DateTime.ParseExact(e.NewValues("Length").ToString(), "m:ss", Nothing, System.Globalization.DateTimeStyles.None)
End If
// správně převést čas
if ((e.NewValues["Length"] != null) && (!string.IsNullOrEmpty(e.NewValues["Length"].ToString())))
e.NewValues["Length"] = DateTime.ParseExact(e.NewValues["Length"].ToString(), "m:ss", null, System.Globalization.DateTimeStyles.None);
Nyní již editace času bude fungovat tak jak má. Hodnotu typu String v kolekci NewValues jsme rovnou převedli na příslušný čas typu DateTime.
Píšeme vlastní Field s validátorem
Na ASP.NET je skvělá jedna věc - je modulární a rozšiřitelné. Jakákoliv věc, která se nám nelíbí, lze přepsat a změnit tak, aniž bychom museli kvůli tomu měnit zbytek aplikace. Nyní si zkusíme napsat vlastní BoundField, který bude mít při editaci navíc validátor znemožňující vyplnit prázdnou hodnotu. Jak ale takový BoundField napsat? Vcelku snadno - využijeme dědičnosti a vytvoříme třídu odvozenou od třídy BoundField. Upravíme si věci, které se nám nelíbí, případně si přidáme svoje vlastní, a je to. Někdo by si mohl myslet, že k tomu budeme potřebovat zdrojové kódy třídy BoundField, ale ve většině případů je to zbytečné. Navíc úprava, kterou provádíme, je velmi jednoduchá, pouze přidáváme validátor.
Přidejte si tedy do projektu novou položku typu Class - třídu.
V dialogovém okně zapište název souboru ValidatedBoundField.vb nebo ValidatedBoundField.cs podle toho, jaký programovací jazyk chcete. Po potvrzení dialogu se nás Visual Studio zeptá, jestli chceme soubor umístit do složky App_Code. Tuto volbu potvrďte. Soubory s kódem, které nejsou součástí konkrétní webové stránky, patří právě do složky App_Code.
Vše, co je nyní v okně s kódem smažte, a vložte tam tento kód. Tím jsme vytvořili třídu v našem vlastním jmenném prostrou MyWebControls. Je důležité zařadit třídu do nějakého prostoru, budeme totiž její umístění potřebovat později. Dále jsme třídu zdědili od třídy BoundField.
Imports System.Web.UI
Imports System.Web.UI.WebControls
Namespace MyWebControls
Public Class ValidatedBoundField
Inherits BoundField
End Class
End Namespace
using System.Web.UI;
using System.Web.UI.WebControls;
namespace MyWebControls
{
public class ValidatedBoundField : BoundField
{
}
}
Protože pole může být součástí složitější validace, bylo by asi vhodné přidat mu vlastnost ValidationGroup, která se nastaví jak TextBoxu, tak i validátoru, pro případ, že bychom potřebovali přiřadit tlačítko do nějaké validační skupiny. Pokud totiž máme na stránce více tlačítek, každé z nich může potřebovat validovat jiné komponenty. Tím, že nastavíme jak tlačítku, tak i komponentám a jejich validátorům hodnotu vlastnosti ValidationGroup na stejnou hodnotu, docílíme toho, že se při kliknutí na tlačítko budou kontrolovat jen komponenty ze stejné skupiny. Přidáme tedy dovnitř třídy vlastnost, její hodnotu si budeme uchovávat v normální privátní proměnné:
Private _ValidationGroup As String
Public Property ValidationGroup() As String
Get
Return _ValidationGroup
End Get
Set(ByVal value As String)
_ValidationGroup = value
End Set
End Property
private string _ValidationGroup;
public string ValidationGroup
{
get { return _ValidationGroup; }
set { _ValidationGroup = value; }
}
A nyní nám zbývá poslední věc - přidat do metody InitializeCell kód, který zjistí, jestli je uvnitř buňky TextBox a pokud ano, přidá mu pomocí kódu validátor. Metoda InitializeCell se v komponentě BoundField zavolá v okamžiku, kdy je třeba vytvořit komponenty. Protože je naše třída odvozená od třídy BoundField, máme zde tuto proceduru také, stačí ji tedy pomocí klíčového slova override upravit.
Za deklarací vlastnosti udělejte několik volných řádků a pak napište ve VB.NET overrides a v C# jen override. Jakmile stisknete mezerník, ukáže se seznam metod a vlastností, které můžeme přepsat. Vybereme metodu InitializeCell a stiskneme mezerník znovu. Vygeneruje se kostra metody, uvnitř ní bude jeden řádek: MyBase.InitializeCell(cell, cellType, rowState, rowIndex) či base.InitializeCell(cell, cellType, rowState, rowIndex);. Pokud bychom chtěli kód této metody úplně nahradit naším vlastním, tento řádek bychom museli smazat. My jej tam ale necháme a za něj přidáme náš další kód. Tím dosáhneme toho, že se nejprve spustí kód původní metody ve třídě BoundField a potom ten náš. Po přidání našeho kódu bude metoda vypadat takto:
Public Overrides Sub InitializeCell(ByVal cell As System.Web.UI.WebControls.DataControlFieldCell, ByVal cellType As System.Web.UI.WebControls.DataControlCellType, ByVal rowState As System.Web.UI.WebControls.DataControlRowState, ByVal rowIndex As Integer)
MyBase.InitializeCell(cell, cellType, rowState, rowIndex)
'pokud jsme v buňce s daty
If cellType = DataControlCellType.DataCell Then
'pokud jsme v režimu editace nebo přidávání
If (rowState And DataControlRowState.Edit) <> 0 OrElse (rowState And DataControlRowState.Insert) <> 0 Then
'pokud je první komponenta TextBox, pak přidáme validátor
If cell.Controls.Count > 0 AndAlso TypeOf cell.Controls(0) Is TextBox Then
'našli jsme TextBox
Dim txb As TextBox = CType(cell.Controls(0), TextBox)
'vytvoříme validátor
Dim v As New RequiredFieldValidator()
'nastavit validační skupinu
If Not String.IsNullOrEmpty(_ValidationGroup) Then
v.ValidationGroup = _ValidationGroup
txb.ValidationGroup = _ValidationGroup
End If
'nastavit validátor
v.ErrorMessage = "*"
txb.ID = "TextBox" & DataField
v.ControlToValidate = txb.ID
v.ID = txb.ID & "Validator"
'přidat validátor za TextBox
cell.Controls.Add(v)
End If
End If
End If
End Sub
public override void InitializeCell(System.Web.UI.WebControls.DataControlFieldCell cell, System.Web.UI.WebControls.DataControlCellType cellType, System.Web.UI.WebControls.DataControlRowState rowState, int rowIndex)
{
base.InitializeCell(cell, cellType, rowState, rowIndex);
//pokud jsme v buňce s daty
if (cellType == DataControlCellType.DataCell)
{
//pokud jsme v režimu editace nebo přidávání
if ((rowState & DataControlRowState.Edit) != 0 || (rowState & DataControlRowState.Insert) != 0)
{
//pokud je první komponenta TextBox, pak přidáme validátor
if (cell.Controls.Count > 0 && cell.Controls[0] is TextBox)
{
//našli jsme TextBox
TextBox txb = (TextBox)cell.Controls[0];
//vytvoříme validátor
RequiredFieldValidator v = new RequiredFieldValidator();
//nastavit validační skupinu
if (!string.IsNullOrEmpty(_ValidationGroup))
{
v.ValidationGroup = _ValidationGroup;
txb.ValidationGroup = _ValidationGroup;
}
//nastavit validátor
v.ErrorMessage = "*";
txb.ID = "TextBox" + DataField;
v.ControlToValidate = txb.ID;
v.ID = txb.ID + "Validator";
//přidat validátor za TextBox
cell.Controls.Add(v);
}
}
}
}
A jak naše metoda funguje? Nejprve se zavolá kód původního BoundField. Pak je podmínka, která nás pustí dál jen pokud je typ buňky DataCell, tzn. pokračujeme jen když jsou v naší buňce data (přeskakujeme tím buňky záhlaví a zápatí). Další podmínka otestuje stav řádku - pokud je řádek v režimu Edit nebo Insert (může mít více hodnot zároveň, proto to testujeme takto podivným způsobem). Třetí podmínka zjistí, jestli jsou v buňce nějaké komponenty (kolekce cell.Controls) a pokud ano, zjistí, jestli je první z nich TextBox. Pokud ano, máme vyhráno a vytváříme validátor.
Do proměnné txb si uložíme první prvek z kolekce Controls a přetypujeme jej na typ TextBox. Kolekce Controls může totiž uchovávat všechny komponenty, které jsou odvozeny od typu Control. Abychom však viděli vlastnosti TextBoxu, musíme provést to přetypování. Na dalším řádku vytvoříme nový RequiredFieldValidator, tedy validátor, který hlídá, aby políčko nebylo prázdné.
Pokud jsme nastavili hodnotu vlastnosti ValidationGroup, pak ji nastavíme jak TextBoxu, tak i novému validátoru. Dále nastavíme validátoru vlastnost ErrorMessage na hodnotu *, takže pokud textové pole nebude vyplněno správně, ukáže se právě tato hvězdička.
Abychom mohli propojit TextBox a validátor, potřebujeme TextBoxu přiřadit nějaké ID, které by bylo unikátní (aby žádná jiná komponenta uvnitř GridView neměla toto ID stejné). Dostatečně unikátní název by měl být TextBox + název sloupce v databázi, ten je v rámci tabulky unikátní. Validátoru tedy nastavíme potřebné ID do vlastnosti ControlToValidate a nakonec pojmenujeme i validátor a to tak, že vezmeme ID TextBoxu a přidáme za něj slovo Validator.
Na závěr do kolekce komponent v buňce přidáme i náš vytvořený validátor, který se již o zbytek funkcionality postará sám. To je v zásadě vše. Je jasné, že bychom mohli přidat do komponenty ještě hromadu dalších vlastností, mohli bychom validovat i čísla, regulární výrazy atd., ale to už je nad rámec tohoto článku. Pokud chápete princip, jakým jsme tento vlastní BoundField vytvořili, jistě si jej budete moci rozšířit sami. Pokud ne, nastudujte si něco o objektově orientovaném programování, určitě se vám to bude ještě hodit.
Použití vlastní komponenty ve stránce
Abychom mohli naši komponentu použít ve stránce AlbumDetail.aspx, musíme nahoru přidat direktivu Register, které řekneme, že vše, co bude začínat prefixem my má hledat v namespace MyWebControls, kde je naše komponenta.
<%@ Register Namespace="MyWebControls" TagPrefix="my" %>
Tento řádek tedy přidejte úplně nahoru do stránky AlbumDetail.aspx, hned za první direktivu Page.
Nyní najděte opět kód komponenty GridView1 a skoro naposledy jej změňte takto:
<asp:GridView ID="GridView1" runat="server" DataSourceID="SqlDataSource3" DataKeyNames="SongId" AutoGenerateColumns="False" OnRowUpdating="GridView1_RowUpdating">
<Columns>
<asp:TemplateField HeaderText="Číslo" SortExpression="Number" ItemStyle-Width="60px">
<EditItemTemplate>
<asp:TextBox ID="TextBox1" runat="server" Text='<%# Bind("Number") %>' Width="40px"></asp:TextBox>
<asp:CustomValidator ID="CustomValidator1" runat="server" ErrorMessage="*" ValidateEmptyText="true" ControlToValidate="TextBox1" OnServerValidate="CustomValidator1_ServerValidate"></asp:CustomValidator>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label1" runat="server" Text='<%# Bind("Number") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<my:ValidatedBoundField DataField="Title" HeaderText="Název skladby" SortExpression="Title" ItemStyle-Width="160px" ControlStyle-Width="140px" />
<asp:TemplateField HeaderText="Délka" SortExpression="Length" ItemStyle-Width="80px">
<EditItemTemplate>
<asp:TextBox ID="TextBox2" runat="server" Text='<%# Bind("Length", "{0:m:ss}") %>' Width="60px"></asp:TextBox>
<asp:CustomValidator ID="CustomValidator2" runat="server" ErrorMessage="*" ValidateEmptyText="false" ControlToValidate="TextBox2" OnServerValidate="CustomValidator2_ServerValidate"></asp:CustomValidator>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label2" runat="server" Text='<%# Bind("Length", "{0:m:ss}") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:CommandField ShowEditButton="True" EditText="Upravit" CancelText="Zrušit" UpdateText="Uložit" ItemStyle-Width="80px" />
<asp:CommandField ShowDeleteButton="True" DeleteText="Smazat" ItemStyle-Width="50px" />
</Columns>
</asp:GridView>
Všimněte si, že místo složitého TemplateFieldu jsme akorát přidali náš ValidateBoundField, který je na jeden řádek a dělá skoro to samé, co složitý a dlouhý TemplateField. Pokud máme tabulku jednu, je vytváření vlastního fieldu zbytečně složité a zdlouhavé, pokud ale v projektu máme desítky tabulek, je určitě lepší jednou ho napsat a pak jej všude používat.
Mazání skladeb
Mazání záznamů je víceméně hotové, protože jsem záměrně přidal do minulé ukázky jeden sloupec typu CommandField, který umí záznamy mazat, stačí mu nastavit vlastnost ShowDeleteButton na True. Jediná funkce, která by zde byla nasnadě, je dialogové okno, které se uživatele zeptá, jestli záznam opravdu chce smazat. Stalo se mi již několikrát, že se uživatlé přehlédli a smazali si něco, co neměli. S dialogovým oknem se jim to jen tak nestane.
Nehodlám tím ale zbytečně prodlužovat článek, klidně si sami zkuste vytovořit vlastní field odvozený od CommandField, najít tam komponentu LinkButton (případně Button nebo ImageButton, to záleží na nastavení vlastnosti ButtonType) a nastavit jí hodnotu vlastnosti OnClientClick na "javascript: return confirm('Opravdu chcete tento záznam smazat?');", aby se v prohlížeči před kliknutím zobrazila hláška? A zkuste to ještě tak, že tomuto CommandFieldu přidáte vlastnost Question, kde půjde nastavit text této otázky. A nezapomeňte, že pokud bude hodnota této vlastnosti obsahovat apostrof, tak to nebude fungovat!
Přidávání skladeb
Pro přidávání skladeb k albu použijeme ještě komponentu FormView. Její kód bude vypadat takto, takže jej přidejte hned za náš GridView:
<asp:FormView ID="FormView2" runat="server" DataSourceID="SqlDataSource3" DefaultMode="Insert" OnItemInserting="FormView2_ItemInserting">
<InsertItemTemplate>
<table>
<tr>
<td>
<asp:TextBox ID="TextBox1" runat="server" Text='<%# Bind("Number") %>' Width="40px" ValidationGroup="InsertSong"></asp:TextBox>
<asp:CustomValidator ID="CustomValidator1" runat="server" ErrorMessage="*" ValidateEmptyText="true" ControlToValidate="TextBox1" ValidationGroup="InsertSong" OnServerValidate="CustomValidator1_ServerValidate"></asp:CustomValidator>
</td>
<td>
<asp:TextBox ID="TextBox3" runat="server" Text='<%# Bind("Title") %>' Width="140px" ValidationGroup="InsertSong"></asp:TextBox>
<asp:RequiredFieldValidator ID="RequiredFieldValidator3" runat="server" ErrorMessage="*" ControlToValidate="TextBox3" ValidationGroup="InsertSong"></asp:RequiredFieldValidator>
</td>
<td>
<asp:TextBox ID="TextBox2" runat="server" Text='<%# Bind("Length", "{0:m:ss}") %>' Width="60px" ValidationGroup="InsertSong"></asp:TextBox>
<asp:CustomValidator ID="CustomValidator2" runat="server" ErrorMessage="*" ValidateEmptyText="false" ControlToValidate="TextBox2" ValidationGroup="InsertSong" OnServerValidate="CustomValidator2_ServerValidate"></asp:CustomValidator>
</td>
<td>
<asp:LinkButton ID="LinkButton1" runat="server" CommandName="Insert" ValidationGroup="InsertSong">Přidat</asp:LinkButton>
</td>
</tr>
</table>
</InsertItemTemplate>
</asp:FormView>
Pro první a třetí políčko jsem použil stejný kód, který je v jejich šablonách EditItemTemplate v komponentě GridView. Oba dva validátory CustomValidator jsou napojeny na ty samé procedury, takže budou validovat stejně jako uvnitř GridView. Do druhého sloupce jsem přidal normální RequiredFieldValidator a na konec jsem přidal tlačítko LinkButton, které musí mít nastavenou vlastnost ItemCommand na hodnotu Insert, aby FormView věděl, že kliknutím na toto tlačítko se má přidat záznam. Máme zde ovšem ještě 2 problémy, které musíme vyřešit.
Když se pozorně podíváme na InsertCommand v komponentě SqlDataSource3, vidíme, že se nevyplňuje sloupec AlbumId, což zřejmě skončí s chybou, protože nebude jasné, do kterého alba novou písničku zařadit. Upravte tedy kód SqlDataSource3 takto:
<asp:SqlDataSource ID="SqlDataSource3" runat="server" ConnectionString="<%$ ConnectionStrings:ConnectionString %>"
DeleteCommand="DELETE FROM [Songs] WHERE [SongId] = @SongId"
InsertCommand="INSERT INTO [Songs] ([AlbumId], [Number], [Title], [Length]) VALUES (@AlbumId, @Number, @Title, @Length)"
SelectCommand="SELECT [SongId], [Number], [Title], [Length] FROM [Songs] WHERE ([AlbumId] = @AlbumId) ORDER BY [Number]"
UpdateCommand="UPDATE [Songs] SET [Number] = @Number, [Title] = @Title, [Length] = @Length WHERE [SongId] = @SongId">
<SelectParameters>
<asp:QueryStringParameter Name="AlbumId" QueryStringField="id" Type="Int32" />
</SelectParameters>
<DeleteParameters>
<asp:Parameter Name="SongId" Type="Int32" />
</DeleteParameters>
<UpdateParameters>
<asp:Parameter Name="Number" Type="Int32" />
<asp:Parameter Name="Title" Type="String" />
<asp:Parameter Name="Length" Type="DateTime" />
<asp:Parameter Name="SongId" Type="Int32" />
</UpdateParameters>
<InsertParameters>
<asp:Parameter Name="Number" Type="Int32" />
<asp:Parameter Name="Title" Type="String" />
<asp:Parameter Name="Length" Type="DateTime" />
<asp:QueryStringParameter Name="AlbumId" QueryStringField="id" Type="Int32" />
</InsertParameters>
</asp:SqlDataSource>
Do vlastnosti InsertCommand jsem akorát přidal sloupec AlbumId a do sekce InsertParameters jsem přidal parametr AlbumId, jehož hodnota se vytáhne z parametru id v adrese stránky (z QueryStringu). To by byla první věc.
Jako druhou věc musíme vyřešit opět ten samý problém s převodem délky skladby, vytvořte tedy proceduru události ItemInserting komponenty FormView. Tato událost se spustí těsně před přidáním záznamu do databáze. Stačí stejným způsobem jako u GridView upravit hodnotu v kolekci args.Values.
' správně převést čas
If (e.Values("Length") IsNot Nothing) AndAlso (Not String.IsNullOrEmpty(e.Values("Length").ToString())) Then
e.Values("Length") = DateTime.ParseExact(e.Values("Length").ToString(), "m:ss", Nothing, System.Globalization.DateTimeStyles.None)
End If
// správně převést čas
if ((e.Values["Length"] != null) && (!string.IsNullOrEmpty(e.Values["Length"].ToString())))
e.Values["Length"] = DateTime.ParseExact(e.Values["Length"].ToString(), "m:ss", null, System.Globalization.DateTimeStyles.None);
A to je celé přidávání záznamů, teď by měly jít přidávat, upravovat i mazat jednotlivé skladby u každého alba.
Dokončení aplikační logiky
Aplikace má ještě jednu nepříjemnou vlastnost, kterou vyřeším až příště. Pokud přidáváme nové album, nemůžeme přidávat skladby. To jde až při úpravě, což je na jednu stranu praktické pro nás (nemusíme řešit, jak přidat skladby do alba, které ještě v databázi není), ale nepraktické pro uživatele. Musí nejprve přidat album, pak jej na úvodní stránce najít, zobrazit si jeho detail a kliknout na tlačítko Upravit, aby mohl přidávat skladby. Ale můžeme to udělat tak, že po přidání alba bude uživatel automaticky přesměrován hned na detail tohoto alba v režimu úprav.
V příštím díle si také podrobně ukážeme, jak pracovat se vzhledem ASP.NET aplikací a jak jednoduše a snadno vytvořit skiny pro komponenty. Pro dnešek je to vše.