Tillbaka till start

flowweaver · Installera

Bädda in Flowweaver

Hur mycket ni behöver installera beror på vad ni vill ha ut, och för de flesta är det mindre än de tror. Sidan går i tre steg: svaren i inkorgen, visaren på er sida, och sist det som en utvecklare behöver. Många kan sluta läsa efter det första.

En sak att veta innan du kopplar in det: det här är byggt för den som redigerar, inte för den som installerar. Det avgör mer av det som följer än något tekniskt val gör.

Varje attribut, egenskap, metod och händelse står i API-referensen.

Steg 1 — vill ni bara få in svaren? Välj e-post

Ett formulär behöver någonstans att lämna svaren. Det stället finns redan hos er, och det är inkorgen.

I editorn låter redaktören guiden sluta i en nod som heter E-postresultat. Mottagare, ämne och brödtext skrivs en gång, med svaren insatta som variabler. När besökaren är framme fyller guiden i dem och lämnar ifrån sig ett färdigt meddelande. Ingen databas, ingen mottagare på en server, ingenting någon behöver bygga åt er.

Biblioteket skickar ingenting självt — och säger det rakt ut i editorn: Producerar e-postdata – skickar inte. Meddelandet lämnas över till sidan, som bestämmer vad som händer med det. Det billigaste svaret är att öppna besökarens eget e-postprogram med allt ifyllt; det gör webbläsaren åt er.

<script>
  const guide = document.querySelector("guide-preview");

  guide.addEventListener("preview-node-changed", () => {
    // null ända tills guiden nått sin e-postnod
    const brev = guide.getOutput();
    if (!brev) return;

    window.location.href =
      "mailto:" + brev.resolved.to +
      "?subject=" + encodeURIComponent(brev.resolved.subject) +
      "&body=" + encodeURIComponent(brev.resolved.body);
  });
</script>

Brödtexten går med som ren text — ett mailto: bär ingen formatering. Vill ni ha den formaterad, eller vill ni skicka från er egen server i stället för från besökarens program, är det samma data ni läser: getOutput() lämnar mottagare, ämne och brödtext färdigt ifyllda.

Exempelsidan för e-postresultat kör en sådan guide och visar meddelandet den producerar, rad för rad. Noden byggs i Advanced-editorn.

På väg, men inte färdigt än: mottagare som lämnar svaret direkt i WordPress, i en fil på ert webbhotell eller som en rad i ett kalkylark. De finns inte i dag. Tills de gör det är e-post vägen som inte kräver att någon installerar något åt er.

Steg 2 — en rad lägger visaren på vilken sida som helst

Visaren är en script-tagg. Den fungerar i ett CMS där man kan klistra in HTML, på en sida i ett publiceringsverktyg, i en fil på webbhotellet. Inget konto, ingen token, inget byggsteg.

<script src="https://flowweaver.se/lib/0.12/flowweaver-viewer.global.js"></script>

Resten är er egen guide. Redaktören exporterar den ur editorn som en fil, ni lägger filen bredvid sidan, och sidan hämtar den:

<link rel="stylesheet" href="https://flowweaver.se/lib/tokens.css">

<guide-preview></guide-preview>

<script src="https://flowweaver.se/lib/0.12/flowweaver-viewer.global.js"></script>

<script>
  fetch("min-guide.json")
    .then((svar) => svar.json())
    .then((guide) => { document.querySelector("guide-preview").graph = guide; });
</script>

Filen är er. En äldre fil öppnas i en nyare visare — migreringarna körs när grafen sätts — så en guide ni exporterade i våras fungerar efter en uppdatering av biblioteket.

Det där är hela installationen för den som inte har en utvecklare. Resten av sidan är för den som har en.

Steg 3 — versionerna, npm och editorn

Flowweaver är två vanliga custom elements — <guide-preview> som kör en färdig guide, och <guide-editor> som redaktören bygger den i. Inget ramverk krävs. Härifrån och ned står det som en utvecklare behöver veta: vilken version ni bör peka på, paketen, och editorn.

Två sätt att hämta biblioteket. Välj det första om du bara vill komma igång; det andra passar när du redan har ett byggsteg och vill versionshantera beroendet.

Visaren är MIT och får köras var som helst. Editorn får provas fritt för utveckling, test och utvärdering. Produktion är all annan användning, oavsett vilken server den körs på, och kräver en licens. Villkoren för den är inte satta än.

Väg 1 — script-tagg

Två filer: bundeln som registrerar elementen, och tokens.css med temavariablerna. Sedan sätter du grafen på elementet. Samma uppsättning som i steg 2, men med grafen skriven för hand i stället för hämtad ur en exporterad fil.

Fungerar överallt, även i CMS:er där man bara kan klistra in en vanlig script-tagg. Filen slutar på .global.js — det är det klassiska bygget.

Adresserna nedan är rullande. /lib/ byggs om från main vid varje ändring, så ni får alltid det senaste — även när det senaste bryter något. Bra för att prova, fel för något ni kör i drift.

I produktion: peka på en linje. 0.12 är varje 0.12.x-släpp. Den bär fram en rättelse till er utan att ni rör något, och aldrig en brytande ändring — en sådan höjer numret. Vill ni att ingenting alls ska röra sig, skriv hela versionen: 0.12.0.

<script src="https://flowweaver.se/lib/0.12/flowweaver-viewer.global.js"></script>

Byt siffran medvetet, och läs CHANGELOG först — den säger vad ni behöver göra när något bryter.

<link rel="stylesheet" href="https://flowweaver.se/lib/tokens.css">

<guide-preview></guide-preview>

<script src="https://flowweaver.se/lib/flowweaver-viewer.global.js"></script>

<script>
  document.querySelector("guide-preview").graph = {
    startNodeId: "q1",
    nodes: [
      { id: "q1", type: "question", position: { x: 0, y: 0 }, data: {
        title: "Har du fyllt 18 år?",
        variableName: "isAdult",
        options: [
          { id: "yes", label: "Ja", value: "yes" },
          { id: "no", label: "Nej", value: "no" },
        ] } },
      { id: "ok", type: "result", position: { x: 400, y: 0 }, data: {
        title: "Du kan gå vidare" } },
    ],
    connections: [
      { id: "c1", from: { nodeId: "q1", portId: "yes" }, to: { nodeId: "ok", portId: "input" } },
    ],
  };
</script>

Ordningen mellan taggarna spelar ingen roll — sätter du grafen innan bundeln hunnit ladda plockas värdet upp när elementet registreras.

Det där är inte ett påhittat exempel — rutan nedan kör precis den koden, med bundeln hämtad från den här sajten.

Laddar …

Som ES-modul

Har du redan ett byggsteg, eller vill slippa ett globalt namn, finns samma bundle utan .global:

<script type="module">
  import "https://flowweaver.se/lib/flowweaver-viewer.js";

  document.querySelector("guide-preview").graph = {
    startNodeId: "q1",
    nodes: [
      { id: "q1", type: "question", position: { x: 0, y: 0 }, data: {
        title: "Har du fyllt 18 år?",
        variableName: "isAdult",
        options: [
          { id: "yes", label: "Ja", value: "yes" },
          { id: "no", label: "Nej", value: "no" },
        ] } },
      { id: "ok", type: "result", position: { x: 400, y: 0 }, data: {
        title: "Du kan gå vidare" } },
    ],
    connections: [
      { id: "c1", from: { nodeId: "q1", portId: "yes" }, to: { nodeId: "ok", portId: "input" } },
    ],
  };
</script>

Filerna följer senaste versionen. Behöver du en låst version, peka på dess mapp — till exempel /lib/0.12/ — eller lägg filerna på din egen sajt.

Väg 2 — npm

Paketet ligger på GitHub Packages. Peka scopet dit och lägg ett token med read:packages i din .npmrc:

@johanfuruskog-create:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=DITT_TOKEN
npm install @johanfuruskog-create/flowweaver-viewer
npm install @johanfuruskog-create/flowweaver-editor

Installera antingen visaren eller editorn — editorn bäddar redan in visaren för sin förhandsvisning, och drar in visarpaketet som beroende för sina typer och tokens.css.

import "@johanfuruskog-create/flowweaver-viewer";
// eller
import "@johanfuruskog-create/flowweaver-editor";

TypeScript-typer följer med. GraphData och de övriga graftyperna återexporteras från båda ingångarna.

Editorn

Samma mönster, men du lyssnar på ändringar och sparar dem själv. feature-level styr hur mycket redaktören ser.

<guide-editor feature-level="advanced"></guide-editor>

<script type="module">
  import "@johanfuruskog-create/flowweaver-editor";

  const editor = document.querySelector("guide-editor");
  editor.graph = existingGraph;
  editor.addEventListener("graph-changed", (event) => save(event.detail));
</script>

basic ger den avskalade editorn, advanced är standardläget med variabler och regler, och service är profilen för e-tjänster. Dolda fält tas aldrig bort ur grafen — en avancerad guide kan öppnas i enkelt läge och sedan i avancerat igen, utan dataförlust.

mode säger vad redaktören får göra — readonly är förvalet, och en okänd inställning stannar där. mode-notice="off" tystar panelens ruta om varför fälten är låsta. Sätt den bara om din egen yta redan säger det, till exempel en rad ovanför editorn som namnger den som håller guiden: annars ser låsta fält trasiga ut i stället för avsiktliga.

Byggt för redaktören, inte för utvecklaren

Tänk på vem som öppnar det här en vanlig tisdag. En regel ändrades i morse, någon väntar på ett svar, och guiden måste vara rätt innan den möter någon som behöver den. Det är inte kunskapen som saknas — de vet precis vad en besökare behöver bli tillfrågad om. Det som saknas är visshet om vad verktyget gör om de rör vid det.

Verktyg för det här jobbet byggs oftast för utvecklaren först, och det är sällan någons fel: de som skriver verktyget förstår modellen bäst, så den kortaste vägen är att exponera den och låta gränssnittet följa med. Det är glasklart för den som byggde det. Alla andra blir i tysthet ombedda att lära sig en datastruktur innan de får ställa en fråga.

Alltså blir man försiktig. Försiktighet ser ut som noggrannhet och kostar som väntetid: ändringen väntar på någon som inte är rädd för systemet, och guiden glider en bit till från reglerna den beskriver. En guide ingen vågar redigera är en guide som blir inaktuell.

Målet är inte att det ska gå att redigera. Det är att det ska kännas tryggt.

Det mesta av det handlar om en enda sak: det ska vara lätt att backa. Pröva, titta på vad det gjorde, ta tillbaka det om det blev fel. Varje ändring går att ångra. Dolda fält överlever ett byte av feature-level, så man förlorar ingenting på att titta. Att bocka av ett språk lämnar översättningarna kvar. Ingenting förstörs, alltså behöver ingenting bekräftas — och det editorn visar är det som sparas, vilket är löftet allt annat vilar på.

Exportera och importera

Verktygsraden skriver ut hela guiden som en JSON-fil och läser in en igen. Den finns av samma skäl som ångra, fast ett nummer större: en redaktör som kan ta en kopia före en stor ändring är en redaktör som gör ändringen.

Värt att visa era redaktörer tidigt, för kopian är deras snarare än er. De kan spara den, lämna den till en kollega, sova på den över en helg, eller ta tillbaka den efter ett experiment som inte ledde någonstans — utan att be någon om en databasåterläsning eller förklara vad som hände. Import kör samma versionsmigreringar som inläsning, så en fil ur en äldre utgåva öppnas i en nyare.

Språk

Tre inställningar, och de rör sig oberoende av varandra. De flesta misstag som görs mot det här biblioteket kommer av att behandla dem som en.

Inställning Vems Sätts med
Verktygets språk — palett, panel, menyer ert editor-locale
Guidens innehåll — frågor, svar, resultat redaktörens och översättarens active-locale, och verktygsraden
Vilka språk som får erbjudas ert declareLocales()
Att översätta innehållet till ett språk får aldrig kräva att verktyget översätts till samma språk. Berättelse 017, regeln de tre inställningarna finns för att hålla

En somalisk guide väntar inte på en somalisk palett — det är vad åtskillnaden köper er.

Svenska och engelska är inbyggda

Varje text paketet levererar — knappar, fältetiketter, valideringsmeddelanden, hela editorn — finns i båda, komplett. Ni registrerar dem inte och ni underhåller dem inte. Ingenting nedan behövs om de två är de språk ni erbjuder.

Deklarera vilka språk som finns

Editorn visar en kryssruta per deklarerat språk, och redaktören bockar för de språk just den guiden ska erbjudas på. Källspråket är alltid ibockat och går inte att bocka av.

import { init } from "@johanfuruskog-create/flowweaver-editor";

init(document.querySelector("guide-editor"), {
  graph,
  mode: "administrator",
  languages: {
    offer:   ["sv", "en", "fi"],  // vad redaktören får bocka i
    source:  "sv",                // vad en NY guide skrivs på
    content: "sv",                // vilket språk editorn öppnar i
    tool:    "sv",                // gränssnittets eget språk
  },
  onSave: (graph) => save(graph),
});

Att utelämna offer är inte samma sak som att inte erbjuda något. Redaktören ser då bara de språk guiden redan bär och kan inte lägga till något — vilket är rätt, för listan är er att bestämma, men det betyder också att ingen kan lägga till ett språk förrän ni sagt vilka som finns.

Ett tredje språk

För ett språk paketet inte levererar: skicka med texterna. Uppdelat efter vem som läser dem — viewer är vad en besökare läser, editor är verktygets egna ord. Ni kan skicka det ena utan det andra — ett paket för besökaren är ofta allt som behövs.

const wiring = init(element, {
  languages: { offer: ["sv", "en", "fi"], source: "sv" },
  packs: {
    fi: {
      viewer: { "nav.next": "Seuraava", "nav.previous": "Edellinen" },
    },
  },
});

wiring.coverage.fi.viewer;   // { filled: 2, total: 164, missing: [...] }

init lämnar tillbaka vad varje paket faktiskt täcker, så ni får veta att ett paket når 2 av 92 när ni lämnar över det i stället för när en besökare möter luckan. localeStrings("sv") ger er paketets egna texter som mall att översätta från.

Hela visaren, i en egen fil

Två nycklar inlinade är så det börjar. Ett språk ni faktiskt erbjuder vill ha alltihop, och det hör hemma i en fil ni kan lämna till en översättare och versionshantera, snarare än mitt i er uppsättningskod.

// locales/fi.js — en fil per språk, i ert eget repo.
export default {
  viewer: {
    "step.number": "Vaihe",
    "step.result": "Tulos",
    "nav.next": "Seuraava",
    "nav.previous": "Edellinen",
    "nav.restart": "Aloita alusta",
    "validation.required": "Kenttä on pakollinen.",
    // … 119 totalt
  },
};
import fi from "./locales/fi.js";

const wiring = init(editor, {
  languages: { offer: ["sv", "en", "fi"], source: "sv" },
  packs: { fi },
});

wiring.coverage.fi.viewer;   // { filled: 164, total: 164, missing: [] }

164 nycklar är hela ytan besökaren möter — knappar, fältetiketter, valideringsmeddelanden, alltihop. Be om listan innan ni börjar:

import { coverageOf } from "@johanfuruskog-create/flowweaver-editor";

coverageOf("fi").viewer.missing;   // de 164 nycklarna, som en lista
coverageOf("fi").editor.missing;   // verktygets egna 1097 — ett annat jobb

Den andra raden är den som är värd att läsa två gånger. Att översätta visaren är 164 texter och ger besökaren hela guiden på sitt språk. Att översätta editorn är 1097 och ger redaktören en finsk palett — värt att göra en dag, aldrig ett villkor för det första. Det är den uppdelningen paketets två halvor finns för att göra tydlig.

Vad en besökare ser när något saknas

Ingenting går sönder och ingen ruta är tom. En saknad text faller tillbaka, i den här ordningen:

det efterfrågade språket
  → dess basspråk        (en-GB faller till en)
  → guidens egen källa   (settings.sourceLocale)
  → engelska
  → vad texten nu har

Notera tredje steget: en finsk läsare av en svensk guide får svenska på det som inte översatts, inte engelska. Källan är den text författaren faktiskt skrev och den enda som garanterat är komplett.

Och det sägs i stället för att döljas. En besökare som bett om ett språk guiden inte har får läsa varför:

Guiden är inte översatt till finska. Den visas därför på svenska.

Notisen dyker upp på de steg där språket faktiskt bytte under dem, och väljer engelska före källan — hela dess syfte är att läsas av någon som inte kunde läsa guiden.

Kontrollera en uppsättning

checkSetup(element) rapporterar vad som saknas och vad det leder till, inklusive språk en guide erbjuds på utan texter bakom sig.

import { checkSetup } from "@johanfuruskog-create/flowweaver-editor";

checkSetup(editor);
// { ok: false, findings: [{ id, severity, what, consequence }] }

Den levande versionen av allt det här finns på språksidan, som kör de riktiga anropen och skriver ut vad de gav.

Det här bygger ni hos er

Flowweaver är frontend. Sex ansvar går ut ur den, och bakom vart och ett står ett system som är ert — det är därför guiderna och svaren aldrig behöver lämna er. Varje ansvar har ett skrivet kontrakt som säger vad ni ska svara på, inte hur ni gör det, och Status säger hur långt vi själva kommit.

Detaljerna, och en guide som heter Så integrerar ni på en förmiddag, står i värdsystemskontraktet.

Ansvar Vad ni bygger Status
Lagring Arbetskopian, de frusna versionerna, pekaren på den publicerade och låset. Sex regler säger vad som får ändras — en version ändras aldrig, gallring är er och numren döps aldrig om — och var det ligger väljer ni. Färdig
Inlämning En väg som tar emot svaren och svarar med ett referensnummer. Guiden bär aldrig adressen — bara ett id som ni översätter på er sida, så en intern adress kan inte finnas i en guides JSON eller i besökarens nätverkstrafik. Färdig
E-post Utskicket är ert, och nyckeln stannar hos er. Besökaren kan också få öppna sitt eget e-postprogram utan någon server alls — men den vägen bifogar inga filer. Delvis
Identitet Vem redaktören är avgör ni. Kontraktet säger bara vad Authorization får vara — ett token ur er inloggning, eller en cookie. Besökarens identitet är ännu bara en skiss. Delvis
Uppslag Fält som söker i era register frågar en BFF hos er, och den avgör vad som får synas. Visaren ställer frågan och ritar svaret; den känner inga register. Färdig
Filer Bilagor och gallringen av dem är era, eftersom det är era personuppgifter som annars ligger kvar. Guiden bär länkar till bilder, aldrig bilderna själva. Delvis

api.flowweaver.se är vår referensimplementation av alla sex. Node och SQLite är val vi gjorde där, och varje del går att byta — ingenting behöver skickas till en central Flowweaver-tjänst för att någon av sömmarna ska fungera.

Kontrakten i sin helhet är filer i repot, en per söm. Repot är privat i dag, så hör av er så visar vi dem — och säg gärna till om ni hellre läser dem som sidor här.

Vad din sida äger

Biblioteket är avsiktligt fritt från sidoeffekter utanför komponenterna. Tre saker gör det alltså inte åt dig:

Ansvar Vad du behöver göra
Lagring Editorn autosparar inte, och påstår det aldrig. Lyssna på graph-changed och spara grafen där du vill ha den. Ctrl+S skickar ett avbrytbart save-request: anropa preventDefault(), spara, och säg vad som hände — annars säger editorn bara att sidan sköter sparningen. Vill du inte hitta på en egen modell finns den skriven: arbetskopia, frusna versioner och en publicerad pekare, enligt lagringssömmen ovan.
Versioner <guide-versions> ritar en guides versioner och skickar en avsikt när någon vill något — den sparar ingenting och bestämmer ingenting. Var versionerna ligger väljer du, och elementet erbjuder åtta handlingar du kan svara på: lista, läs, lägg till, uppdatera, kopiera, byt namn, ta bort, anteckna. Du svarar på dem du har — en värd som följer lagringssömmen ovan svarar på öppna och återställ, och låter publiceringen skapa versionerna. Det finns färdiga exempel att utgå från: versionssidan sparar i webbläsaren, och SiteVision-modulen i repot lägger samma åtta över tre olika lagringar.
Tema tokens.css följer operativsystemets ljusa/mörka läge. Vill du styra det själv sätter du data-theme="dark" eller "light" på <html>. Typsnittet är --fw-font, operativsystemets eget (system-ui) tills du pekar om det på taggarna: guide-editor, guide-preview, guide-versions { --fw-font: …; }. Layouten prövas före varje släpp mot fyra typsnitt av olika bredd och radhöjd, ett av dem serif.

Storlek

Visaren är 53 kB gzip och editorn 127 kB. Bara visaren behöver laddas av slutanvändare — bundlarna är separata just därför.

Höjd

Editorns höjd styrs med CSS-variabler, om standardvärdena inte passar:

<guide-editor
  style="--flowweaver-height: 800px; --flowweaver-min-height: 600px"
></guide-editor>

Vidare

Kom igång med editorn beskriver hur en guide byggs. Startsidan samlar fler exempel, från enkel guide till e-tjänst med beräkningar.