V řadě aplikací požadujeme, aby formulář korektně reagoval nejen na kliknutí na tlačítko, ale také na odeslání klávesou Enter. Ukážeme si, jak aplikaci upravit tak, aby správně reagovala i na odeslání klávesou Enter a obešli jsme se přitom bez vestavěné podpory pro výchozí tlačítko, která je závislá na podpoře klientského skriptování.

O čem to vlastně chci mluvit? Je to taková „ošklivá a nepěkná věc“ – pokud jednoduše bez rozmyslu naklikáme formulář (a bohužel je takových na webu vidět spousta), dočkáme se formuláře, který při odeslání klávesou Enter v lepším případě neudělá nic, v horším případě se aplikace dostane do nějakého podezřelého stavu. Zvlášť otravné je to u jednoduchých vyhledávacích políček, kdy je pak uživatel nucen klikat myší na tlačítko, jinak se výsledků nedočká.

Jak problém vzniká?

Předně je potřeba si říct, že se nám tu potkává několik nepříznivých faktorů dohromady:

  • Chování prohlížečů při odeslání formuláře klávesou Enter je různorodé – některý prohlížeč „stiskne“ první tlačítko formuláře, některý nestiskne žádné a leckdy také záleží na konkrétní úpravě kódu stránky či na tom, zda je ve formuláři tlačítko jedno nebo více.
  • Proti nám je i událostní model zpracovávání formuláře v ASP.NET – je logické, že pokud v aplikaci nadefinujeme, že se má obsluhovat událost kliknutí na tlačítko, aplikace se nemůže chovat „dobře“ v případě odeslání klávesou Enter.

Tak co s tím?

V ASP.NET je k dispozici podpora pro DefaultButton, která nám zajistí „stisknutí tlačítka“ klientským skriptem, bez podpory klientského skriptování je však neúčinná. Osobně si myslím, že by bylo pro ASP.NET v další verzi potřeba navrhnout nějakou „default“ obsluhu události – tedy takovou, která se provede vždy, když byl formulář odeslán tak, že vlastně nedorazila žádná informace o původci události (to je právě ono stisknutí Enter). Takovou možnost nyní nemáme, proto se pokusíme ji nahradit pomocí stávajících prostředků.

K dispozici máme možnost zachytávat události na více prvcích formuláře (TextChanged, SelectedIndexChanged a další). Jednoduše se tedy nabízí obsluhovat jak kliknutí na tlačítko, tak událost změny obsahu formuláře (jeho textových polí, zatrhávacích políček a podobně). To zní sice nadějně, ovšem jednoduchým nastavením obsluh všech možných událostí si lehce přivodíme stav, kdy se nám část zpracovávající formulář provede i vícekrát – a zajisté nechceme provádět například vyhledávání v databázi třeba třikrát jen proto, že uživatel změnil text v textovém poli, kliknul u toho na checkbox a to celé odeslal kliknutím na tlačítko.

Zamezení vícenásobného spuštění obslužné metody

Proti vícenásobnému spuštění obslužné metody si doplníme „semafor“ logickou proměnnou, například IsFormProcessed s výchozí hodnotou false. V obslužné metodě otestujeme, zda není true – pokud není, nastavíme ji na true a zpracujeme formulář. Pokud již bude hodnota true, formulář zpracovávat nebudeme – byl již zpracován nějakým předchozím voláním této obsluhy.

private Boolean IsFormProcessed = false;
private void Btn_Click(Object sender, EventArgs e)
{
  if (!IsFormProcessed)
  {
    IsFormProcessed = true;
    Bind_Page(); // naplnit stránku výsledky
  }
}

Problém s validátory

Pokud se rozhodneme zpracovávat různé události prvků stránky, rázem nám další problém nachystají validátory ve stránce. V případě, že je PostBack vyvolán prvkem, který sám nezpůsobí provedení metody Page.Validate – nemá nastaveno CausesValidation="True" – musíme tuto metodu zavolat ručně. Obyčejně se totiž právě v obslužných metodách událostí dotazujeme na Page.IsValid, avšak pokus o přístup k této vlastnosti před provedením Page.Validate skončí výjimkou. Budeme proto potřebovat minimálně dvě metody pro obsluhu událostí – jednu takovou, na jakou jsme zvyklí, a druhou, která nejprve provede Page.Validate a pak může s klidem zavolat tu první.

Obezličkou s „ručním“ zavoláním Page.Validate si pomáháme proto, že zejména v ASP.NET 1.1, ale u některých prvků i v ASP.NET 2.0, není k dispozici možnost vynutit validaci nastavením CausesValidation="True". Pokud ve formuláři nemáme takový prvek, který nepodporuje CausesValidation, můžeme část s ručním voláním Page.Validate vynechat.

private Boolean IsFormProcessed = false;
private void Enter_Click(Object sender, EventArgs e)
{
  Page.Validate();
  Btn_Click(sender,e);
}
private void Btn_Click(Object sender, EventArgs e)
{
  if (!IsFormProcessed)
  {
    IsFormProcessed = true;
    Bind_Page(); // naplnit stránku výsledky
  }
}

Je zřejmé, že na obslužnou metodu Enter_Click() či Btn_Click() můžeme dle potřeby navázat řadu dalších prvků, které ve formuláři máme, tedy různá další tlačítka, radiobuttony a jiné. Výsledkem bude vždy pouze jedno provedení oné hlavní výkonné části – vyhneme se tak nežádoucímu vícenásobnému spouštění dotazů do databáze, posílání mailů a podobně.

A co ten „velký“ ViewState?

Když už řešíme „mravné chování“ aplikace, je na místě také rozhodnout, nakolik budeme šetřit objem stránky a tedy potřebný přenos dat na server pomocí ViewState. Pokud se totiž uživateli při práci s formulářem povede stránku odeslat tak, že se neprovede žádná obsluha události (a to lze jednoduše – kliknout do textového pole a stiskem Enter odeslat již dříve odeslaný text), pak se nám vykazovací prvky s vypnutým ViewState vyprázdní – tedy například vyhledané výsledky v Repeateru prostě zmizí. S ohledem na „slušné“ chování aplikace a problematické řešení tohoto nechtěného efektu bych proto zvážil, nakolik bude vadit větší ViewState jako daň za řádné chování aplikace a případně jej nechat zapnutý.

Kulišárna se skrytým polem

V případě, že máme ve formuláři pouze jedno tlačítko (formulář má tedy pouze jednu funkci), můžeme použít ještě jeden trik – vložíme do formuláře prvek HiddenField a vypneme mu ViewState. Následně nastavíme obsluhu události ValueChanged na stejnou metodu, jako obsluhu TextChanged u textového pole. Tak se stane, že při každém odeslání formuláře dojde k události vyvolané tímto skrytým polem. Je to lepší, než obsluhovat formulář kompletně v části Page_Load jen proto, že se některé prohlížeče chovají „divně“. Finta se skrytým polem se tedy skvěle hodí pro různé vyhledávače, které nemají různé funkce ovládané různými odesílacími tlačítky.

<asp:HiddenField Id=“inputHiddenField“ EnableViewState=“False“ RunAt=“Server“ />
<asp:TextBox Id=“txbKeywords“ CausesValidation=“True“ RunAt=“Server“ />
<br />
<asp:Button Id=“btnSearch“ Text=“Button“ RunAt=“Server“ />

Zaregistrujeme obsluhy událostí

Zbývá nám už jen zaregistrovat obsluhy události. Jak jsem napsal výše, budeme registrovat nejen obsluhu pro kliknutí na tlačítko btnSearch, ale také pro změnu obsahu prvků formuláře. Můžeme také sledovat změnu textu v textovém poli txbKeywords a dalších prvcích formuláře – nebo použít dříve popsanou fintu se skrytým polem s vypnutým ViewState (v ukázce je zakomentováno).

private void InitializeComponent()
{
  this.txbKeywords.TextChanged += new System.EventHandler(this.Enter_Click);
  // this.inputHiddenField.ValueChanged += new System.EventHandler(this.Enter_Click);
  this.btnSearch.Click += new System.EventHandler(this.Btn_Click);
  this.Load += new System.EventHandler(this.Page_Load);
}

Ukázkový kód stránky s formulářem

Po uplatnění všech výše uvedených poznatků vznikne následující kód (dle uvážení vyřadíme či použijeme skryté pole inputHiddenField):

<form RunAt=“Server“>
  <asp:HiddenField Id=“inputHiddenField“ EnableViewState=“False“ RunAt=“Server“ />
  <asp:TextBox Id=“txbKeywords“ CausesValidation=“True“ RunAt=“Server“ />
  <br />
  <asp:Button Id=“btnSearch“ Text=“Button“ RunAt=“Server“ />
</form>
private Boolean IsFormProcessed = false;
private void Enter_Click(Object sender, EventArgs e)
{
  Page.Validate();
  Btn_Click(sender,e);
}
private void Btn_Click(Object sender, EventArgs e)
{
  if (!IsFormProcessed)
  {
    IsFormProcessed = true;
    Bind_Page(); // naplnit stránku výsledky
  }
}
private void InitializeComponent()
{
  this.txbKeywords.TextChanged += new System.EventHandler(this.Enter_Click);
  // this.inputHiddenField.ValueChanged += new System.EventHandler(this.Enter_Click);
  this.btnSearch.Click += new System.EventHandler(this.Btn_Click);
  this.Load += new System.EventHandler(this.Page_Load);
}

Závěrem bychom si měli říci, že určitě stojí za zvážení, zda u velmi jednoduchých formulářů – vyhledávacích políček – raději nepoužít klasické zpracování přes vlastnosti Request.Form a Request.QueryString a netrápit sebe komplikovaným kódem a uživatele přeposíláním hodnoty ViewState.

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

Žádný příspěvek v diskuzi

Odpovědět