Control pro zadávání předem neznámého počtu položek

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:

  1. 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).
  2. 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.
  3. 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í.
  4. 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.
  5. 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

Napsat komentář

Vyplňte detaily níže nebo klikněte na ikonu pro přihlášení:

WordPress.com Logo

Komentujete pomocí vašeho WordPress.com účtu. Log Out / Změnit )

Twitter picture

Komentujete pomocí vašeho Twitter účtu. Log Out / Změnit )

Facebook photo

Komentujete pomocí vašeho Facebook účtu. Log Out / Změnit )

Google+ photo

Komentujete pomocí vašeho Google+ účtu. Log Out / Změnit )

Připojování k %s