Vlatkovic.NET

Web site o tehnologiji, programiranju, muzici, kuvanju, knjigama, u stvari o svemu i svačemu...
Dizajn: www.studio7designs.com opensource web templates.


Najnovije na blogu

 Tuesday, 15 January 2008
I opet malo programiranja, ovog puta o proširivanju postojećih asp.net severskih kontrola na primeru GridView-a.

Cilj ovog teksta je da pokaže kako se postojeće ASP.NET serverske kontrole mogu relativno lako dograditi tako da im se poveća funkcionalnost. Kao primer sam uzeo GridView koji u osnovnoj postavi nema  multiselect ali uz malo dorade postaje vrlo sličan gridovima koje srećemo u windows programiranju. Ideja je da možemo da izaberemo više redova u GridView tako što ćemo prilikom izbora držati taster CTRL.

Realizacija je sledeća: kad god kliknemo na neki red u GridView kontroli izvrši se registracija tog klika i nakon postback-a se čitaju indeksi kliknutih redova na osnovu kojih vraćamo redove u kontroli koji su izabrani.

Prvi korak je da kreiramo novu klasu koja će predstavljati DataGrid sa multiselect opcijom. Ona će biti izvedena iz  System.Web.UI.WebControls.GridView klase i čime obezbeđujemo nasleđivanje svih osobina i metoda ove klase na koje smo navikli.

public class MultiSelectGridView : System.Web.UI.WebControls.GridView

Zatim je potrebno da na svaki red koji generiše osnovna  GridView klasa “zalepimo” onclick javascript događaj (event handler). To radimo tako što promenimo OnRowDataBound metodu iz osnovne klase.

1: protected override void OnRowDataBound(GridViewRowEventArgs e)
2:  {
3:   base.OnRowDataBound(e);
4:
5:   if (e.Row.DataItem != null)
6:   {
7:    e.Row.Attributes.Add("OnClick", "msel(event)");
8:
9:    _id = this.PageIndex + "." + e.Row.RowIndex.ToString();
10:    e.Row.Attributes.Add("index", _id);
11:   }
12:  }

Glavna stvar se nalazi u liniji 7, gde na Row element dodajemo atribut OnClick koji će izvršavati msel(event) javascript funkciju na svaki klik. Javascript funkcija msel registruje gde se izvršio click događaj i upisuje indeks kliknutog elementa u skriveno (hidden) polje čiju vrednost ćemo kasnije, u postback-u, da parsiramo. Ovu funkciju ćemo da kreiramo i registrujemo kasnije.
Sledeća stvar se nalazi na linijama 9 i 10. Naime, tu kreiramo indeks elementa koji je kliknut i vezujemo ga za sam element. Njegova konstrukcija je vrlo jednostavna, “_id = broj stranice + tačka + indeks trenutnog reda”. Dobijeni indeks se upisuje kao dodatni atribut u element koji predstavlja red u GridView-u.
Da bi na klijentskoj strani znali koji red je izabran kreiramo Javascript funkciju “msel“ koju ćemo kasije da registrujemo na strani (podvučeni delovi code-a se automatski generišu u runtime-u).

Pre nego što prokomentarišem javascript code, da kažem par reči o html code-u koji generiše standardni GridView. GridView je u stvari obična tabela tj <table><tr><td>... konstrukcija. <tr> tag predstavlja red u našoj kontroli (Row) i na njega se “lepi” OnClick event. Sam event se zapravo okida na <td> elementu ali se propagira do prvog roditeljskog (parent) elementa koji ima handler tj definisan onclick a to je <tr> element. I evo javascript-a koji se generiše.
Da prokomentarišemo javascript.

1:<script language="javascript">
2: function msel(e, o)
3: {

kreiramo referencu na skriveni (hidden) html element koji u sebi sadrži informacije koji redovi su selektovani

4:  var selected = document.getElementById(o);

Zatim kreiramo referencu prema Event-u koji je inicirao izvršenje funkcije. Ova referenca nam treba da bi saznali koji je objekat inicirao Event i da li je pri kliku bio pritisnut taster CTRL. Linija 6 daje neposredni objekat koji je inicirao event, u ovom slučaju <td> tj ćeliju koja se nalazi unutar <tr> elementa. Na liniji 7 hvatamo sam <tr> element koji nosi informaciju o tome koji je indeks kliknutog elementa.

5:  e = e ? e : ((window.event) ? event : null);
6:  var obj = e.target ? e.target : ((e.srcElement) ? e.srcElement : null);
7:  obj = obj.parentElement ? obj.parentElement : obj.parentNode;
8:  var objParent = obj.parentElement ? obj.parentElement : obj.parentNode;

Na linijama 9 i 10  kreiramo objekte String tipa koji redom nose podatke o izabranim redovima i o trenutno kliknutom objektu tj njegovom indeksu.

9:  var sel = new String(selected.value);
10:  var id = new String(obj.getAttribute("index"));
11:  var cssclass;
12:  

Linija 13 ispituje da li je prilikom klika bio pritisnut i taster CTRL. Ukoliko jeste na liniji 14 proveravamo da li je red već izabran. Ukoliko jeste, ukloni ga iz liste izabranih i promeni css klasu za tekuću liniju. U suprotnom ubaci ga na listu izabranih i promeni css klasu.

13:  if (e.ctrlKey) {
14:   if (sel.indexOf("-" + id + "-") > -1) {
15:    selected.value = sel.replace("-" + id + "-", "");
16:    obj.className = "CssClassRow";
17:   }
18:   else {
19:    selected.value = sel + "-" + id + "-";
20:    obj.className = "CssClassRowSelected";
21:   }
22:  }

Ukoliko CTRL nije pritisnut (linija 23) lista izabranih indeksa svodi se na trenutno izabran a css klase se menjaju tako da su svi objekti neselektovani. 

23:  else {
24:   if (sel.indexOf("-" + id + "-") > -1) {
25:    cssclass = "CssClassRow"; selected.value = "";
26:   }
27:   else {
28v    cssclass = "CssClassRowSelected";
29:    selected.value = "-" + id + "-";
30:   }
31:   var trs = objParent.getElementsByTagName("tr");
32:   for(i = 0; i < trs.length; i++)
33:    if (trs[i].getAttribute("index"))
34:     trs[i].className = "CssClassRow";obj.className = cssclass;
35:    }
36:  }

 Napomena: podvučeni delovi se generišu

Funkcija  disableSelection samo ne dozvoljava da se na kliknutim redovima izvrši selekcija teksta. Za detalje pogledajte http://www.dynamicdrive.com/dynamicindex9/noselect.htm.

37: function disableSelection(target) {  
38:  if (typeof target.onselectstart!="undefined")   
39:   target.onselectstart=function(){return false;};  
40:  else if (typeof target.style.MozUserSelect!="undefined")    
41:   target.style.MozUserSelect="none";   
42:  else   
43:   target.onmousedown=function(){return false;};  
44:  target.style.cursor = "default";
45: }
46:</script>


Ok, sledeća metoda koju menjamo u odnosu na originalnu GridView klasu je OnRowCreated.

1:protected override void OnRowCreated(GridViewRowEventArgs e)
2:{
3: base.OnRowCreated(e);
4:
5: _id = this.PageIndex + "." + e.Row.RowIndex.ToString();
6:
7: if (selected != null && selected.IndexOf("-" + _id + "-") > -1)
8:     e.Row.CssClass = CssClassRowSelected;
9: else
10:     e.Row.CssClass = CssClassRow;
11:}

Opet kreiramo indeks, linija 5, i ako je trenutni red u listi selektovanih onda mu menjamo css klasu.

I na kraju menjamo CreateChildcontrols metodu. Zapravo samo je proširujemo. Cilj je da između svakog postback-a očuvamo stanje selektovanih redova.

1:protected override void CreateChildControls()
2:{

Naravno pustimo da se kreiraju sve potrebne kontrole koje se kreiraju za GridView (Linija 3)

3: base.CreateChildControls();
4:
5: string[] selectedArray;
6:
7: if (!string.IsNullOrEmpty(selected))
8: {

Izdvojimo indekse redova selektovanih na client strani (linija 9.) .

9:  selectedArray = selected.Split('-');
10:

Kreiramo listu int vrednosti koja će sadržati indekse izabranih redova.

11:  SelectedIndexs = new List<int>();

Kreiramo string niz koji će u sebi sadržati tekuću stranu i tekući index na strani.

12:  string[] pageAndIndex;

Za svaki element u selectedArray a na osnovu stranice i indeksa preračunamo pravi indeks i ubacimo u  SelectedIndexs.

13:  foreach (string s in selectedArray)
14:  {
15:   if (s.Length > 0)
16:   {
17:    pageAndIndex = s.Split('.');
18:    SelectedIndexs.Add(int.Parse(pageAndIndex[0]) * this.PageSize + int.Parse(pageAndIndex[1]));
19:   }
20:  }
21: }
22:

Registrujemo skriveno html polje u koje upisujemo koji su izabrani redovi.

23: this.Page.ClientScript.RegisterHiddenField(selectedKey, selected);

Registrujemo blok sa javascript funkcijama opisanim gore.

24: this.Page.ClientScript.RegisterClientScriptBlock( ( (System.Web.UI.WebControls.GridView)this).GetType(), "MultiselectGridView_js", js);
25:

I na kraju na GridView kontrolu dodamo još jednu literal kontrolu koja u sebi sadrži poziv funkcije  disableSelection koja ne dozvoljava da se selektuje tekst na upravo kreiranoj kontroli.

26: Literal l = new Literal();
27: l.Text = "<script language=\"javacript\" type=\"text/javascript\">disableSelection(document.getElementById(\"" + this.ClientID + "\"));</script>";
28: this.Controls.Add(l);
29:}

I to je to, kompletan code možete da skinete ovde.

Tuesday, 15 January 2008 23:41:27 (Central Europe Standard Time, UTC+01:00)
 Saturday, 22 December 2007

Ljudi obožavaju da trpaje sve živo u web.config (uopšte u .config fajlove). To je u principu ok ali ako naletite na projekat u kome ima preko stotinu raznih vrednosti koje treba podesiti da bi radilo onda 'oće i muka da pripadne. E sad, od količine ne može da se pobegne ali ako se ta količina organizuje i sistematizuje onda podešavanje web.cofig-a i ne mora da bude tako mučna operacija. Ovo je moj skromni predlog kako da se reši problem.

Konfiguracioni fajlovi u .Net (web.config, app.config) podržavaju simpatičnu stvar koja se zove <configSections>. Unutar ovih tagova možete proizvoljno da definišete <sectionGroups> i <section> tagove. Ovo znači da možete da kreirate bilo kakvu logičku strukturu podataka i svom .config fajlu. Pretpostavimo da radimo WEB projekat koji u solution-u sadrži DAL (data access layer) projekat zadužen da komunicira sa bazom i tumba podatke i BLL (business logic layer) koji sadrži neku poslovnu logiku između DAL i WEB projekta.

solExpl.gif

Svaki od ovih projekata može potencijalno da vuče vrednosti iz konfiguracionog fajla. Na primer, očigledno je da DAL projektu (onaj što se igra sa bazom) treba string koji mu govori gde je baza podataka i koju bazu gađa. Ili, projektu BLL onaj koji sadrži neku logiku treba podatak sa kog web servisa recimo da vuče neke podatke. Da te tražene vrednosti ne stoje samo kao lista poželjno je da napravimo strukturu koja će odražavati našu logičku organizaciju. I evo primera kako može da izgleda:


Ovako počinje web.config

<configSections>
        <sectionGroup name="VlatkovicNET.TestApp">
            <!-- Common -->
            <section name="CommonApp" type="System.Configuration.NameValueSectionHandler"/>
            <!-- Util -->
            <section name="Util" type="System.Configuration.NameValueSectionHandler"/>
            <sectionGroup name="DAL">
                <section name="ConnectionStrings" type="System.Configuration.NameValueSectionHandler"/>
                <section name="StoragePaths" type="System.Configuration.NameValueSectionHandler"/>
                <section name="XMLStorage" type="System.Configuration.NameValueSectionHandler"/>
            </sectionGroup>
            <sectionGroup name="BLL">
                <section name="WebServices" type="System.Configuration.NameValueSectionHandler"/>
                <section name="CacheServers" type="System.Configuration.NameValueSectionHandler"/>
            </sectionGroup>
        </sectionGroup>
    </configSections>

Šta ovde imamo? Prvi blok “definicija sekcija početak” odnosi se na kreiranje strukture podataka koja će se upisati u .config fajl. Poželjno je da struktura konfiguracije prati strukturu aplikacije. <configSections> definiše grupu koja nosi ime sastavljeno od imena Namespace-a i imena projekta “VlatkovicNET.TestApp” koja u sebi pak definiše grupe koje će da sadrže podešavanja karakteristična za BLL i DAL projekte. Type=”System.Configuration.NameValueSectionHandler” unutar definicije sekcija govori koji handler će da obrađuje našu strukturu parova. U ovom slučaju to je .NET handler NameValueSectionHandler . Možete da pišete i svoj ali to nije predmet ovog teksta. I još jedna napomena, struktura ovde nema nikakve veze sa funkcionalnošću već samo sa organizacijom i preglednošću.

Sledeći blok su same definicije vrednosti za prethodno definisanu strukturu. Na primer:

    <VlatkovicNET.TestApp>
        <CommonApp>
            <add key="CompanyName" value="VlatkovicNET"></add>
            <add key="ContactEmail" value="pera@test.com"></add>
            <add key="SmtpServer" value="127.0.0.1"></add>
        </CommonApp>
        <Util>
            <add key="HelpUrl" value="http://www.helpme.com"></add>
            <add key="Language" value="en"></add>
        </Util>
        <DAL>
            <ConnectionStrings>
                <add key="MSSql"    value="..."></add>
                <add key="MySql"    value="..."></add>
                <add key="Oracle"    value="..."></add>
            </ConnectionStrings>
            <StoragePaths>
                <add key="Images"    value="..."></add>
                <add key="Docs"    value="..."></add>
            </StoragePaths>
            <XMLStorage>
                <add key="path1"    value="..."></add>
                <add key="path2"    value="..."></add>
                <add key="path3"    value="..."></add>                        
            </XMLStorage>
        </DAL>
        <BLL>
            <WebServices>
                <add key="service1" value="http://server1/service1.asmx" ></add>
                <add key="service2" value="http://server1/service2.asmx"></add>
            </WebServices>
            <CacheServers>
                <add key="server1" value="cacheServer1" ></add>
                <add key="server2" value="cacheServer2"></add>
            </CacheServers>
        </BLL>
    </VlatkovicNET.TestApp>

Primer:

<VlatkovicNET.TestApp>
   <DAL>
      <ConnectionStrings>
         <add key="MSSql" value="..."></add>

unutar projekta DAL koji pripada namespace-u VlatkovicNET.TestApp definišemo grupu ConnectionStrings koja sadrži vrednost za MSSql connection string.


Sada ostaje da samo pročitamo ove vrednosti iz .config fajla. Jednostavno, pošto je .config xml file dovoljno je da definišemo XPath izraz do svakog ključa (key) a njegova vrednost je value iz NameValue para koga ćemo da čitamo. Ako niste bliski sa XPath-om nedajte da vas to obeshrabri, bar u ovom slučaju on je jako jednostavan a i nije loše da se nešto novo nauči (brrr ne sviđa mi se ova rečenica, učite ono što volite). So, evo ga primer:


Prvo definišemo metode za dobijanje vrednosti:

    public class ConfigurationManager
    {
        private static object lock_GetIntFromConfig = new object();

        /// <summary>
        /// Gets the int from config.
        /// </summary>
        /// <param name="path2Key">The path to key.</param>
        /// <param name="key">The key.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns></returns>
        public static int GetIntFromConfig(string path2Key, string key, int defaultValue)
        {
            lock (lock_GetIntFromConfig)
            {
                string val = GetValueFromConfig(path2Key, key);

                if (val != null)
                    int.TryParse(val, out defaultValue);

                return defaultValue;
            }
        }


        private static object lock_GetDecimalFromConfig = new object();
        /// <summary>
        /// Gets the decimal from config.
        /// </summary>
        /// <param name="path2Key">The path to key.</param>
        /// <param name="key">The key.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns></returns>
        public static decimal GetDecimalFromConfig(string path2Key, string key, decimal defaultValue)
        {
            lock (lock_GetDecimalFromConfig)
            {
                string val = GetValueFromConfig(path2Key, key);

                if (val != null)
                    decimal.TryParse(val, out defaultValue);

                return defaultValue;
            }
        }



        private static object lock_GetValueFromConfig = new object();
        /// <summary>
        /// Gets the value from config.
        /// </summary>
        /// <param name="path2Key">The path to key.</param>
        /// <param name="key">The key.</param>
        /// <returns></returns>
        public static string GetValueFromConfig(string path2Key, string key)
        {
            lock (lock_GetValueFromConfig)
            {
                NameValueCollection nvc = (NameValueCollection)ConfigurationSettings.GetConfig(path2Key);
                if (nvc != null)
                    return nvc[key];
                else
                    return null;
            }
        }
    }

Primer: vraćanje vrednosti specifičnog connection stringa za projekat
GetValueFromConfig("VlatkovicNET.TestApp/DLL/ConnectionStrings", "MSSql");

Primer: direktno uzimanje int vrednosti iz .config-a
GetIntFromConfig("VlatkovicNET.TestApp/BLL/Timeouts", "Cache", 5)

Kao što vidite XPath izraz se piše jednostavno, sa znakom “/” odvajate grupe i dodajete sekciju na kraju. I metodama prosledite XPath i key koji obeležava vrednost.

Metode ispisane ovde možete po želji da stavite u osnovnu klasu za vaše strane i kontrole ili u neki zaseban framework koji ćete da vučete sa sobom gde god da krenete. Ovde možete da skinete primer cele aplikacije pa da pogledate. Primer je samo ilustracija, nije reprezentativan :-) Primer možete da proširujete normalno pa da recimo kreirate malli generator klase koja opisuje vašu konfiguraciju u .config fajlu. Gledaću da u budućnosti napišem pravi generator sa code DOM.

Za kraj, nadam se da nisam izmislio toplu vodu i udavio vas u njoj :-) I da ne zaboravim, zahvaljujem se gospodinu Dejanu Miličiću na recenziji i više nego korisnim savetima.

Primer code-a

Saturday, 22 December 2007 13:52:10 (Central Europe Standard Time, UTC+01:00)