Aby se při editaci položek v dlouhém DataGridu stránka při post-backu nevyrolovala vždy nahoru, máme k dispozici volbu SmartNavigation. Tato volba bohužel často koliduje s jinými skripty ve stránce a správně funguje jen v Internet Exploreru, a to ještě ne vždy – někdy dokonce působí pád prohlížeče. V následujícím článku si ukážeme, jak udržet pozici editované položky vlastním způsobem, který spolehlivě funguje ve všech nejpoužívanějších prohlížečích.

Nejprve stručné zopakování, k čemu je vůbec SmartNavigation dobrá. Pokud máme dlouhý seznam řádků, například v DataGridu nebo DataListu, který umožňuje editaci v místě, pak při zapnutí editačního módu u řádku nacházejícího se při načtení stránky mimo viditelnou část okna dojde ke „zmizení“ editovaného řádku. Dojde totiž k post-backu, stránka se znovu nahraje, čímž se zobrazí horní část stránky, zatímco editovaný řádek je kdesi dole mimo viditelnou část a uživatel musí okno narolovat, aby řádek viděl. SmartNavigation zajistí, že po post-backu se stránka sama naroluje do pozice ve které byla, když byl vyvolán post-back.

Původní princip SmartNavigation využívá vnořeného skrytého rámce (iframe). Náš způsob pro udržení pozice bude pracovat jinak. Do formuláře přidáme dvě skrytá pole, která nám budou při post-backu udržovat vodorovnou a svislou pozici narolování okna. Aby tato pole ovšem měla co držet, musíme zajistit jejich naplnění. Plnit a udržovat v nich hodnotu při rolování stránkou a změně velikosti okna budeme jednoduchým JavaScriptem. Při zavádění stránky pak obnovíme pozici narolování okna na hodnoty ze zmíněných skrytých polí.

Abychom mohli snadno použít náš doplněk v libovolné (třeba už hotové) aplikaci, upravíme JavaScript do uživatelského ovládacího prvku.

Nejprve si ukažme torzo stránky s DataGridem, do které zaregistrujeme a přidáme náš ovládací prvek:

<%@ Page Language=“C#“ Trace=“False“ EnableSessionState=“False“ Debug=“False“ %>
<%@ Register TagPrefix=“mycode“ TagName=“SaveScroll“ Src=“SaveScroll.ascx“ %>
<%@ Import Namespace=“System.Data“ %>
<%@ Import Namespace=“System.Data.SqlClient“ %>
<script language=“C#“ runat=“server“>
void Page_Load(object sender, System.EventArgs e)
{
  if (!IsPostBack)
  { // není post-back, naplnit DataGrid
    BindDataGrid();
  }
}
void BindDataGrid()
{
  // naplnit DataGrid např. pomocí SqlDataReaderu
}
</script>
<html>
  <head>
    <meta http-equiv=“Content-Type“ content=“text/html; charset=utf-8″>
    <!–[if gte IE 6]><meta http-equiv=“Page-Enter“ content=“BlendTrans(Duration=0.1)“ /><![endif]–>
    <title>Lepší DataGrid…</title>
  </head>
  <body>
    <form runat=“server“>
      <asp:datagrid id=“Datagrid1″ AutoGenerateColumns=“true“ DataKeyField=“id“ RunAt=“server“>
      </asp:datagrid>
      <mycode:SaveScroll RunAt=“server“ />
    </form>
  </body>
</html>

Vidíme, že náš ovládací prvek je ve stránce přidán úplně na konec formuláře – proč je právě zde, vyplyne z dalšího popisu kódu ovládacího prvku:

<%@ Control Language=“C#“ %>
<script language=“C#“ runat=“server“>
void Page_Load(object sender, System.EventArgs e)
{
   ltrSF.Text = __SAVESCROLLY.ClientID;
   ltrSFX.Text = __SAVESCROLLX.ClientID;
   ltrRF.Text = __SAVESCROLLY.ClientID;
   ltrRFX.Text = __SAVESCROLLX.ClientID;
}
</script>
<input id=“__SAVESCROLLY“ value=“0″ type=“hidden“ runat=“server“ />
<input id=“__SAVESCROLLX“ value=“0″ type=“hidden“ runat=“server“ />
<script type=“text/javascript“>
<!– <![CDATA[
 function saveScroll()
{
  var sScrollX;
  var sScroll;
  if (document.documentElement && document.documentElement.scrollTop)
  {
    sScroll = document.documentElement.scrollTop;
    sScrollX = document.documentElement.scrollLeft;
  }
  else if (document.body)
  {
    sScroll = document.body.scrollTop;
    sScrollX = document.body.scrollLeft;
  }
  else
  {
    sScroll = 0;
    sScrollX = 0;
  }
  document.getElementById(‚<asp:Literal Id=“ltrSF“ RunAt=“server“ />‘).value = sScroll;
  document.getElementById(‚<asp:Literal Id=“ltrSFX“ RunAt=“server“ />‘).value = sScrollX;
}
function restoreScroll()
{
  var sScroll = document.getElementById(‚<asp:Literal Id=“ltrRF“ RunAt=“server“ />‘).value;
  var sScrollX = document.getElementById(‚<asp:Literal Id=“ltrRFX“ RunAt=“server“ />‘).value;
  if (sScroll > 0 || sScrollX > 0)
  {
    if (document.documentElement && document.documentElement.scrollTop)
    {
      document.documentElement.scrollTop = sScroll;
      document.documentElement.scrollLeft = sScrollX;
    }
    else if (document.body)
    {
      if (window.navigator.appName == ‚Netscape‘)
        window.scroll(sScrollX, sScroll);
      else
      {
        document.body.scrollTop = sScroll;
        document.body.scrollLeft = sScrollX;
      }
    }
    else
    {
      window.scroll(sScrollX, sScroll);
    }
// odkomentovat pro možné využít absolutního pozicování Panelu
// if (document.getElementById(‚pnlNewItem‘) != null )
// document.getElementById(‚pnlNewItem‘).style.top = sScroll + ‚px‘;
  }
}
window.onload = restoreScroll;
window.onscroll = saveScroll;
window.onresize = saveScroll;
//]]> –>
</script>

Ovládací prvek do stránky vloží dvě skrytá vstupní pole, která budou zpracována na serveru, mají tedy přidán atribut runat=“server“. Zde jen zopakuji, že takovýto prvek drží (pamatuje si) svůj obsah i při odeslání stránky (post backu), není třeba se tedy dál nijak o zpětné získání jeho obsahu starat. Prvky jsou také pojmenovány, abychom s jejich hodnotami mohli zacházet. Identifikátory jsou voleny tak, aby šance, že dojde ke kolizi názvu s jiným prvkem ve stránce byla minimální – proto ta podtržítka v názvu.

Dále je obsahem prvku obslužný JavaScript. Část tohoto klientského skriptu vytváříme jednoduše pomocí prvku Literal – do klientského skriptu tak dostaneme skutečné identifikátory našich skrytých vstupních polí, tak jak budou renderovány v prohlížeči klienta. Identifikátor prvků použitých v ovládacím prvku je totiž dynamicky přizpůsobován podle toho, v jakém nadřízeném prvku se náš uživatelský prvek nachází. Ve skutečnosti může náš prvek __SAVESCROLLY mít v prohlížeči název třeba _ctl11:__SAVESCROLLY. Aby náš JavaScript pracoval se správnými názvy prvků, vytvoříme názvy, které náš skript bude používat, až za běhu v události Page_Load. Vlastnosti Text ovládacích prvků Literal zde naplníme pomocí vlastnosti ClientID, která udává název, pod jakým bude prvek renderován u klienta.

Nyní si popíšeme funkci klientského skriptu. Tento sestává z funkcí saveScroll pro ukládání pozic narolování okna a restoreScroll pro obnovení pozice okna. Obnovení okna je navázáno na událost window.onload, naopak aktuální pozici okna ukládáme při událostech window.onscroll a windows.onresize. Při pohybu s obsahem okna, rolováním nebo změně velikosti se tak provádí funkce ukládající aktuální stav, při zavedení stránky se obnoví pozice okna podle obsahu uloženého ve skrytých prvcích formuláře.

Ukládání pozice narolování okna je trochu komplikované v tom, že ne každý prohlížeč tyto údaje poskytuje ve stejném objektu a například u Internet Exploreru je informace dostupná v závislosti na použitém Doctype v různých objektech (např. document.documentElement.scrollTop vs. document.body.scrollTop). Připravíme si tedy dvě proměnné pro svislou a vodorovnou souřadnici pozice a podle toho, v kterém objektu se nám podaří objevit danou vlastnost, je naplníme – poslední větev rozhodování, kde do proměnných naplňujeme „ze zoufalství“ nulu, by bylo možné vypustit, já jsem ji však ponechal pro případné budoucí rozšíření o vlastnosti dostupné v některých zvláštních prohlížečích, které jsem v době vývoje neměl k dispozici pro otestování. Nakonec zjištěné hodnoty v proměnných naplníme jako hodnotu připravených skrytých polí formuláře – přistupujeme k nim pomocí getElementById.

Obnovení pozice okna probíhá opačným způsobem, připravené proměnné naplníme ze skrytých polí formuláře. Následně zjistíme, kterou vlastnost (objekt) v daném prohlížeči můžeme uplatnit a podle toho pak nastavíme hodnoty souřadnic narolování okna. Zde jen podotýkám, že v prohlížečích na bázi Mozilly je pro zápis k dispozici jen vlastnost window.scroll, vlastnost document.body.scrollTop je zde bohužel jen pro čtení. Toto nevadí, pokud máme nastaven margin a padding body na 0px, pokud je nějaký okraj nebo odsazení nenulové, hodnoty se liší a pozice okna pak při post-backu právě o tento rozdíl „poskakuje“. Lidově řečeno je to však lepší, než drátem do oka – bez našeho skriptu by se totiž stránka vyrolovala úplně nahoru, a to je pro uživatele mnohem nepohodlnější.

Ve skriptu vidíte na konci funkce restoreScroll ještě zakomentované řádky, které já používám pro určení pozice absolutně pozicovaného Panelu (je ve stránce renderován jako DIV), ve kterém přidávám do zdroje dat nový záznam. Protože mám odkaz pro přidání nového záznamu v zápatí DataGridu a uživatel se k němu musí „narolovat“, je pro něj pohodlnější, když se panel po kliknutí na odkaz objeví ve viditelné části okna, což právě zajistí ony zakomentované řádky. Pokud byste chtěli tohoto využít, je kromě odkomentování potřeba nastavit také identifikátor vašeho panelu – ten můj je pojmenován pnlNewItem.

Třešničkou na dortu je použití přechodu stránky Blend-Trans, který dokonale zamaskuje post-back. Protože zde chceme použít velmi krátký čas přechodu 0,1s, který podporuje IE6+, vložíme speciální metaelement, který přechod stránky zajišťuje, do podmíněného komentáře. Tuto třešničku si tak dopřejí jen uživatelé IE6+, uživatelé předchozích verzí IE by mohli být totiž rušeni přechodem trvajícím 1s. Jiné prohlížeče než IE přechody nepodporují vůbec. Použití tohoto metaelementu vidíte v hlavičce ukázky stránky výše.

Jak je z ukázky patrno, do již hotové aplikace prostě přikopírujete soubor s ovládacím prvkem SaveScroll.ascx, zaregistrujete jej do stránky, přidáte element prvku před konec formuláře a případně přidáte do hlavičky zmíněný přechod stránky. Pro hromadné nasazení ve vašich aplikacích by se určitě hodilo přepsání prvku na serverový ovládací prvek, který mnohem lépe podporuje myšlenku OOP, ale jehož vytvoření je pracnější a přesahuje rozsah tohoto článku.

Popisovaný zdrojový kód si můžete stáhnout.

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