Pääsen työni puolesta tutustumaan myös muiden tekemiin Power Appseihin. Se on todella hyödyllistä. Niistä oppii usein jotain uutta. Vähintään hämmästyy siitä, miten monella tavalla asiat voi tehdä.

Näen tekijöiden ensimmäisiä ja toisia Power Appseja. Olen huomannut tiettyjen tekniikoiden ja outoksien toistuvan niissä. Tällä kertaa käydään hieman läpi niitä.

Haluan korostaa, etteivät nämä ole virheitä tai tyhmyyksiä. Tehdyt ratkaisut toimivat mainiosti. Saman asian voi kuitenkin tehdä suorituskykyisemmin, ylläpidettävämmin, vähemällä koodilla tai muuten vain elegantimmin.

Ja kyllä. Ensimmäisissä omissa Power Appseissani tein asiat juuri näitä samoja erikoisia menetelmiä käyttäen.

Rivin tietojen hakeminen

Haluamme esittää sovelluksessa asiakkaan asiakasnumeron (Accounts-taulusn Account Number -kenttä).

Tämä on todella usein toteutettu seuraavasti

  • suodatetaan (filter) asiakkaiden joukosta oikea rivi (tällä kertaa asiakas nimeltään Timo )
  • otetaan tulosjokosta ensimmäinen (first) rivi.

First(Filter(Accounts, 'Account Name' = "Timo")).'Account Number'

Tästä syntyy itseasiassa kaksi API-kutsua.

Käytännössä tiedämme etsimiämme rivejä olevan tasan yhden. Tällöin suoraviivaisempaa (ja nopeampaa) on hakea rivi LookUp-funktiolla.

LookUp(Accounts, 'Account Name' = "Timo").'Account Number'

Komento muodostaa yhden kutsun tietolähteeseen.

Kun haetaan yhtä riviä, tehdään se (lähes aina) LookUp-funktiolla.

Turhat LookUp:it

Mutta ei niitä mainioita LookUp-komentoja kannata tehdä turhaan.

Meillä on galleriassa lista asiakkaista (Accounts) ja haluamme näyttää listan vierellä valitun asiakkaan asiakasnumeron. Käytämme tähän kaavaa

LookUp(Accounts, 'Account Name' = galAccounts.Selected.'Account Name').'Account Number'

Haemme nyt aivan turhaan LookUp-funktiolla asiakkaan tiedot. Ne on jo haettuna galleriaan. Voimme viitata kaikkiin gallerian rivin tietoihin.

galAccounts.Selected.'Account Number'

Sama toimintamalli näkyy usein muuallakin. Meillä on jo haettuna rivin tiedot. Mutta jostain syystä eri puolella sovellustamme haemme niitä uudelleen.

Syynä on usein se, että galleriassa tai muuttujassa ei jostain syystä olekaan arvoa kaikille rivin kentille. Vaikka pitäisi. Ja päätämme hakea tiedot uudelleen LookUp:lla, jotta saamme puuttuvan arvon käyttöömme.

Mikseivät rivin kaikkien kenttien arvot ole aina käytettävissämme? Syy on useimmiten alustan suorittamassa optimoinnissa. Se tulkitsee, ettemme tarvitse kyseistä kenttää, jonka vuoksi arvoa ei oteta mukaan tietoja haettaessa.

Tällöin kannattaa ylimääräisen LookUp:in sijasta tehdä jotain muuta. Esimerkiksi ottaa seuraava optimointi pois päältä, jolloin tulosjoukossa on aina kaikki kentät mukana.

Mystinen With

Ylimääräisiä LookUp-toimintoja voi karsia helposti myös With-funktiota käyttämällä.

Haluamme näyttää asiakkaan kotikaupungin ja maan tekstikentässä muodossa ”Kaupunki (Maa)”. Helppoa! Muodostetaan merkkijono jossa on

  • asiakkaan kaupunki (haetaan LookUp:lla)
  • merkit ” ( ”
  • asiakkaan maa (haetaan LookUp:lla)
  • merkki ”)”
LookUp(Accounts, 'Account Number' = "500").'Address 1: City' & 
" (" & 
LookUp(Accounts, 'Account Number' = "500").'Address 1: Country/Region' & 
")"

Mutta… Teemme Dataverseen kaksi kyselyä. Ensin haemme kaupungin ja heti perään maan. Tämän voi tehdä fiksummin with-funktiolla. Ideana on että tarvittavat rivit haetaan ensin väliaikaiseen muuttujaan. Jonka jälkeen tätä väliaikaista muuttuja voi hyödyntää toimintoja tehtäessä.

Meidän esimerkissämme siis

  • haetaan asiakkaan tiedot väliaikaiseen muuttujaan (varMyAccount)
  • mudostetaan merkkijono hyödyntäen muuttujaan haetun asiakkaan tietoja
With(
    {varMyAccount:LookUp(Accounts, 'Account Number' = "500")},
    varMyAccount.'Address 1: City' & " (" & varMyAccount.'Address 1: Country/Region' & ")"
)

Näin haemme asiakkaan tiedot tietovarastosta ainoastaan kerran.

Turhat muuttujat

Todella usein sovelluksessa törmää muuttujaan, joka kertoo ollaanko lomakkeella katsomassa, muokkaamassa vai luomassa riviä.

Esimerkiksi avataan lomakenäyttö muokkaustilaan.

ViewForm(SettingForm);
Navigate(SettingFormScreen);
Set(varMode,"View");

Tai siirrytään luomaan uutta riviä.

NewForm(SettingForm);
Navigate(SettingFormScreen);
Set(varMode,"New");

varMode-muuttuja on täysin tarpeeton. Lomakkeen tilan voi tarkistaa suoraan lomakkeelta.

  • SettingForm.Mode = FormMode.View -> Olemme katselemassa
  • SettingForm.Mode = FormMode.Edit -> Olemme muokkaamassa
  • SettingForm.Mode = FormMode.New -> Olemme luomassa uutta

Samaa näkee myös muissa yhteyksissä. Luodaan omia muuttujia, vaikka samaan lopputulokseen pääsisi ilman.

Jokainen uusi muuttuja on paikka inhimillisille virheille.

Globaalit muuttujat

Power Appsissa on kahdenlaisia muuttujia. Globaaleja ja paikallisia muuttujia. Monet käyttävät ainoastaan globaaleja muuttujia.

Set(gblMyName, "Timo")

Globaali muuttuja on käytettävissä sovelluksen kaikilla näytöillä, sovelluksen käynnistyessä (App OnStart), sekä komponenteissa mikäli olet sen sallinut. Niiden käyttö on helppoa, kun ei tarvitse miettiä sen enempää.

Mutta ne ovat suorityskykysyöppöjä. Alusta joutuu kahlaamaan läpi kaikki näytöt muuttujan arvon päivittyessä.

Globaaleja muuttujia ei kannata käyttää, ellei ole pakko. Muutoin käytetään aina paikallisia muuttujia (Context variable).

UpdateContext({locMyName: "Timo"})

Paikallista muuttujaa voi käyttää ainoastaan yhden näytön sisällä. Usein se riittää.

With-funktion sisällä määriteltävää väliaikaista muutujaa voi ajatella kolmantena muuttujatyyppinä. Sitä voi käyttää ainoastaan with-lauseen sisällä.

Ryhmien (group) käyttö

Ryhmät (group) on tapa niputtaa Power Appsissa käytettyjä kontrolleja yhteen. Esimerkiksi tekstikenttä ja siihen liittyvä otsikko.

Periaatteessa näppärää, mutta näitä ryhmiteltyjä kontrolleja on todella tuskaista muokata. Epähuomiossa tulee muokattua ryhmän kaikkia kontrolleja, kun piti muokata vain yhtä. Tai siirrettyä kaikkia, kun piti siirtää vain yhtä.

Ryhmät ovat jäänne ajalta, jolloin mitään fiksumpaa ei ollut tarjolla. Nykyään kannattaa käyttää tähän säilöjä (container).

Niiden kanssa on helpompaa työskennellä. Säilön sisällä olevien kontrollien sijainti on aina suhteessa itse säilön sijaintiin. Uusien säilöjen (Horizontal/Vertical container) avulla voi rakentaa responsiivisia sovelluksia.

Samojen kontrollien toistaminen

Navigaatio on erinomainen esimerki kontrollien turhasta toistamisesta. Tyypillisesti kukin navigaation elementti on oma painike (button).

Jos haluamme pyöristää navigaatioelementtien kulmia tai vaihtaa väriä, tulee muutokset tehdä jokaiselle painikkeelle. Mikäli haluamme uuden kohdan navigaatioon, joudumme lisäämään näytölle uuden painikkeen.

Fiksumpaa onkin toteuttaa navigaatio galleriana. Tämän jälkeen painikkeen muutokset tehdään yhteen gallerian sisällä toistuvaan painikkeeseen. Myös uusien elementtien lisääminen onnistuu helposti.

Mikäli navigaatiota käytetään usealla näytöllä, kannattaa se toteuttaa vielä komponenttina.

Kaikki navigaatioon tehtävät muutokset toteutetaan komponenttiin. Muutokset näkyvät saman tien kaikkialla missä komponenttia käytetään.

Samaa koodia turhaan monessa paikassa

Välillä törmää tilanteisiin, joissa tekijä ei ole ehkä ollut aivan varma miten asiat toimivat. Samoja komentoja on sitten varmuuden vuoksi laitettu vähän joka paikkaan.

Esimerkiksi lista asiakkaista (Accounts).

Kun galleriasta valitaan rivi, siirrytään muokkaamaan sitä (Navigate). Samalla asetetaan lomake muokkaustilaan (EditForm) ja alustetaan se (ResetForm).

EditForm(frmAccount);
ResetForm(frmAccount);
Navigate('Accounts Screen') 

Mikäli näytöltä painetaan uuden asiakkaan luonti-painiketta (new), tehdään sama sillä erotuksella, että lomake asetetaan luontitilaan (NewForm).

ResetForm(frmAccount);
NewForm(frmAccount);
Navigate('Account Form Screen')

Mutta mitä tehdään lomakkeella, kun käyttäjä painaa takaisin-painiketta (back)?

Alustetaan lomake ennen siirtymistä takaisin.

ResetForm(frmAccount);
Navigate('Accounts Screen')

Tämä alustaminen on turhaa, sillä lomake alustetaan asiakkaat-näytöllä aina ennen lomakkeelle siirrtymistä.

Oikeasti lomakkeen alustaminen on turhaa myös siellä. Lomake alustetaan automaattisesti, kun sen tila (mode) asetetaan (NewForm ja EditForm).

Meille riittäisi asiakas-näytöllä komennot (siirryttäessä muokkaamaan):

EditForm(frmAccount); Navigate('Accounts Screen') 

Ja siirryttäessä luomaan uutta riviä:

NewForm(frmAccount); Navigate('Accounts Screen')

Lomakkeen takaisin-painikkeella ei tarvitse tehdä muuta kuin navigoida takaisin.

Näyttöjen väliset riippuvuudet

Näytöiltä ei kannata viitata toisilla näytöillä oleviin kontrolleihin. Tämä mudostaa näyttöjen välille riippuvuuden (dependency) ja vaikeuttaa näin alustan suorittamaa optimointia.

Mutta se voi aiheuttaa myös erikoisia tilanteita. Ohessa esimerkki elävästä elämästä.

Meillä on näyttö, jossa voi ylläpitää linkkejä erilaisiin dokumentteihin. Yksi näistä on sovelluksen käyttöohje. Linkit tallennetaan Dataverseen.

Sovelluksen aloitusnäytöllä on painike, josta käyttöohjeen saa auki.

Download(inpHelpURL.Text)

Painikkeen painamien antaa kuitenkin virheen.

Toisella painalluksella ohje aukeaa. Mitä tässä oikeasti tapahtuu?

Painikkeessa viitataan kenttään, joka on toisella näytöllä. Kentän arvo haetaan Dataversestä. Alusta yrittää epätoivoisesti optiomoida, milloin kentän arvo tulisi hakea. Ettei sitä haeta turhaan. Tässä skenariossa optimointi menee pieleen ja ohjelinkin (inpHelpUrl) arvoa ei ole olemassa kun download-toimintoa jo suoritetaan.

Optimoinnin saa pois päältä asetuksista (Delayed load). Tämän jälkeen toteutus taas toimii.

Toinen vaihtoehto luonnollisesti on hankkiutua eroon näyttöjen välisistä riippuvuuksista.