IMA.js – Načítání a vykreslování dat
Publikováno: 29.4.2020
V předchozím díle jsme si zajistili data pro naši aplikaci a jejich zpracování pomocí modelů. Nyní můžeme začít s jejich zobrazováním.
Před přečtením tohoto dílu si prosím stáhněte zdrojové kódy aplikace z GitHubu. V textu tohoto dílu nebudeme pracovat se všemi potřebnými soubory pro zprovoznení aplikace, ale projdeme pouze ty nejdůležitější pro její fungování.
Router
Celý chod aplikace začíná zde. Pro příchozí HTTP požadavek se IMA.js snaží najít shodnou routu. Každá routa má přiřazený Controller a View, který se má v případě shody načíst a vyrenderovat. Seznam rout se nachází v konfiguračním souboru app/config/routes.js
. Jednotlivé parametry routy si zde nebudeme popisovat, najdete je v dokumentaci. Doporučujeme si však dokumentaci routeru pročíst.
Pro naší aplikaci potřebujeme upravit pouze výchozí routu s názvem home
. Abychom mohli vytvářet krásné SEO URL ve tvaru weather.xyz/ostrava nebo weather.xyz/ceske-budejovice přidáme do path parametr :?location
. Pokud jste si četli dokumentaci, tak už víte, že :
se v routě označuje parametr a ?
nepovinný parametr.
// app/config/routes.jsrouter.add('home','/:?location',HomeController,HomeView).add('filtered','/:filter',HomeController,HomeView).add(RouteNames.ERROR,'/error',ErrorController,ErrorView).add(RouteNames.NOT_FOUND,'/not-found',NotFoundController,NotFoundView);
Controller
Router předává slovo Controlleru, který byl přiřazený k shodné routě. Controller tímto začíná svůj životní cyklus. Pro nás je důležitá metoda load()
, ve které načteme všechna potřebná data. Tedy ne všechna, některá necháme, aby se načetla až na straně klienta.
Než ale začneme s načítáním, potřebujeme Controlleru předat ForecastService a GeoCoderService, které jsme vytvořili v předchozím díle. Uděláme to pomocí DI.
import{Router,CookieStorage}from'@ima/core';importAbstractPageControllerfrom'app/page/AbstractPageController';importForecastServicefrom'app/model/forecast/ForecastService';importGeoCoderServicefrom'app/model/geocoder/GeoCoderService';exportdefaultclassHomeControllerextendsAbstractController{staticget$dependencies(){return[Router,CookieStorage,ForecastService,GeoCoderService,'$Settings.App.defaultLocation'];}constructor(router,cookieStorage,forecastService,geoCoderService,defaultLocation){super();this._router=router;this._cookieStorage=cookieStorage;this._forecastService=forecastService;this._geoCoderService=geoCoderService;this._defaultLocation=defaultLocation;}
Jak vidíte, nepředali jsme si jen zmiňované Services, ale i něco navíc. Konkrétně:
- Router – budeme ho potřebovat pro přesměrování při zadání špatného názvu města.
- CookieStorage – vytáhnutí informací o městě, které si uživatel nastavil (více si o tom povíme v 5. díle).
- Nastavení
defaultLocation
– jak jsme popisovali v úvodu 2. dílu, pokud nebudeme mít žádný uživatelský vstup, použijeme výchozí město. Řetězec$Settings
představuje souborapp/config/settings.js
. Zbytek řetězce je cesta k nastavení.
load(){letgeoCoderPromise=Promise.resolve(this._getDefaultLocation());// zde začneme s výchozím městemconst{ location, lat, lon }=this.params;// parametry u URLif(location){geoCoderPromise=this._geoCoderService.geoCodeMunicipality(this.params.location);// načtení informací o městu podle parametru z URL}elseif(!location&&(typeoflat !== 'undefined'||typeoflon !== 'undefined')){this._router.redirect(this._router.link('home'));// na dotazy se souřadnicemi bez názvy města neodpovídame}geoCoderPromise.then(geoLocation=>{if(location&&(typeoflat==='undefined'||typeoflon==='undefined'||lat !== geoLocation.lat||lon !== geoLocation.lon)){// doplnění nebo oprava souřadnic do URL this._router.redirect(this._router.link('home',{ location,lat: geoLocation.lat,lon: geoLocation.lon}));}returngeoLocation;});// Když známe město, můžeme načíst předpověď.constforecastPromise=geoCoderPromise.then(location=>this._forecastService.getForecast(location.lat,location.lon));return{location: geoCoderPromise,forecast: forecastPromise,forecastDetail: null,// detail předpovědi si necháme k načtení na klienta (ušetříme prostředky a čas na serveru)forecastDetailLoading: true};}
Donačítání dat
Abychom nemuseli stahovat velké množství dat k vygenerování odpovědi ze serveru, můžeme odložit načítání dat na stranu klienta. Na serveru si tak stáhneme jen to nejnutnější k vytvoření smysluplné odpovědi, kterou můžou přečíst i vyhledávače.
K tomuto účelu se používá metoda Controlleru zvaná activate()
. Zavolá se při oživení aplikace nebo aktivaci příslušné routy na straně klienta. Můžeme v ní tak načítat data pro první zobrazení ale i při každém vstupu na stránku.
activate(){const{ location }=this.getState();// získání dat ze stateif(location){this._forecastService.getDetailedForecast(location.lat,location.lon).then(forecastDetail=>this.setState({ forecastDetail,forecastDetailLoading: false}));}else{this.setState({forecastDetailLoading: false});}}
Meta informace
V Controlleru využijeme ještě jednu metodu – setMetaParams()
. Ta nám dovoluje nastavit meta informace do záhlaví stránky. Nastavené informace se potom využívají v DocumentView (komponenta, která zajišťuje render hlavního HTML markupu).
setMetaParams(loadedResources,metaManager,router,dictionary,settings){const{ location, forecast }=loadedResources;if(!location|| !forecast){return;}consttodayForecast=forecast.daily[0];constlocationShortTitle=location.title.split(',')[0];consttitle=`Počasí ${locationShortTitle || location.title} - IMA.js Example`;constdescription=`${todayForecast.localDate}: ${todayForecast.summary}`;consturl=router.getUrl();metaManager.setTitle(title);metaManager.setMetaName('description',description);metaManager.setMetaProperty('og:title',title);metaManager.setMetaProperty('og:description',description);metaManager.setMetaProperty('og:type','website');metaManager.setMetaProperty('og:url',url);}
View
Načtená data jsou předávána jako props speciální React komponentě. Tato komponenta se nazývá View. Od klasické komponenty, kterou si budeme popisovat v následujícím bodě, se nijak zásadně neliší. HomeView se bude starat o zobrazení názvu města a předpovědi počasí. Každý den předpovědi bude renderovaný samostatnou komponentou, kterou pojmenujeme ForecastDay. Musíme také vyřešit stav, kdy změníme město a data se začnou načítat znovu (na straně klienta). Po určitý čas nebudeme mít dostupná žádná data a musíme vykreslit načítání. Práci si ulehčíme použitím předpřipravených komponent z balíčku IMA-UI-atoms.
- Nejprve si tedy nainstalujeme
npm install @ima/plugin-atoms --save
. - V app/build.js přidáme
'@ima/plugin-atoms'
dolet vendors.common
a'./node_modules/@ima/plugin-atoms/dist/*.less',
dolet less
let less = [
'./app/assets/less/app.less',
+ './node_modules/@ima/plugin-atoms/dist/*.less',
...
let vendors = {
common: [
+ '@ima/plugin-atoms',
import{PageContext,AbstractComponent}from'@ima/core';importReact,{Fragment}from'react';import{Loader}from'@ima/plugin-atoms';importForecastDayfrom'app/component/forecastDay/ForecastDay';importForecastDetailfrom'app/component/forecastDetail/ForecastDetail';exportdefaultclassHomeViewextendsextendsAbstractComponent{staticgetcontextType(){returnPageContext;}render(){return(<divclassName="container">{this._renderPlaceAndForecast()}</div>);}_renderPlaceAndForecast(){const{ forecast, location }=this.props;const{ activeDay }=this.state;if(!forecast|| !location){
return<Loader/>;// zde ještě nemáme načtená data, zobrazíme načítání}return(<Fragment><divclassName="location"><h1className="location-title">{location.title}</h1></div><divclassName="forecast-days"><ul>{forecast.daily.map((day,index)=>(<ForecastDaykey={index}forecast={day}place={forecast.place}isActive={index===activeDay}// activeDay máme uložený ve state komponenty (view)onClick={event=>this.onDayClick(event,index)}/>// viz. níže))}</ul></div>{this._renderDetailedForecast()}</Fragment>);}onDayClick(event,index){event.preventDefault();const{ forecast }=this.props;if(forecast.daily[index] !== undefined){this.setState({ activeDay: index});}}}
Donačtená data
Jak už víme, tak detailní předpověď počasí pro jeden den donačítáme na straně klienta. Metoda _renderDetailedForecast()
v našem View bude zobrazovat tato data. Bohužel API, které využíváme vrací detailní předpověď pro všechny dny předpovědi. My si však tato zpracujeme a vykreslíme jen ta, která se týkají zvoleného dne (activeDay
).
_renderDetailedForecast(){const{ forecastDetail, forecastDetailLoading }=this.props;const{ activeDay }=this.state;if(forecastDetailLoading){return<Loader/>;// podobně jako v _renderPlaceAndForecast() zobrazíme Loader dokud nemáme data}return(<divclassName={this.cssClasses('forecast-detail')}>{forecastDetail.filter(day=> day.dayId===activeDay)// vyfiltrujeme si data pro zvolený den.map((day,index)=>(<ForecastDetailkey={activeDay+index}{ ...day}/>))}</div>)}
Komponenty
Stejně tak jako View i komponenty používají React s tím rozdílem, že nerozšiřují React.Component
ale AbstractComponent
z @ima/core
. AbstractComponent poskytuje přístup k utilitám, které jsme si nastavili v app/config/bind.js
– řádek oc.constant('$Utils')
.
Tyto utility jsou dostupné pod this.utils
. Některé, jako například Router, EventBus nebo Dictionary mají speciální metody, které umožňují snadnější použití. Odkazy můžete vytvářet pomocí this.link('route', { param: 'value' })
, vytvářet eventy přes this.fire('eventName', { data })
, překládat texty metodou this.localize('string', { param })
a definovat CSS třídy pomocí this.cssClasses({ classes })
.
Utilita this.cssClasses
slouží k ulehčení definování CSS tříd. Můžete pomocí ní snadněji definovat třídy v závislosti na podmínce nebo nějáké hodnotě. Např.:
this.cssClasses({'forecast-day': true,'forecast-day--active': this.props.isActive})
Argumentem může být i string (např. this.cssClasses('forecast-day')
) a nemusíte tak definovat objekt tříd.
ForecastDay a ForecastDetail
Aby jsme zlepšili čitelnost HomeView oddělili jsme opakované části kódu do komponent ForecastDay a ForecastDetail. Na těchto komponentách je zvláštní pouze to, že místo AbstractComponent rozšiřují AbstractPureComponent
. Jak již název napovídá, jedná se o React Pure Componenty.
Devtooly
Než se rouzloučíme, rádi bychom ještě ukázali jak je možné ladit volání funkcí v IMA.js aplikacích pomocí IMA Devtools, které jsme již zmínili v předchozím díle.
Po nainstalování IMA.js Devtools rozšíření do prohlížeče Google Chrome, je nutné toto rozšíření nejprve aktivovat. To lze provést přepnutím přepínače do správné polohy v popupu, který zobrazíme kliknutím na IMA.js ikonu v adresním řádku prohlížeče. Tento popup navíc zobrazuje, zda daná webová stránka běží na IMA.js nebo ne a jaká je případná aktuální verze, jazyk a environment aplikace.
Po jejich zapnutí je třeba stránku znovu načíst. Po načtení se do Chrome Developer Tools vloží nová záložka s názvem IMA.js, kde se nachází již zmiňované devtooly. Rozhraní těchto nástrojů se skládá ze dvou sloupců a vyhledávácího panelu. V levém sloupci jsou zobrazené chronologicky všechny události, které byly v aplikaci zachyceny. V pravém sloupci záložky Args, Payload a Events představují:
- Argumenty, se kterými byla funkce volána.
- Data, které daná funkce vrátila.
- Pole událostí, zobrazeno v takovém pořadí, v jakém do devtoolů z aplikace příjdou, ze kterých se následně vyextrahuje Payload a Args. V případě např. load metody, která vrací více promisů, se v Events nachází jednotlivé snapshoty výsledků funkce, tak jak se postupně každý promise resolvuje.
Teď se vrátíme zpět k naší aplikaci, kde si můžeme prohlédnout např. jak se vyresolvoval náš dotaz na API. Ten najdeme pod Http
třídou a get
metodou, kterou voláme z našeho Resource přes HTTP Agenta. Po rozkliknutí vidíme v záložce Args
argumenty, které odpovídají souřadnicím, se kterými funkci voláme a v Payload
potom výsledek API volání. Stejným způsobem lze zkontrolovat i jiná volání, případně si je vyfiltrovat pomocí vyhledávání, které příjímá i regulární výrazy.
Ti bystřejší si jistě všimly, u některých funkcí se v levém sloupci vedle jejich názvu zobrazil také tag ve formátu resolved [x]ms
. Ten se zobrazuje pouze u promisů, kde x
odpovídá času, za jak dlouho se daný promise vyresolvoval.
Závěr
V tomto díle jsme v naší ukázkové aplikaci zajistili načítání a zobrazování dat pomocí Controlleru, View a komponent. Po aplikaci CSS (less) stylů naše stránka vypadá jako na přiloženém snímku obrazovky.
V dalším díle se podíváme na jeden detailní příklad komponenty, na které si ukážeme práci s eventy, HOC a Controller Extensions.