Tag Archives: JavaScript

Mutasd az importjaidat, megmondom, ki vagy

Na jó, azt talán nem, de arra utalhat, hogy mennyire karbantartható a kódod.

Írtál-e már ilyet:

import { SomeClass } from 'somelib';
...
const s = new SomeClass();

Vagy épp ilyet:

import * as someFunc from 'somelib';
...
const s = someFunc();

Ismerős, ugye? Én is rengetegszer írtam le hasonló sorokat, már csak azért is, mert minden könyvtár, library, package README-je pontosan ezt ajánlja. Van ezzel a mintával azonban (legalább) két probléma, ami miatt egy ideje igyekszem kerülni.

Függőségek kezelése

Ha gátlástalanul követed ezt a mintát, egyetlen alkalmazáson belül ugyanazt a sort le fogod írni százszor (vagy még többször). Miért? Mert viszi a kezed a copy-paste. Egyszerű, kényelmes, működik, nem igaz?

Így viszont nagyon nehéz lesz meghatározni, hogy a Te alkalmazásodnak pontosan mire is van szüksége, mitől függ, mi a dependenciája. A kódból mindössze annyi fog látszódni (de csak miután a kedvenc editorod “Find in All Files” funkciójával rákeresel), hogy a te kódod számtalan helyen hivatkozik egy külső csomagra, tehát az a csomag kell neki, függ tőle.

A valóság azonban más: a te alkalmazás logikádnak nem egy csomagra van szüksége, hanem egy funkcióra, egy logikára, amit történetesen egy külső csomagban implementált valaki más. Csakhogy ez most abszolút nem látszik a kódodból.

Íme egy konkrétabb példa a népszerű lodash csomaggal (*):

var _ = require('lodash');

Ez a csomag számtalan segédfüggvényt tartalmaz, szinte kizárt, hogy a te alkalmazásodnak mindegyikre szüksége van. Hogyan derítheted ki, hogy pontosan mit használsz? Rákeresel az összes fájlodban arra, hogy “_”.

De miért érdekes ez?

Például mert előfordulhat, hogy a hivatkozott csomagot le akarod cserélni valami másra. A nyílt forráskódú világban csomagok jönnek, mennek, ma még ez volt a legjobb, holnap már az lesz. Mert a régiben van egy bug, amit a fejlesztője már sosem fog kijavítani, vagy mert csak az új kompatibilis a legújabb Node verzióval, ami meg kell az appod többi részének. Vagy egyszerűen azért, mert frissen akarod tartani az appod kódját, és csak olyan dependenciákat szeretnél, amik körül aktív a közösség.

Szinte biztos, hogy nem fogsz találni egy másik könyvtárt, csomagot, ami API szinten kompatibilis a régivel. Olyanra van szükséged, ami funkció szinten kompatibilis, amit csak akkor tudsz kideríteni, ha pontosan ismered, hogy milyen funkcióra hivatkozol.

Ha ez megvan, akkor már csak az kell, hogy a lehető legkevesebb helyen kelljen átírni a hivatkozást a kódodban.

Ebből rögtön következik is a megoldás: törekedj arra, hogy a külső csomagokra való hivatkozásokat pontosan egyetlen helyen írd le a kódodban, azaz csomagold be őket egy osztályba, ami csak azokat a funkciókat publikálja, amire a te appod épít. Ez a módszer ráadásul olvashatóbbá is tudja tenni a kódodat, például “sha256()” helyett sokkal jobb függvény nevet is ki tudsz találni, igaz?

Ez a módszer nekem nagyon bevált, bár megjegyzem, nem követem fanatikusként, mert a framewörk szintű libek (pl. Angular) csomagolása túl nagy overhead lenne.

Tesztelhetőség

Ha egy osztályod importon keresztül hivatkozik egy másikra, akkor annak az osztálynak a unit tesztelése rémálom lesz, mert a hivatkozott osztály mockolásához a fájl betöltő logikát kell meghekkelni (például Mocking Request-Promise).

A megoldás nem újdonság: DI. Nem csak azt nyerjük vele, hogy könnyebb a tesztelés, de elég ránézni tipikusan a konstruktorra, és messziről látszik, hogy az adott osztály milyen más osztályoktól függ.

TypeScriptben például sokszor importálunk interface-eket csak azért, hogy legyen típus leírónk, ami miatt nagyon el tudnak szaporodni az import sorok, és gyakran nem látszik ránézésre, hogy melyik import hoz be funkcionalitást, és melyik csak típus információt. Ezen is segít a DI, ha törekszünk arra, hogy az importot csak típus leírásokhoz használjuk, minden másra ott a DI.

Mi ebben az újdonság?

Semmi az ég világon. Tényleg. Ennek ellenére mégsem akarnak kipusztulni ezek a kódok. A README-k, tutorialok, cikkek, blogok mindig az egyszerűségre fognak törekedni, olyan példákat fogsz bennük találni, amik segítenek a megértésben, az elindulásban. Nem az a céljuk, hogy vakon kövesd őket egy komolyabb alkalmazásban, ahol neked a karbantarthatóság és fenntarthatóság legalább olyan fontos, mint az egyszerűség. Amikor mégis leírsz egy ilyen sort legközelebb, gondolj arra, hogy ezzel mit veszel a nyakadba.

programming-is-thinking

 

*: A példa kicsit sántít, mert a lodash lehetővé tesz finomabb importokat is, csak épp kevesen használják.

 

Technorati-címkék: ,,
Advertisements

Mi lesz veled, Angular? Gondolatok az Angular Connect 2017 után

Angular Connect WelcomeA héten volt szerencsém részt venni a Londonban rendezett két napos Angular Connect 2017 konferencián, ebben a blog bejegyzésben pedig néhány ott hallott gondolatot és benyomást jegyzek fel részint magamnak emlékeztetőül, részint mert másnak is hasznosak lehetnek.

Az itt leírtak közül több nem szó szerint így hangzott el az előadók szájából, ez már az én kivonatom. A fontosabb előadásokról videófelvétel készült, azok megtekinthetőek a konferencia honlapján, az tekinthető hiteles forrásnak.

Át kell állni Angular@latest-re

Tisztán látszik, hogy a Google nagyon komolyan veszi az Angulart. Nem egyszerűen egy szabadidőben készülő open source projektről van szó, hanem egy olyan technológiáról, amibe azért invesztálnak, mert ők maguk is használják. Az egyik legnagyobb projektjük, a Google Cloud Console több, mint 10 ezer TypeScript és 3 ezer Angular fájlt tartalmaz.

Ez nem újdonság, így volt ez már az AngularJS idején is, ám ebből rögtön két dolog is következik.

Az első, hogy a fejlesztés nem áll meg. Jó elvek szerint készült, és stabil alapokon áll már az Angular, ezért látványos új funkciókkal egyre ritkábban fogunk találkozni, viszont a motorháztető alatt folyamatos fejlesztések történnek minden verzióban. Itt a legfontosabb célterület a teljesítmény, aminek egyik összetevője az első oldalbetöltés ideje (Time To Interactive), a másik pedig az alkalmazás futása közbeni sebesség. Egyik területen sem muzsikál rosszul az Angular már most sem, de a csapat nem állt meg, minden verzióban egyre kisebb a bundle és egyre gyorsabb az app.

Ehhez az kell, hogy nagyon sok energiát öljön a fejlesztőcsapat abba, hogy a fordító egyre okosabb legyen. Csak egy példa: a Build Optimizer a TypeScript fordító sajátosságait és képességeit kihasználva tovább pofozza a kódot annak érdekében, hogy a minification még hatékonyabb legyen. Olyan trükköket csinál a kód variálásával, amivel egy átlagos projektben egy átlagos fejlesztő biztosan nem foglalkozna, Google méretekben viszont már sokat számít, mi pedig élvezhetjük a sok munka és kutatás eredményeit.

A második következménye annak, hogy a Google komolyan használja az Angulart, hogy a folyamatos upgrade-nek egyszerűnek kell lennie. A fejlesztő csapat szerint az Angular API stabil, nem várhatók breaking change-ek, a v4-ről v5-re történő átállás például a legtöbb projektnél egyszerű verzió szám átírás a package.json-ban, és máris új fordítót kap az app és gyorsabban fog futni. A Google alkalmazásainak eddig 25%-át migrálták át Angularra.

Adott tehát egy olyan keretrendszer, ami minden verzióban egyre hatékonyabb lesz, és a készítők szerint nem lesz nehéz az átállás az újabb verziókra. Tegyük még hozzá azt a tényt, hogy az Angular fejlesztők száma már meghaladja az AngularJS fejlesztők számát, és hogy az AngularJS-be valószínűleg már sokáig nem fog invesztálni a Google és máris ott tartunk, hogy az élő projekteket célszerű átmigrálni Angularra, és folyamatosan frissíteni a legújabb verzióra, azaz most v5-re.

Upgrade, de hogyan?

Angular upgradeKérdés, hogy mit kezdjünk a meglévő AngularJS kódbázissal, hogyan vigyük át Angularra?

Hogy upgrade vagy újraírás a jó megoldás, arra nincs egyértelmű, minden esetre működő válasz. A hivatalos álláspont szerint meg kell vizsgálni a kódbázis sajátosságait és a jövőbeli várható üzleti elvárásokat, és ezeknek a fényében kell dönteni. Ez egyáltalán nem új és nem meglepő.

Nincs meglepetés sajnos azzal kapcsolatban sem, hogy az upgrade-et hogyan érdemes kivitelezni. Nincs varázsszer, az ngUpgrade "az" eszköz, legfeljebb azt lehet variálni, hogy milyen lépésben haladunk. Ha az alkalmazásunk több "oldalból" áll, akkor érdemes lehet route-onként haladni.

Összességében tehát az upgrade területén nincs más lehetőségünk, mint időt allokálni rá, és végigcsinálni. Itt szerintem a legtöbb projektben nem a mérnöki, hanem a menedzsment oldal fogja a nehézséget jelenteni.

Mit kezdjünk a nagy kódbázissal?

BazelA folyamatosan bővülő kódbázis egyre nagyobb kihívást jelent. Növekszik a repository mérete, növekszik a fordítási idő, egyre több csapat dolgozik ugyanazon a kódon, egyre nagyobb problémát okoz a függőségek kezelése. Mit lehet tenni?

A Google és a Facebook a monorepo híve, az alkalmazások és a library-k egyetlen nagy repository-ban vannak, míg az Amazonnál és a Netflixnél a microrepository-ban hisznek, ott minden független egymástól. Bármelyiket is valljuk, a választás feltételekhez kötött.

A Google részben nyilvánossá tette a saját fejlesztésű build eszközét, a Bazelt, aminek az erőssége, hogy a kód módosításai után csak azt fordítja újra, amire kihatással lehet. Ha nincs publikus API változás, akkor csak az adott komponenst, ha van, akkor pedig azokat, amik arra épülnek. Így elérték, hogy az edit-and-refresh élmény 2 másodperc alatt legyen. Ehhez persze az is kellett, hogy a kódot okosan szervezzék. Átlagosan náluk 1 package-ben 1 Angular modul van, 1 modul pedig átlagosan 1.5 komponenst tartalmaz. Ezzel nem csak azt nyerik, hogy gyors a build, hanem azt is, hogy tiszták az interfészek és dokumentáltak a függőségek.

Továbbá érdemes megnézni, hogy az Angular.io webhely buildeléséhez milyen megoldásokat választottak, és abból mit tudunk felhasználni. Ez az Angular alkalmazás náluk az állatorvosi ló, sok benchmark erre vonatkozik.

DRY vagy PRY?

A Don’t Repeat Yourself elv szerint üldözni kell minden kód duplikációt, ha valahol ismétlődő kódot találunk, akkor azt azonnal refaktorálnunk kell.

Ám ha ezt túl korán tesszük meg, lábon lőhetjük magunkat. A közös kódrészlet függőséget jelent, az interfésze contractként kezelendő, aminek a változásait csak megfelelő körültekintéssel és kommunikációval lehet elvégezni.

Ha van megbízható CI infrastruktúránk és jó teszt lefedettségünk, akkor ez költséges, de nem kockázatos. Kiszervezzük a közös kódot egy saját package-be, és ha bármi változik a package-ben, akkor újrafordítunk minden olyan alkalmazást és package-et, ami arra épül és újra is teszteljük azokat.

Ám ha nem vagyunk ilyen szerencsés helyzetben, akkor a túl korán alkalmazott DRY nem csak költséges, de kockázatos is lehet. Amíg tehát egy kódrészlet gyakran változik, addig inkább a Please Repeat Yourself (PRY) elvet érdemes követni, és csak a stabilizálódott kódokat kiszervezni közös helyre.

Mielőtt azonban vallást váltunk és elkezdjük követni a PRY elvet, érdemes feltenni magunknak a kérdést: vajon vissza fogunk-e térni később, és refaktoráljuk-e a már stabil kódrészletünket, vagy a jól működő dolgokhoz általában nem nyúlunk?

Zárójelben jegyzem meg, hogy a build eszközeink (TypeScript compiler, Angular compiler, Build Optimizer, minifier, Webpack stb.) nagyon agresszíven törekszenek a duplikáció kiszűrésére, “mutatványos környezetben” (production) tehát nem nagy a veszteség.

Hogyan teszteljünk nagy kódbázist?

CucumberA tesztelés terén nem tartogatott meglepetéseket a konferencia. Volt ugyan egy előadás a SerenityJS-ről, ami nem a Protractorban megszokott Page Objects elvet követi, hanem a Screenplay Patternt, de engem nem győzött meg.

Egyrészt azért, mert szerintem a Page Object módszert is lehet úgy használni, hogy a felsorolt problémákat elkerülje (kód mérete, olvashatósága, duplikációk stb.), másrészt pedig azért, mert nem látom, hogy ez miért jobb, mint a jól bevált Gherkin-Cucumber páros.

Ami viszont fontos – bár nem új – gondolat az előadásból, hogy a teszteket igenis tervezni kell. A jól megtervezett teszt esetekből lesznek a jól megtervezett teszt implementációk, azaz a karbantartható teszt kód.

Mi várható 2018-ban?

A fordító és a runtime folyamatos fejlesztésén kívül az Angular csapat természetesen újabb funkciókkal is bővíti a keretrendszert, amiből a PWA, az Angular Universal és az Angular Elements kapott nagyobb teret a konferencián.

PWA

PWAA PWA, vagy teljes nevén Progressive Web Apps, a legújabb (?) név arra a jelenségre, hogy webes technológiákkal készített és az interneten hosztolt programkód olyan felhasználói élményt nyújtson, mint egy telepített, vastag kliens alkalmazás. Technológiai szinten az újdonságot a Service Worker jelenti, ami biztosítja az offline működés feltételeit. Ezt a komponenst az Angular csapat becsomagolta egy @angular/service-worker package-be, ami lehetővé teszi, hogy nagyon kényelmesen készítsünk Angularral PWA-t. A CLI 1.6 bétájában már megjelenik a –service-worker kapcsoló, későbbi verziókban pedig várható, hogy ez lesz az alapértelmezett, tehát érdemes készülni rá.

A PWA nem egy fekete-fehér terület, vannak hátrányai (például verzió frissítés), ami miatt egyáltalán nem biztos, hogy minden esetben megéri használni. Érdemes viszont megismerkedni vele és szem előtt tartani mint lehetőséget (és mint biztonsági tényezőt is), mert jelentősen növelhetjük vele a felhasználói élményt.

Angular Universal

Angular Universal

A 4.0 verziótól már az Angular részeként elérhető Angular Universal célja, hogy platform és eszköz támogatást adjon szerver oldali HTML renderelés megvalósításához. A feladat nem csak annyi, hogy a backendről érkező HTML sztringet beszúrunk a weboldal közepére, hanem támogatni kell a kereső motorokat és az oldalról tartalmat kiollózó egyéb alkalmazásokat (pl. Facebook, Slack), miközben többféle stratégiát (pl. statikus renderelés fordításkor) is kell tudnia támogatni.

Olyan környezetben, ahol a teljesítmény problémákat okoz, érdemes megismerkedni a fogalommal és az Angularos megvalósítással.

Angular Elements

Angular LabsA szoftverfejlesztés egyik problémája, hogy általában le kell tenni a voksunkat valamelyik keretrendszer mellett, és utána annak a szabályai szerint kell játszanunk. Különösen bosszantó ez egy olyan környezetben, mint a web, ahol mindegy, hogy milyen kliens oldali platformot választunk, végül mezei HTML és JavaScript kód fog futni a böngészőben, miközben az egyes keretrendszerek között nincs, vagy csak nagyon körülményes az átjárás.

Ezen a problémán próbál segíteni az egyelőre csak az Angular Labs részeként elérhető Angular Elements, aminek az a célja, hogy a Custom Elements szabvány lehetőségeit felhasználva úgy csomagolja be az Angularos komponenseinket, hogy más keretrendszerek számára standard HTML elementnek tűnjenek. A kezdeményezés még nagyon gyerekcipőben jár, jövőre lehet belőle valami, mindenesetre üdítő látvány, hogy az Angular csapat nem zárkózik be, nyitott más platformok felé, és támogatni akarja a library fejlesztőket.

Ezek voltak a visszatérő témák, de ezeken kívül még sok más terület is terítékre került: forms, animations, DevKit, Schematics, ABC, Query, DI, aria, internationalization, flex layout, security, burnout, VR – szerencsére a videók mind megtekinthetők a Youtube-on. Mindkét nap keynote-tal kezdődött, érdemes megnézni őket.

Ráadásként az Angular csapat egy ígéretet is tett: kigyomlálják a Github repóban található közel 2000 issue-t és több, mint 300 PR-t. Külön embert vettek fel erre 🙂

 

Technorati-címkék: ,,,

Öröklésnél elveszik az Error message tulajdonsága

Ilyen kódot már mindenki látott:

try {
  throw new Error('Oh, nooooo!');
} catch (e) {
  console.log(e.message);            // Oh, nooooo!
  console.log(e instanceof Error);   // true
}

Ha nagyon sok hasonló keletkezik, akkor előbb-utóbb az ember rájön, hogy szükség lenne saját hiba osztályokra, még JavaScriptben is. Jó ötletnek tűnhet valahogy így létrehozni egyet:

function OhNoooError() {
  Error.call(this, "Oh, nooooo!");
  this.name = "OhNoooError";
}

Az eredmény azonban meglepő lehet:

try {
  throw new OhNoooError();
} catch (e) {
  console.log(e.message);                 // undefined
  console.log(e instanceof OhNoooError);  // true
  console.log(e instanceof Error);        // false
}

Az elkapott kivételünk ugyanis nem számít klasszikus Errornak (nem abból származik), és elveszítette a message tulajdonságát is.

Íme egy módszer, amivel ezeket a hibákat orvosolni lehet:

OhNoooError.prototype = Object.create(Error.prototype);
OhNoooError.prototype.constructor = OhNoooError;

function OhNoooError() {
  this.message = "Oh, nooooo!";
  this.name = "OhNoooError";
}

A kimenet is szebb lesz:

try {
  throw new OhNoooError();
} catch (e) {
  console.log(e.message);                 // Oh, nooooo!
  console.log(e instanceof OhNoooError);  // true
  console.log(e instanceof Error);        // true
}

A lényeg tehát, hogy a message tulajdonságot kézzel kell beállítani. Ha az öröklést esetleg CoffeeScriptben az extends kulcsszóval valósítjuk meg, a message tulajdonsággal akkor is manuálisan kell elbánnunk.

 

Technorati-címkék: ,

Ingyenes e-book Windows (Phone) 8.1 fejlesztéshez

2843.9780735611111f_7E0540F4A Windows 8 megjelenésével egy időben adta ki a Microsoft Press Kraig Brockschmidt Programming Windows Store Apps with HTML, CSS and JavaScript c. könyvét ingyenes e-book formájában. A 8.1 verzió megjelenésével a könyv kissé elavult, de szerencsére Kraig nem csüggedve tovább dolgozott rajta.

Most jelent meg a könyv második kiadása, amely 478 oldallal kibővítve immár a Windows 8.1 újdonságait is lefedi, sőt jelentős része alkalmazható Windows Phone 8.1 fejlesztéseknél is. A teljes könyv így 1311 oldalas lett, és szerencsére továbbra is ingyenesen tölthető le a Microsoft Virtual Academy oldaláról a kapcsolódó példakódokkal együtt.

 

Tetszőleges kiterjesztés megadása fájl mentésekor

Windows 8 alatt a File Open Picker dialógusablaknak viszonylag egyszerűen megadhatjuk, hogy tetszőleges típusú fájlt kiválaszthat a felhasználó, egyszerűen használjuk a szokásos csillagot:

var openPicker = new Windows.Storage.Pickers.FileOpenPicker();
openPicker.fileTypeFilter.replaceAll(["*"]);

Ha azt szeretnénk, hogy a felhasználó tetszőleges kiterjesztéssel menthesse el a fájlt, a fentiek alapján megpróbálkozhatunk ezzel:

var savePicker = new Windows.Storage.Pickers.FileSavePicker();
savePicker.fileTypeChoices.insert("Tetszőleges", ["*"]);

Ám ez hibára vezet:

0x80070057 – JavaScript runtime error: The parameter is incorrect.

WinRT information: This file picker does not allow the all files extension.

No, ez pech. Szerencsére van megoldás, írjuk ezt:

savePicker.fileTypeChoices.insert("Tetszőleges", ["."]);
Technorati-címkék: ,,,

Ajaxos fájl feltöltés

Időnként felmerül, hogy jó lenne úgy feltölteni egy fájlt, hogy közben az oldal többi része nem változik, magyarul Ajaxosan. Rossz hírem van, az XMLHttpRequest objektum Level 1 változata ezt nem tudja, tehát ha régebbi böngészőkre is tekintettel kell lennünk, akkor nincs mese trükközni kell.

A legelterjedtebb trükközési módszer az iframe alkalmazása, azt ugyanis bátran lehet postbackelni, az egyetlen szépséghiba, hogy a válasz, például validációs hibák is az iframe-be fognak megérkezni. Tehát ha rejtett iframe-mel dolgozunk, akkor a választ onnan ki kell venni, és az oldal megfelelő részén meg kell jeleníteni. Van tehát feladat bőven, szerencsére a jQuery Form Plugin sokat tud segíteni a megvalósításban.

Először is készítsünk egy view-modelt szerver oldalon, amiben egy HttpPostedFileBase típusú tulajdonság fogja képviselni a feltöltött fájlt. Mellécsaptam még egy kötelező Name tulajdonságot, csak úgy a demonstráció kedvéért:

public class UploadVM
{
    [Required( ErrorMessage = "Please enter a name!" )]
    public string Name { get; set; }

    [Attachment]
    public HttpPostedFileBase Attachment { get; set; }
}

Az [Attachment] a korábbi cikkben bemutatott, fájl validálásra használt attribútum. Ellenőrzi, hogy van-e feltöltve fájl, illetve hogy helyes a kiterjesztése és nem túl nagy-e a mérete.

Ehhez a modellhez már készíthetünk egy űrlapot, ami fel fogja küldeni a fájlt és a megadott nevet:

@using( Html.BeginForm( "Index", "Home", FormMethod.Post, 
        new { id = "myForm", enctype = "multipart/form-data" } ) )
{
    <p>
        <label for="txtName">Name:</label>
        <input type="text" id="txtName" name="Name" />
    </p>

    <p>
        <label for="fupAttachment">File:</label>
        <input type="file" id="fupAttachment" name="Attachment" />
    </p>

    <p>
        <input type="submit" value="Upload" />
    </p>    

    <div id="errors"></div>
}

Fontos, hogy a generált formnak multipart/form-data értékű enctype attribútuma legyen, mert azzal tud csak fájl utazni, illetve létrehoztam még egy errors azonosítójú div-et is, ahol majd a hibaüzeneteket fogjuk megjeleníteni.

Ez az űrlap a HomeController Index nevű actionjére lő, amit így implementálhatunk:

[HttpPost]
public ActionResult Index( UploadVM model )
{
  if( !this.ModelState.IsValid )
  {
    string firstError = ModelState.First( m => m.Value.Errors.Any() )
                                  .Value.Errors[ 0 ].ErrorMessage;
    return this.FileUploadFailure( firstError );
  }

  // Process the file here

  string message = String.Format( "The file '{0}' is successfully uploaded.", 
                                  model.Name );
  return this.FileUploadSuccess( message );
}

Ha a view-model valamelyik tulajdonsága hibás, akkor az attribútumoknak köszönhetően a hibák bekerülnek a ModelStatebe, amit a metódus elején szokás szerint ellenőrzünk. Ha van hiba, akkor az első hibaüzenettel térünk vissza, ha nincs, akkor pedig feldolgozzuk a fájlt és egy siker üzenettel térünk vissza.

A visszatérési érték egy JSON objektum, mert ezt tudjuk kliens oldalon barátságosan feldolgozni. Csakhogy ne feledjük, hogy a válasz egy iframe-be fog beíródni, és nem minden böngésző szeret iframe-be application/json típusú tartalmat kapni. Szerencsére a jQuery Form Plugin támogatja azt a trükköt, hogy a JSON tartalmat egy <textarea> elembe ágyazva küldjük vissza a szerverre text/html típusú válaszként, onnan ő majd kiveszi a JSON tartalmat.

Hogy ez a csomagolás egyszerű legyen, készítettem egy saját result típust:

public class FileUploadJsonResult : JsonResult
{
  public override void ExecuteResult( ControllerContext context )
  {
    this.ContentType = "text/html";
    context.HttpContext.Response.Write( "<textarea>" );
    base.ExecuteResult( context );
    context.HttpContext.Response.Write( "</textarea>" );
  }
}

Az egyszerű példányosításhoz pedig két bővítő metódust, melyek közül az egyikkel sikert, a másikkal hibát lehet jelezni:

public static FileUploadJsonResult FileUploadSuccess( 
  this Controller controller, string successMessage = null )
{
  return new FileUploadJsonResult
  {
    Data = new { Success = true, Message = successMessage }
  };
}

public static FileUploadJsonResult FileUploadFailure( 
  this Controller controller, string errorMessage )
{
  return new FileUploadJsonResult
  {
    Data = new { Success = false, Message = errorMessage }
  };
}

Itt a Data tulajdonságban bármilyen szerkezetű objektumot összerakhatunk, az fog megérkezni válaszként a kliensre JSON formátumban. Itt most az egyszerűség kedvéért egy Success tulajdonságban jelzem, hogy a feltöltés hibátlanul megtörtént-e, és egy Message tulajdonságban hibaüzenetet vagy sikeres feltöltésre utaló üzenetet küldök vissza, amit a böngésző megjeleníthet.

Készen vagyunk tehát a szerver oldallal: van egy formunk, ami elPOSTolható egy actionnek, ami validálja a feltöltött tartalmat, és ha minden mező érvényes adatot tartalmaz, akkor feldolgozza őket, és az eredményt JSON válaszban jelzi.

Már csak a kliens oldal van hátra, amit természetesen a jQuery Form Plugin segítségével valósítunk meg. Ahogy a neve is mutatja, ez egy jQuery plugin, amit a form elemet burkoló jQuery objektumra (itt épp $form) kell ráhúznunk:

$form.ajaxForm({
    iframe: true,
    dataType: "json",
    beforeSubmit: function () {
      // TODO     },
    success: function (result) {
      // TODO 
    },
    error: function () {
      // TODO 
    }
});

Az alábbi eseménykezelőket célszerű megvalósítanunk:

  • beforeSubmit: ez a POST elküldése előtt fut le, itt írhatjuk ki például, hogy a feltöltés folyamatban van, vagy például a jQuery BlockUI plugin segítségével letilthatjuk az űrlapon lévő vezérlőket.
  • success: ez akkor hívódik meg, ha látszólag hibátlan volt a feltöltés, bár tapasztalatom szerint akkor is meg tud hívódni, ha a szerver valamilyen hibát jelez. Ha nem volt hiba, akkor a paraméterül kapott változóban a szerverről visszaküldött JSON objektum jelenik meg, de számítsunk rá, hogy hiba esetén ez lehet undefined is!
  • error: hiba esetén ez fut le.

Íme egy példa a success callback implementálására:

if (!result) {
$errors.html('<div class="validation-summary-errors"><ul><li>Oooops....
                </li></ul></div>');
}
else {
  $form.resetForm();

  if (result.Success === true) {
    var message = result.Message;
    if (message && message.length > 0) {
      $errors.html( message );
    }
  }
  else {
    $errors.html('<div class="validation-summary-errors"><ul><li>{0}
                  </li></ul></div>'.format(result.Message));
  }
}

Ebben a kódban a hibák megjelenítését egy kicsit összetett HTML darabkával végezzük. Ennek az a jelentősége, hogy a klasszikus ASP.NET MVC-s validálás is pont ilyen HTML-t generál magából, tehát a mi Ajaxos hibáink is pont ugyanolyan stílussal fognak megjelenni.

A format egy a szerver oldali String.Formathoz hasonló függvény, amit például így implementálhatunk JavaScriptben:

String.prototype.format = function () {
  var args = arguments;
  return this.replace(/\{\{|\}\}|\{(\d+)\}/g, function (m, n) {
    if (m === "{{") { return "{"; }
    if (m === "}}") { return "}"; }
    return args[n];
  });
};

Ha a szerverről nem csak az első, hanem összes ModelState hibát visszaadnánk, akkor egy ciklusban renderelhetnénk ki őket.

A cikkhez tartozó forráskód rengeteg kommenttel letölthető innen: http://sdrv.ms/10QQLSp

 

Technorati-címkék: ,

A jQuery szakít a régi IE verziókkal

jquery-logoMa megjelent a jQuery legújabb, 1.9 verziója és vele együtt a 2.0 verzió bétája. Bár a két verziónak ugyanaz az API-ja (pár dolgot az 1.9-ből éppúgy kivettek, mint a 2.0-ból), mégis óriási különbség van közöttük:

  • Az 1.9 verzió – a korábbiakhoz hasonlóan – fut Internet Explorer 6, 7 és 8 verziókon, ahogy ők mondják “oldIE”-n.
  • A 2.0 nem fog futni oldIE-n. Ez számos egyszerűsítést tett lehetővé, aminek köszönhetően a 2.0 verzió gyorsabb és kisebb lesz, mint az 1.9 verzió.

A Release Notes szerint a fejlesztők mindkét verziót támogatni fogják a jövőben, de én úgy sejtem, hogy ahogy az lenni szokott, ez nem lesz mindig így. Előbb-utóbb az 1.9 el fog avulni, az új funkciók pedig könnyen lehet, hogy csak a 2.0 verzióba fognak bekerülni.

Vajon ez kinek rosszabb, az IE-nek vagy a jQuery-nek? A fejlesztők fognak átállni a jQuery-ről más könyvtárra, vagy a weboldalak fognak lemondani a régi Internet Explorer támogatásáról?

 

Technorati-címkék: ,,