Ukažme si základní schema jednoduchého controlu, který umožní uživateli zadat data předem neurčeného počtu položek – umožní tedy uživateli dynamické přidávání dalších položek – např. řádky objednávky, několik telefonních čísel, několik e-mail e-mailů – v našem případě několik zásilek k přepravě.
Netvrdím, že níže uvedený postup je jediný možný a uvítám jakoukoliv diskuzi pod článkem, berte to spíše jako nasměrování pro další snažení…
Neřeším také, jak na control navázat již existující data (možná v příštím článku), a i vytahování dat z controlu není úplně user-friendly. Nicméně cílem je demonstrovat základní schéma dynamického počtu položek a vylepšení kolem už si každý jistě umí udělat.
Základní schema by se dalo shrnout:
- Vytvoříme si control představující jednu položku – není to sice nezbytně nutné, ale usnadní to další manipulaci – control samozřejmě může už existovat, pokud se ptáme jen na jména milenek, může nám stačit už TextBox – nicméně většinou budeme potřebovat control, který bude obsahovat nekolik prvků, validátory, DropDownListy naplněné daty, atp. V našem konkrétním případě jsem si teda připravil UserControl Zasilka.ascx, který má za předka třídu Zasilka (code-behind).
- Vytvoříme CompositeControl, který bude v CreateChildControls dynamicky přidávat tolik položek, kolik je právě potřeba. Aby nám fungoval postback a viewstate, musí být control-tree vystavěn při každém roundtripu znovu, a to právě ve fázi Init. V CompositeControl je připraveno schéma pomocí metody CreateChildControls(), která je volána právě už ve fázi Init. Zásadním problémem zde je, jak ve fázi Init zjistit, kolik položek máme zrovna vygenerovat, když ještě nemáme načtena post-back data, ani ViewState.
- Aktuální počet položek si musíme ukládat tak, abychom ho znali už ve fázi Init, například do hidden-inputu. Lze využít i Session, či jiná uložiště, vlastní hidden-input se však zde nabízí jako ideální.
- Přidání další položky realizujeme v event-handleru pomocí Controls.AddAt(), odebírání položky pomocí Controls.RemoveAt(). Klikne-li uživatel na tlačítko „přidat další položku“, dozvíme se o tom v event-handleru až v poměrně pokročilé fázi zpracování postbacku, když už je control-tree vytvořen (jinak by ani event nemohl být korektně obsloužen). V této fázi jsou existující položky i naplněny daty a nebylo by tedy vhodné znovu rebuildovat control-tree, protože bychom tato data ztratili, popř. museli znovu dotahovat. Protože však control-tree díky jeho pevné stavbě známe, nic nám nebrání přidat další položku do existujícího control-tree, popř. položku odebrat.
- Data z controlu vytahujeme například přímým přístupem do Controls, jelikož strukturu položek v Controls známe.
Kód takového controlu s dynamickým počtem položky by tedy mohl vypadat nějak následovně:
public class Zasilky : CompositeControl { // Počet zásilek si mezi postbacky posíláme v input-hidden, // z něj ho načítáme přímo pomocí Form, ukládáme až těsně // před renderem OnPreRender(), abychom měli poslední stav // po případném přidání/odebrání. public int PocetZasilek { get { if (_pocetZasilek == null) { _pocetZasilek = 1; object tmp = Page.Request.Form[this.ClientID + "_PocetZasilek"]; if (tmp != null) { _pocetZasilek = Convert.ToInt32(tmp); } } return (int)_pocetZasilek; } set { _pocetZasilek = value; } } private int? _pocetZasilek; // abychom mohli buttony referencovat (chceme je skrývat), // uložíme si odkaz na ně do private fieldu private LinkButton pridejZasilkuLB; private LinkButton odeberZasilkuLB; // uloží nám počet položek do input-hidden protected override void OnPreRender(EventArgs e) { Page.ClientScript.RegisterHiddenField(this.ClientID + "_PocetZasilek", _pocetZasilek.ToString()); base.OnPreRender(e); } // klasické schéma CompositeControlu protected override void CreateChildControls() { Controls.Clear(); CreateControlHiearchy(); ClearChildViewState(); } // budujeme control-tree private void CreateControlHiearchy() { // položky - jsou naschvál na začátku, což nám usnadňuje jejich snadné referencování pomocí Controls[i], // jinak bychom museli přístup k nim mít složitější for (int i = 0; i < PocetZasilek; i++) { CreateZasilkaControl(Controls.Count); } Literal lit1 = new Literal(); lit1.Text = "<tr><td colspan=\"3\" class=\"zindent\">"; Controls.Add(lit1); // tlačítko pro přidání položky pridejZasilkuLB = new LinkButton(); pridejZasilkuLB.ID = "PridejZasilkuLB"; // <-- nutno nastavit ID, jinak nám můžou blbnout postbacky pridejZasilkuLB.Text = (string)HttpContext.GetGlobalResourceObject("Zasilky", "PridejZasilku"); pridejZasilkuLB.CssClass = "arrow"; pridejZasilkuLB.Click += new EventHandler(pridejZasilkuLB_Click); pridejZasilkuLB.CausesValidation = false; Controls.Add(pridejZasilkuLB); Literal lit3 = new Literal(); lit3.Text = "<br/>\n"; Controls.Add(lit3); // tlačítko pro odebrání položky odeberZasilkuLB = new LinkButton(); odeberZasilkuLB.ID = "OdeberZasilkuLB"; // <-- nutné nastavit ID, jinak nám můžou blbnout postbacky odeberZasilkuLB.Text = (string)HttpContext.GetGlobalResourceObject("Zasilky", "OdeberPosledniZasilku"); odeberZasilkuLB.CssClass = "arrow"; odeberZasilkuLB.Click += new EventHandler(odeberZasilkuLB_Click); odeberZasilkuLB.CausesValidation = false; odeberZasilkuLB.Visible = (PocetZasilek > 1); Controls.Add(odeberZasilkuLB); Literal lit2 = new Literal(); lit2.Text = "</td></tr>\n"; Controls.Add(lit2); } // vytvoření jedné položky v control-tree private void CreateZasilkaControl(int index) { Zasilka zasilkaControl = (Zasilka)Page.LoadControl("~/Controls/Zasilka.ascx"); zasilkaControl.ID = "ZasilkaUC_" + index.ToString(); // <-- nutné nastavit ID kvůli korektním postback Controls.AddAt(index, zasilkaControl); } // obsluha události - přidání položky private void pridejZasilkuLB_Click(object sender, EventArgs e) { PocetZasilek++; CreateZasilkaControl(PocetZasilek - 1); // přidáme za poslední položku odeberZasilkuLB.Visible = true; } private void odeberZasilkuLB_Click(object sender, EventArgs e) { if (PocetZasilek > 1) { PocetZasilek--; Controls.RemoveAt(PocetZasilek); // odebereme poslední položku if (PocetZasilek == 1) { odeberZasilkuLB.Visible = false; } } } }
Vytahování dat z controlu by pak v nejjednoduším případě mohlo vypadat nějak takto:
for (int i=0; i < Zasilky.PocetZasilek; i++) { Zasilka zasilka = (Zasilka)Zasilky.Controls[i]; uloziste[i] = zasilka.Hmotnost; } // kdyby byl položkou jen TextBox for (int i=0; i < Zasilky.PocetZasilek; i++) { TextBox polozka = (TextBox)MyControl.Controls[i]; uloziste[i] = polozka.Text; }
Attachment: screenshot.gif