top of page
blog_zahlavi_pozadi_00.jpg

Blog

  • Obrázek autoraJan Hora

Lekce 12 - webová aplikace pro sledování výdajů

Aktualizováno: 3. 11. 2020

V minulé lekci jsme využili Tabulky pro plánování budoucích výdajů, dneska si zkusíme probrat možnosti jak si zapisovat skutečné výdaje. Ke stejnému účelu existuje spousta hotových aplikací a možná některou i používáte. My ale chceme využít možností, které nám dává Google Workspace (dříve G Suite).

Jak výdaje zadávat pomocí Google Workspace?


Přímo zapisovat do tabulky

Hodí se pro případy, že si večer v klidu sednete s notebookem a všechny výdaje zadáte. Zadávat výdaje průběžně z mobilu nebude úplně ono.

Využít Google formuláře

Jednoduché řešení, data se ukládají přímo do tabulky, dobře použitelné i na mobilu. Trochu nevýhoda je, že bychom se nenaučili nic z Apps Scriptu. Navíc typy nákupů bychom museli měnit přímo ve formuláři.

Vlastní webová aplikace

Bingo. Nejsložitější řešení, ale řekneme si spoustu nových věcí o Apps Scriptu. A nebojte se, nebude to zas tak složité.

Jak na to?

Potřebujeme tabulku na ukládání dat. Tady nás nic nepřekvapí, podobně jako v minulé lekci bude na jednom listu seznam všech výdajů a na druhém listu si budeme udržovat seznam kategorií. V dalších lekcích můžeme doplnit součty výdajů po kategoriích a měsících, případně i nějaké grafy. Dál potřebujeme, aby výsledná aplikace obsahovala formulář pro zadávání výdajů. Nebudeme to příliš komplikovat, vystačíme si se třemi položkami, kategorie do které výdaj spadá, částka a popis o jaký výdaj šlo. Pokud něco málo tušíte o HTML a CSS, bude se vám to v této části hodit. Ve finále by měl formulář naší aplikace vypadat zhruba takto.


No a nakonec budeme potřebovat několik funkcí, které to všechno oživí, odešlou data z formuláře na server, uloží data do tabulky, zobrazí jestli se data uložila a podobně. Už víte, že skript je možné přibalit k tabulce, nebo mít jako samostatný soubor na Disku. V tomto případě si skript přibalíme k tabulce, není důvod ho mít oddělený, když bude sloužit jen konkrétní tabulce.

Tabulka

Otevřete si moji tabulku, je sdílená jen pro čtení. Přes Soubor - Uložit kopii si uložte její kopii na svůj Disk. Ve své kopii tabulky si zvolte Nástroje - Editor skriptu abyste se dostali ke skriptům, které jsou k tabulce přibaleny.

Formulář

Než se pustíme do práce, musíme si vysvětlit několik věcí. V předchozích lekcích jsme vždy psali skripty v Apps Scriptu. Skripty běžely na serverech Google a většinou v našich příkladech upravovaly obsah nějaké naší tabulky.

Náš formulář bude taky potřebovat skripty, nepůjde už ale o Apps Script, ale JavaScript. Tyto skripty běží v prohlížeči uživatele, který si aplikaci spustil. Zdá se to jako drobnost, protože v prvních lekcích jsme si řekli, že Apps Script je vlastně verze JavaScriptu. Ale rozdíl je zásadní, JavaScript běžící v prohlížeči nemůže využívat žádnou z knihoven pro práci s tabulkami, ale může třeba přečíst obsah formuláře a odeslat ho na server. A naopak Apps Script na serveru může samozřejmě zapisovat do tabulky, ale nemá žádnou možnost zjistit, co jste zadali do formuláře. Takže aby formulář fungoval a data se zapsala do naší tabulky, budeme potřebovat oba druhy skriptů. Když se podíváte do svého editoru skriptů, vidíte v levém sloupci 4 "soubory". server.gs - obsahuje všechny skripty napsané v Apps Scriptu a běžící na serveru. formular.html - je HTML kód naší aplikace. javascript.html - obsahuje funkce (psané v JavaScriptu) pro obsluhu formuláře. css.html - jsou styly upravující vzhled naší aplikace. Aby náš formulář získal nějakou konkrétní podobu a byl spustitelný v prohlížeči, musíme ho publikovat jako webovou aplikaci. To jsme v minulých lekcích zatím nedělali. Ale není to nic složitého. Nejprve potřebujeme speciální funkci, která se musí jmenovat doGet a která se postará o zobrazení naší aplikace. Tu najdete hned jako první v souboru server.gs, vypadá takto. function doGet() { var template = HtmlService.createTemplateFromFile('formular'); var page = template.evaluate() .setTitle('Výdaje') .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL); return page; } Na prvním řádku vidíte použití knihovny HtmlService, kterou jsme ještě v předchozích lekcích nepoužili. První řádek vyrobí šablonu ze souboru formular. Je to náš soubor formular.html, v parametru metody se uvádí jen samotný název. Zbytek funkce převede šablonu do tvaru potřebného pro výstup, nastaví titulek záložky prohlížeče, nastaví způsob vložení do stránky a výsledek se odešle do prohlížeče uživatele, který si naši aplikaci spustí. Ještě drobnost, při načítání souboru formular.html se do něj zároveň zahrnou i naše funkce ze souboru javascript.html a styly pro úpravu vzhledu formuláře v souboru css.html. Postarají se o to řádky v souboru formular.html, které vypadají takto:

<?!= require('javascript') ?> <?!= require('css') ?> Oba výrazy zavolají naši funkci require, která je v souboru server.gs a vypadá takto. function require(nazev){ return HtmlService.createHtmlOutputFromFile(nazev).getContent(); } Zase využívá knihovnu HtmlService a prostě načte soubor daného názvu (opět se neuvádí přípona) a vrátí ho ve formě textu. V souboru formular.html je ještě jedno volání funkce, které ve formuláři naplní seznam kategorií. <select id="kategorie"> <?!= vypis_kategorie() ?> </select> První a poslední řádek je normální HTML kód pro select a prostřední řádek zavolá serverovou funkci vypis_kategorie, která seznam kategorií načte z tabulky a sestaví z nich prvky selectu. Funkce vypadá takto:

function vypis_kategorie(){ var kat; var html = '<option value="">Vyberte</option>'; var data = SpreadsheetApp.getActive().getRangeByName('kategorie').getValues(); for(var i=0; i<data.length;i++){ kat = data[i][0]; if(kat != ''){ html += '<option value="' + kat + '">' + kat + '</option>'; } } return html } Vidíte, že je docela krátká. Nejprve si připravíme do proměnné html první položku seznamu, která bude mít text Vyberte. V dalším kroku si načteme všechna data z pojmenovaného rozsahu kategorie. Jak v tabulce vytvořit pojmenovaný rozsah jsme si ukazovali v minulé lekci, v mojí tabulce je na listu Kategorie. Pak už jen v cyklu procházíme všechna data a pokud je položka vyplněná (rozsah může být delší než kolik kategorií zadáme) sestavíme z ní další položku selectu. Pozor na to, že položky pole data nejsou přímo jednotlivé hodnoty z buněk, ale opět pole, v tomto případě o jednom prvku, protože rozsah obsahuje jen jeden sloupec. Proto konkrétní položku z pole data získáme takto kat = data[i][0]; Funkci doGet jsme si vysvětlili, ještě se podíváme na soubor css.html ve kterém jsou styly pro náš formulář. Pokud o stylech nic netušíte, pak asi bude stačit, když si řekneme, že pomocí nich se upravuje vzhled našeho formuláře, velikost a typ písma, barva textu, šířky jednotlivých prvků a podobně. A samozřejmě nejen pro náš formulář, stejnými styly se určuje vzhled každé webové stránky. Styly pro náš formulář jsou přizpůsobené tomu, že výslednou aplikaci chceme používat na telefonu a to v pozici na výšku. Pokud otočíte telefon na šířku, nebo si formulář spustíte na počítači, bude vypadat o poznání hůře. Pokud bychom plánovali formulář používat na telefonu i na počítači, pak bychom si s našimi styly museli dát mnohem víc práce. Nebo bychom mohli využít některou z knihoven, které mají podobné věci tak říkajíc v popisu práce. Jednou z často používaných je třeba Bootstrap. Tím jsme si vysvětlili tu část, která se odehraje, pokud si výslednou aplikaci vy nebo někdo jiný spustí. Zavolá se funkce doGet, ta sestaví výslednou podobu naší aplikace, doplní do ní funkce napsané v JavaScriptu a výsledek se zobrazí v prohlížeči. Teď si zkusíme vysvětlit co se bude dít, pokud uživatel v aplikaci klikne na tlačítko Ulož. Když se podíváte do souboru formular.html pak HTML kód tlačítka vypadá takto. <button onclick="uloz()">Ulož</button> Atribut onclick určuje jméno funkce, která se spustí v případě, že uživatel na tlačítko klikne myší, nebo se ho dotkne na mobilu. Po funkci uloz() tedy budeme chtít aby vzala data z formuláře a postarala se nějak o jejich uložení do naší tabulky. Funkci najdete v souboru javascript.html a vypadá takto function uloz(){ var kategorie = document.getElementById("kategorie").value; var castka = document.getElementById("castka").value; var poznamka = document.getElementById("poznamka").value; if(kategorie == '' || castka == ''){ return; } var data = [kategorie, castka, poznamka]; google.script.run .withSuccessHandler(hotovo) .withFailureHandler(chyba) .uloz_data(data); } První 3 řádky vyhledají podle id jednotlivé prvky našeho formuláře, tedy kategorii, částku a poznámku. Dál otestujeme jestli uživatel vybral kategorii a zadal částku, pokud ne funkce končí. V reálné aplikaci by bylo dobré uživatele upozornit nějakou hláškou, co zapomněl vyplnit. Ale píšeme aplikaci sami pro sebe, takže tohle můžeme vynechat. No a vyplnění poznámky nekontrolujeme, chceme aby bylo možné položku uložit i bez poznámky. Pak si z údajů z formuláře vytvoříme pole a názvem data, které budeme odesílat na server. Na zbytku funkce zavoláme spuštění funkce na serveru, která nám data uloží do tabulky. Základní podoba volání by byla google.script.run.uloz_data(data); čili spusť na serveru funkci uloz_data a předej jí jako parametr tyto údaje. V téhle podobě by funkce fungovala, ale nedozvěděli bychom se, že už skončila. My budeme chtít, aby se formulář po úspěšném uložení dat vynuloval, abychom mohli zadat další položku. No a pokud by se data neuložila, budeme chtít zobrazit nějaké upozornění, že došlo k problému. Proto máme do volání doplněný řádek

.withSuccessHandler(hotovo) který určuje, že při úspěšném ukončení se zavolá funkce s názvem hotovo. A analogicky řádek

.withFailureHandler(chyba) určuje jméno funkce, která se zavolá v případě, že spuštění funkce uloz_data skončí chybou. Ještě si ukážeme obě zmíněné funkce. Funkce hotovo, vypadá takto function hotovo(){ document.getElementById("kategorie").value = ''; document.getElementById("castka").value = ''; document.getElementById("poznamka").value = ''; var el = document.getElementById("hlaska_ok"); el.innerHTML = 'Uloženo'; setTimeout(function(){el.innerHTLM = '';}, 2000); } Na prvních třech řádcích vynulujeme náš formulář, abychom mohli zadávat další položku. Dál budeme chtít zobrazit nějakou hlášku, že uložení proběhlo v pořádku. K tomu máme v našem souboru formular.html připravený element DIV, který má id hlaska_ok. Řádek var el = document.getElementById("hlaska_ok"); nám tento element najde a řádek el.innerHTML = 'Uloženo'; do něj uloží text Uloženo. Text o úspěšném uložení nechceme mít zobrazený trvale, po několika vteřinách chceme text opět smazat. K tomu nám poslouží metoda setTimeout, která v JavaScriptu umožní provést nějakou akci s definovaným zpožděním. Prvním parametrem je funkce, který se má provést, druhý parametr je čas zpožděním v milisekundách. Takže náš řádek setTimeout(function(){el.innerHTML = '';}, 2000); počká 2 vteřiny a pak text v divu smaže, tím hláška zmizí. No a funkce chyba vypadá takto function chyba(err){ var el = document.getElementById("hlaska_ko"); el.innerHTML = err; } Stejně jako funkce hotovo najde podle id příslušný DIV a nastaví mu text chyby, který došel ze serveru. Tentokrát necháme chybu zobrazenou trvale a pomocí stylů máme barvu textu nastavenou na červenou, aby bylo jasné, že došlo k nějakému problému. Teď se podíváme na funkci, která by nám data měla uložit do naší tabulky. Je v souboru server.gs a vypadá takto function uloz_data(data){ var sheet = SpreadsheetApp.getActive().getSheetByName('Výdaje') data.unshift(new Date()) var uloz = [data] var radek = sheet.getLastRow() + 1 var range = sheet.getRange(radek, 1, 1, data.length) range.setValues(uloz) } Řádek var sheet = SpreadsheetApp.getActive().getSheetByName('Výdaje') nám získá list Výdaje z aktivní tabulky. Aktivní tabulka je samozřejmě ta, ke které je skript přibalen. Další řádek data.unshift(new Date()) nám na začátek pole, které došlo z formuláře, a které obsahuje položky [kategorie, castka, poznamka] doplní další prvek a uloží do něj aktuální datum a čas. Budeme tak mít v tabulce přehled o tom, kdy jsme který výdaj zadávali. Na dalším řádku si pole data vložíme do jiného pole.

var uloz = [data] Už jsme na to narazili v minulých lekcích, při čtení dat z tabulky, získáme pole, jehož prvky jsou opět pole a při zápisu logicky musíme mít data připravena ve stejném tvaru. Další řádek var radek = sheet.getLastRow() + 1 nám získá číslo prvního volného řádku na našem listu. Na dalším řádku vybereme oblast do které budeme data vkládat.

var range = sheet.getRange(radek, 1, 1, data.length) Už dříve jsme se setkali s tím, že vybraná oblast musí mít stejné “rozměry” jako vkládaná data, jinak zápis skončí chybou. Vidíte, že zde vybíráme oblast, která začíná na řádku, jehož hodnotu máme v proměnné radek, oblast začíná ve sloupci 1 ( sloupec A), počet řádků vybrané oblasti je 1 a počet sloupců je daný délkou pole data, tedy 4 (3 hodnoty z formuláře a ve funkci doplněné datum). Pak už jen data vložíme do vybrané oblasti range.setValues(uloz) Teď si zkusíme skript publikovat, abychom si mohli prohlédnout výsledek. V menu zvolte Publikovat - Zprovoznit jako webovou aplikaci. Objeví se menu, ve kterém vybereme způsob, jak budeme aplikaci publikovat.


Možná uvidíte tento panel česky, mě se momentálně zobrazuje anglicky a sem tam nějaký výraz česky. Při publikování je potřeba vybrat verzi projektu (Project version). Při prvním publikování máte na výběr pouze Nové, při dalším publikování uvidíte v seznamu všechny předtím publikované verze a jako poslední položku Nové. Pokud při publikaci vždy vyberete položku Nové, budou vám postupně vznikat nové a nové verze aplikace. Kdykoliv se tak můžeme vrátit a publikovat starší fungující verzi, pokud se v nové verzi objeví problém. Dál musíme vybrat volbu Execute the app as, čili česky Spustit aplikaci jako. Na výběr máte dvě možnosti Me (Já) - v závorce je e-mail uživatele, který je vlastníkem skriptu. User accessing the web app (Uživatel užívající aplikaci)

První volba znamená, že skript poběží pod vaším účtem a bude přistupovat k vašim tabulkám, kalendářům, nebo souborům na Disku, odesílat e-maily vaším jménem a podobně. A to i v případě, že aplikaci bude používat jiný uživatel. Druhá volba znamená, že skript bude přistupovat k datům uživatele, který si aplikaci spustí. Spustíte-li ji vy bude přistupovat k vašim datům, pokud si ji spustí váš kolega, bude aplikace přistupovat k jeho Disku, Gmailu atd. Další volba, kterou musíme zvolit je Who has access to the app, neboli česky, Kdo může aplikaci spustit. Pokud máte Gmail účet, máte na výběr 3 možnosti. Only myself (Pouze já) - aplikaci můžete spustit jen přihlášeni ke svému účtu, nikdo jiný. Přesněji řečeno k účtu pro který skript autorizujete, viz. dále. Anyone (Kdokoliv) - aplikaci může spustit kdokoliv, musí ale být přihlášený k nějakému Google účtu. Anyone even anonymous (Kdokoliv včetně nepřihlášených) - aplikaci může spustit kdokoliv a nemusí být přihlášený ke Google účtu, stačí když bude znát adresu naší aplikace. Ale samozřejmě přihlášený být může, ničemu to nevadí. Pokud skript publikujete na firemním účtu, máte ještě možnost publikovat aplikaci tak, aby ji mohli spustit pouze účty ve vaší firmě. Já mám v nabídce Anyone within AppSatori, vy tam budete mít název vaší firmy.

Kombinací těchto dvou voleb můžete dosáhnout takového chování aplikace jaké potřebujete. Prozatím to nebudeme komplikovat a aplikaci budete používat jen vy sami. Takže první volbu nastavte na Me (Já) a druhou na Only myself (Pouze já) a klikněte na Deploy (Publikovat). Výsledná aplikace pak bude přistupovat k vašim datům a pro spuštění budete muset být přihlášený ke svému účtu. Ve chvíli kdy kliknete na tlačítko Deploy, vyvolá se tento dialog.



Skripty by měly číst seznam kategorií a ukládat výdaje do vaší tabulky, ale nemůžou to dělat bez vašeho svolení, musíte jim to nejprve povolit, skript autorizovat. O autorizaci jsme si už povídali v Lekci 2 případně si oživte jak probíhá. Při publikování webové aplikace je postup úplně stejný. A jak už jsme zmiňovali dříve, pokud například povolíte skriptu zapisovat do tabulek a později skript upravíte, že kromě do tabulky bude vaše výdaje zapisovat i do kalendáře, bude skript nutné znovu autorizovat, protože rozsah potřebných práv se zvětšil. Po skončení autorizace a publikování zůstává otevřený panel v této podobě.


V políčku Current web app URL je adresa vaší aplikace, tu si zkopírujte a otevřete v novém panelu. Adresa aplikace je pořád stejná i když publikujete nové verze. Takto spustíte publikovanou verzi aplikace, pokud v kódu provedete nějaké změny v aplikaci se neprojeví dokud nepublikujete novou verzi. Na panelu je pod URL aplikace ještě text Test web app for your latest code. Klik na odkaz latest code spustí aplikaci s aktuální verzí kódu. Můžete tedy kód průběžně upravovat, hned testovat jak se změny projeví a nezdržovat se publikací. Původní publikovaná verze funguje beze změn i když provádíte změny kódu. Změny se projeví až při publikování nové verze. Novou verzi pak publikujete až ve chvíli, kdy máte otestováno, že vše funguje jak má. Tak a jak vypadá naše aplikace? Na počítači to bude docela hrůza, ale styly jsme připravovali pro mobil, takže když si okno prohlížeče zmenšíme aby odpovídalo mobilu na výšku bude aplikace vypadat celkem dobře.




Pokud si vše zkoušíte pod Gmail účtem, doplní vám Google nad aplikaci šedý pruh s upozorněním, že aplikaci nevytvořil Google, vypadá nějak takhle.

Na firemním účtu se tento šedý pruh nedoplňuje. Jinak zdá se všechno funguje, seznam kategorií odpovídá tomu jaké kategorie máme v tabulce. Do políčka částka je možné zadat pouze číslo, takže by se nám nemělo stát, že jako výdaj zadáme text, který nám později bude v tabulce zlobit. Do pole Popis lze naopak zadat cokoliv. Vyzkoušejte si, jestli lze vkládat nové výdaje a jestli se vám objeví ve vaší tabulce. Ještě si řekneme něco k hledání chyb. V dřívějších lekcích jsme si ukazovali jak lze funkce v Apps Scriptu ručně spouštět, krokovat, prohlížet si mezivýsledky a podobně. Teď jsme ve složitější situaci, protože část skriptů nám běží na serveru a část lokálně v prohlížeči. A v obou skriptech samozřejmě můžeme udělat chybu. Naštěstí máme jak na serveru tak v prohlížeči možnost chyby odhalit. Na serveru k tomu máme k dispozici příkaz console. Pokud v Apps Scriptu napíšete console a doplníte tečku, nabídnou se vám jednotlivé metody. Podrobný popis by asi vydal na samostatnou lekci, tak jen zmíním, že si takto můžete zapisovat hodnoty jednotlivých proměnných nebo vypisovat informace o chybách. Všechny údaje zapsané do console pak naleznete v hlavním panelu Apps Scriptu na adrese https://script.google.com/ kde máte kompletní přehled o všech svých Apps Script projektech. Druhou možností jak se dostat na údaje konkrétního projektu je vybrat v editoru skriptů volbu Zobrazit - Stackdriver logging. Objeví se vám panel s odkazem na hlavní panel Apps Scriptu přímo na konkrétní projekt.


A co chyby JavaScriptu v prohlížeči? Tam máme opět k dispozici opět příkaz console, ale popsat všechny možnosti by opět vydalo na jednu nebo více lekcí, orientačně si je můžete prohlédnout třeba zde.

My si zatím jen řekneme jak si v prohlížeči zobrazit místo kam příkaz console údaje vypisuje. Tady záleží na tom, který používáte prohlížeč. V Chromu a Firefoxu si tento panel můžete zobrazit klávesovou zkratkou CTRL-SHIFT-I, v prohlížeči Edge klávesou F12. Případně se dá i vyhledat přes nabídku, hledejte položku Vývojářské nástroje. Každý prohlížeč má tento panel trochu jiný a jinak pojmenované jednotlivé položky. V Chromu tak hledejte v panelu položku Console, Firefox používá počeštěné Konzole a Edge Konzola. U všech prohlížečů si panel můžete zobrazit buď jako postranní, nebo ve spodní části stránky. Takto například vypadá obsah konzole u těchto tří prohlížečů v případě, že do prohlížeče zadáme adresu https://www.google.com/

Nejprve Chrome

Takto vypadá konzole ve Firefoxu

A nakonec Edge

Vidíte, že každá z konzolí vypadá jinak a i vypisuje pro stejnou webovou adresu podstatně jiné informace. V konzoli se objevují i hlášení o chybách, které ve svých JavaScriptových funkcích uděláte. Pokud například v poslední funkci hotovo() na řádku 31, který vypadá takto setTimeout(function(){el.innerHTML = '';}, 2000); smažete poslední závorku, takže řádek bude vypadat takto:

setTimeout(function(){el.innerHTML = '';}, 2000; Chrome vám po startu aplikace vypíše v konzoli hlášení, které vypadá takto.

Vidíte, že chyba celkem jasně informuje o chybějící pravé závorce na řádku 31. A ještě jedna ukázka chyby, upravte si řádek 31 aby vypadal takto setTimeout(function(){el.innerHTLM = '';}, 2000); Na první pohled si možná žádné chyby nevšimnete, ale místo správného innerHTML je na řádku innerHTLM. Když si pak zkusíte zadat nějaký výdaj, tak se objeví hláška Uloženo, ale už nezmizí, právě kvůli našemu překlepu. Ale v konzoli se žádná chybová hláška nevypíše. Podobné chyby se proto zpravidla hledají mnohem hůř.

Pokud máte jakékoliv dotazy, obraťte se na nás na ahoj@appsatori.eu :)

bottom of page