Tag Archives: Ajax

ASP.NET AJAX 4: Kevesebb töltögetés

Az ASP.NET csapat nemrég elérhetővé tette a Microsoft AJAX Library Preview 6 változatát, amivel egy rakás olyan újdonság vált elérhetővé, amellyel végül sokkal kevesebb Javascriptet kell a weboldal látogatóira rátukmálnunk.

Daraboljuk fel

Az egyik legfájóbb probléma az ASP.NET AJAX 3.5 alkalmazásánál, hogy viszonylag sok és viszonylag nagy Javascript fájlok folynak le a kliensre és erre nem igazán tudunk hatással lenni. Hiába használtunk csak egy kis funkciót a nagy ASP.NET AJAXból vagy akár csak a Microsoft AJAX Libraryből, jött vele minden. A jó hír az, hogy az ASP.NET 4.0-tól (már a béta1-től) kezdve több kis Javascript fájl van, dokumentálták, hogy melyik mit tud és hogyan függnek egymástól:

Microsoft_AJAX_Library_refactored

Rakjuk össze

Persze aki foglalkozott már teljesítménynöveléssel rögtön a fejéhez kap, hogy így a kliensnek sok fájlt kell letölteni, az sok HTTP kérés, ami még akkor is lassú, ha mindegyik 304 (Not modified) üzenettel tér vissza. Korábban volt egy kezdeményezés az AJAX Control Toolkit fejlesztőitől egy ToolkitScriptManager osztály formájában, de ezt kevesen ismerték és még kevesebben használták. A jó hír az, hogy a beépített ScriptManager osztály is támogatja a script combiningot, csak annyi dolgunk van, hogy a <Scripts> gyermek elemeit körbevesszük egy <CompositeScript> elemmel. Ez már a 3.5 verzióban is működik, ha explicit módon adjuk meg a szkripteket és akkor is csak azokra a szkriptekre, amelyek használják a ScriptManagert. Kilógnak például a validátorok, akik előbb születtek, mint a ScriptManager. Szerencsére a 4.0 verzióban már ez is jól fog menni mind a ScriptResource.axd-vel, mind pedig a WebResource.axd-vel leküldött fájlokra.

Nyomjuk össze

Ha összerakunk jó sok szkriptet egybe, akkor ugyan csökken a HTTP kérések száma, de a letöltendő szkript mennyiség még nagy. Szerencsére a Preview 6-ban már van egy Microsoft AJAX Minifier, ami nem csak, hogy jobban tömörít, mint a JSMin, a Packer és a YUI Compressor, de ráadásul MSBuild taskként be is tudjuk építeni a build folyamatunkba. Erről majd írok nemsokára, mert nagyon egyszerűen megy, az egyetlen baja, hogy csak Javascriptet tud összenyomni, CSS-t nem 😦

Töltsük máshonnan

Ha ideáig eljutottunk, akkor már csak azt töltik le a felhasználók, amire szükségük van, csakhogy azt minden egyes ASP.NET AJAXot használó webhelyről újra és újra megteszik. Ez nekik lassúság, a webhelyek gazdáinak pedig sávszélesség, amiért fizetniük kell. Szerencsére a Microsoft létrehozott egy Content Delivery Networkot (CDN) és erre a CDN-re felpakolta a Microsoft AJAX Library fájljait és a jQuery Library-t. Nekünk csak annyi dolgunk van, hogy a Microsoft FQDN-jét adjuk meg forrásként, az infrastruktúra pedig gondoskodik arról, hogy valóban a legközelebbi szerverről töltődjenek le a fájljaink. Ráadásul ha már egy Microsoft CDN-t használó webhelyet meglátogatott a felhasználó, akkor a szkriptek ott lesznek a böngészője gyorsítótárában és a következő MS CDN-t használó webhelynél már le sem kell tölteni ezeket.

Csak akkor, ha kell

A Preview 6-ban megjelent egy script loader komponens, ami lehetővé teszi, hogy egyrészt ne nekünk kelljen foglalkoznunk azzal, hogy a library egyes szkriptjei hogyan függnek egymástól, másrészt akkor és azt a részét töltsük le a library-nek a kliensre, amire éppen akkor és ott szükség van. Elég csak a script loaderre hivatkoznunk az oldal tetején:

  <script src="http://ajax.microsoft.com/ajax/beta/0910/Start.js" type="text/javascript"></script>

Kódban pedig elég megadnunk, hogy melyik komponensre van szükségünk. Ha például DataView-val akarunk adatot kötnünk, nem kell tudnunk, hogy melyik .js fájlban tanyázik, elég ennyi írnunk:

  Sys.require( Sys.components.dataView ); 

Amint letöltődött a szkript fájl, a rendszer meghívja az onReady eseménykezelőt, ahol már használhatjuk is a komponenst:

  Sys.onReady( function() 
{
Sys.create.dataView("#myDiv",
{
data:countries

});
});

Ütős lesz a következő .NET Framework, nem véletlenül lett 4.0 és nem 3.6 🙂

Advertisements

ASP.NET AJAX 4: Kliens oldali adatkötés – Sys.Observer

A cikksorozat előző részeiben ([1], [2]) az egyszeri adatkötéssel ismerkedtünk, ami sok esetben nagyon hasznos, de mégis az az igazi, ami a megváltozott adatot automatikusan képes frissíteni a felhasználói felületen. Ehhez arra van szükségünk, hogy észrevegyük, ha egy objektum valamelyik tulajdonsága megváltozik, ami lássuk be, JavaScriptben nem is olyan egyszerű feladat.

JavaScriptben ugyanis nincsenek property-k, legalábbis nem a .NET-es értelemben, azaz nincs lehetőségünk getter és setter metódusok írására. Ugyan van egy javasolt elnevezési konvenció – amit egyébként a Microsoft AJAX Library osztályai követnek is – miszerint írjunk get_ és set_ kezdetű metódusneveket, de ez nagyon körülményes megoldás lenne olyan esetekben, amikor primitív adatstruktúrákkal dolgozunk. Képzeljünk el egy webszolgáltatástól JSONban visszakapott Person objektumokból álló tömböt, hol van abban get_ és set_ metódus? Sehol.

Hasonló problémákra az elterjedt megoldás az observer tervezési minta használata: adott egy publisher, akinek a birtokában van az adat és ő szól az érdeklődő subscribereknek, ha az adat megváltozott. Egy JSONos objektum tömb valóban tartalmazza az adatot, csak éppen nem képes szólni, ha az megváltozik. Természetesen nincs akadálya, hogy felruházzuk a tömbünket ilyen funkciókkal, de ha ezt minden esetben nekünk kell megírnunk, akkor az egyrészt nagyon sok munka (JavaScriptben a sima “sok”=”brutál sok”), másrészt nagyon idegesítő lesz a sokféle implementációval vacakolni.

Itt jön a képbe a Sys.Observer osztály, ami képes egy tetszőleges objektumot igazi publisherré tenni.

Vegyük a korábban már látott országokat egy tömbben:

  var countries = [
        { Name: 'Austria', Capital: 'Vienna' },
        { Name: 'Australia', Capital: 'Canberra' },
        // És így tovább...
    ];

És jelenítsük meg a már jól ismert DataView vezérlővel deklaratív módon:

  <ul 
    class="sys-template" 
    sys:attach="dv" 
    dv:data="{{ countries }}">
    <li>
        {{ Name }} ({{ Capital }}) 
    </li>
  </ul>

Készítsünk két linket, amivel a forrás adatokat manipuláló függvényeket fogjuk hívni:

  <a href="#" onclick="onAdd()">Hozzáad</a> 
  <a href="#" onclick="onDelete()">Töröl</a>

Ezek után, ha az onAdd és onDelete függvényekben megpróbáljuk közvetlenül módosítani a countries változót, akkor hiába várjuk, hogy a módosítások megjelenjenek a felületen. Nosza vessük be a Sys.Observer osztályt és tegyük a countries gyűjteményt observable-lé:

  Sys.Observer.makeObservable(countries);

Majd figyeljük meg például a Visual Studio Watch ablakában, hogy hogyan változott a saját kis primitív adatstruktúránk: Sys.Observer.makeObservable eredménybe

A gyűjteményünk kapott néhány metódust és ha ezeket használjuk az adatok módosítására, akkor arról minden subscriber értesülni fog. A metódusok nevei eléggé magukért beszélnek, egyedül annyit emelnék ki, hogy ha nem akarjuk az értesítést azonnal elküldeni – például mert több tulajdonságot módosítunk sorban – akkor a módosítások előtt hívjuk meg a beginUpdate, a végén pedig az endUpdate metódust. Az előbbi felfüggeszti az értesítés küldést, az utóbbi pedig elküldi őket. A makeObservable metódust egyszerűbb esetekben akár el is hagyhatjuk, ekkor azonban a Sys.Observer osztály “statikus” metódusainak első paraméteréül át kell adnunk, hogy melyik objektummal dolgozzanak.

Visszatérve a fenti példára, írjuk meg az onAdd és onDelete metódusokat az új metódusok segítségével:

  function onAdd()
  {
    countries.add({ Name: 'France', Capital: 'Paris' });
  }

  function onDelete()
  {
    countries.removeAt(9);
  }

Ezzel készen is vagyunk, ha kipróbáljuk látni fogjuk, hogy a gyűjtemény módosításai azonnal látszódnak a felhasználói felületen, nem kell külön frissítenünk. Már csak azzal vagyok adós, hogy miért? A megoldás pedig igen egyszerű: a DataView okos osztály, észreveszi, hogy observable adatforrással dolgozik és automatikusan feliratkozik a változásaira, nekünk ezzel nem kell foglalkoznunk.

Érdemes kipróbálni, hogy mi történik, ha nem a gyűjteményen, hanem a gyűjtemény egyik elemén módosítunk, például így:

  countries[7].Name = 'Magyarország';

Megmondom már most: semmi, a DataView rá se bagózik. De hogy mindennek mi köze a live bindinghoz, azt azonban csak legközelebb árulom el…

A cikkhez tartozó forráskód letölthető innen.

ASP.NET AJAX 4: Kliens oldali adatkötés – webszolgáltatáshoz

A cikksorozat előző részében bemutattam, hogyan használhatjuk az ASP.NET AJAX 4 kliens oldali DataView vezérlőjét helyi változókban tárolt adatok adatkötéssel történő megjelenítéséhez. Ebben a részben kiszakadunk a böngészőből és egy webszolgáltatástól kérjük le az adatokat.

A webszolgáltatás elkészítése

A feladat ugyanaz, mint az előző cikkben, országok listáját akarjuk megjeleníteni, ezért készítettem egy Country osztályt, amiben semmi logika nincs, csak összefogja a Name és Capital tulajdonságokat. Mivel ASMX webszolgáltatásból akarom használni, ezért fontos, hogy legyen neki paraméter nélküli konstruktora.

Ezek után készítettem egy CountryWebService.asmx nevű fájlt, benne a CountryService osztállyal. Fontos, hogy mivel AJAX-ból akarjuk meghívni ezt a webszolgáltatást, ezért nem csak a WebSerice, hanem a ScriptService attribútumot is rá kell ragasztanunk.

A webszolgáltatás osztályban készítettem egy privát List<Country> típusú mezőt, ami az adatbázisunkat reprezentálja, ezen fognak futni a kliens lekérdezések.

Eddig tehát semmi extra, itt tartunk:

  [WebService( Namespace = "http://balassy.spaces.live.com/Samples/" )]
  [WebServiceBinding( ConformsTo = WsiProfiles.BasicProfile1_1 )]
  [System.Web.Script.Services.ScriptService]
  public class CountryWebService : System.Web.Services.WebService
  {
    private List<Country> countries;

    public CountryWebService()
    {
        this.countries = new List<Country>()
        {
            new Country( "Austria", "Vienna" ),
            new Country( "Australia", "Canberra" ),
            // És még jó sok további Country...

        };
    }
  }

A WebMethodot úgy írtam meg, hogy paraméterként megkaphassa, hogy az országok neve vagy fővárosa szerint, illetve növekvő vagy csökkenő sorrendben kívánja a kliens megkapni az adatokat:

  [WebMethod]
  public Country[] GetCountries( string orderby, bool asc )
  {
    Func<Country, string> keySelector;        

    switch( orderby )
    {
        case "Name":
            keySelector = new Func<Country, string>( c => c.Name );
            break;
        case "Capital":
            keySelector = new Func<Country, string>( c => c.Capital );
            break;
        default:
            keySelector = new Func<Country, string>( c => c.Name );
            break;
    }

    IEnumerable<Country> result = asc ? 
this.countries.OrderBy( keySelector ) :
this.countries.OrderByDescending( keySelector ); return result.ToArray(); }

Ugyanez WCF-ben

Ha ugyanezt a webszolgáltatást nem ASMX-ben, hanem WCF-ben akarjuk elkészíteni, akkor kicsit többet kell dolgoznunk vele. Ugyanúgy szükségünk lesz egy CountryService osztályra és benne egy GetCountries metódusra, amiket szokás szerint meg kell jelölnünk a ServiceContract és OperationContract attribútumokkal. Továbbá mivel a szolgáltatás Country példányokat ad vissza, arra kellenek a DataContract és DataMember attribútumok.

Mivel a WCF szolgáltatásunkat AJAX-ból akarjuk meghívni, még nem vagyunk készen. Először is kell a szolgáltatás osztályunkra az AspNetCompatibilityRequirements attribútum:

  [AspNetCompatibilityRequirements( 
RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed )]

Továbbá – mint minden WCF szolgáltatáshoz, ehhez is – feltétlenül szükség van egy gigantikus méretű, alig átlátható konfigurációs XML-re a web.config fájlba:

  <system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
    <behaviors>
        <endpointBehaviors>
            <behavior name="CountryServiceAspNetAjaxBehavior">
                <enableWebScript />
            </behavior>
        </endpointBehaviors>
        <serviceBehaviors>
            <behavior name="CountryServiceBehavior">
                <serviceMetadata httpGetEnabled="true" />
                <serviceDebug includeExceptionDetailInFaults="true" />
            </behavior>
        </serviceBehaviors>
    </behaviors>
    <services>
        <service behaviorConfiguration="CountryServiceBehavior" name="CountryService">
            <endpoint address="" binding="webHttpBinding" 
contract="CountryService"
behaviorConfiguration="CountryServiceAspNetAjaxBehavior" /> </service> </services> </system.serviceModel>

A sok beállítás közül a két legfontosabb a webHttpBinding és az enableWebScript.

Ha eddig készen vagyunk, akkor a kliensnek már mindegy, hogy milyen technológia dolgozik szerver oldalon.

Kliens oldali adatkötés deklaratívan

Az adatkötéshez kliens oldalon megint csak egy egyszerű HTML fájlt készítettem, benne script blokkokkal pedig betöltöttem az ASP.NET AJAX 4 JavaScript fájljait az ajax.microsoft.com CDN-ről. Az országok felsorolását most is ul-li listában végezzük el a kliens oldali DataView vezérlő segítségével, ami deklaratívan mindössze ennyi:

  <ul
      class="sys-template"
      sys:attach="dv"
      dv:autofetch="true"
      dv:dataprovider="Services/CountryWebService.asmx"
      dv:fetchoperation="GetCountries"
      dv:fetchparameters="{{ { orderby: 'Name', asc: true } }}" >
      <li>
          {{ Name }} ({{ Capital }}) 
      </li>
  </ul>

dv:dataprovider

A DataView dataProvider tulajdonságával adjuk meg, hogy honnan jönnek az adatok. Ez lehet egy sima JSONos webszolgáltatás URI, egy Sys.Net.WebServiceProxy példány vagy bármilyen osztály, ami implementálja a Sys.Data.IDataProvider interfészt, azaz van fetchData metódusa. Az utóbbira példa a Sys.Data.AdoNetServiceProxy osztály, amivel ADO.NET Data Services (Astoria) szolgáltatásokat hívhatunk meg, a legegyszerűbb esetben pedig közvetlenül rámutathatunk az .asmx vagy az .svc fájlra.

dv:fetchoperation

A DataView a fetchOperation tulajdonságban várja annak a metódusnak a nevét, amit a webszolgáltatáson meg akarunk hívni. Ide lehet query stringet is írni, ami ADO.NET Data Services (Astoria) esetén további lehetőségeket ad szűrésre, rendezésre, lapozásra.

dv:fetchparameters

A fetchParameters tulajdonságon keresztül paraméterezhetjük fel a meghívandó webmetódusunkat. Egyetlen JSON “dictionary”-t kell csak összeállítanunk.

dv:autofetch

Az autoFetch tulajdonság true-ra állításával adhatjuk meg, hogy a DataView az oldal betöltődésekor azonnal forduljon az adatforráshoz és töltse fel magát adatokkal. Ha ezt false-ra állítjuk, akkor nekünk kell meghívni a fetchData metódust, például így (feltételezve, hogy a fenti ul elemet elláttuk egy id=”list” attribútummal):

  $find('list').fetchData();

Kliens oldali adatkötés kódból

Természetesen itt is van lehetőség arra, hogy a HTML markupot és az adatkötést szétválasszuk, tehát a teljes adatkötést tisztán kódból végezzük el. Csupaszítsuk le a markupot (az előző cikkben bemutattam, hogyan lehet a {{}} elemektől megszabadulni, itt most csak az adatforrás kezelésére koncentrálok, ezért ezeket most meghagytam):

  <ul
    id="list"
    class="sys-template" >
    <li>
        {{ Name }} ({{ Capital }}) 
    </li>
  </ul>

Az adatkötést pedig az oldal inicializálásakor végezzük el, így:

  <script type="text/javascript">
    Sys.Application.add_init(appInit);
    function appInit()
    {
        $create(
            Sys.UI.DataView,
            {
              autoFetch: false,    // Hogy legyen lehetőségünk még inicializálni
              dataProvider: "Services/CountryService.svc",
              fetchOperation: "GetCountries",
              fetchParameters: { orderby: 'Name', asc: true }
            },
            null,
            null,
            $get("list")
        );
        // Ide jöhetne további inicializálás...
        $find('list').fetchData();
    }
  </script>

Ennyi, ki lehet próbálni, működni fog.

Rendezés

Miután rátettük a kezünket a fetchParameters tulajdonságra, semmi akadálya, hogy a felhasználó határozza meg, milyen sorrendben akarja látni az adatokat. Hogy egyszerűbb legyen az életünk, a rendezés adatait és a DataView vezérlőt kiemelhetjük globális fetchParams és dv változókba:

  var fetchParams = { orderby: 'Name', asc: true }
  var dv;
  Sys.Application.add_init(appInit);
  function appInit()
  {
      $create
( Sys.UI.DataView, { autoFetch: false,
dataProvider: "Services/CountryService.svc", fetchOperation: "GetCountries", fetchParameters: fetchParams }, null, null, $get("list") );
      dv = $find('list');
      dv.fetchData();
  }

Kellene még két metódus, amellyel gyorsan lehet rendezési szempontot és irányt váltani és amelyek azonnal frissítik a DataView vezérlőt:

  function sortBy(col)
  {
    fetchParams.orderby = col;
    dv.fetchData();
  }

  function sortAsc(dir)
  {
    fetchParams.asc = dir;
    dv.fetchData();
  }

Végül már csak UI elemekre van szükség ezeknek a függvényeknek a meghívásához:

  <a href="#" onclick="sortBy('Name')">Név szerint</a> 
  <a href="#" onclick="sortBy('Capital')">Főváros szerint</a> 
  | 
  <a href="#" onclick="sortAsc(true)">A-Z</a> 
  <a href="#" onclick="sortAsc(false)">Z-A</a>

Rakjuk össze mindezt egy oldalra és a felhasználó boldogan kattintgathat a linkekre, a háttérben a megfelelő sorrendben fognak letöltődni az adatok és azonnal meg is jelennek a böngészőben – természetesen AJAXosan frissítve az oldalnak csak az érintett részét.

A szép az egészben, hogy a tudomány nem áll meg itt: a rendszer képes arra is, hogy észrevegye, ha egy változó értéke megváltozik és automatikusan frissítse az értéket a felhasználói felületen. Folyt. köv.

A cikkhez tartozó forráskód letölthető innen.

Amikor nem lehet redirectelni

Nagyon hasznos és kényelmes az AJAX használata, azt azonban egy pillanatra sem szabad elfelejteni, hogy aszinkron postback (pl. page method) esetén nincs akkora szabadságunk, mint egy sima szinkron kérés esetén. Bár bizonyos esetekben a teljes oldal életciklus lefut, még egy sima Response.Redirect sem működik.

Ha mégis megpróbáljuk, az alábbi hibaüzenetet kapjuk, sajnos csak futási időben:

Response.Redirect cannot be called in a Page callback.

A megoldás az, hogy ebben az esetben szerver oldalon lemondunk az átirányításról:

  if( !this.Page.IsCallback )
  {
    this.Response.Redirect( "MasikOldal.aspx" );
  }

Helyette kénytelenek vagyunk a böngészőnek JavaScriptet visszaküldeni, ami a window.location tulajdonság írásával kliens oldalról végzi az átirányítást.

Tudtok jobb megoldást?

Technorati-címkék: ,,

ModalPopupExtender GyIK

Az AJAX Control Toolkitben lévő ModalPopupExtender (MPE) igen hasznos vezérlő, előszeretettel használjuk olyan helyzetekben, amikor az információ nem fér ki az oldalra vagy a felhasználó figyelmét az információk egy részére kell szűkíteni. Sajnos a dokumentációból hiányoznak olyan információk, amibe mindenki előbb-utóbb belefut.

Kommunikáció az oldal többi részével

Bár a ModalPopupExtender által megjelenített felugró ablak külön ablaknak tűnik, az valójában az oldal része. Tipikusan egy div (ez lesz az asp:Panelből), ami mindig ott figyel az oldalon, csak éppen többnyire rejtve van. Amikor az MPE megjeleníti, akkor ráadásul beállítja rá, hogy position:absolute és z-index:10001, ettől kerül minden más tartalom fölé.

Ha tehát az a feladatunk, hogy a felugró ablakból elérjük az oldal többi részét, például JavaScriptben, akkor ugyanúgy kell megírnunk a kódot, mintha nem is lenne MPE.

Villanás az oldal betöltésekor

Előfordulhat, hogy az oldal betöltésekor látjuk egy pillanatra felvillanni az MPE által generált ablakot, majd rögtön el is tűnik. A megoldás az, hogy a PopupControlID tulajdonságban megadott vezérlőre (tipikusan asp:Panelre) beállítjuk, hogy style=”display:none”. A tapasztalat azt mutatja, hogy ezt nem jó CSS osztályban beállítani, mert akkor nem működik minden böngészőben jól az MPE.

A háttér elsötétítése

Az MPE ablaka akkor válik valóban felugrónak látszóvá, ha jól elkülönül az oldal többi részétől. Bár be lehet állítani az MPE-nek, hogy DropShadow=”true”, szerintem rém gagyi az az árnyék, amit rajzol. Ennél sokkal jobb árnyékokat szokás manapság rajzolni. Egy lehetséges alternatíva a háttér elsötétítése valamilyen mértékben. Ezt szerencsére támogatják a mai böngészők, természetesen mindegyik más CSS szintaxissal. Ha a BackgroundCssClass=”popupBackground”, akkor a háttér CSS osztály lehet például ez (40%-os átlátszóság):

  div.popupBackground
  {
    background-color: Black; 
    filter:alpha(opacity=40);
    opacity: .40;
    -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=40)";
  }

Szerver oldali kód OK-nál

Gyakran előfordul, hogy a felugró ablakra teszünk Mehet és Mégsem gombokat, amiből a Mégsem esetén elég bezárni az ablakot, a Mehetnél viszont szerver oldali kódot kellene futtatni. Mindkét gombhoz használhatunk asp:Button vezérlőt és a Mehet gombnál a szokásos módon feliratkozhatunk a Click eseményre a code behindban. Arra kell csak odafigyelnünk, hogy az OkControlID tulajdonságot ne állítsuk be az MPE-n, a CancelControlID-nak viszont adjuk meg a Mégsem gomb szerver oldali azonosítóját (amit az asp:Button elemben az ID tulajdonságnál megadtunk). Az Ok/CancelControlID tulajdonságokban megadott gomb ugyanis nem fog postbackelni, annak működését felülírja az MPE.

Kinyitás szerver oldali kódból

Miután a felugró ablakban lévő valamelyik vezérlő postbackel és lefut a szerver oldali kódunk, az oldal újratöltődik és a popup ablak eltűnik. De mi van akkor, ha épp a felugró ablakban megadott valamilyen érték nem megfelelő és ott szeretnénk akár kézzel, akár egy CustomValidatorral hibaüzenetet megjeleníteni? Ez esetben a szerver oldali eseménykezelőből nekünk kell manuálisan felugrasztani az MPE ablakát, erre szolgál a Show() metódus. Ha épp el akarjuk rejteni, akkor hívhatjuk a Hide()-ot code behindban.

Inline UpdateProgress

Nem mondhatom magamról, hogy dizájner vér folyik az ereimben, mégis többen egyetértettek már azon ötletemmel, hogy az AJAXos háttérműveletet jelző pörgő gif az aszinkron postbacket kiváltó vezérlő, tipikusan Button mellett legyen. Az ASP.NET AJAX fejlesztői szerint azonban senki ne akarjon UpdateProgresst inline használni.

Ha megnézzük Reflectorban az UpdateProgress forráskódját azonnal látszik, hogy valamelyik idióta elég biztosra ment ezzel kapcsolatban:

Az UpdateProgress vezérlő forráskódja

Ott virít középen a display:block CSS beállítás. Aki érti, hogy ez miért kell ide, kérem írja meg, úgy szeretném megérteni…

Ha már így belekukkantottunk, az is látszik, hogy az UpdateProgress mindig pontosan egy div elemet renderel magából, amire azonban nem tudunk kívülről CSS osztályt ültetni. Csakhogy semmi akadálya, hogy az egészet becsomagoljuk egy külső divbe:

  <div class="progress">
    <asp:UpdateProgress runat="server" AssociatedUpdatePanelID="up" 
DisplayAfter="0" DynamicLayout="false"> <ProgressTemplate> <asp:Image runat="server"
ImageUrl="progress.gif"
AlternateText="Mentés folyamatban..." /> </ProgressTemplate> </asp:UpdateProgress> </div>

Majd a külső divre megadott CSS classra állítsunk be olyan stílust, ami igazából az UpdateProgress által generált divre fog alkalmazódni:

  div.progress
  {
    display: inline;
  }
  div.progress div
  {
    display: inline !important;
  }

Íme az eredmény:

UpdateProgress inline megjelenítése

Technorati-címkék: ,,,,

Kliens oldali validálás és OnClientClick

Ha egy weboldalon valaminek a törlésére készítünk funkciót, postback előtt mindig célszerű rákérdeznünk a felhasználónál, hogy valóban szeretné-e törölni a kiválasztott elemet. Az ilyen “valóban használni kívánja a légzsákot” jellegű kérdések megvalósítására kiváló hely a Button osztály OnClientClick tulajdonsága, az egyetlen hátránya, hogy összevész a validator vezérlők kliens oldali szkriptjeivel.

A megerősítést kérő ablak feldobása mindössze ennyi:

    <asp:Button ID="btnGo" runat="server" Text="Mehet" 
        OnClientClick="return confirm('Biztos, hogy ezt akarod?');" 
        onclick="btnGo_Click" />

Ez tökéletesen működik egészen addig, amíg a gombhoz nem kapcsolódik validator, ebben az esetben ugyanis hiába van engedélyezve a validator kliens oldali szkriptje, megtörténik a postback és csak a válasz megérkezése után jelenik meg a hibaüzenet kliens oldalon. E helyzet még rosszabb, ha a gomb szerver oldali eseménykezelőjéből kifelejtjük a Page.IsValid ellenőrzést, mert akkor még le is fut a teljes kódunk, mintha a validator ott sem lenne. A probléma tehát az, hogy rossz a kliens oldali szkriptek futásának sorrendje.

Szerencsére a validatorokhoz van kliens oldali API, ami részben dokumentált, részben pedig erős nézés módszerével (ld. pl. IE8 script debugger) megfejthető. Ez utóbbi úton jutottam az alábbi megoldáshoz, ami megoldja a fenti problémát:

  <asp:Button ID="btnGo" runat="server" Text="Mehet" 
    OnClientClick="Page_ClientValidate('myGroup'); if(!Page_IsValid) return false; return confirm('Biztos, hogy ezt akarod?');" 
    onclick="btnGo_Click" 
ValidationGroup="myGroup" />