Kort mål
- Opret en datatabel
Quotesmed felterne:text,author,topic,likes,shownCount,favorite. - Vis et tilfældigt citat fra tabellen.
- Knapper til Nyt citat, Like ⭐ og Favorit ❤️ (opdaterer data).
- (Udvid) Søg/filtrér og Tilføj nyt citat.
Opgave 1 – Lav datatabel i App Lab
- Åbn dit projekt i App Lab → fanen Data.
- Lav en ny tabel
Quotes med disse kolonner (id findes altid automatisk – du skal ikke oprette en kolonne der hedder
id):text(Text) – selve citatetauthor(Text)topic(Text)likes(Number) – startværdi 0shownCount(Number) – startværdi 0favorite(Boolean) – startværdi false
- (Valgfrit) Indsæt et lille startdatasæt manuelt eller via CSV (se eksempel længere nede).
Opgave 2 – Design UI (IDs)
I Design opret disse elementer. Brug gerne samme ID’er (så passer koden nedenfor direkte):
- Label – citattekst:
lblQuote - Label – forfatter:
lblAuthor - Label – likes-tæller:
lblLikes(vis fx “Likes: 0”) - Knap – Nyt citat:
btnNew - Knap – Like ⭐:
btnLike - Knap – Favorit ❤️:
btnFav
(Udvid senere) Søg/filtrér:
- Text input – søg på forfatter:
inputSearch - Knap – Søg:
btnSearch
(Udvid senere) Tilføj citat:
- Text input – ny tekst:
inputNewText - Text input – ny forfatter:
inputNewAuthor - Text input – nyt emne:
inputNewTopic - Knap – Tilføj:
btnAdd - Label – statusbesked:
lblMsg
Opgave 3 – Læs data og vis tilfældigt citat
Tilføj denne kode i Kode (Vis tekst). Den henter alle citater, blander dem og viser ét ad gangen uden gentagelser i en runde.
// ===== Citat-Viewer – grundkode =====
var allQuotes = [];
var pool = []; // indekser for resterende citater i denne runde (blandet)
var current = null; // den record der vises nu
function loadQuotes(callback) {
readRecords("Quotes", {}, function(records) {
allQuotes = records;
shufflePool();
if (callback) callback();
});
}
function shufflePool() {
pool = [];
for (var i = 0; i < allQuotes.length; i++) { pool.push(i); }
// Fisher–Yates-blanding
for (var j = pool.length - 1; j > 0; j--) {
var k = randomNumber(0, j);
var tmp = pool[j];
pool[j] = pool[k];
pool[k] = tmp;
}
}
function showRandom() {
if (allQuotes.length === 0) {
setText("lblQuote", "(Ingen data i tabellen endnu)");
setText("lblAuthor", "");
return;
}
if (pool.length === 0) shufflePool();
var idx = pool.pop();
current = allQuotes[idx]; // gem hele recorden (inkl. id)
// Opdater UI
setText("lblQuote", current.text || "(mangler tekst)");
setText("lblAuthor", current.author ? "— " + current.author : "");
setText("lblLikes", "Likes: " + (current.likes || 0));
setProperty("btnFav", "text", current.favorite ? "❤️ Favorit" : "🤍 Favorit");
// Tæl visning og gem tilbage (UPDATE)
current.shownCount = (current.shownCount || 0) + 1;
updateRecord("Quotes", current, function(updated) {
// Opdater også i allQuotes-listen, så data passer lokalt
allQuotes[idx] = current;
});
}
// Event: Nyt citat
onEvent("btnNew", "click", function() {
showRandom();
});
// Indlæs data ved app-start
loadQuotes(function(){
showRandom();
});
Hvorfor gemmer vi hele recorden i current?
Fordi updateRecord() bruger objektet inkl. det automatisk tildelte id. Vi ændrer feltet (fx shownCount) på current og sender hele recorden ind igen.
Opgave 4 – Like ⭐ og Favorit ❤️ (UPDATE)
Tilføj disse events. De opdaterer både UI og databasen.
// Like-knap: +1 like
onEvent("btnLike", "click", function(){
if (!current) return;
current.likes = (current.likes || 0) + 1;
setText("lblLikes", "Likes: " + current.likes);
updateRecord("Quotes", current, function(updated){ /* valgfrit: feedback */ });
});
// Favorit-knap: toggle true/false
onEvent("btnFav", "click", function(){
if (!current) return;
current.favorite = !current.favorite;
setProperty("btnFav", "text", current.favorite ? "❤️ Favorit" : "🤍 Favorit");
updateRecord("Quotes", current, function(updated){ /* valgfrit */ });
});
Tip
Vi arbejder hele tiden på current (det viste citat) – så undgår vi at glemme id i updateRecord().
Opgave 5 – Søg/filtrér på forfatter
En enkel model er at læse alle records én gang og så filtrere i JavaScript (case‑insensitive). Tilføj event til btnSearch:
onEvent("btnSearch", "click", function(){
var q = (getText("inputSearch") || "").toLowerCase();
var candidates = allQuotes.filter(function(r){
return (r.author || "").toLowerCase().indexOf(q) !== -1;
});
if (candidates.length === 0) {
setText("lblQuote", "Ingen citater matcher din søgning.");
setText("lblAuthor", "");
return;
}
current = candidates[randomNumber(0, candidates.length - 1)];
setText("lblQuote", current.text);
setText("lblAuthor", current.author ? "— " + current.author : "");
setText("lblLikes", "Likes: " + (current.likes || 0));
setProperty("btnFav", "text", current.favorite ? "❤️ Favorit" : "🤍 Favorit");
});
Note
Vil du kun hente præcise matches fra databasen, kan du bruge readRecords("Quotes", {author: "Præcis Navn"}, callback). Men “indeholder‑søgning” er lettest at forklare ved at filtrere i JS.
Opgave 6 – Tilføj nyt citat (CREATE)
- Lav felter/knap:
inputNewText,inputNewAuthor,inputNewTopic,btnAdd, samt statuslabellblMsg. - Tilføj koden her:
onEvent("btnAdd", "click", function(){
var t = (getText("inputNewText") || "").trim();
var a = (getText("inputNewAuthor") || "").trim();
var top = (getText("inputNewTopic") || "").trim();
if (t === "" || a === "") {
setText("lblMsg", "Skriv både tekst og forfatter.");
return;
}
var newRec = { text: t, author: a, topic: top, likes: 0, shownCount: 0, favorite: false };
createRecord("Quotes", newRec, function(created){
if (created) {
setText("lblMsg", "Tilføjet ✔");
// læg i lokalt array, så det kan vises med det samme
allQuotes.push(created);
setText("inputNewText", "");
setText("inputNewAuthor", "");
setText("inputNewTopic", "");
} else {
setText("lblMsg", "Kunne ikke tilføje. Tjek net/forbindelse.");
}
});
});
Tip
createRecord() returnerer normalt den oprettede record inkl. det automatisk tildelte id. Vi skubber den ind i allQuotes så UI kan bruge den med det samme.
(Valgfri) Opgave 7 – Redigér og slet (UPDATE/DELETE)
Vis evt. et lille “Admin”-område med to knapper. Husk at bruge current (som har id):
// Redigér: skift fx topic på det viste citat
function setTopic(newTopic){
if (!current) return;
current.topic = newTopic;
updateRecord("Quotes", current, function(updated){ /* feedback */ });
}
// Slet det viste citat
function deleteCurrent(){
if (!current) return;
deleteRecord("Quotes", current, function(success){
if (success) {
// Fjern fra lokalt array og vis et nyt
allQuotes = allQuotes.filter(function(r){ return r.id !== current.id; });
setText("lblMsg", "Slettet ✔");
current = null;
showRandom();
}
});
}
Eksempel på CSV til hurtig import
Kopier til en .csv-fil og importér i tabellen Quotes (lad systemet selv lave id). Bemærk: sand/falsk skrives som true/false.
text,author,topic,likes,shownCount,favorite
"Stay hungry, stay foolish.",Steve Jobs,inspiration,0,0,false
"Simplicity is the ultimate sophistication.",Leonardo da Vinci,design,0,0,false
"Creativity is intelligence having fun.",Albert Einstein,creativity,0,0,false
"If you can dream it, you can do it.",Walt Disney,motivation,0,0,false
"The only way to learn a new programming language is by writing programs in it.",Dennis Ritchie,coding,0,0,false
"Programs must be written for people to read, and only incidentally for machines to execute.",Harold Abelson,coding,0,0,false
"First, solve the problem. Then, write the code.",John Johnson,coding,0,0,false
"Make it work, make it right, make it fast.",Kent Beck,coding,0,0,false
Fejlforebyggelse & gode vaner
- Asynkronitet: Kode der bruger
recordsskal ligge inde i callback-funktionen. - Bevar hele recorden: Brug
current+updateRecord("Quotes", current, ...)for at undgå at nulstille felter. - Tomt datasæt: Vis en venlig besked i UI hvis tabellen er tom.
- Ydelse: I undervisning (små datasæt) er det fint at læse alle og filtrere i JS.
Refleksion
- Hvor i koden læser du data fra tabellen?
- Hvor opdaterer du data (likes/favorite/shownCount)?
- Hvad er fordelen ved at gemme den viste record i
current? - Hvordan undgår vi gentagelser, når vi viser citater?