Tímto článkem navazuji na předchozí texty o prvku Pagination – ten je však použitelný pouze jako součást formuláře. Zde vytvoříme prvek, který umožní stránkování v podobě klasických odkazů, které jsou dobře přístupné i pro vyhledávače. Na rozdíl od stránkovatelného GridView bude funkční nezávisle na klientském skriptování a můžeme jej pohodlně použít třeba i pro Repeater.

Odkazy namísto odesílání formuláře

Motivací k vytvoření prvku bylo vytvoření podobného stránkování, jaké známe z prvku DataGrid, pro použití s prvkem Repeater. Stránkování bude probíhat formou odkazů s parametry (bez formuláře běžícího na serveru), které zajistí nezávislost na klientském skriptování a také přístupnost a použitelnost i pro vyhledávače. Pro stránkování jsou uživateli nabídnuty volitelně odkazy vpřed,zpět a/nebo sada prvků ukazujících číslo stránky a zvolenou stránku. Je zajištěno také inteligentní vykreslení daného počtu čísel stránek a automaticky doplněn odkaz na „další sadu stránek“, přičemž prvky, které nemají v dané situaci smysl, jsou zakázané (například přechod na předchozí stránku v situaci, kdy jsme na první stránce).

Pro programátora je důležité, že prvek správně vykreslí právě zvolenou stránku díky vlastnosti SelectedPageIndex a také umí předat parametr s číslem stránky, na kterou uživatel klikne.

Rozdíly proti formulářovému (PostBackovému) prvku

Vyjdeme z článku  Stránkovací prvek Pagination, využijeme většinu logiky tzv. kompozitního prvku. Je ovšem potřeba si uvědomit, že chování stránky s tímto prvkem – bez formuláře, bez PostBacku – bude odlišné od dobře známého webového formuláře.

Formulář běžící na serveru nemůžeme využít, a tak nelze použít vestavěné validátory. Můžeme sice použít klasické formulářové prvky pro formulář odesílaný metodou GET, ovšem případnou validaci si budeme muset logicky vyřešit po svém. Stránka bez formuláře bohužel také nevyvolává žádné události, zpracovat předané parametry tak musíme také sami prostřednictvím kolekce Request.QueryString.

Protože budeme číslo stránky předávat v podobě GET parametru, musíme mít možnost formovat výslednou URL stránkovacích odkazů. Inspiroval jsem se podobným způsobem, jaký používá prvek pro vytočení čísla v mobilním formuláři PhoneCall pro vlastnost AlternateFormat – do námi zadané podoby URL zapracujeme číslo stránky prostým formátovacím výrazem {0}.

Neopomeneme také dobrou přístupnost a použitelnost – mezi odkazy vložíme nedělitelnou mezeru, aby nikdy nedošlo k „nalepení“ odkazů na sebe.

Screenshoty příkladů použití

Ukázka kódu generovaného prvkem

<span><a disabled=“disabled“>Prev</a>&nbsp;<a href=“Test.aspx?Page=2″>Next</a></span>
<span><a disabled=“disabled“>1</a>&nbsp;<a href=“Test.aspx?Page=2″>2</a>&nbsp;<a href=“Test.aspx?Page=3″>3</a> . . . <a href=“Test.aspx?Page=10″>10</a>&nbsp;<a href=“Test.aspx?Page=11″>…</a></span>

Odkazy podobného typu jsou pro usnadnění stylování uzavřeny do elementů span, pro přístupnost jsou vkládány nedělitelné mezery (aby nedošlo k „nalepení“ odkazů na sebe).

Nestylovaný prvek (na konci odkaz na „další sadu“ stránek – „tři tečky“)

Nestylovaný prvek LinkPagination

Návrh stylování téhož prvku

Stylovaný prvek LinkPagination

Sestavení prvku

Postup tvorby bude stejný jako u formulářového prvku Pagination, využijeme dědění od Control s rozhraním INamingContainer a v CreateChildControls vytvoříme jeho jednotlivé ovládací prvky.

[System.Web.UI.ToolboxData(„<{0}:LinkPagination RunAt=\“Server\“></{0}:LinkPagination>“)]
public class LinkPagination : System.Web.UI.Control, INamingContainer
{
}

Nadeklarujeme všechny WebControly, ze kterých má být prvek složen.

public class Pagination : System.Web.UI.Control, INamingContainer
{
  protected System.Web.UI.WebControls.HyperLink hprPrev = new System.Web.UI.WebControls.HyperLink();
  protected System.Web.UI.WebControls.HyperLink hprNext = new System.Web.UI.WebControls.HyperLink();
  protected System.Web.UI.WebControls.Label lblNumeric = new System.Web.UI.WebControls.Label();
  protected System.Web.UI.WebControls.Label lblNextPrev = new System.Web.UI.WebControls.Label();
  .
  .
}

V konstruktoru rovnou přednastavíme výchozí vlastnosti použitých prvků:

public LinkPagination() : base()
{
  lblNextPrev.Visible = false;
  hprPrev.Text = „Prev“;
  hprPrev.Enabled = false;
  hprNext.Text = „Next“;
  hprNext.Enabled = false;
  lblNumeric.Visible = false;
}

Přepsáním metody CreateChildControls() prvku podstrčíme všechny prvky, ze kterých se má skládat:

protected override void CreateChildControls()
{
  this.Controls.Add(lblNextPrev);
  lblNextPrev.Controls.Add(hprPrev);
  lblNextPrev.Controls.Add(new LiteralControl(„&nbsp;“));
  lblNextPrev.Controls.Add(hprNext);
  this.Controls.Add(new LiteralControl(Environment.NewLine));
  this.Controls.Add(lblNumeric);
}

Využíváme metody Controls.Add() pro přidávání podřízených, přímé vložení části HTML kódu vytváříme přímo LiteralControl.

Interní proměnné, enumerace a vlastnosti

Budeme potřebovat několik proměnných pro udržení „aritmetiky“ prvku a nastavení chování prvku.

private Int32 _virtualItemCount = 0, _pageCount = 0, _selectedPageIndex = 0;
private Int32 _pageSize = 10, _pageButtonCount = 10;
private LinkPaginationNumericRepeatDirection _numericRepeatDirection = LinkPaginationNumericRepeatDirection.Horizontal;
private LinkPaginationMode _mode = LinkPaginationMode.AllControls;
private String m_NavigateUrlTemplate = „?Page={0}“;
private Int32 m_NavigateUrlIndexOffset = 1;

Pro přepínání vzhledu ještě doplníme potřebné dvě enumerace LinkPaginationMode a LinkPaginationNumericRepeatDirection.

public enum LinkPaginationMode
{
  AllControls,
  NextPrev,
  Numeric
}
public enum LinkPaginationNumericRepeatDirection
{
  Horizontal,
  Vertical
}

Obslužné metody

zajišťují vnitřní „inteligenci“ prvku – aby prvek sám dovedl korektně vypočítat vše potřebné, proto si alespoň stručně popíšeme, která se o co stará.

private void _calculatePageCount()
{
  if (_virtualItemCount > 0 && _pageSize > 0)
  {
    _pageCount = _virtualItemCount / _pageSize;
    if (_virtualItemCount % _pageSize > 0)
      _pageCount++;
  }
  else
    _pageCount = 0;
}

_calculatePageCount() přepočítává počet stránek ze zadaného počtu položek na stránce (PageSize) a celkového počtu položek (VirtualItemCount).

private void SetNumericLinkSelectedIndex(Int32 selectedIndex)
{
  foreach (Control c in lblNumeric.Controls)
  {
    if (c is HyperLink)
    {
      HyperLink link = (HyperLink)c;
      if (link.Text.Equals((selectedIndex + 1).ToString()))
      {
        link.Enabled = false;
        return;
      }
    }
  }
}

SetNumericLinkSelectedIndex() zajišťuje nalezení a zneaktivnění odkazu na právě zvolený index stránky – uživatel jednak vizuálně rozpozná, kterou stránku má zvolenou, a současně stránka dle pravidel SEO neodkazuje sama na sebe.

private HyperLink CreateNumericLink(String text, String navigateUrl, Boolean enabled)
{
  HyperLink link = new HyperLink();
  link.Text = text;
  link.NavigateUrl = navigateUrl;
  link.Enabled = enabled;
  return link;
}

CreateNumericLink() je pomocná metoda, která vytvoří nový odkaz na „číslo“ stránky – se správně nastavenou URL, číslem stránky a určením, zda je odkaz aktivní.

Veškerá inteligence se jako u prvku Pagination odehrává v metodě přepisující obsluhu události OnPreRender(), dochází zde k finálnímu přepočtu hodnot důležitých pro zobrazení správného počtu a také stavu ovládacích prvků.

override protected void OnPreRender(EventArgs e)
{
   if (_virtualItemCount > 0)
  {
    _calculatePageCount();
    if (_selectedPageIndex < 1)
      hprPrev.Enabled = false;
    else
      hprPrev.Enabled = true;
    if (_selectedPageIndex + 1 < _pageCount)
      hprNext.Enabled = true;
    else
    {
      _selectedPageIndex = _pageCount – 1;
      hprNext.Enabled = false;
    }
  }
  else
  {
    hprPrev.Enabled = false;
    hprNext.Enabled = false;
  }
  lblNextPrev.Visible = true;
  if (_mode == LinkPaginationMode.Numeric)
    lblNextPrev.Visible = false;
  lblNumeric.Visible = false;
  if (_mode != LinkPaginationMode.NextPrev)
  {
    lblNumeric.Visible = true;
    Int32 currentSetFirstItemValue = ((_selectedPageIndex / _pageButtonCount) * _pageButtonCount);
    Int32 actualItemValue = currentSetFirstItemValue;
    if (actualItemValue >= _pageButtonCount)
    {
      if (lblNumeric.Controls.Count > 0)
        lblNumeric.Controls.Add(new LiteralControl(„&nbsp;“));
      lblNumeric.Controls.Add(CreateNumericLink(„…“, String.Format(m_NavigateUrlTemplate, (actualItemValue – 1 + m_NavigateUrlIndexOffset).ToString()), true));
    }
  while (actualItemValue < (currentSetFirstItemValue + _pageButtonCount) && actualItemValue < _pageCount)
  {
    if (lblNumeric.Controls.Count > 0)
      lblNumeric.Controls.Add(new LiteralControl(„&nbsp;“));
    lblNumeric.Controls.Add(CreateNumericLink((actualItemValue + 1).ToString(), String.Format(m_NavigateUrlTemplate, (actualItemValue + m_NavigateUrlIndexOffset).ToString()), true));
      actualItemValue++;
    }
    if (actualItemValue < _pageCount)
    {
      if (lblNumeric.Controls.Count > 0)
      lblNumeric.Controls.Add(new LiteralControl(„&nbsp;“));
      lblNumeric.Controls.Add(CreateNumericLink(„…“, String.Format(m_NavigateUrlTemplate, (actualItemValue + m_NavigateUrlIndexOffset).ToString()), true));
    }
    if (lblNumeric.Controls.Count > 0)
    {
      if (_selectedPageIndex > 0)
        SetNumericLinkSelectedIndex(_selectedPageIndex);
      else
        SetNumericLinkSelectedIndex(0);
    }
    else
    {
      lblNumeric.Controls.Add(CreateNumericLink(„1“, String.Format(m_NavigateUrlTemplate, (_selectedPageIndex + m_NavigateUrlIndexOffset).ToString()), false));
      SetNumericLinkSelectedIndex(0);
    }
  }
  hprPrev.NavigateUrl = String.Format(m_NavigateUrlTemplate, _selectedPageIndex + m_NavigateUrlIndexOffset – 1);
  hprNext.NavigateUrl = String.Format(m_NavigateUrlTemplate, _selectedPageIndex + m_NavigateUrlIndexOffset + 1);
  base.OnPreRender(e);
}

Zde se těsně před vykreslením prvku v cyklu vytvoří nové HyperLink prvky pro odkazy na jednotlivé stránky, ale také se rozhodne, zda se má zobrazit odkaz na předchozí či následující sadu stránek, a také zda povolit tlačítko Předchozí a Další. Je-li potřeba, přidají se navíc nedělitelné mezery, aby odkazy na stránky byly dobře použitelné.

Využívá se již zmíněná metoda _calculatePageCount(), následně se pracuje s hodnotami celkového počtu položek i počtu stránek. Podle těchto se rozhodne o tom, jestli jsou nějaké stránky před a po aktuálně zvolené a podle toho se povolí nebo zakážou patřičné odkazy. Podle nastavení PaginationMode se povolí zobrazení buďto jen odkazů zpět/vpřed  nebo jen odkazů přímo na stránky nebo se zobrazí obojí.

Pokud je povoleno zobrazení odkazů na čísla stránek, provádí se několik dalších výpočtů. Vypočítává se, zda s aktuální zvolenou stránkou nejsme v části, kdy je potřeba doplnit odkazy pro předchozí či následující sadu stránek – tyto odkazy jsou vidět na ukázkových screenshotech. Počítá se zde s již přepočítanou hodnotou celkového počtu stránek, z které se vyhodnotí, zda vůbec je stránek více než jedna. Pokud je index zvolené stránky větší než 0, tak použijeme metodu SetNumericLinkSelectedIndex(). Pokud je však k dispozici pouze jedna stránka, vykreslí se jeden odkaz s hodnotou indexu stránky 0 a zobrazujícím číslo stránky 1 v podobě neaktivního odkazu.

Nakonec pomocí base.OnPreRender(e); zavoláme původní obsluhu této události, aby bylo korektně zachováno veškeré chování prvku zděděného z prvku Control.

Veřejné vlastnosti

Vlastnosti si popíšeme jen stručně, zájemce o studium implementace odkazuji na přiložený zdrojový kód a článek o prvku Pagination, ze kterého tento prvek vychází.

public String GoToPageText
Text tlačítka pro přechod na zvolenou stránku, tlačítko se zobrazuje pouze pokud klient nepodporuje JavaScript

public String GoToPageToolTip
Tooltip tlačítka pro přechod na zvolenou stránku, tlačítko se zobrazuje pouze pokud klient nepodporuje JavaScript

public String NumericPagesToolTip
Tooltip radiobuttonů pro přechod na zvolenou stránku

public String NextPageText
Text tlačítka pro přechod na následující stránku

public String NextPageToolTip
Tooltip tlačítka pro přechod na následující stránku

public String PrevPageText
Text tlačítka pro přechod na předchozí stránku

public String PrevPageToolTip
Text tlačítka pro přechod na následující stránku

public String NextPrevCssClass
Třída CSS pro odkazy zpět/vpřed

public String NumericLinkCssClass
Třída CSS pro odkazy na čísla stránek

public String NavigateUrlTeplate
Šablona URL pro vytvoření odkazu na číslo stránky, výchozí je ?Page={0}, do naší vlastní URL dosadíme číslo stránky sekvencí {0}

public String NavigateUrlIndexOffset
Offset číslování stránek, výchozí je 1, tj. první stránka s indexem 0 bude do URL předávána hodnotou 1

public Int32 VirtualItemCount
Vrací nebo nastaví virtuální (celkový) počet položek, které jsou stránkovány – vlastnost má týž význam, jako u prvků DataGrid, GridView

public Int32 SelectedPageIndex
Vrací nebo nastaví index právě zvolené stránky

public Int32 PageCount
Pouze vrací celkový počet stránek ke stránkování

public Int32 PageSize
Vrací nebo nastaví počet položek v jedné stránce – vlastnost má týž význam, jako u prvků DataGrid, GridView

public Int32 PageButtonCount
Vrací nebo nastaví maximální počet položek číselných odkazů stránek, na konci nebo začátku seznamu je generován odkaz pro přechod na „další sadu“ stránek, pokud nějaká předchází/následuje – vlastnost má týž význam, jako u prvků DataGrid, GridView

public LinkPaginationNumericRepeatDirection NumericRepeatDirection
Styl zobrazení čísel stránek – radiobuttonů

public LinkPaginationMode Mode
Režim zobrazení stránkovacích prvků

Enumerace

public enum LinkPaginationMode
Režim zobrazení ovládacích prvků
AllControls (čísla stránek i tlačítka vpřed/zpět)
NextPrev (pouze tlačítka vpřed/zpět)
Numeric (pouze čísla stránek)

public enum LinkPaginationNumericRepeatDirection
Způsob zobrazení čísel stránek
Horizontal (vodorovně)
Vertical (svisle)

Příklad použití ve stránce

 <div class=“pagingElement“>
  <Pc:LinkPagination Id=“pgnSearch“ Mode=“AllControls“ NavigateUrlTemplate=“TestLinkPagination.aspx?Site=Interval.cz&Page={0}“ NavigateUrlIndexOffset=“1″ NextPrevCssClass=“pageButton“ PageSize=“10″ RunAt=“server“ />
</div>

V ukázce vidíme nastavení režimu na číslované stránky i tlačítka vpřed/zpět, počet řádků na stránce na 10.

Jednoduché zpracování předaného čísla stránky

 private Int32 PageIndex = 0;
void Page_Load(Object sender, EventArgs e)
{
  if (!String.IsNullOrEmpty(Request.QueryString[„Page“]))
    if (Int32.TryParse(Request.QueryString[„Page“].ToString(), out PageIndex))
  pgnSearch.SelectedPageIndex = (PageIndex – 1);
  pgnSearch.VirtualItemCount = 1000;
}

Ověříme, zda parametr, v kterém předáváme v QueryStringu číslo stránky, je k dispozici. Pokud ano, vyzkoušíme jeho hodnotu přeparsovat do připravené proměnné. Poté ji použijeme k nastavení zvoleného indexu stránky SelectedPageIndex.

Kompletní nasazení prvku metody, ve které pracujeme se zdrojem dat, je analogické s použitím formulářového  prvku Pagination, ať se jedná o použití ve spojení s Univerzální stránkovací procedurou nebo pro případ, že předem neznáme celkový počet záznamů.

Jakkoli se může jevit použití prostých odkazů bez formuláře krokem zpět oproti moderním web forms – neměli bychom s vaničkou vylít i dítě a webové formuláře nadužívat – nejsou všelékem a v řadě případů mohou vytvořit skutečnou bariéru pro vyhledávací stroje. V tuto chvíli máme k dispozici jak variantu stránkování formuláře, tak variantu čistě s použitím odkazů. Pokud požadujeme stránkovat výstup složitějšího formuláře s validací uživatelského vstupu, zvolíme formulářový prvek Pagination, jinak je určitě vhodnější volit tento „odkazový“ stránkovací prvek – a to nejen s ohledem na SEO, ale i třeba na to, že můžeme komukoli poslat odkaz přesně na danou stránku.

Stažení ukázkové stránky s prvkem LinkPagination

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

Odpovědět