pondělí 26. prosince 2011

ASP.NET MVC3–Binding příklad

V tomto příspěvku popíši, jak namapovat data pomocí “nesouvislých indexů” – ony se ty anglické výrazy špatně překládají, respektive nejsem jazykový odborník.

Problém

Představte si, že máte k dispozici seznam telefonních čísle a jmen a chcete je zobrazit uživateli, který pomocí zaškrtávátka určí, které z nich chce vložit do svého seznamu kontaktů. Běžně se tento problém řeší tak, že se uvažuje, že každý záznam má své ID (identifkátor).
Ale zkusme vyřešit situaci, kdy prostě žádné ID není k dipozici a nebo ho použít nechceme, tedy třída vypadá takto:
  1. public class Contact
  2. {
  3.     public string Name { get; set; }
  4.     public string Phone { get; set; }
  5. }
Zdroj dat nám nahradí tento kód:
  1. public static IEnumerable<Contact> Get()
  2. {
  3.     return new Contact[] {
  4.         new Contact() {Name= "Pepa", Phone= "123456789"},
  5.         new Contact() {Name= "Petr", Phone=  "987654321"},
  6.         new Contact() {Name= "Lopuch", Phone= "456789123"}
  7.     }.AsEnumerable();
  8. }
Kontroler není složitý:

  1. public class HomeController : Controller
  2. {
  3.     [HttpGet]
  4.     public ActionResult Index()
  5.     {
  6.         return View(Contact.Get());
  7.     }
  8. }
A View Index pak vypadá takto:
  1. @model IEnumerable<Contact>
  2. @{
  3.     ViewBag.Title = "Index";
  4. }
  5. <h2>Import - contacts</h2>
  6.  
  7. @using (Html.BeginForm())
  8. {
  9.     foreach (var contact in Model)
  10.     {
  11.     <input type="checkbox" name="index" value="@contact.GetHashCode()" /> @string.Format("{0}-{1}", contact.Name, contact.Phone) <br />
  12.     <input type="hidden" name="@string.Format("[{0}].Name", contact.GetHashCode())" value="@contact.Name" />
  13.     <input type="hidden" name="@string.Format("[{0}].Phone", contact.GetHashCode())" value="@contact.Phone" />
  14.     }
  15.     <input type="submit" value="send" />
  16. }

Metoda kontrolleru, který bude zpracovávat přijímaná data, pak vypadá takto:
  1. [HttpPost]
  2. public ActionResult Index(Contact[] contacts)
  3. {
  4.     return View("IndexPosted", contacts);
  5. }

a ve View IndexPosted vypisujeme přijaté kontakty pomocí jednoduchého kódu:
  1. <h2>Posted contects</h2>
  2. @foreach (var contact in Model)
  3. {
  4.      @string.Format("{0}-{1}", contact.Name, contact.Phone) <br />
  5. }
A takto se to chová:
imagea po odeslání stlačením tlačítka Send: image

Závěr

Veškeré kouzlo spočívá v checkboxu pojmenovaném index. Hodnota checkboxu je na server odeslána pouze v případě, že je zaškrtnut. MVC3 framework na serveru se pak snaží vytvořit kolekci objektů třídy  Contact (viz cílová akce formuláře Index(Contact[]  contacts)) – a hodnutu index použivá pro nalezení hodnot jednotlivých objektů.
Samozřejmě by se dal tento jednoduchý příklad řešit i jinak, ale všimněte si některých výhod tohoto řešení:
  • uživateli neposíláme žádné ID a Hashcode, který ho do jisté míry nahrazuje, je naprosto umělá hodnota.
  • Metodu pro zpracování dat do formuláře můžeme použít i pro zpracování normálního formuláře
  • díky tomu, ze využíváme model zpracování MVC frameworku, došlo by i k oveření všech podmínek, které bychom pro model případně definovali.
Celý zdrojový kód projektu BindingExample je dostupný na CodePlex – ale prakticky vše je uvedeno v tomto článku.

Poznámka na konec – index se nemusí jmenovat jen index, ale můžeme používat i prefixů při řešení složitějších případů:

  1. @Html.CheckBox("phones.index", new { value = contact.GetHashCode() })
  2.     @Html.Hidden(string.Format("phones[{0}].Name", contact.GetHashCode()), contact.Name)

neděle 18. prosince 2011

Vzhůru do oblak!…..a stejně skončíme v přístavu

Minulý příspěvek popisoval jednoduchou konzolovou aplikaci, která z údajů ze sešitu programu Excel udělá PDF dokument s kartičkami. Takový dokument pak stačilo jen vytisknout, rozstříhat či rozřezat a papírové kartičky byly hotové.
Tento příspěvek popíše, jak tuto aplikaci upravit tak, že:
  • umožní načítat z libovolného zdroje dat (například místo Excel z CSV souboru)
  • zdrojový kód bude přístupný v aktuální podobě každému a nemusí se přidávat jako příloha ke stažení
  • aplikace bude dostupná přes webové stránky – tedy pošleme soubor a obdržíme zpět PDF dokument
A proč nadpis “Vzhůru do oblak!”? – výsledný web bude v cloudu. To je dnes takové moderní slovo – v prostředí .NET nejčastěji spojované s Azure. Ale Azure nepoužijeme – Microsoft sice nabízí trial verze, jenže ani u nich není zaručeno, že na konci za to nebude platit (zde si neodpustím poznámku, že MS na Azure a WP7 předvádí jak velká firma není schopna správných rozhodnutí a nechápe, co je pro masivnější používání daných produktů klíčové – ale to je jen můj povzdech a soukromý názor). Takže místo Azure použijeme služeb AppHarbor.
Aktualizace 18.12 17:00 – jak jsem se dočetl zde, MS konečně umožnil bezplatné zkoušení. I tak je krok za AppHarbor.

Úprava stávající aplikace

Nejprve změníme typ z konzolové aplikace na knihovnu. To je jednoduchá změna, klikneme pravým tlačítkem na název projektu a ve vlastnostech změním typ:
image
Nyní se musí upravit samotná aplikace. Nejprve upravíme načítání dat – nyní je v kódu napevno zakodóváno načítání přes ADO.NET z Excel souboru:
Stávající kód
  1. void ReadAndPrintExcelSheet(...)
  2.         {
  3.             ...
  4.             Action<DbCommand> read = (command) =>
  5.             {
  6.                 command.CommandText = "SELECT * FROM [Sheet1$]";
  7.  
  8.                 using (DbDataReader reader = command.ExecuteReader())
  9.                 {
  10.                     ...
  11.                     while (reader.Read())
  12.                     {
  13.                         ....
  14.                     }
  15.                 }
  16.                 ...
  17.             };
  18.  
  19.             Db.Work("Excel", read);
  20.         }
Tohoto provázání se zbavíme poměrně snadno – zavedeme tento interface ICardData:
public interface ICardData
{
    string Word { get; }
    string Pronunciation { get; }
    string Example { get;  }
    string Meaning { get;  }
    string ExampleTranslation { get; }
}
Do definice třídy pro generování PDF pak přidáme delegáta  Func<IEnumerable<ICardData>>, který bude ukazovat na metodu, která bude schopna poskytnout údaje o kartičkách – přičemž nyní naší třídy pro generování PDF už nebude zajímat, jak to tato metoda dělá (tedy jestli čte databázi, CSV soubor, google dokument atd.):
Upravený kód
  1. Func<IEnumerable<ICardData>> getCardData;
  2. ...
  3. void ReadAndPrintExcelSheet(...)
  4. {
  5.     ...
  6.  
  7.     foreach (var cardData in getCardData())
  8.     {
  9.         ....
  10.     }
  11.  
  12.     ...
  13. }
Pokud porovnáte nový a předešlý kód navzájem, došlo ke zjednodušení. Dle mého názoru je kód i přehlednější. Zbývá ještě udělat metodu pro delegáta getCardData :-).

Poznámka pro ICardData"

Interface ICardData je implementován abstraktní třídou CardData – tato abstraktní třída pak slouží jako předek pro třídy navázané na konkrétní zdroje dat (bude ukázáno dále):
  1. public abstract class CardData: ICardData
  2. {
  3.     public string Word { get; protected set; }
  4.     public string Pronunciation { get; protected set; }
  5.     public string Example { get; protected  set; }
  6.     public string Meaning { get; protected set; }
  7.     public string ExampleTranslation { get; protected set; }
  8. }
V zdrojovém kódu je implementována i původní  podpora pro čtení Excel souborů – ale tento kód není využit (budou se zpracovávat jen CSV soubory). Slouží tedy spíše jako ukázka, jak implementovat podporu dalších zdrojů údajů o kartičkách. Jedná se o třídy DbCardDataProvider.cs, DbDataReaderCardData.cs a Database.cs.

Čteme CSV soubor

Pro čtení CSV souboru jsem si “vypůjčil” kód od Janathana Wooda.  Provedl jsem jen pár úprav – zajímá mne jen čtení ze streamu, místo čárky jsem jako oddělovač definoval středník. Výsledek je v souboru CsvStreamReader.cs.
Kód této třídy je jednoduchý – umí vzít vstupní stream a postupně jej číst “po řádcích”, přičemž každý řádek načte do objektu třídy CsvRow. To dělá metoda ReadRow(CsvRow), která vrací true, dokud nedosáhneme konce dat a aktualizuje vlastnosti objektu CsvRow tak, aby obsahoval data právě načteného řádku.
Nyní ještě zbývá udělat pomocnou třídu CsvStreamReaderCardDataProvider, která nám poskytne metodu pro delegáta Func<IEnumerable<ICardData>> pro třídu generující PDF dokument. 






  1. public class CsvStreamReaderCardDataProvider : IDisposable

  2. {

  3.     CsvStreamReader reader = null;

  4.  

  5.     public CsvStreamReaderCardDataProvider(MemoryStream stream)

  6.     {

  7.         this.reader = new CsvStreamReader(stream);

  8.     }

  9.  

  10.     public IEnumerable<ICardData> GetCardData()

  11.     {

  12.  

  13.         CsvRow row = new CsvRow();

  14.  

  15.         while (reader.ReadRow(row))

  16.         {

  17.             if (row.Count < 4)

  18.                 continue;

  19.  

  20.             CsvStreamReaderCardData card = new CsvStreamReaderCardData(row);

  21.  

  22.             yield return card;

  23.         }

  24.     }

  25. }





Funkčnost třídy je jednoduchá, vytvoří si instanci CsvStreamReaderu a poté v metodě GetCarData čte řádky, převede CsvRow na objekt třídy implementující ICardData – v tomto případě na objekt třídy CsvStreamReaderCardData, která je potomkem abstraktní třídy CardData:






  1. public class CsvStreamReaderCardData : CardData

  2. {

  3.     public CsvStreamReaderCardData(CsvRow row)

  4.     {

  5.         this.Word = row[0];

  6.         this.Pronunciation = row[1];

  7.         this.Example = row[2];

  8.         this.Meaning = row[3];

  9.         if (row.Count > 4)

  10.             this.ExampleTranslation = row[4];

  11.         else

  12.             this.ExampleTranslation = string.Empty;

  13.     }

  14. }





 



Proč vlastně stream a ne soubor (aneb iTextSharp v paměti)?



Jak jsem nastínil již v úvodu, má celá aplikace bežet v cloudu. Tam ukládání a čtení souborů může představovat problém – a protože nepředpokládám velké soubory, tak se veškeré zpracování (upload od uživatele a čtení) odehraje jen v paměti.


Výše uvedené má ještě jeden dopad – při generování PDF souboru se tento vytvářel na disku. Nyní i tento dokument musíme vytvořit v paměti a poté poslat uživateli zpět, tedy místo tohoto kódu:






  1. PdfWriter writer = PdfWriter.GetInstance(pdfDocument,

  2.                         new FileStream(

  3.                         Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),

  4.                         "Flashcards.pdf"),

  5.                         FileMode.Create));





použijeme tento kód:






  1. var stream = new MemoryStream();

  2. PdfWriter writer = iTextSharp.text.pdf.PdfWriter.GetInstance(pdfDocument, stream);

  3. writer.CloseStream = false;








Souhrn změn původní aplikace



Mimo změny v typu aplikace (z konzolové na knihovnu) a způsobu načítání dat došlo i k dalším změnám:



  • Hlavní kód se přesunul ze souboru Program.cs do souboru Creator.cs, stejně se změnili i názvy tříd (program na Creator).


  • Třída Creator má jednu hlavní metodu CreatePdf, která vyžaduje dva vstupní paramatry – metodu pro získání údajů kartiček a třídu fontů, které se mají použít


  • správné umístnění kartičky na A4 je řešeno pomocí dvou statických funkcí BackNextPositionProvider a FrontNExtPositionProvider – zpřehlednil se tak tisk stránky, který se odehrává v metodě PrintFlashCardPage


  • kartičky se ze zdoje načítají do jediného pole, to se navíc při zpracování stránky neinicilizuje znovu, ale pouze se vyprázdní příkazem Array.Clear(pageCardData, 0, pageCardData.Length);



Zpracování je nyní toto:



  1. zavolá se metoda CreatePdf – inicilizuje se pdf dokument


  2. Načtou se kartičky pro stránku pomocí odkazu  na metodu getCardData


  3. Zavolá se metoda pro vložení stránky, které se mimo pole kartiček  předají  i odkazy na metodu vyrábějící kartičky pro rubovou stránku a odpovídají metoda pro výpočet polohy kartiček na stránce (tedy odkazy na metody CreateForeignFlashcard a FrontNextPositionProvider)


  4. Totéž se provede i pro lícovou stránku, jen se předávají metody CreateNativeFlashcard a BackNextPositionProvider


  5. Metoda CreatePdf vrátí vytvořený Pfd dokument jako stream


Mimo výše uvedené jsou v projektu třídy pro získání dat z CSV souborů a ze sešitů Excel.





Třída FlashcardFonts



Tato třída obsahuje fonty používané pro vypsání textů na kartičky. Protože má aplikace fungovat na webu, je nutné šetřit systémové prostředky a tedy stačí vytvořit tyto fonty jen jednou a poté je sdílet pro všechna volání objektů třídy Creator a jejich metody CreatePdf – pro všechna tato volání se použije stejná instance.


Protože jsou kartičky vykreslovány pomocí fontu Arial, je tento font přidán do AppData webovské aplikace a ve tříde FlashcardFonts pak slouží pro inicializaci základního fontu, ze kterého pak výchází ostatní.








Web aplikace



Web aplikace využívá knihovnu pro tvorbu Pdf dokumentů. Je to MVC3 aplikace vytvořená ze šablony visual studia, obsahuje jen jeden controller nazvaný Home, který zobrazí jednoduchý formulář umožňující upload souboru:









  1. @using (Html.BeginForm("UploadCsv", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))

  2. {

  3.     <input type="file" name="file" id="file" />

  4.     <input type="submit" value="submit" />

  5. }





Data odeslaná tímto formulářem na server jsou zpracována v metodě UploadCsv:






  1. MemoryStream downloadStream;

  2. string fileName;

  3.  

  4. using (MemoryStream uploadedStream = new MemoryStream())

  5. {

  6.     Request.Files[0].InputStream.CopyTo(uploadedStream);

  7.     fileName = Path.GetFileNameWithoutExtension(Request.Files[0].FileName);

  8.     uploadedStream.Position = 0;

  9.  

  10.     using (CsvStreamReaderCardDataProvider provider = new CsvStreamReaderCardDataProvider(uploadedStream))

  11.     {

  12.         

  13.         Creator creator = new Creator();

  14.         downloadStream = creator.CreatePdf(provider.GetCardData, fonts);

  15.     }

  16. }

  17.  

  18. downloadStream.Position = 0;

  19.  

  20. return new FileStreamResult(downloadStream, "application/pdf")

  21. {

  22.     FileDownloadName = fileName + "_flashcard.pdf"

  23. };





Obsah odeslaného souboru je vložen do streamu (Request.Files[0].InputStream.CopyTo(uploadedStream);) , následně je tento stream čten pomocí CsvReaderu (ten je skryt v objektu provider)  pomocí objektu provider . Následně je zavolán Creator, jako zdroj údajů o kartičkách mu slouží metoda GetCardData objektu provider – objekt creator pak  vytvoří stream obsahující Pdf dokument. Ten je následně odeslán zpět uživateli jako application/pdf s názvem odpovídajíc šabloně <jméno souboru>_flashcard.pdf.





Sdílíme zdrojový kód –CodePlex



V příloze k tomuto článku už nenaleznete zdrojový kód popisovaného řešení – ten je nyní umístněn na CodePlex serveru a to na adrese http://flashcardcreator.codeplex.com. Zdrojový kód si můžete volně procházet přímo v prohlížeči, popřípadě stáhnout a upravit jak chcete:


image


Poznámka – co je to CodePlex?



Dle wikipedie: CodePlex je internetový projekt společnosti Microsoft určený k hostování otevřeného softwaru. CodePlex byl založen v květnu 2009. K repozitářům lze přistupovat pomocí verzovacích systémů Team Foundation Server nebo Subversion. Uživatelé mají dále k dispozici nástroje pro sledování požadavků, chyb, podporu RSS, statistiky, diskuzní fóra,, vlastní Wiki atd. Ač se většina zdejších projektů týká .NET Frameworku, včetně ASP.NET a Microsoft SharePointu, jsou zde projekty zabývající se SQL, WPF a Windows Forms a další.





Jdeme do oblak



Náš projekt z CodePlex můžeme vystavit přímo na web díky službě AppHarbor. Základní verze služby je zdarma a zprovoznění je za normálních podmínek otázkou několika minut. V nápovědě AppHarbor naleznete i postup, jak spojit CodePlex s vaší aplikací – a při každém commitu/check-inu zdrojového kódu dojde i k aktualizace aplikace na AppHarbor.


Zatím jsem se ale setkal s těmito problémy:



  • ne každý commit zdrojového kódu na CodePlex se promítne do AppHarborAppHarbor dělá build jednou za cca 14-15 minut – i tak je mezi aktualizací zdrojových kódů na CodePlex a aktualizací AppHarbor občas několikaminutová prodleva:





    CodePlex:


    image


    AppHarbor:


    image


    image


  • Vložil jsem souboru arial.ttf do složky App_Data, ale zapomněl jsem nastavit (přenastavit) ve vlastnostech Build Action na Content.  Což vedlo k tomu, že při lokálním spuštění vše fungovalo, ale na AppHarbor soubor fyzicky nebyl – trvalo mi dost dlouho, než jsem přišel na to, kde je chyba – uznávám, je to školácká chyba. Kdybych použil svůj osvědčený postup spočívající v samostatném webu na lokálním IIS a deploymentu z VS, tak bych na to přišel dříve.






























Nepopisuji podrobně, jak s oběma bezplatnými službami pracovat – obě požadují vytvoření účtu, což by nikomu nemělo činit problém a založení projektu na obou z nich je velmi jednoduché a přímočaré – v případě problémů existují diskuzní fóra i nápovědy u obou služeb.


Služba běží na adrese http://flashcard.apphb.com a základní rozhraní je velmi jednoduché:


image


Stačí vybrat soubor (ukázka souboru ve formátu UTF-8 CSV je přiložena jako slovicka.txt) a odeslat na server. Zpět vám přijde PDF dokument.


Závěr



Prosím mějte na paměti, že kód je pouze demonstrační.  Řetez událostí vypadá při použití CodePlex a AppHarbor takto: vývojář udělá změny a provede check-in změn do CodePlex. CodePlex upozorní AppHarbor na změnu, AppHarbor získá z CodePlex zdrojové kódy, zbuilduje je a nasadí.

neděle 11. prosince 2011

Víkendová rychlovka–tisk kartiček

Mé školou povinné děti se učí angličtinu. Což znamená učit se slovíčka. Mají slovníčky, jenže ty mají některé nevýhody – například se často naučíte slovíčka v nechtěném kontextu ostatních slov, tedy nakonec je umíte pěkně vyjmenovat v řadě, ale otázka na nahodilé slovíčko zůstane bez odpovědi. A nebo se špatně zvýrazňují obtížná slovíčka, aby jste si je mohli častěji opakovat.
Jistou naději jsem vkládal do elektronických pomůcek - jenže chytrý mobil sloužil všemu možnému, jen ne na opakovaní slovíček a tak putoval pryč. Jedinou možností se tak jeví klasické papírové kartičky – jenže jejich příprava bere čas, obzvláště pokud by jste si je měli sami psát. Nutné je také zmínit, že na rozdíl od aplikaci jako AnyMemo jsou kartičky zcela tiché a slovíčko vám tedy nepřečtou.
Dal jsem tedy dohromady slovíčka na listu v sešitě Excel, stáhnul knihovnu iTextSharp a udělal aplikaci, která si údaje z Excelu načte a vytvoří PDF s kartičkami – ty stačí pak již jen vytisknout, rozstříhat a učit se, učit se, učit se (© Lenin).

Excel Sheet jako vstup

Formát informací je jednoduchý – slovíčko, přepis výslovnosti, příklad použití, český význam a překlad příkladu – vše na listě s názvem Sheet1 (pokud se list jmenuje jinak, musí se upravit příkaz pro načtení v programu) . Excel sheet pak vypadá takto:
image
Poznámka – lze samozřejmě použít i jiný formát, ale musí se pak upravit aplikace – není to těžké. Použit je Excel 2010.

iTextSharp

Tuhle levnou, protože zadarmo, knihovnu na tvorbu PDF naleznete na této adrese. Trochu času zabralo přijít na nejlepší a nejjednodušší způsob vykreslení kartiček – nakonec každou kartičku vykresluji jako tabulku na určité pozici. Výhodou tabulek je možnost určit jak font použitý na obsah buňky, tak i pozadí buňky, její velikost a ohraničení. Obsah lze i otočit a lze tak dosáhnout možnosti přetáčet kartičky po delší straně s obráceným textem.
Vzhledem k tomu, že jsme v Evropě, tiskne se na papír A4, s okrajem 10mm (výsledné pédéefko by tedy mělo jít vytisknout na jakékoliv tiskárně):
image
Vlastní aplikace je velmi jednoduchá – otevře se Excel soubor a z něj se načítají data – to se odehrává v metodě ReadAndPrintExcelSheet. Ta načítá údaje ze sešitu a vytváří jednoduché objekty pro obě strany kartiček (objekty tříd ForeignFlashcardContent a NativeFlashcardContent). Ty pak vloží do pole na správné místo (aby při tisku na obě strany se obě strany kartiček správně “slicovaly”). Jakmile je načten dostatečný počet kartiček pro tisk stránky (v mém případě se tiskne 8 řádků po 4 sloupcích, tedy dohromady 32 kartiček), zavolá se vlastní “tisk” do PDF. Pro každý načtený údaj zhotovena kartička (metody CreateForeignFlashcard a CreateNativeFlashcard) a ta je umístněna na stránku (metoda PrintFlashCardPage). A to je vše.
Abych nemusel počítat body při generování PDF, je zde metoda mmToDots, která zadanou míru v milimetrech převede na počet bodů, se kterým metody iTextSharp knihovny pracují.
Abych mohl vykreslovat speciální znaky (hlavně u výslovnosti), používám jaké základ všech písem v dokumentu font Arial:
BaseFont baseFont = iTextSharp.text.pdf.BaseFont.CreateFont(@"C:/Windows/Fonts/ARIAL.TTF", BaseFont.IDENTITY_H, iTextSharp.text.pdf.BaseFont.EMBEDDED);

Celý kód je napsán prakticky jen ve statických metodách – nehledejte tam žádné architektonické kouzla a čáry, je to prostě jednoduchá a jednoúčelová aplikace, která ovšem udělá co má: kartičky.
Umístnění vstupního Excel souboru je v app.config – celý program lze samozřejmě upravit na čtení jména souboru z příkazové řádky apod. – každopádně nezapomeňte :
app.config
  1. <?xml version="1.0" encoding="utf-8" ?>
  2. <configuration>
  3.     <connectionStrings>
  4.         <add name="Excel" connectionString="Dsn=Excel Files;dbq=D:\Slovicka.xlsx;driverid=790;maxbuffersize=2048;pagetimeout=5" providerName="System.Data.Odbc" />
  5.     </connectionStrings>
  6. </configuration>
a výstupní Flashcards.pdf pak naleznete ve svých dokumentech díky tomuto příkazu:
  1. PdfWriter writer = PdfWriter.GetInstance(pdfDocument,
  2.                                          new FileStream(
  3.                                             Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
  4.                                                         "Flashcards.pdf"),
  5.                                             FileMode.Create));

A toto je “hardwarový” výstup celého programu:
IMG_7305
Zdrojový kód je přiložen včetně použitého xlsx souboru v balíčku zip.

středa 7. prosince 2011

“Vyber vše” aneb kombinace ASP.NET MVC a jQuery

V tomto článku popíši možné řešení zadání, podle kterého se má uživateli zobrazit seznam možností ve formě zaškrtávacího políčka, přičemž jedno ze zaškrtávacích políček má sloužit jako “Vyber vše” – pokud jej uživatel zaškrtne, zaškrtnou se všechny možnosti, pokud zaškrtnutí zruší, zruší se i všechny možnosti:
 image     image     image
Řešení podobného zadání jsou na netu mraky – takže k tomuto mraku přispěji dalším obláčkem.  Aby řešení bylo co nejvíce univerzální, stanovil jsem si tyto podmínky:
  • Všechny ovládané zaškrtávátka musí být uzavřeny do elementu <fieldset>
  • První zaškrtávátko slouží pro výběr všeho.

Obecné Html a javacript

Html kód splňující výše uvedené podmínky  pak vypadá takto:
<fieldset>
    <input type="checkbox" name="all" /><label for="all">Vyber ve</label><br /><br/>
    <input type="checkbox" name="praha" /><label for="praha">Praha</label><br />
    <input type="checkbox" name="brno" /><label for="brno">Brno</label><br />
    <input type="checkbox" name="ostrava" /><label for="ostrava">Ostrava</label>
</fieldset>
a odpovídající jQuery javascript pak takto:
selectAllCheckbox.js
  1. jQuery(function () {
  2.     jQuery('fieldset :checkbox').click(function () {
  3.         var $checkboxes = jQuery(this).closest('fieldset').find(':checkbox');
  4.         if ($checkboxes.index(this) == 0) {
  5.             $checkboxes.not(this).attr('checked', this.checked);
  6.         }
  7.         else {
  8.             $checkboxes.setSelectAllCheckbox();
  9.         }
  10.     });
  11.  
  12.     jQuery('fieldset').each(function () {
  13.         jQuery(this).find(':checkbox').setSelectAllCheckbox();
  14.     });
  15. })
  16.  
  17. jQuery.fn.setSelectAllCheckbox = function SetSelectAll() {
  18.     var first = this.first();
  19.     jQuery(first).attr('checked', this.not(first).filter(':checked').length == (this.length - 1));
  20. }
Metoda na řádcích 17.-20. nastaví předané ovládací zaškrtávátko buď do stavu zaškrtnuto a nebo nezaškrtnuto podle stavu ostatní zaškrtávátka (ovládací zaškrtávátko bude zaškrtnuto, pokud všechna ostatní jsou zaškrtnuta.
Kód na řádcích 2-10 zajišťuje, že při každém kliknutí na zaškrtávátko umístněné v elementu <fieldset> je zjištěno, zda se jedná o první zaškrtávátko v daném elementu. Pokud ano, nastaví se všechna ostatní zaškrtávátka podle stavu právě kliknutého (řádek 5.). Pokud ne, provede se funkce na řádcích 17. – 20.
Kód na řádcích 12. 15. slouží pro správná nastavení po načtení stránky do prohlížeče.
Upozorňuji, že kód není dokonalý, například neřeší důsledně situace, kdy je do sebe zanořeno více elementů fieldset.

Html helper method

Element <fieldset> nemusím generovat ručně. Můžeme použít rozšíření HtmlHelperu a použít tuto konstrukci v našem view. Tvorbu jednoduchých rozšíření nemá asi cenu popisovat, proto rovnou popíši něco o stupeň složitější, tedy konstrukci podobnou @using(Html.BeginForm….):
@using (Html.BeginSelectAllCheckbox())
{
    <input type="checkbox" name="all" /><label for="all">Vyber ve</label><br />
    @Html.CheckBox("Praha")<label>Praha</label><br />
    @Html.CheckBox("Brno")<label>Brno</label><br />
    @Html.CheckBox("Ostrava")<label>Ostrava</label>
}
Upozornění - je mi jasné, že v tomto případě se může hloubavému čtenáři zdát, že jde o přístup “s kanónem na vrabce”, ale chci na tomto jednoduchém příkladu demonstrovat tvorbu vlastních rozšíření – v některém z dalších příspěvků chci popsat složitější řešení a není tedy na škodu  začít jednoduchým příkladem.
Metoda BeginSelectAllCheckbox je pak statické metoda statické třídy, která vypadá takto:
public static class HelperExtension
    {
        public static IDisposable BeginSelectAllCheckbox(this HtmlHelper helper )
        {
            Action begin = () => {helper.ViewContext.Writer.Write("<fieldset>");};
            Action end = () =>    {helper.ViewContext.Writer.Write("</fieldset>");};

            return new DisposableHelper(begin, end);
        }
       
        private class DisposableHelper : IDisposable
        {
            private readonly Action end;

            public DisposableHelper(Action begin, Action end)
            {
                this.end = end;
                begin();
            }

            public void Dispose()
            {
                end();
            }
        }
    }
Tvorba vlastních rozšíření HtmlHelperu objektu v MVC tedy není složitá a princip je doufám z kódu patrný – díky using dojde kzavolání Dispose metody, která provede závěrečnou akci – zavolá metodu, kterou jsme při vytváření třídy definovali pomocí delegáta Action.
Přiložen je i ukázkový MVC projekt s veškerým kódem.

pondělí 5. prosince 2011

MVC3: odkaz v textu

MVC3 nabízí tuto metodu, pokud chceme na stránce vypsat odkaz:
@Html.ActionLink("klikni zde","Index")
Co ale dělat, pokud chceme vypsat odkaz jako součást delšího text, například:
Nejste registrován v našem systému, prosím klikněte zde a zaregistrujte se.

Řešením může byt rozdělení textu na části:
<p>Nejste registrován v našem systému, prosím klikněte @Html.ActionLink("zde","Index") a zaregistrujte se.</p>

To je fungující řešení – problém s ním ale nastane v okamžiku, kdy začneme stránku lokalizovat a texty umístíme do resource souborů – obvykle se pak celý text rozdělí na části:

image

a z nich se pak generuje stránka:
<p>@Resources.FirstPart @Html.ActionLink("zde","Index") @Resources.SecondPart</p>



To ale není příliš pěkné – vznikají zbytečné klíče. Spíše by bylo lepší mít toto:

image

Pokud ale zapíšeme tento kód:
<p>@string.Format(Resources.Registrace, Html.ActionLink("zde","Index"))</p>

objeví se nám na stránce:

image

Což není co bychom asi chtěli. Správný zápis totiž je:
<p>@Html.Raw(string.Format(Html.Encode(Resources.Registrace), Html.ActionLink("zde","Index")))</p>
A dostaneme:
image

Vysvětlení:


  • Html.Encode použijeme, abychom správně zakodovali HTML značky v původním textu, tedy aby resource mohl například obsahovat text “Musí být <> od 0”.
  • Html.Raw pak brání dalšímu kódování a text je vypsán tak jak je – nedojde tak k Html kodování vloženého odkazu.