A Javascript programozási nyelv

Frontend keretrendszerek

Egyirányú adatkapcsolás

Az AngularJS egy Google által fejlesztett, nyílt forráskódú JavaScript keretrendszer dinamikus webes alkalmazásokhoz. Segítségével nagyban egyszerűsödik a webes alkalmazások frontend fejlesztése. Használatával a HTML eszköztára kibővül és az alkalmazások komponensei még egyértelműen elkülönülnek. Az Angular adatkapcsolásának, illetve függőség injektálásának köszönhetően, rengeteg felesleges boilerplate kód elhagyható.

A keretrendszer főbb célkitűzései:

Telepítés

Telepítés rendkívül egyszerű az angularjs.org angularjs.org oldalról töltsük le a legutolsó verziót. Választhatunk a normál illetve a tömörített kód között. A használatához nincs semmilyen különleges követelmény, mindössze a html fájlunk header részében hivatkozzunk a már előbb letöltött angular.js /angular.min.js fájlra.

<script src="lib/angular/angular.min.js"></script>

Modulok

Mint minden nagyon keretrendszerhez az Angularhoz is léteznek különféle kiegészítő modulok. Ezek segítségével egyszerűen tudjuk az alap keretrendszer eszköztárát bővíteni. Természetesen akár saját kiegészítéséket is írhatunk. A modulok legmegbízhatóbb forrása az ngmodules.org. A telepítésükhöz a legkényelmesebb mód egy csomagkezelő használat, ilyen például a Bower (bower.io).

Angular alkalmazások felépítése

A manapság elterjedt keretrendszerekhez hasonlóan itt is az MVC (Model View Controller) modellel találkozhatunk. A nézeteink a HTML fájlok lesznek, míg a kontrollerek valamint a modellek JavaScript nyelven íródnak.

Kontroller koncepció

Kétirányú adatkapcsolás

A legtöbb template rendszer csak egyirányú adatkapcsolást (data binding) tesznek lehetővég, egyesítik a template és modell komponenseket egy nézetté. Az egyesítést követően a modellben történt változások nem tükröződnek automatikusan a nézetben. Ennél sokkal problémásabb, hogy a nézeten történő változások nem lesznek visszavezetve a modellbe. Az inkonzisztencia elkerülése érdekében fejlesztőnek gondoskodnia kell a folyamatos szinkronizálásról.

Egyirányú adatkapcsolás

Ezzel szemben az Angulár más megközelítést választott. Elsőként a template lefordul a böngészőben (nyers HTML kiegészítve a speciális Angular direktívákkal). A fordítás eredménye ellentétben a fenti esettel nem statikus lesz. Ennek köszönhetően bármilyen változás a nézetben azonnal tükröződik a modellben, illetve a modell változásai is tovább terjednek a nézetbe. Ennek köszönhetően az állapot egyedül a modellben tárolódik (single-source-of-truth), ami nagyban könnyíti a fejlesztő életét. Lényegében a nézet tekinthető a modell egy projekciójának. Ennek köszönhetőn a kontroller teljesen izolált, így a tesztelhetőséget is elősegíti.

Egyirányú adatkapcsolás

Nézet

Az AngularJS követi a HTM szabvány, de kibővíti azt direktívákkal. Ezek a direktívák rendkívüli mértékben könnyítik meg az életünket, és ezeknek köszönhetően nem is lesz szükségünk közvetlen DOM manipulációra.

Néhány fontosabb direktíva:

Példa a használatukra: Kontroller definiálás, illetve kétirányú adatkapcsolás az ng-modell segítségével.

<body ng-controller="MainCtrl"> <input type="text" ng-model="color" placeholder="Enter a color" /> <hello-world/> </body>
Egyszerű for-each jellegű ciklus.
<tr ng-repeat="row in game.grid">

Angular kifejezések

Az Angular kifejezések JavaScript szerűek, amiket {{ }} közé helyezhetünk el. Példa néhány helyes kifejezésre:

Angular vs. JavaScript kifejezések

Az Angular kifejezések nagyon hasonlóak a JavaScript kifejezésekre a következő kivételektől eltekintve:

Ha olyan kifejezést szeretnénk írni, ami a fenti megszorítások miatt nem megvalósítható, akkor nagy valószínűséggel annak már egy kontroller metódusban a helye. Ha a kontrollerben szeretnénk kiértékelni egy Angular kifejezést, akkor azt az eval() segítségével tehetjük meg.

Szűrők

A szűrők a html nézetben található kifejezésekre alkalmazhatók a következő szintaxissal.

{{ expression | filter }}
Például írhatunk egy egyszerű árfolyam szűrűt, ami megfelelő formátumra hozza a kifejezést.
{{ 12 | currency }}
Szűrők alkalmazhatók más szűrők eredményeire, azaz láncolhatjuk őket.
{{ expression | filter1 | filter2 | ... }}
Igény szerint egyes szűrőknek argumentumaik is lehetnek.
{{ expression | filter:argument1:argument2:... }}

Kontroller

Az Angular kontrollerek szabványos JavaScript nyelven íródnak. Lényegében itt valósul meg az alkalmazás üzleti logikája. Amikor a kontrollert hozzácsatoljuk a DOM-hoz az ng-controller direktíva segítségével, akkor a megnevezett kontrollerből létre fog jönni egy új példány. Ilyenkor létrejön egy új gyerek scope, és a kontroller konstruktorán keresztül beinjektálhatjuk a paramétereket.

Mire használjuk a kontrollereket:

Mire ne használjuk:


ExtJS_logo

Az ExtJS a sencha által fejlesztett, MVC/MVVM architekrútát megvalósító JavaScript keretrendszer üzleti webalkalmazások fejlesztéséhez.
A több mint 150 grafikus komponens és több száz osztályt tartalmazó rendszer teljes böngésző és platform függetlenséget garantál (Safari 6+, Firefox, IE 8+, Chrome, Opera 12+, Safari/iOS 6+, Chrome/Android 4.1+, IE 10+/Win 8).

A komplexitása és OOP megközelítése miatt nem bemutatkozó oldalak készítésére alkották, habár biztosít lehetőséget dom manipulálásra, animációkra és minden egyéb, a többi JS library segítségével megvalósítható műveletre. Segítségével egyszerűen, gyorsan lehet single-page adminisztrációs felületeket létrehozni. A szerverrel történő kommunikációt támogatja JSON, XML, HTML formában is, erre külön proxy osztályokat hoztak létre.

Az Ext osztályait és osztály szerkezetét nem ismertetem, mert több száz osztály tartozik ide, miknek az áttekintése szinte lehetetlen. A dokumentáció nagyon részletes és beszédes, fejlesztés során elkerülhetetlen a megtekintése. Továbbá rengeteg leírás és példa található az Ext JS 5.0 oldalán is.

Architektúra

Az Ext JS támogatást nyújt mind MVC, mind MVVM architektúrájú alkalmazások fejlesztéséhez. Mindkét megközelítésnek az a lényege, hogy logikailag szétválasszuk a kódot az újrafelhasználhatóság és átláthatóság érdekében. Természetesen nem kötelező ezeket a mintákat követni, de az osztály szerkezet és a Sencha Cmd ezekhez lett tervezve, így a leghatékonyabb fejlesztés az előző két architektúra követésével biztosított.

Alkalmazás készítése

Az alkalamzás vázának elkészítéséhez segítséget nyújt nekünk Sencha által fejlesztett Sencha Cmd. Ez egy egszerűen kezelhető parancssoros alkalmazás, ami létrehozza nekünk a megfelelő mappa szerkezetet és egy-egy generált fájlt, majd a fejlesztés végén megcsinálja a minimalizálást, illetve, ha szükséges, a sass fájlok generálását is.
A telepítése után a következő parancsot futtatva elkészül az alkalmazásunk váza:

sencha generate app -ext MyApp ./app cd app sencha app watch

Alkalmazás struktúrája

Mielőtt neki fog az ember az első alkalmazásának érdemes egy pillantást vetni a szerkezetre.

File hierarchia

Az Ext JS alkalmazások egységes mappa struktúrát követnek minden alkalmazásban. Ez lehetővé teszi, hogy a fejlesztők könnyebben kiigazodjanak rajta egy-egy új projekt megismerésekor.
Javasolt minden osztályt az app mappába helyezni és ott namespaceek szerint további almappákat létrehozni.

Namespace

Minden osztályt tartalmazó fájl első sora az osztály "címe". Ezt a címet namespaceként emlegíti az Ext Js-es terminológia. A namespace deklaráció formája a következő:

<AlkalmazasNev>.<mappa>.<OsztalyNev>

A fenti alkalmazásban "MyApp" lesz az AlkalmazasNev, "view" a mappa, "main" az almappa és "Main" az Osztály/fájl név. A namespace helyes megadása azért fontos, mert az Ext.Loader-e ez alapján fogja betölteni a szükséges fájlokat.
Ha a fájlunkat nem találja, akkor a következő hibát láthatjuk a console-ban:

Az alkalmazás

Miután megértettük a szerkezet alapjait és az alkalmazás felépítését nyissuk meg az index.html-t.

<!DOCTYPE HTML> <html> <head> <meta charset="UTF-8"> <title>MyApp</title> <!-- The line below must be kept intact for Sencha Cmd to build your application --> <script id="microloader" type="text/javascript" src="bootstrap.js"></script> </head> <body></body> </html>

Ez lesz az alkalmazásunk "belépő" pontja, mivel általában single-page alkalmazásokat fejlesztünk ext-ben, ez az egyetlen html fájl szükséges. Az Ext JS Microloadert használ az erőforrások betöltéséhez az app.json alapján. Ez teszi lehetővé, hogy ne az index.html-be kellejen beszúrni a szükséges hivatkozásokat, valamint így az alkalamzásra érvényes minden meta információ egy helyen tárolható. Ennek köszönhetően a Sencha Cmd is egyszerűbben tud dolgozni az alkalmazásunkkal.
Innentől mindent JavaScript segítségével fogunk megoldani. Nagy előnye az Ext JS-nek, hogy minden komponens legenerálja a saját dom elemét, így, ha nem kívánunk custom komponenseket használni, akkor az egyszerű index.html-en kívül többet nem is kell html kódot írnunk.

Pont ezért sem javasolt bemutatkozó oldalakra, vagy bármilyen egyéb, a nagy közönség számára készült oldalt ext-ben fejleszteni. A saját html-ek sokszor nem validak, ennek oka a böngésző függetlenségben keresendő, így tudják megvalósítani, hogy tényleg minden platformon és böngészőben a megfelelő módon jelenjenek meg a komponensek. Egy másik probléma a generált html-lel, hogy rettenetesen sok dom elmet fog beszúrni. A kész dom fa nagyon terjengős és ennek kezelése néha belassul a layout műveletek során. Erre vannak megoldások, de egy lassab kliens-nél mindig problémás lesz.

Ha szeretnénk saját html elemeket létrehozni, akkor kód szinten kapunk támogatást az Ext.DomHelper osztálytól, továbbá az Ext.Template egy nagyon erős és jól használható template kezelést biztosít a számunkra.

App.js

A Sencha Cmd által generált szerkezetben találunk egy Application osztályt (Application.js) amit fel is használunk az app.js-ben (ebből fog származni a globális App objektumunk):

/* * This file is generated and updated by Sencha Cmd. You can edit this file as * needed for your application, but these edits will have to be merged by * Sencha Cmd when upgrading. */ Ext.application({ name: 'MyApp', extend: 'MyApp.Application', autoCreateViewport: 'MyApp.view.main.Main' //------------------------------------------------------------------------- // Most customizations should be made to MyApp.Application. If you need to // customize this file, doing so below this section reduces the likelihood // of merge conflicts when upgrading to new versions of Sencha Cmd. //------------------------------------------------------------------------- });

Az autoCreateViewport beállítással tudjuk megadni, hogy melyik View objektumunkat szeretnénk rákötni automatikusan a body-ra. Ezt az Ext fogja elintézni a Viewport Plugin segítségével.

Application.js

Minden Ext JS alkalmazás az Application osztály egy példányával indul

Ext.define('MyApp.Application', { extend: 'Ext.app.Application', name: 'MyApp', stores: [ // TODO: add global/shared stores here ], launch: function () { // TODO - Launch the application } });

Az Application osztály az alkalmazásra vonatkozó globális beállításokat tartalmaz, mint például a névtér, vagy a megosztott store-ok, stb...

View

A Viewk egyszerű komponensek, amik az Ext.Component osztályból származnak. A viewk tartalmaznak minden megjelenített elemeket, nem kerül bele semmilyen logika, csak a megjelenítéssel kapcsolatos beállításokat írunk bele, mint például a layout, szövegek. Minden az alkalamazás logikával kapcsolatos kód a ViewControllerbe megy majd, amiről később beszélünk.
A generált Main.js-ben megnézhetjük, hogy fest egy View osztály.

Ext.define('MyApp.view.main.Main', { extend: 'Ext.container.Container', xtype: 'app-main', controller: 'main', viewModel: { type: 'main' }, layout: { type: 'border' }, items: [{ xtype: 'panel', bind: { title: '{name}' }, region: 'west', html: '<ul>...</ul>', width: 250, split: true, tbar: [{ text: 'Button', handler: 'onClickButton' }] },{ region: 'center', xtype: 'tabpanel', items:[{ title: 'Tab 1', html: '<h2>Content ...</h2>' }] }] });

Ez a View egy Container elemet definiál border layouttal és két régióval ('west', 'center'). A két régió tartalmaz 1-1 panelt (a TabPanel a Panelből származik).
A fenti kódban két beállítást emelnék ki:

Controller config

A Controller kulcsszóval tudjuk megadni a Viewhoz tartozó Controllert. Ezáltal a Controller egy Containere lesz a View-nak, 1-1 kapcsolat áll a két objektum között és a controllerben tudjuk kezelni a View eseményeit.

ViewModel config

A ViewModel kulcsszóval tudjuk megadni a Viewhoz tartozó ViewModelt. A ViewModel egy adatszolgáltató a View és a View gyermek elemei számára. A ViewModel által tartalmazott adatok kötésekkel tudjuk átadni a komponenseknek, amiket azok megjelenítenek, vagy módosítanak.

A fenti példában láthatjuk, hogy a 'west' panel címe a ViewModeltől származik. Ez azt jelenti, hogy, ha változik az adat a ViewModelben, akkor a cím is megváltozik azonnal.

Controller

Nézzük meg a generált MainController.js-t:

Ext.define('MyApp.view.main.MainController', { extend: 'Ext.app.ViewController', requires: [ 'Ext.MessageBox' ], alias: 'controller.main', onClickButton: function () { Ext.Msg.confirm('Confirm', 'Are you sure?', 'onConfirm', this); }, onConfirm: function (choice) { if (choice === 'yes') { // } } });

Ha visszatekintünk a Main.js nevű View-ra, akkor láthatjuk, hogy a ToolBar gombjának configjában megadtunk egy kezelőt a handler kulcsszóval. Ez a kezelő a Controller azonos nevű függvénye. Látható, hogy ez a függvény a Controllerben van definiálva. Ez fog lefutni a gombra történő klikk esetén.
Ebből látszik, hogy a logikát a controllerben tudjuk megvalósítani, és teljes mértékben elkülönül a megjelenítéstől.

ViewModel

Nézzük meg a generált MainModel.js fájlunkat.

Ext.define('MyApp.view.main.MainModel', { extend: 'Ext.app.ViewModel', alias: 'viewmodel.main', data: { name: 'MyApp' } //TODO - add data, formulas and/or methods to support your view });

A ViewModelek az adat objektumokat kezelik. Ezen keresztül tudnak a Viewk a számukra érdekes adatokhoz férni kötések segítségével, így lesznek minden változásról értesítve. A ViewModel a ViewControllerhez hasonlóan a Viewhoz tartozik. A komponens hierarchiának köszönhetően a View gyerekei is hozzá férnek a View ViewModeléhez.

A fenti ViewModelben az az adatokat inline adtuk meg, ebből látszik, hogy ez az adat igazából bárhonnan jöhet. Másik fájlból, vagy akár a szerverről az Ext által biztosított valamelyik proxyn keresztül (AJAX, REST, stb.).

Model és Store

A Modelek és Storeok a az Ext Data Packagenek a részei. Ezek biztosítják az alkalmazás által használt adatokat. Ezek küldik a szervernek az adatokat, ezek kérik le őket, ők kezelnek mindent az adatokkal kapcsolatban.

Model

Az Ext.data.Model reprezentál minden perzisztens adatot az alkalmazásunkban. Minden Model Fieldekből és függvényekből áll, ezekkel tudjuk "modellezni" az adatainkat. A Modeleket többségében a Storeokkal együtt használjuk. A Storeokat pedig "adatkapcsoló" komponensekkel használjuk, mint például Gridek, fák, chartok vagy comboboxok.
Modeleket a következő módon definiálhatunk:

Ext.define('MyApp.model.User', { extend: 'Ext.data.Model', fields: [ {name: 'name', type: 'string'}, {name: 'age', type: 'int'} ] });

A namespaceeknél leírtak alapján létre kell hoznunk egy egy User.js-t az app/model mappában.

Field

A Modelek adatokat írnak le, amik értékeket vagy tulajdonságokat írnak le, ezeket Field-nek nevezzük. A Modelek tudják ezeket a Fieldeket deklarálni a 'fields' kulcsszóval. A Fenti példában két fieldet hoztunk létre. egy String 'name'-et illetve egy integer 'age' field-et.
JavaScriptben mint tudjuk, nincs int típus, de az Ext mégis tesz megszorításokat és ellenőrzi a Field értékét a típusnak megfelelően valamint ha számot kap, de nem egészet, akkor kerekíti is azt. További adattípusokat is találhatunk a dokumentációban.

Habár célszerű megadni típust a Fieldnek, ez nem kötelező. Ha nem adunk meg fieldeket, akkor a Model maga fogja deklarálni őket a kapott értékek alapján. Ha azonban használjuk a field konfikurációt, akkor pár hasznos attributumot adhatunk meg nekik:

Store

A Store rekordok (Model példányok) egy kliens oldali cache-e. A Storeok függvényeket biztosítanak rendezéshez, filterezéshez, lekérdezéshez az általuk tárolt rekordokon.
A következő módon tudunk Storet definiálni:

Ext.define('MyApp.store.User', { extend: 'Ext.data.Store', model: 'MyApp.model.User', data : [ {firstName: 'Seth', age: '34'}, {firstName: 'Scott', age: '72'}, {firstName: 'Gary', age: '19'}, {firstName: 'Capybara', age: '208'} ] });

A modelhez hasonlóan megint létre kell hoznunk a megfelelő User.js fájlt a megadott helyen (app/store).

A Storet át is kell adni az Alkalmazásnak. Ha globálisan elérhetővé szeretnénk csinálni, akkor az Application.js-ben, különben pedig a megfelelő Viewnak/komponensnek.
A Store bekötését a store kulcsszóval tehetjük meg:

stores: [ 'User' ],

A Storeunk esetében is igaz, hogy inline adtuk meg az adatokat. Ezt is meg lehet oldani proxy segítségével, és akkor rögtön a szerverről fogja letölteni a megfelelő adatokat.