Productbiografie ‐ Liam - Liamvanbart1/Framez GitHub Wiki

Productbiografie - Liam

Leerdoelen

  • Ik wil nog meer leren over CSS.

Ik heb de basis aan het begin van de opleiding geleerd, maar heb daar te lang op vertrouwd. Ik merk tijdens de minor dat er nog veel dingen zijn die ik niet weet en die vaak handiger/beter zijn dan de oplossingen die ik bedenk.

  • Ik wil graag meer leren over back-end.

Tijdens de API zijn we hier al een klein beetje mee bezig geweest door NodeJS te gebruiken en dit vond ik erg interessant. Ik hoop hier tijdens de meesterproef meer over te kunnen leren.

  • Ik wil tijdens de meesterproef graag taakgerichter worden en mijn taken op tijd afmaken.

Ik merk dat ik vaak goede ideeën heb, maar deze door tijdgebrek uiteindelijk niet kan realiseren.

Week 1

Aan het begin van deze week zijn wij met het team naar Framer Framed gegaan om meer informatie over de opdracht te krijgen. Voorafgaand aan de afspraak hebben we met het team een vragenlijst opgesteld. Wij waren als tweede team aan de beurt, dus de opdrachtgever had al een vrij vast verhaal dat hij aan ons vertelde. We kunnen kiezen uit twee opdrachten, namelijk:

  • Opdracht 1: Ontwerp en bouw een ‘mirror’ voor de huidige WordPress website van Framer Framed voor een specifieke doelgroep met specifieke toegankelijkheidseisen.

  • Opdracht 2: Ontsluiting van het digitale archief van Framer Framed voor een specifieke doelgroep met specifieke eisen op het gebied van toegankelijkheid. Ontwerp en bouw een nieuwe interface voor het digitale archief van Framer Framed opgeslagen en georganiseerd in de database van Bibliograph.

Ik wist eigenlijk al meteen dat opdracht 1 niet mogelijk zou zijn voor ons aangezien de meesterproef bedoeld was een afsluitende opdracht voor alles wat we deze periode hebben geleerd. Het probleem hierbij is dat we niks met Wordpress hebben gedaan en het uitvogelen van de Wordpress Api en de ingewikkelde achterkant van de website zou te veel tijd kosten. Daarom was het eigenlijk al vrij snel duidelijk dat we ons moeten focussen op opdracht 2.

Het probleem tijdens de debrief was echter dat ik niet de kans kreeg om aan de opdrachtgever uit te leggen waarom opdracht 1 niet haalbaar zou zijn. Omdat hij vond dat één van de twee teams opdracht 1 moest uitwerken, werd daar helaas geen ruimte voor gegeven. Hierdoor kwamen we een beetje vast te zitten en konden wij ook niet verder.

Na overleg met Declan op woensdag hadden we besloten toch voor opdracht 1 te gaan. Hij gaf aan dat we niet dwars moesten gaan liggen tegenover de opdrachtgever, maar dat we een duidelijk voorstel moesten schrijven waarin we uitlegden waarom opdracht 2 de betere optie zou zijn — en dit goed moesten uitwerken.

Uiteindelijk hebben wij besloten om een nieuwe ontsluiting te maken van het digitale archief van Framer Framed waarbij wij de focus leggen op toegankelijkheid voor de volgende doelgroepen:

  • Toegankelijk voor Mensen met een visuele beperking (slechtziend of blind)
  • Toegankelijk voor gebruikers van screenreaders of spraaknavigatie
  • Toegankelijk voor gebruikers zonder muis

Hierbij maken we het visueel wel aantrekkelijk maar proberen we het zo toegankelijk mogelijk te maken. We zitten helaas met wat beperkingen vanuit de database omdat hier bijvoorbeeld geen Alt teksten bij de afbeeldingen staan.

Na het bepalen van de opdracht zijn we bezig gegaan met de het maken van lo-fi wireframes en een sitemap. IMG_6873

Scherm­afbeelding 2025-05-27 om 13 41 15 Scherm­afbeelding 2025-05-27 om 13 41 09 Scherm­afbeelding 2025-05-27 om 13 42 25 Scherm­afbeelding 2025-05-27 om 13 42 45

Uiteindelijk hebben wij gekozen voor het laatste wireframe. Daarnaast hebben we ook de codeconventies geformuleerd.

Week 2

Deze week begon op maandag, toen wij op bezoek gingen bij de opdrachtgever. Wij hadden ons hier goed op voorbereid, omdat we de opdrachtgever moesten overtuigen. We zijn met beide teams tegelijkertijd naar Framer Framed gegaan en hebben duidelijk uitgelegd dat opdracht 1 niet voldoende was voor de meesterproef, en dat we daarom beiden voor opdracht 2 moesten gaan.

De opdrachtgever baalde hiervan, omdat hem niet was verteld dat WordPress geen optie was. Hij gaf aan dat hij behoorlijk wat tijd had gestoken in het formuleren van de opdracht, en het is dan ook logisch dat hij teleurgesteld was. Later tijdens de meeting sloot de developer aan die ons gaat ondersteunen tijdens de meesterproef hierbij hebben wat meer duidelijkheid kunnen krijgen over de opzet van de database en de mogelijkheden qua endpoints. Al met al was dit een goed gesprek en was het ook fijn dat we eindelijk door konden gaan met ontwerpen/plannen.

Na het overleg met de opdrachtgever ben ik begonnen met één van mijn leerdoelen namelijk meer designen ik heb ervoor gekozen om het design voor onze app te maken in figma. Ik heb de huidige stijl van Biblio-graph genomen als inspiratie.

Scherm­afbeelding 2025-05-27 om 14 27 27 Scherm­afbeelding 2025-05-27 om 14 27 13 Scherm­afbeelding 2025-05-27 om 14 37 28 Scherm­afbeelding 2025-05-27 om 14 37 58

Ik heb de designs tijdens de feedbacksessie besproken met Sanne. Hij gaf aan dat we beter moesten nadenken over de doelgroep waarvoor we ontwerpen — namelijk mensen met een visuele beperking, zoals slechtzienden. Naar aanleiding daarvan heb ik ervoor gekozen om het design aan te passen, en om hier ook extra aandacht aan te besteden tijdens de technische uitwerking in code. Omdat ik niet te lang bezig wil zijn met designen in figma maar liever verder ga in code zelf heb ik besloten om niet verder te itereren op het design.

Week 3

Aan het begin van deze week hebben we van de developer de nieuwe API gekregen en de bijbehorende Endpoints. Ik heb besloten om te gaan werken aan de zoekfunctie. Hieronder volgt het begin hiervan.

Ten eerste heb ik besloten om alle endpoints in een .env-bestand te zetten, zodat deze niet openbaar op GitHub komen te staan. Ik merkte namelijk dat bij het opvragen van een gebruiker het gehashte wachtwoord zichtbaar is, wat natuurlijk niet de bedoeling is. Vervolgens heb ik een nieuwe route aangemaakt voor het search component. We willen de search over de hele app bruikbaar maken. Hiervoor moet het fetchen naar de search endpoint gebeuren in de client en niet in de server aangezien dit ervoor zou zorgen dat bij elke letter de pagina opnieuw zou laden.

const baseUrl = process.env.NIEUWE_BASE_URL;
const query = req.query.q;

try {
  const apiUrl = new URL(`/search?s=${encodeURIComponent(query)}`, baseUrl);
  const response = await fetch(apiUrl);
  const data = await response.json();

  res.json({ results: data });
} catch (err) {
  console.error("Search failed:", err);
  res.status(500).json({ error: "Search error" });
}

Op woensdag heb ik met victor samen naar een bug gekeken. Omdat ik new URL gebruikte (geleerd tijdens api van Declan) pakte hij alleen de baseURL uit het .env bestand dit zorgde ervoor dat de search parameters dus /api/ff/search niet werden mee gegeven. ik heb dit aangepast door het volgende aan te passen.

app.get("/search", async (req, res) => {
  const baseUrl = process.env.NIEUWE_BASE_URL;
  const query = req.query.q;

  try {
    const apiUrl = new URL("/api/ff/search", baseUrl);
    apiUrl.searchParams.append("s", query); // Veilig en automatisch geëncodeerd

    // new URL is minder foutgevoelig en zorgt ervoor dat de string correct wordt geparsed naar een geldige URL

    const response = await fetch(apiUrl);
    console.log(response, "response");
    const data = await response.json();
    console.log(data, "data");

    res.json({ results: data });
  } catch (err) {
    console.error("Search failed:", err);
    res.status(500).json({ error: "Search error" });
  }
});

Het gebruik van new URL() zorgt voor automatische validatie, correcte samenvoeging van base-URL en pad, eenvoudige en veilige toevoeging van queryparameters, en maakt de URL makkelijker te beheren en debuggen dan het handmatig samenstellen van een string.

Het fetchen gebeurt zoals eerder gezegd dus in de front-end dat doe ik met de volgende code:

const input = document.getElementById("search-input");
const overlay = document.getElementById("search-overlay");
const resultsContainer = document.getElementById("search-results");

let timeout;
input.addEventListener("input", () => {
  const query = input.value.trim();

  clearTimeout(timeout);

  if (query.length < 2) {
    overlay.classList.add("hidden");
    resultsContainer.innerHTML = "";
    return;
  }

  timeout = setTimeout(async () => {
    try {
      const res = await fetch(`/search?q=${encodeURIComponent(query)}`);
      const data = await res.json();

      if (data.results && data.results.length > 0) {
        resultsContainer.innerHTML = data.results
          .map(
            (result) =>
              `<div>${result.title || result.title_en || result.name}</div>`
          )
          .join("");
        overlay.classList.remove("hidden");
      } else {
        resultsContainer.innerHTML = "<div>No results found</div>";
        overlay.classList.remove("hidden");
      }
    } catch (err) {
      resultsContainer.innerHTML = "<div>Error loading search</div>";
      overlay.classList.remove("hidden");
      console.error(err);
    }
  }, 300);
});

De setTimeout zorgt hier voor debouncing, omdat de functie anders erg vaak wordt aangeroepen. Zonder deze techniek zou bij elke toetsaanslag de fetch worden uitgevoerd. Wat er nu gebeurt, is dat de functie pas 300 ms na de laatste toetsaanslag wordt aangeroepen.

Helaas zijn de resultaten uit de search heel erg verschillend op dit moment kan ik op geen manier identificeren wat voor object ik terug krijg. Wanneer je zoekt via de algemene zoekfunctie, bevatten de resultaten soms een ff_id, soms een aid, soms een uuid, enzovoort. Wij willen graag kunnen zien wat voor type object het resultaat is, zodat we deze kunnen categoriseren. Als het bijvoorbeeld een persoon is, linken we naar de detailpagina van die persoon; als het een evenement is, dan linken we naar de detailpagina van dat evenement. Omdat dit nu niet mogelijk is heb ik besloten verder te gaan aan het maken/stylen van de header.

Week 4

Deze week begon met een gesprek met de opdrachtgever. Tijdens het gesprek kwamen een aantal belangrijke punten naar voren, maar er was ook wat verwarring. Cas, onze opdrachtgever, leek niet helemaal duidelijk te hebben welke opdracht wij precies aan het uitvoeren zijn. Hij gaf namelijk aan dat de website iets nieuws moest bieden, in plaats van een simpele kloon van de huidige site zonder verbeteringen. Er was onduidelijkheid of wij opdracht 1 (de remake van de FF-site) of opdracht 2 (een database met focus op toegankelijkheid) aan het maken waren.

Gedurende het gesprek noemde Cas verschillende features van de FF-site, zoals recente evenementen, die hij graag toegevoegd zou zien. Wij hebben geprobeerd duidelijk te maken dat dit niet ons primaire doel is. Nadat we onze plannen voor de homepagina presenteerden — waarbij ‘search the archive’ prominent aanwezig is — leek hij dit beter te begrijpen. Toch stelde hij later alsnog voor om bepaalde FF-features toe te voegen, waardoor de communicatie hierover niet helemaal soepel verliep.

Cas benadrukte daarnaast het belang van herkenbaarheid: de site moet duidelijk als onderdeel van Framer Framed herkenbaar zijn. Bijvoorbeeld door elementen over te nemen zoals het logo linksboven en het menu met vaste pagina’s.

Een ander belangrijk aandachtspunt voor Cas is de toegankelijkheid. Hij vindt dat de site iets moet bieden wat de huidige site niet heeft, zodat bezoekers in verschillende situaties effectief geholpen worden. Naast onze focus op mensen met visuele beperkingen, noemde hij ook gebruikers met langzaam internet en de mogelijkheid om webpagina’s te kunnen printen als voorbeelden.

Tijdens het gesprek bespraken we mogelijke features zoals een ingebouwde screenreader, breadcrumbs en een link naar het menu van de FF-site.

We hebben afgesproken dat we volgende week maandag alle pagina’s kunnen laten zien. De week daarna besteden we tijd aan de puntjes op de i. Ook is er een datum gepland voor het eindgesprek.

Ik ben deze week verder gegaan met de search-functie en het linken van de pagina's hieraan.

Na het probleem bij de developer neer te hebben gelegd, is hij met een oplossing gekomen, namelijk nodetypes: deze types definiëren wat een object is. Voor nu konden wij de volgende nodetypes vinden:

const allowedTypes = [
  "person",
  "organisation",
  "collective",
  "event",
  "launch",
  "article",
  "review",
];

Vervolgens gebruik ik deze types om de goede route te kiezen.

            el.addEventListener("click", (e) => {
              e.preventDefault();
              const uuid = el.dataset.uuid;
              const type = el.dataset.type;

              let href = "#";
              switch (type) {
                case "person":
                  href = `/person/${uuid}`;
                  break;
                case "organisation":
                  href = `/organisation/${uuid}`;
                  break;
                case "collective":
                  href = `/collective/${uuid}`;
                  break;
                case "event":
                  href = `/event/${uuid}`;
                case "launch":
                  href = `/event/${uuid}`;
                  break;
                case "article":
                  href = `/event/${uuid}`;
                  break;
                case "review":
                  href = `/event/${uuid}`;
                  break;
                default:
                  return;
              }

Nu ik op basis van het type door kan linken naar de juiste route om de data weer te geven kan ik gelukkig weer verder. We hebben op dit moment nog niet alle routes af, maar zijn hier mee bezig. Zodra de achterkant van het search-component werkt, ga ik verder met de styling en daarna begin ik met het maken van de homepagina. Het design van de homepagina viel goed in de smaak, wat fijn is om te horen.

Week 5

Deze week begon met een probleem namelijk dat wanneer ik op sommig search resultaten klikte ik geen pagina te zien kreeg maar ik de volgende error kreeg:

Fout bij ophalen event: TypeError: Cannot read properties of undefined (reading 'title_nl') at file:///Users/liamvanbart/Documents/school/WebDesignAndDev/Framez/server/server.js:131:22 at process.processTicksAndRejections (node:internal/process/task_queues:105:5) at async file:///Users/liamvanbart/Documents/school/WebDesignAndDev/Framez/node_modules/@tinyhttp/app/dist/app.js:25:13 at async file:///Users/liamvanbart/Documents/school/WebDesignAndDev/Framez/node_modules/@tinyhttp/app/dist/app.js:255:13 GET 500 Internal Server Error /event/undefined

Dit bleek te komen doordat sommige search resultaten geen uuid hebben. Ik heb dit opgelost door een filter toe te voegen wat de resultaten zonder uuid niet laat zien.

const filtered = data.results.filter(result => {
  return (
    result.uuid && 
    result.nodetype && 
    allowedTypes.includes(result.nodetype.toLowerCase())
  );
});
⚠️ **GitHub.com Fallback** ⚠️