Terug naar het overzicht

Hallo wereld: OpenIntegration-tutorial

Onze tutorial voor iedereen die een manier wil vinden om eigen integraties op hun OpenPaper te laten zien.

Met deze korte tutorial willen we jullie laten zien hoe eenvoudig het is om een zelfstandige OpenIntegration-extensie te programmeren, om deze vervolgens in de paperlesspaper-app als integratie-plugin te gebruiken.

Alles wat je hiervoor nodig hebtzijn wat kennis van HTML/CSS en het werken met Docker, evenals een server of een cloudinstantie bij een aanbieder van jouw keuze. Belangrijk is alleen dat de server via een domein en met een geldig SSL-certificaat vanaf het internet bereikbaar is. Voor echte tests raden we natuurlijk onze OpenPaper7 of OpenPaperL aan.

Nou dan, laten we beginnen!

Bildschirmfoto 2026-04-27 um 09

Hier dus onze basisstructuur. We hebben een Dockerfile nodig om onze integratie later in een Docker-container te verpakken. Omdat het voor een OpenIntegration noodzakelijk is om HTML-bestanden en config.json via een webserver aan de paperlesspaper-app terug te geven, hebben we gekozen voor NGINX als webserver — daarom ook de nginx.conf. De door ons gemaakte Docker-image zal dus gebaseerd zijn op nginx:alpine.

In de /src map bevinden zich de twee minimaal noodzakelijke bestanden voor een OpenIntegration: config.json en index.html

In de config.json kunnen de metadata voor jullie OpenIntegration worden opgegeven: naam, beschrijving en versienummer. Daarnaast worden hier ook de instellingen geconfigureerd die de gebruiker van jullie integratie later zelf kan instellen. In ons voorbeeld is dat slechts een tekstveld voor het invoeren van een naam.

Hier onze

json
{
  "name": "This one is a basic Hello-World example, which greets you with your name",
  "version": "1.0.0",
  "description": "A simple plugin that displays a greeting message based on the user's name.",
  "nativeSettings": {
    "orientation": "portrait"
  },
  "formSchema": {
    "type": "object",
    "properties": {
      "name": {
        "type": "string",
        "description": "The name of the user to greet.",
        "default": "OpenPaper-Lover"
      }
    }
  },
  "renderPage": "./index.html"
}

Onder nativeSettings kunnen jullie instellingen voor het fotolijstje doorgeven, bijvoorbeeld dat jullie integratie alleen in portretmodus draait. De formSchema-gedeelte definieert ons tekstinvoerveld. Belangrijk is de opgave van de renderPage dat is het relatieve pad binnen jullie serverdomein waar het bestand ligt dat de afbeelding voor het fotolijstje teruggeeft. Wij geven ons hier tevreden met een eenvoudig tekstinvoerveld met de naam: "name". Wie meer wil weten welke mogelijkheden er voor invoervelden zijn kan hier de typedefinitie van de interface bekijken.

Voor onze index.html hebben we alleen het volgende HTML-basisskelet nodig:

html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Openintegration: Hello World</title>
  </head>
  <body>
    <div id="website-has-loading-element" class="hidden-marker"></div>
    <main class="screen">
      <section>
        <h2 class="subtitle">Hello</h2>
        <h1 class="title" id="name">World</h1>
        <h4 id="clock">00:00</h4>
      </section>
    </main>
  </body>
</html>

Hier gebeurt niet veel, behalve dat Hello World wordt weergegeven. De <div id="website-has-loading-element" class="hidden-marker"></div> geeft onze rendering-server aan dat de webpagina pas volledig is geladen wanneer er een div met id="website-has-loaded" op de pagina te vinden is.

Als jullie asynchrone oproepen of grote afbeeldingen in jullie integratie gebruiken, kan dit nodig zijn. Onze rendering-server stopt anders na een paar seconden zelfstandig en rendert alleen wat tot dan toe geladen is. Jullie kunnen alles eens in de browser bekijken, dan weten jullie ongeveer hoe het er uiteindelijk op jullie OpenPaper uit zou zien.

Het geheel zou nu ongeveer zo moeten uitzien:

Das erste html wird auf dem Rahmen angezeigtdesign-htmlDas erste html wird auf dem Rahmen angezeigt

Omdat we het iets mooier willen maken, moeten we in de HTML-header wat CSS toevoegen:

html
<style>
      :root {
        color-scheme: light dark;
      }
      * {
        box-sizing: border-box;
      }

      html,
      body {
        width: 100%;
        height: 100%;
        margin: 0;
        padding: 0;
        font-family:
          ui-sans-serif,
          system-ui,
          -apple-system,
          BlinkMacSystemFont,
          "Segoe UI",
          sans-serif;
        background: #fff;
        color: #000;
      }

      .screen {
        width: 100vw;
        height: 100vh;
        padding: 1.3em;
        display: flex;
        align-items: center;
        justify-content: center;
      }

      .title {
        font-size: clamp(1rem, 1.4vw + 0.8rem, 2rem);
        font-weight: 600;
        text-align: center;
        margin: 0 0 0.2rem 0;
      }

      .subtitle {
        font-size: clamp(0.9rem, 0.9vw + 0.6rem, 1.3rem);
        opacity: 0.75;
        text-align: center;
        margin: 0 0 0.2rem 0;
      }

      #clock {
        font-size: clamp(0.5rem, 0.5vw + 0.2rem, 0.8rem);
        opacity: 0.25;
        text-align: center;
        margin: 5rem 0 0.2rem 0;
      }

      .hidden-marker {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip: rect(0, 0, 0, 0);
        white-space: nowrap;
      }
    </style>

Al beter, toch?

Jetzt mit Formatierungformatted-design-1Jetzt mit Formatierung

Alles wat we nu nog nodig hebben is wat JavaScript. We willen namelijk Hello "Naam uit de instellingen" tonen, en niet alleen "World". Daarnaast voegen we nog even het tijdstip van het renderen toe; dat is handig om te zien of de afbeelding na wijzigingen in de code daadwerkelijk al is bijgewerkt. Het volgende codeblok hebben jullie dus nodig aan het einde (voor de sluitende body-tag) van je bronbestand:

html
<script>
    // live clock
    const clockElement = document.getElementById("clock");
    const now = new Date();
    const hh = String(now.getHours()).padStart(2, "0");
    const mm = String(now.getMinutes()).padStart(2, "0");
    clockElement.textContent = `${hh}:${mm}`;

    // mark page loaded
    const markLoaded = function () {
      const markerLoaded = document.createElement("div");
      markerLoaded.id = "website-has-loaded";
      document.body.appendChild(markerLoaded);
    };


    // Handling Settings Page Updates
    window.addEventListener("message", (event) => {
      const data = event.data;
      const payload = data.data || {};
      if (data.type === "INIT" && data.cmd === "message") {
        if (payload.meta?.pluginSettings?.name) {
          document.getElementById("name").textContent =
            payload.meta?.pluginSettings?.name;
        }
        markLoaded();
      }
    });
  </script>

Zoals in onze documentatie beschreven, voegen we een eventlistener toe aan de pagina, die luistert naar het message-event. Omdat er hier verschillende berichten tussen app en integratie worden uitgewisseld, zijn we alleen geïnteresseerd in het bericht wanneer dit de type="INIT" en cmd=="message" heeft. Het vooraf door ons in de config.json aangemaakte veld voor de naam (name) vinden we in het ontvangen JS-object onder payload.meta?.pluginSettings?.name; De daarin aanwezige string schrijven we nu in onze HTML-kop met de ID name.

Om de rendering-engine te laten weten dat we klaar zijn met het laden en tonen van gegevens, wordt de functie markLoaded aangeroepen. Deze voegt aan het einde van het document een div met de id website-had-loaded toe. Klaar!

Als je je integratie nu op een Docker-host beschikbaar wilt maken, kun je gewoon het volgende Dockerfile gebruiken:

dockerfile
FROM nginx:alpine
# Remove default nginx config and content
RUN rm /etc/nginx/conf.d/default.conf && \
    rm -rf /usr/share/nginx/html/*
# Copy optimized nginx config
COPY nginx.conf /etc/nginx/nginx.conf
# Copy static files
COPY src/ /usr/share/nginx/html/
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Zoals je ziet kopiëren we een nginx.conf in het Dockerfile. Deze is ook vrij simpel. Hier moet alleen op worden gelet dat de CORS-headers correct zijn ingesteld:

plaintext
worker_processes auto;
events {
    worker_connections 1024;
}
http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;
    sendfile on;
    keepalive_timeout 65;
    server {
        listen 80;
        server_name _;
        add_header Access-Control-Allow-Origin "*" always;
        add_header Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE, OPTIONS" always;
        add_header Access-Control-Allow-Headers "Authorization, Content-Type, Accept, Origin, X-Requested-With" always;
        root /usr/share/nginx/html;
        index index.html;
        location / {
            if ($request_method = OPTIONS) {
                return 204;
            }
            try_files $uri $uri/ =404;
        }
    }
}

De Access-Control-Allow-Origin moet zo ingesteld zijn dat in ieder geval web.paperlesspaper.de, api.paperlesspaper.de, capacitor://localhost (voor iOS) en http://localhost (voor Android) toegestaan zijn. We staan in het voorbeeld uit eenvoud alle URL's toe.


Hoe voeg je je integratie nu toe in de paperlesspaper-app? Heel eenvoudig:

  • Je gaat in de app naar de bibliotheek en klikt op "Nieuwe afbeelding"
  • Uit de lijst kies je "Integratie-plugin"
Bildschirmfoto 2026-05-04 um 11

In de verschijnende instellingenpagina moet je nu een link naar je config-manifest (config.json ) invoeren.

Voor ons voorbeelddomein helloworld.paperlesspaper.de zou dit zijn:

Bildschirmfoto 2026-05-04 um 12

Na het invoeren van de integratieconfiguratie-URL en een klik op "Laad" verschijnt het invoerveld name. Hier kun je nu de gewenste naam invoeren, die vervolgens op het OpenPaper wordt weergegeven.

Na een klik op "Verder" en het selecteren van het gewenste frame in de volgende dialoog is je integratie actief. De gewenste weergave zal na het volgende verversingsinterval op het OpenPaper worden getoond.

Dat is alles! De complete broncode vind je in de Openintegration-HelloWorld-GitHub-repository.

Veel plezier ermee! Vragen en opmerkingen graag naar support@paperlesspaper.de