Skripty

Vlastní JavaScript funkce rozšiřují formulář tam, kde pravidla nestačí. Skripty musí povolit správce tenantu.

Skript je JavaScript, který běží v prohlížeči každého uživatele formuláře s jeho oprávněními a není izolovaný. Kdo smí měnit konfiguraci formuláře, může tak spustit libovolný kód. Používejte skripty jen tam, kde pravidla a výrazy nestačí, a kód pečlivě kontrolujte.

Kdy použít skript

Skript je poslední možnost. Zkuste nejdřív tyto nástroje, protože jsou bezpečnější, nevyžadují povolení tenantem a návrhář je dokáže zkontrolovat:

Skript se hodí pro složitou logiku (cykly, více podmínek, práce s poli a objekty), pro hlášky s vlastním textem podle stavu formuláře a pro kontroly, které nejdou zapsat podmínkami.

Politika skriptů (povolení tenantem)

Skripty jsou ve výchozím stavu vypnuté. Zapíná je správce tenantu jednou pro všechny weby (tenantová Storage Entity FormCraft365_ScriptsPolicy = allow; starší klíč SpfxFormCustomizer_ScriptPolicy = enabled se čte jen tehdy, když nový klíč neexistuje, a deny v novém klíči má vždy přednost). Chybějící nebo neznámá hodnota znamená vypnuto. Podrobně: Politika skriptů.

Formulář otevřený návrhářem na tenantu bez povolených skriptů: informační pruh o vypnutých skriptech v horní části formuláře

Editor skriptů

Bez licence Pro je editor jen pro čtení: nahoře je vysvětlení se štítkem Pro, existující skripty jdou zobrazit, ale ne měnit, a nejdou přidávat funkce. Skripty běží dál (rozhoduje jen politika skriptů). Viz Licencování.

V návrháři otevřete záložku Skripty. Obsahuje tři části a nápovědu s přehledem ctx:

Tlačítkem Otestovat syntaxi zkontrolujete, že kód jde přeložit. Kód se píše jako tělo funkce, ne jako deklarace: do kódu nepište function nazev() {…} kolem něj. Tělo běží v přísném režimu a jako async, takže můžete použít await a return. Jediný parametr je ctx.

Editor skriptů: pole onFormLoad a onBeforeSave, seznam vlastních funkcí a nápověda k ctx s tlačítkem Otestovat syntaxi

Skripty při načtení a před uložením

onFormLoad

Spustí se jednou po načtení formuláře (a znovu po změně režimu formuláře nebo konfigurace pravidel). Hodí se pro uvítací hlášku, předvyplnění hodnot nebo počáteční skrývání polí. Skript se nečeká, formulář se vykreslí hned. Návratová hodnota se ignoruje.

onBeforeSave

Spustí se při Uložit po validaci polí a po pravidlech onSave. Výsledek rozhoduje o uložení:

if (!ctx.getValue("Cena")) return "Před uložením vyplňte cenu.";
const celkem = Number(ctx.getValue("Cena")) * Number(ctx.getValue("Kusy") || 1);
if (celkem > 100000 && !ctx.getValue("Schvalovatel")) {
  return "Objednávky nad 100 000 Kč musí mít schvalovatele.";
}

Pojmenované funkce

Funkce definujete v editoru a voláte je akcí callFunction v pravidle (cíl $script) nebo akcí callFunction tlačítka. Pravidlo v návrháři vybírá z názvů definovaných funkcí; odkaz na neexistující funkci kontrola konfigurace odmítne. Návratová hodnota funkce se ignoruje. Funkce volaná z pravidla always se spustí, když podmínky začnou platit nebo se změní jejich hodnoty, nejvýše 10krát (ochrana proti smyčce).

{
  "scripts": {
    "functions": [
      {
        "id": "fn-dph",
        "name": "prepocitejDph",
        "description": "Dopočítá cenu s DPH",
        "code": "const bez = Number(ctx.getValue('CenaBezDph')) || 0;\nctx.setValue('CenaSDph', Math.round(bez * 1.21 * 100) / 100);"
      }
    ]
  }
}

Fáze init a user: kdy je formulář „změněný“

Skript běží ve dvou fázích a podle toho se chová ctx.setValue:

FázeKdyVýchozí shouldDirty u ctx.setValue
initonFormLoad, funkce volané z pravidel onLoad a první vyhodnocení pravidel při otevřenífalse: formulář se nepovažuje za změněný
useronBeforeSave, funkce z pravidel onSave, tlačítka a pozdější změny polítrue: formulář je změněný

Volání z pravidel a tlačítek

Asynchronní kód, chyby a ladění

Ukázky skriptů

Uvítací hláška podle režimu (onFormLoad)

if (ctx.formMode === "new") {
  ctx.notify("Vítejte, " + (ctx.currentUser?.title || "") + ". Vyplňte prosím všechna povinná pole.", "info");
}

Předvyplnění hodnoty při nové položce (onFormLoad)

if (ctx.formMode === "new" && !ctx.getValue("Oddeleni")) {
  ctx.setValue("Oddeleni", "Obchod");   // fáze init: formulář zůstane nezměněný
}

Skrytí sekce podle skupiny (onFormLoad)

const groups = (ctx.currentUser?.groups || []).map(g => g.toLowerCase());
if (groups.indexOf("schvalovatele") < 0) {
  ctx.setVisible("section:sec_schvaleni", false);
}

Přepočet po kliknutí na tlačítko (pojmenovaná funkce)

const bez = Number(ctx.getValue("CenaBezDph")) || 0;
ctx.setValue("CenaSDph", Math.round(bez * 1.21 * 100) / 100);
ctx.notify("Cena s DPH byla přepočítána.", "success");

Validace před uložením (onBeforeSave)

const od = ctx.getValue("Od");
const doo = ctx.getValue("Do");
if (od && doo && String(doo) < String(od)) {
  return "Datum „Do“ nesmí být před datem „Od“.";
}

Bezpečnostní upozornění

Skript má stejná práva jako přihlášený uživatel, může číst a měnit data, volat SharePoint a další služby. Povolení skriptů platí pro celý tenant.

API kontextu ctx

Každý skript dostane objekt ctx. Členy typu „jen pro čtení“ nelze měnit. Cíl target u funkcí setVisible a setReadOnly je interní název pole, nebo section:<id sekce>, nebo tab:<id záložky>. Stavy nastavené skriptem se vrátí do výchozího stavu po změně režimu formuláře nebo konfigurace pravidel.

ctx.formMode

Typ: "new" | "edit" | "display" (jen pro čtení). Aktuální režim formuláře: nová položka, úprava, zobrazení.

if (ctx.formMode === "display") return;

ctx.itemId

Typ: number | undefined (jen pro čtení). ID upravované položky. U nové položky (která ještě nebyla uložena) je undefined.

if (ctx.itemId) console.log("Upravuje se položka", ctx.itemId);

ctx.locale

Typ: string | undefined (jen pro čtení). Kód aktuálně zvoleného jazyka formuláře (cs, en, de, sk). Při přepnutí jazyka se hodnota aktualizuje.

ctx.notify(ctx.locale === "en" ? "Saved draft" : "Koncept uložen", "info");

ctx.currentUser

Typ: { id?, email?, loginName?, title?, groups? } | undefined (jen pro čtení). Přihlášený uživatel: ID, e-mail, přihlašovací jméno, zobrazované jméno a názvy SharePoint skupin, ve kterých je členem (groups).

const jeSchvalovatel = (ctx.currentUser?.groups || []).indexOf("Schvalovatelé") >= 0;

ctx.getValue

Typ: (fieldName: string) => unknown. Vrátí aktuální hodnotu pole podle interního názvu. Typ hodnoty závisí na poli: text, číslo, ano/ne, datum jako text, u osob, vyhledávání a spravovaných metadat objekt nebo pole objektů. Neexistující pole vrátí undefined.

const cena = Number(ctx.getValue("Cena")) || 0;

ctx.getAllValues

Typ: () => Record<string, unknown>. Vrátí kopii všech aktuálních hodnot formuláře (změna kopie formulář neovlivní).

const vse = ctx.getAllValues();
console.log(Object.keys(vse).length + " polí");

ctx.setValue

Typ: (fieldName: string, value: unknown, options?: { shouldDirty?: boolean; shouldValidate?: boolean }) => void. Nastaví hodnotu pole. shouldValidate je ve výchozím stavu true (pole se hned zvaliduje). shouldDirty závisí na fázi: v init false, v user true, v režimu zobrazení vždy false. Hodnotu zadejte v tvaru, který pole očekává (číslo, text, objekt s id a title pro osobu nebo vyhledávání).

ctx.setValue("Stav", "Schváleno");
ctx.setValue("Poznamka", "", { shouldDirty: true });

ctx.setVisible

Typ: (target: string, visible: boolean) => void. Zobrazí nebo skryje pole, sekci (section:id) či záložku (tab:id). Skript přepíše stav z pravidel pro daný cíl.

ctx.setVisible("tab:tab_priloha", false);

ctx.setRequired

Typ: (fieldName: string, required: boolean) => void. Nastaví povinnost pole (jen pole, ne sekci či záložku).

ctx.setRequired("DuvodZamitnuti", ctx.getValue("Stav") === "Zamítnuto");

ctx.setReadOnly

Typ: (target: string, readOnly: boolean) => void. Zamkne nebo odemkne pole, sekci (section:id) či záložku (tab:id). V režimu zobrazení zůstanou všechna pole zamčená.

ctx.setReadOnly("section:sec_cena", true);

ctx.setCollapsed

Typ: (sectionId: string, collapsed: boolean) => void. Sbalí nebo rozbalí sekci. Id můžete zadat s předponou section: i bez ní. Funguje u sekcí nastavených jako sbalitelné; uživatel ji může potom ručně přepnout.

ctx.setCollapsed("sec_prilohy", true);

ctx.notify

Typ: (message: string, intent?: "info" | "success" | "warning" | "error") => void. Zobrazí zprávu v horní části formuláře. Výchozí typ je info. Zpráva je jedna: nová nahradí předchozí a uživatel ji může zavřít.

ctx.notify("Pozor, termín je v minulosti.", "warning");