Calendar control v ASP.NET

16. září 2004

Framework .NET nám nabízí serverový ovládací prvek Calendar. Použít se dá mnohými způsoby – ve své poslední aplikaci jsem ho využíval k tomu, aby si uživatelé mohli rezervovat místo na určitý termín na konferenci. Protože však byl počet míst v termínu omezen, bylo nutné zakázat uživateli vybrat již obsazený termín. Jak to ale provést?

Nabízely se dvě možnosti řešení výše zmiňované situace. První spočívá v tom, že uživateli umožníme vybrat v Calendaru jakékoli datum a až na serveru projdeme databázi a zkontrolujeme, jestli je volno. V případě že ne, vrátíme zájemci omluvnou zprávu. Jako mnohem efektivnější se mi však jeví postup opačný – už při nahrávání Calendaru projít databázi a plné termíny hned zakázat. Zabráníme tím několika zbytečným požadavkům na server a i pro uživatele to znamená nezanedbatelnou úsporu času.

V tomto článku si napíšeme podobnou aplikaci. Bude se jednat o formulář, který si bude pamatovat dny, které jsme v kalendáři označili. Při renderování pak tyto dny vykreslí jinou barvou a zakáže uživateli zvolený den příště vybrat. Stává se však, že je potřeba již označený termín zrušit. Daný problém si trošku usnadníme, s detaily si potom můžete pohrát sami.

Nejdříve tedy vytvoříme formulář ASP.NET a vložíme do něj ovládací prvek Calendar. Vizuální formátování prvku jsem ponechal spíš pro procvičení základních formátovacích možností kalendáře.

<%@ Page Language=“C#“ %>
<%@ import Namespace=“System.Data“ %>
<%@ import Namespace=“System.Xml“ %>
<html>
<head>
</head>
<body style=“FONT-FAMILY: Verdana“>
  <form runat=“server“>
    <asp:Calendar id=“cal“ runat=“server“ OnDayRender=“cal_DayRender“ BackColor=“White“ Width=“200px“ DayNameFormat=“FirstLetter“ ForeColor=“Black“ Height=“180px“ Font-Size=“8pt“ Font-Names=“Verdana“ BorderColor=“#999999″ CellPadding=“4″>
      <TodayDayStyle forecolor=“Black“ backcolor=“#CCCCCC“></TodayDayStyle>
      <SelectorStyle backcolor=“#CCCCCC“></SelectorStyle>
      <NextPrevStyle verticalalign=“Bottom“></NextPrevStyle>
      <DayHeaderStyle font-size=“7pt“ font-bold=“True“ backcolor=“#CCCCCC“></DayHeaderStyle>
      <SelectedDayStyle font-bold=“True“ forecolor=“White“ backcolor=“#666666″></SelectedDayStyle>
      <TitleStyle font-bold=“True“ bordercolor=“Black“ backcolor=“#999999″></TitleStyle>
      <OtherMonthDayStyle forecolor=“#808080″></OtherMonthDayStyle>
    </asp:Calendar>
  </form>
</body>
</html>

Během nahrávání stránky budeme potřebovat zjistit, které termíny už máme plné. Musíme tedy načíst data z našeho datového zdroje. Já jsem použil XML soubor čistě jen na základě osobních preferencí. Nic vám nebrání sáhnout třeba po MS SQL či Accessu, nebo čemkoli jiném. Metoda Page_Load() může vypadat třeba následovně:

void Page_Load(object sender, EventArgs e)
{
  ds = new DataSet();
  ds.ReadXml(Server.MapPath(„fulldays.xml“));
}

Vycházím z předpokladu, že jsme si na úrovni třídy deklarovali proměnnou ds typu DataSet. Tento DataSet jsme jednoduše naplnili voláním metody ReadXml(). Je tedy na čase konfrontovat kalendář s DataSetem. Využijeme události DayRedner, která se vyvolá při vykreslování jednotlivých dnů. V obslužné metodě této události vždy projdeme dataset a zjistíme, jestli je právě vykreslovaný den již obsazen. Pokud ano, podbarvíme buňku červeně a zakážeme vybrání tohoto dne:

void cal_DayRender(object sender, DayRenderEventArgs e)
{  
  foreach (DataRow dr in ds.Tables[0].Rows)
  {
    if (e.Day.Date == System.Convert.ToDateTime(dr[„date“]))
    {
      if (e.Day.IsOtherMonth)
      {
        e.Cell.BackColor = System.Drawing.Color.LightCoral;
      }
      else
      {
        e.Cell.BackColor = System.Drawing.Color.Red;
      }
    }
  }
}

Metoda je opět jednoduchá, ale pro jistotu si ji projdeme. Data pro událost jsou obsažena v atributu typu DayRenderEventArgs. Tato třída vystavuje veřejné vlastnosti Day a Cell. Vlastnost Day nám nabízí objekt typu CalendarDay, který reprezentuje právě vykreslovaný den. Naproti tomu vlastnost Cell představuje buňku tabulky, do které bude tento den vykreslen. Dále budeme potřebovat znát některé vlastnosti tříd CalendarDay a TableCell. Z veřejných vlastností, které vystavuje třída CalendarDay, nám budou stačit pouze dvě, Date a IsOtherMonth (přičemž druhou zmíněnou využijeme spíše jen pro krásu). Co se pod těmito názvy skrývá, není třeba vysvětlovat. Zmíním jen, že vlastnost Date vrací hodnotu typu DateTime a IsOtherDay hodnotu typu bool. U TableCell využijeme jen vlastnost BackColor. K podrobnému prozkoumání zmiňovaných tříd vás odkážu na dokumentaci .NET Frameworku.

Pojďme se ale konečně podívat na naši metodu. Cyklem foreach postupně procházíme jednotlivé řádky v tabulce. Pokud vykreslovaný den (e.Day.Date) odpovídá hodnotě v tabulce (hodnotu je třeba zkonvertovat na typ DateTime), podbarvíme buňku jinou barvou. Ještě jsme si to „vylepšili“ testem, jestli den patří do daného měsíce či nikoli, abychom barvu buňky diferencovali. Když už máme buňku odlišně zbarvenou, bylo by vhodné ještě zabránit kliknutí na daný den. To nám zajišťuje veřejná vlastnost CalendarDay.IsSelectable. Mohl bych tuto vlastnost nastavit rovnou na tomto místě, ale je tu několik důvodů, proč jsem tak neudělal.

Nyní se musíme zamyslet, jak budeme rozlišovat, jestli termín přidáme nebo jestli termín odebereme. Opět se nabízí několik možností. Jednou z nich může být například projít databázi a pokud se datum vyskytuje, tak ho odebrat, a pokud ne, tak přidat. V praxi by všechno bylo složitější hned o několik faktorů. Jako nejzávažnější by se určitě jevil fakt, že zrušit termín může buď admin nebo uživatel, který se registroval. To není nic těžkého, proto vás nebudu zdržovat implementací. Pro naše potřeby to šikovně obejdeme pomocí prvku RadioButtonList, který bude obsahovat dvě položky, a to „Přidat“ a „Odebrat“. Teď jsme se právě dostali k místu, kde považuji za vhodné řešit, zda je možné na den klepnout myší či nikoli. Pokud bude označenou položkou „Přidat“, budou přístupné dny volné, bude-li označeno „Odebrat“, zpřístupníme dny obsazené.

<asp:RadioButtonList id=“rb“ runat=“server“ Font-Size=“11px“ AutoPostBack=“True“>
  <asp:ListItem Value=“add“ Selected=“True“>Přidat termín</asp:ListItem>
  <asp:ListItem Value=“remove“>Zrušit termín</asp:ListItem>
</asp:RadioButtonList>

Pokud tedy chceme určitým způsobem omezovat vybírání dnů, musíme doplnit metodu cal_DayRender třeba takto:

void cal_DayRender(object sender, DayRenderEventArgs e)
{
  foreach (DataRow dr in ds.Tables[0].Rows)
  {
    if (e.Day.Date == System.Convert.ToDateTime(dr[„date“]))
    {
      e.Day.IsSelectable = !(rb.SelectedItem.Value == „add“);
      // pokud pridavame terminy, zakazeme vyber
      if (e.Day.IsOtherMonth)
      {
        e.Cell.BackColor = System.Drawing.Color.LightCoral;
      }
      else
      {
        e.Cell.BackColor = System.Drawing.Color.Red;
      }
    }
  }
  if (rb.SelectedItem.Value == „remove“)
  {
    if(e.Cell.BackColor != System.Drawing.Color.Red)
      e.Day.IsSelectable = false;
    // pokud rusime terminy a pozadi neni cervene, zakazeme vyber
  }
}

Nyní již máme hotovo téměř všechno, zbývá jen obsloužit událost SelectionChanged kalendáře. Ta bude mít na starosti zápis nového řádku nebo výmaz záznamu z databáze. Rozhodování opět necháme na přepínači. Obslužná metoda by mohla být zhruba taková:

void cal_SelectionChanged(object sender, EventArgs e)
{
  DataRow dr;
  DataRow[] drcol;
  foreach(DateTime day in cal.SelectedDates)
  {
    if(rb.SelectedItem.Value == „add“)
    {
      dr = ds.Tables[0].NewRow();
      dr[„date“] = day.Date.ToShortDateString();
      ds.Tables[0].Rows.Add(dr);
    }
    else
    {
      drcol = ds.Tables[0].Select(„date='“+day.Date.ToShortDateString()+“‚“);
      drcol[0].Delete();
    }
  }
  ds.Tables[0].AcceptChanges();
  SaveDS(ds);
}

V těle metody se nachází cyklus, který prochází všechny označené dny kalendáře a, podle stavu přepínače, buď přidá nový řádek nebo vymaže existující. Metoda SaveDS() nakonec uloží upravený dataset do souboru XML (v našem případě). Může vypadat například takto:

private void SaveDS(DataSet ds) {
  if (ds == null) { return; }
  System.IO.FileStream fs;
  System.Xml.XmlTextWriter writer = null;
  string filename = Server.MapPath(„fulldays.xml“);
  fs = new System.IO.FileStream(filename, System.IO.FileMode.Create);
  writer = new System.Xml.XmlTextWriter(fs, System.Text.Encoding.Unicode);
  writer.Formatting = Formatting.Indented;
  writer.Indentation = 5;
  ds.WriteXml(writer);
  if (writer != null)
    writer.Close();
}

Na závěr bych opět zdůraznil, že zde uvedená aplikace je maximálně odlehčena. Mým cílem bylo především předvést, jak je možné zajistit spolupráci Calendaru s vhodným datovým úložištěm.

Štítky: Články

Mohlo by vás také zajímat

Nejnovější

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *