Viime viikojen kirjoituksissa olemme rakentaneet Power Appsilla muokattavan dynaamisen taulukon.

  • Osa 1 – Muokattavan taulukon käyttöliittymän toteutus siten, että rivit ja sarakkeet luetaan eri listasta/taulukosta/entiteetistä
  • Osa 2 – Rivien hierarkinen esittäminen, muutosten tallentaminen tehokkaasti
  • Osa 3 – Hierarkian järjestäminen, uusien rivien lisääminen

Ratkaisussa syötetään lukuja (projektin varauksia) hierarkiseen rakenteeseen. Tarkastellaan sarjan viimeisessä osassa miten rakennamme lukujen syöttöön liittyvää logiikkaa.

Hierarkian ylempien tasojen automaattinen päivittäminen

Käyttäjä on alunperin ennustanut suunnitteluvaiheen (Design) vievän tammikuussa 10 työpäivää. Myöhemmin hän on luonut suunnittelutyölle osa-kokonaisuuksia (Design phase 1-3) sekä arvioinut kunkin osuuden työmäärät.

Entä kun käyttäjä jälkikäteen nostaa viimeisen suunnitteluvaiheen työmäärää? Tällöin alivaiheiden kokonaistyömäärä nousee suuremmaksi kuin ennustettu kokonaisuus.

Pitäisikö silloin laskea automaattisesti suunnitteluvaiheen (Design) kokonaistyömäärä uudelleen?

Tehdään niin.

Mutta ensin meidän tulee ymmärtää, miten olemme dynaamisen taulukkomme rakentaneet.

Projektit esitetään galleriassa (Gallery 1), jonka sisällä on kutakin projektin vaihetta kohden varauksien esittämiseen oma galleriansa (Gallery 1.1 -1.4).

Haluaisimme laskea varauksia esittävistä gallerioista tiettyjen solujen arvot yhteen.

Emme kuitenkaan pääse käsiksi gallerian tekstikenttien arvoihin, jolloin meidän tulee suorittaa laskenta käyttäen gallerian takana olevaa kokoelmaa.

Mutta…

Olemme tyhmyyksissämme rakentaneet kokokonaisuuden siten, että alkuperäiset varaukset (Collection 1) ja niihin tehdyt muutokset (Collection 2) ovat omissa kokoelmissaan

Täten meillä ei ole olemassa yhtä kokoelmaa, jonka sisältö vastaa sitä mitä loppukäyttäjä näkee galleriassa.

Muutetaan sovelluksen logiikkaa siten, että myös muutokset tallennetaan kokoelmaan, jota käytetään gallerioiden arvojen näyttämiseen. Aivan kuten teimme rivejä järjestäessämme (lue juttu).

Näin meillä on yksi kokoelma, jonka sisältö vastaa käyttöliittymässä näkemäämme. Voimme turvallisin mielin käyttää sitä kaikessa laskennassa.

Muutoksia joutuu tekemään useaan paikkaan. Tässä muutama malliksi.

Kokoelman (colAllocations) alustus.

ClearCollect(
     colAllocations,
     AddColumns(
        Filter('Project allocations',
               FirstDayOfPeriod >= varFilterDayStart And    
               FirstDayOfPeriod <= varFilterDayEnd), 
       "updated", false, "newitem", false)
);

Yksittäisen varauksen muutos (OnChange):

With(
     {varUpdate: LookUp(
                 colAllocations,
                 FirstDayOfPeriod = ThisItem.periodStartDate And     
                 ProjectPhaseID = Value(lblProjectPhaseID.Text)
                 )
      },
      If(IsBlank(varUpdate),
          Patch(colAllocations, 
                Defaults(colAllocations),
                {
                  FirstDayOfPeriod: ThisItem.periodStartDate,
                  ProjectPhaseID: Value(lblProjectPhaseID.Text),
                  Allocation: Value(Self.Text), 
                  newitem: true
                 }
          ),
          Patch(colAllocations, 
                varUpdate, 
                {
                  Allocation: Value(Self.Text), 
                  updated: true
                }
          ) 
      )
)

Ylemmän tason arvon automaattinen päivitys – Ensimmäinen yritys

Takaisin itse aiheeseen. Rakennetaan varauskenttään seuraavanlainen logiikka. Arvo (default) on kenttään syötetty arvo (kuvassa 10). Mikäli riviin liittyy alirivejä, tarkistetaan onko alirivien summa suurempi kuin syötetty arvo. Mikäli on, käytetään tätä alirivien summaa.

Kaavana tämä näyttää jo hieman monimutkaiselta.

Default: With(
     {
      varSum: Sum(
                Filter(colAllocations,
                      FirstDayOfPeriod = ThisItem.periodStartDate  
                      And ProjectPhaseID in 
                         (Filter(colProjectPhases,
                                 ParentID =     
                                 Value(lblProjectPhaseID.Text)           
                                       ).ID
                                )
                          ),
                 Allocation
                 )
       },
       With(
            {
              varCurrentValue: LookUp(colAllocations,
                               FirstDayOfPeriod =          
                               ThisItem.periodStartDate And    
                               ProjectPhaseID =   
                               Value(lblProjectPhaseID.Text)
                               ).Allocation
            },
            If(!IsBlank(varSum),
               Max(varSum,varCurrentValue),
               varCurrentValue)
           )
)

Älä anna With-lauseiden hämätä! Tässä ei tehdä oikeasti muuta kuin

  • haetaan rivin alirivien arvojen summa varSum-muutujaan
  • haetaan kentän nykyinen arvo varCurrentValue-muuttujaan
  • päätetään kumpaa niistä käytetään

Nokkelimmat huomasikin heti, mikä tässä on pielessä.

Tämän jälkeen gallerissa esitetään aina oikea arvo. Se ei kuitenkaan tallennu gallerian taustalla olevaan kokoelmaan. Kaikki tallennus tehdään kentän OnChange-tapahtumassa, joka ei tässä tilanteessa laukea.

Default-ominaisuuden sisällä emme voi päivittää kokoelmaa.

Täytyy keksiä jotain muuta.

Ylemmän tason arvon automaattinen päivitys – Toinen yritys

Lähestytään ongelmaa toisesta suunnasta. Tarkistetaan aina rivin arvon muuttuessa, onko muutoksella vaikutusta hierarkiassa ylemmälle tasolle.

Eli Allocation-kentän OnChange-tapahtumassa

  • Tallennetaan muuttunut arvo ColAllocations-kokoelmaan (kuten tehtiin alunperinkin)
  • Haetaan kaikkien saman hierarkiatason rivien varaukset ja lasketaan ne yhteen. Lopputulos tallennetaan varSum-muuttujaan.
  • Haetaan muuttujaan (varParent) hierarkiassa ylempänä oleva ”summa” rivi.
  • Mikäli muokattavan tason rivien summa on suurempi kuin haettu hierarkian ylemmän tason arvo, päivitetään ylempi taso vastaamaan summaa.

Palautetaan ensin kentän Default-kaava alkuperäiseksi.

Ja lisätään OnChange-tapahtuman loppuun (muutoksen kokoelmaan tallentamisen jälkeen) seuraava kaava.

With({
      varSum: Sum(
               Filter(colAllocations,
                      FirstDayOfPeriod = ThisItem.periodStartDate   
                      And ProjectPhaseID in (Filter(
                          colProjectPhases,
                          ParentID =   
                          Value(lblProjectPhaseParentID.Text)
                          ).ID)
                       ),
                   Allocation
                   ),
       varParent: LookUp(colAllocations,
                     FirstDayOfPeriod = ThisItem.periodStartDate    
                     And ProjectPhaseID = 
                       LookUp(
                          colProjectPhases,
                          ID =Value(lblProjectPhaseParentID.Text)
                          ).ID
                      )
       },
       If(varSum > varParent.Allocation,
         If(IsBlank(varParent),
           Patch(colAllocations,
                 Defaults(colAllocations),
                 {
                   FirstDayOfPeriod: ThisItem.periodStartDate,
                   ProjectPhaseID:      
                     Value(lblProjectPhaseParentID.Text),
                   Allocation: varSum,
                   newitem: true
                  }
           ),
           Patch(colAllocations,
                 varParent,
                 {
                    Allocation: varSum,
                    updated: true
                 }
           )
        )
      )
);

Jälleen With-lauseen käyttö saattaa sekoittaa. Mitään ihmeellistä tässä ei taaskaan tehdä.

  • Lasketaan muuttujaan (varSum) samalla tasolla olevien rivien varausten summa (esimerkiksi kaikkien Design-vaiheen alla olevien rivien)
  • Haetaan hierarkiassa seuraavalla tasolla oleva rivi talteen muttuujaan (varParent)
  • Mikäli rivien summa on suurempi kuin ylemmällä tasolla oleva luku, päivitetään ylemmällä tasolla oleva luku vastaamaan rivien summaa. Ja tämä tehdään kokoelmaan.

Ja nyt toimii!

Vai toimiiko?

Päivitämme hierarkian seuraavan tason lukua. Mutta tämä voi tietenkin johtaa siihen, että myös sitä seuraavaa tasoa tulee päivittää!

Tämä tehdään vastaavalla tavalla samaisen OnChange-tapahtuman sisällä.

Jätetään se kuitenkin kotitehtäväksi.

Päivitetäänkö ylempää tasoa automaattisesti?

Entäpä jos käyttäjä ei halua lukujen päivittyvän automaattisesti? Vaihtoehtoja on monia.

Päivitetäänkö luvut aina?

Tämä on triviaalia toteuttaa. Käyttäjä määrittelee ”jossain”, päivitetäänkö lukuja automaattisesti vai ei. Esimerkiksi toggle-kontrollin avulla näytöllä. Valinnan perusteella määräytyy ajetaanko OnChange-tapahtuman loppuosa vai ei.

Päivitetäänkö tällä kertaa?

Käyttäjälle voidaan tarjota mahdollisuus päättää jokaisen muokkauksen yhteydessä, ylikirjoitetaanko hierarkian ylempien tasojen luvut.

Tällöin OnChange-tapahtumassa ei tehdä päivitystä heti, vaan avataan dialogi, jossa käyttäjä saa päättää päivitetäänkö luku.

If(varSum > varParent.Allocation,
   UpdateContext(
         {
           varShowDialog: true,
           varSumValue: varSum,
           varParentItem: varParent,
           varProjectPhaseId:  
             Value(lblProjectPhaseParentID.Text),
           varFirstDayOfPeriod: ThisItem.periodStartDate
         }
   )
)

Varsinainen tallennus siirretään dialogin ”Yes” -painikkeen taakse.

Dialogi rakennetaan tekstikentistä, laatikoista ja painikkeista. Sen näkyvyyttää sädellään varShowDialog -muuttujalla.

Päivitetään aina tälle sarakkeelle / rivlle

Entä jos haluaisimme tarjota käyttäjälle vaihtoehdot

  • ”Kyllä, päivitä automaattisesti summa ylätasolle, aina kun muokkaan tätä saraketta”
  • ”Kyllä, päivitä automaattisesti summa ylätasolle, aina kun muokkaan tätä riviä”

Ei mitenkään vaikeaa. Luodaan uusi kokoelma, jossa ylläpidetään tietoa siitä mitä rivejä / sarakkeita käyttäjä haluaa automaattisesti päivitettävän ja mitä ei.

Ja otetaan tämä kokoelma mukaan dialogi + tallennus kokonaisuuteen.

Tämäkin on kotitehtävä.

Entä kun ylätason lukuja pienennetään?

Mitä jos käyttäjä käy jälkikäteen muokkaamassa toteutusvaiheen (Implementation) varauksia pienemmäksi?

Meillä on jälleen tilanne, jossa luvut eivät enää täsmää. Pitäisikö käyttäjää varoittaa? Vai automaattisesti pienentää toteutuksen alla olevia lukuja? Millä logiikalla?

Huomaamme että erilaisia käyttötilanteita onkin monia ja kaikkien niiden tulisi toimia järkeväläl tavalla.

Yhteenveto

Olemme saaneet valmiiksi yhdelaisen toteutuksen muokattavasta dynaamisesta taulukosta, joka tallentaa syötetyt tiedot kahteen eri listaan/tauluun/entiteettiin.

Onko ratkaisu no-codea / low-codea?

Ei.

Olisiko tämä kannattanut toteuttaa ”oikealla” koodilla?

Ehkä. Ei kuitenkaan välttämättä heti ensimmäisenä vaihtoehtona.

Oliko toteutus vaikeaa?

Ei. Mutta vaikeampaa on toteuttaa lopuksi kaikki ne pienet yksityiskohdat, jotka tekevät ratkaisusta oikeasti hyvän. Milloin lukuja päivitetään automaattisesti ja millä logiikalla.