תיעוד 200
בניית אפליקציית Jamstack של רשימת משימות
במדריך זה תבנו אפליקציה של רשימת משימות באמצעות HTML, CSS ו-JavaScript. נתוני האפליקציה יישמרו ב-Workers KV.

לפני שמתחילים בפרויקט הזה, כדאי שיהיה לכם ניסיון מסוים ב-HTML, ב-CSS וב-JavaScript. תלמדו:
- איך בנייה עם Workers מאפשרת להתמקד בכתיבת קוד ולשלוח מוצרים מוגמרים.
- איך ההוספה של Workers KV הופכת את המדריך הזה להיכרות טובה עם בנייה של אפליקציות מלאות מונעות נתונים.
אם תרצו לראות את הקוד המוגמר של הפרויקט הזה, מצאו את הפרויקט ב-GitHub ↗︎ והיעזרו ב-הדגמה החיה ↗︎ כדי לסקור את מה שתבנו.
#לפני שמתחילים
כל המדריכים מניחים שכבר השלמתם את מדריך תחילת העבודה, שמגדיר לכם חשבון Cloudflare Workers, את C3 ↗︎ ואת Wrangler.
#1. יצירת פרויקט Workers חדש
תחילה, השתמשו בכלי ה-CLI create-cloudflare ↗︎ כדי ליצור פרויקט Cloudflare Workers חדש בשם todos. במדריך זה תשתמשו בתבנית ברירת המחדל Hello World כדי ליצור פרויקט Workers.
באמצעות npm:
npm create cloudflare@latest -- todosבאמצעות yarn:
yarn create cloudflare todosבאמצעות pnpm:
pnpm create cloudflare@latest todosלהגדרה, בחרו באפשרויות הבאות:
- עבור What would you like to start with?, בחרו
Hello World example. - עבור Which template would you like to use?, בחרו
Worker only. - עבור Which language do you want to use?, בחרו
JavaScript. - עבור Do you want to use git for version control?, בחרו
Yes. - עבור Do you want to add an AGENTS.md file to help AI coding tools understand Cloudflare APIs?, בחרו
Yes. - עבור Do you want to deploy your application?, בחרו
No(נבצע כמה שינויים לפני הפריסה).
עברו אל התיקייה החדשה שיצרתם:
cd todosבתוך תיקיית פרויקט ה-Worker החדש todos, הקובץ index.js מייצג את נקודת הכניסה לאפליקציית Cloudflare Workers שלכם.
כל בקשות ה-HTTP הנכנסות ל-Worker מועברות אל מטפל fetch() כאובייקט request. אחרי שה-Worker מקבל בקשה, התגובה שהאפליקציה שלכם בונה תוחזר למשתמש. המדריך הזה ילווה אתכם בהבנה של אופן הפעולה של תבנית הבקשה והתגובה, ואיך אפשר להשתמש בה כדי לבנות אפליקציות מלאות.
export default {
async fetch(request, env, ctx) {
return new Response("Hello World!");
},
};בקובץ index.js שבברירת המחדל אפשר לראות את תבנית הבקשה והתגובה בפעולה. ה-fetch בונה Response חדש עם טקסט הגוף 'Hello World!'.
כאשר Worker מקבל request, ה-Worker מחזיר ללקוח את התגובה שנבנתה. ה-Worker שלכם יגיש תגובות חדשות ישירות מ-הרשת הגלובלית של Cloudflare ↗︎ במקום להמשיך לשרת המקור שלכם. שרת רגיל היה מקבל בקשות ומחזיר תגובות. Cloudflare Workers מאפשר להגיב על ידי בניית תגובות ישירות ברשת הגלובלית של Cloudflare.
#2. סקירת פרטי הפרויקט
כל פרויקט שתפרסו ל-Cloudflare Workers יכול להשתמש בכלי JavaScript מודרניים כמו מודולי ES, חבילות npm ופונקציות async/await ↗︎ כדי לבנות את האפליקציה. בנוסף לכתיבת Workers, אפשר להשתמש ב-Workers כדי לבנות אפליקציות מלאות באמצעות אותם כלים ואותו תהליך כמו במדריך הזה.
במדריך זה תבנו אפליקציה של רשימת משימות שרצה על Workers, שמאפשרת לקרוא נתונים ממאגר KV ולהשתמש בנתונים כדי למלא תגובת HTML ולשלוח אותה ללקוח.
העבודה הנדרשת ליצירת האפליקציה מחולקת לשלוש משימות:
- כתיבת נתונים ל-KV.
- הצגת נתונים מ-KV.
- הוספת משימות מממשק האפליקציה.
בהמשך המדריך תשלימו כל משימה, תשפרו את האפליקציה בהדרגה, ואז תפרסמו אותה לדומיין שלכם.
#3. כתיבת נתונים ל-KV
כדי להתחיל, צריך להבין איך למלא את רשימת המשימות בנתונים אמיתיים. לשם כך השתמשו ב-Cloudflare Workers KV, מאגר מפתח-ערך שאפשר לגשת אליו מתוך ה-Worker כדי לקרוא ולכתוב נתונים.
כדי להתחיל עם KV, הגדירו namespace. כל הנתונים השמורים במטמון יישמרו בתוך ה-namespace הזה, ועם הגדרה אפשר לגשת אל ה-namespace מתוך ה-Worker באמצעות משתנה מוגדר מראש. השתמשו ב-Wrangler כדי ליצור namespace חדש בשם TODOS עם הפקודה kv namespace create, וקבלו את מזהה ה-namespace המשויך על ידי הרצת הפקודה הבאה בטרמינל:
יצירת namespace חדש של KV:
npx wrangler kv namespace create "TODOS" --previewאפשר לשלב את ה-namespace המשויך עם הדגל --preview כדי לעבוד מול namespace של תצוגה מקדימה במקום namespace של ייצור. אפשר להוסיף namespaces לאפליקציה על ידי הגדרתם בקובץ התצורה של Wrangler. העתיקו את מזהה ה-namespace החדש שיצרתם, ובקובץ התצורה של Wrangler הגדירו מפתח kv_namespaces כדי להגדיר את ה-namespace:
{
"kv_namespaces": [
{
"binding": "TODOS",
"id": "<YOUR_ID>",
"preview_id": "<YOUR_PREVIEW_ID>"
}
]
}[[kv_namespaces]]
binding = "TODOS"
id = "<YOUR_ID>"
preview_id = "<YOUR_PREVIEW_ID>"ה-namespace שהוגדר, TODOS, יהיה זמין עכשיו בתוך בסיס הקוד. עם זה, הגיע הזמן להבין את KV API. ל-namespace של KV יש שלוש מתודות עיקריות שאפשר להשתמש בהן כדי לתקשר עם המטמון: get, put ו-delete.
התחילו לאחסן נתונים על ידי הגדרת קבוצת נתונים ראשונית, שתכניסו למטמון באמצעות המתודה put. הדוגמה הבאה מגדירה אובייקט defaultData במקום מערך של פריטי משימות. ייתכן שתרצו לאחסן מטא נתונים ומידע נוסף בתוך אובייקט המטמון הזה בהמשך. בהינתן אובייקט הנתונים הזה, השתמשו ב-JSON.stringify כדי להוסיף מחרוזת למטמון:
export default {
async fetch(request, env, ctx) {
const defaultData = {
todos: [
{
id: 1,
name: "Finish the Cloudflare Workers blog post",
completed: false,
},
],
};
await env.TODOS.put("data", JSON.stringify(defaultData));
return new Response("Hello World!");
},
};Workers KV הוא מאגר נתונים גלובלי עם עקביות בסופו של דבר. כל כתיבה בתוך אזור משתקפת מיד באותו אזור, אבל לא תהיה זמינה מיד באזורים אחרים. עם זאת, הכתיבות האלה יהיו זמינות בסופו של דבר בכל מקום, ובשלב הזה Workers KV מבטיח שהנתונים בכל אזור יהיו עקביים.
בהינתן נוכחות נתונים במטמון וההנחה שהמטמון עקבי בסופו של דבר, הקוד הזה צריך התאמה קלה: האפליקציה צריכה לבדוק את המטמון ולהשתמש בערך שלו, אם המפתח קיים. אם הוא לא קיים, תשתמשו ב-defaultData כמקור הנתונים לעת עתה (יש להגדיר אותו בעתיד) ותכתבו אותו למטמון לשימוש עתידי. אחרי פיצול הקוד לכמה פונקציות לצורך פשטות, התוצאה נראית כך:
export default {
async fetch(request, env, ctx) {
const defaultData = {
todos: [
{
id: 1,
name: "Finish the Cloudflare Workers blog post",
completed: false,
},
],
};
const setCache = (data) => env.TODOS.put("data", data);
const getCache = () => env.TODOS.get("data");
let data;
const cache = await getCache();
if (!cache) {
await setCache(JSON.stringify(defaultData));
data = defaultData;
} else {
data = JSON.parse(cache);
}
return new Response(JSON.stringify(data));
},
};#הצגת נתונים מ-KV
בהינתן נוכחות נתונים בקוד, שהוא אובייקט הנתונים השמור במטמון של האפליקציה, כדאי לקחת את הנתונים האלה ולהציג אותם בממשק משתמש.
כדי לעשות זאת, צרו משתנה html חדש בסקריפט ה-Workers והשתמשו בו כדי לבנות תבנית HTML סטטית שאפשר להגיש ללקוח. ב-fetch, בנו Response חדש עם כותרת Content-Type: text/html והגישו אותו ללקוח:
const html = `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Todos</title>
</head>
<body>
<h1>Todos</h1>
</body>
</html>
`;
async fetch (request, env, ctx) {
// previous code
return new Response(html, {
headers: {
'Content-Type': 'text/html'
}
});
}יש לכם אתר HTML סטטי שמוצג, ואפשר להתחיל למלא אותו בנתונים. בגוף, הוסיפו תג div עם id של todos:
const html = `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Todos</title>
</head>
<body>
<h1>Todos</h1>
<div id="todos"></div>
</body>
</html>
`;הוסיפו רכיב <script> בסוף תוכן הגוף, שמקבל מערך todos. עבור כל todo במערך, יוצרים רכיב div ומוסיפים אותו לרכיב ה-HTML todos:
const html = `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Todos</title>
</head>
<body>
<h1>Todos</h1>
<div id="todos"></div>
</body>
<script>
window.todos = []
var todoContainer = document.querySelector("#todos")
window.todos.forEach(todo => {
var el = document.createElement("div")
el.textContent = todo.name
todoContainer.appendChild(el)
})
</script>
</html>
`;הדף הסטטי יכול לקבל את window.todos ולהציג HTML על פיו, אבל עדיין לא העברתם נתונים מ-KV. כדי לעשות זאת, תצטרכו לבצע כמה שינויים.
תחילה, המשתנה html יהפוך לפונקציה. הפונקציה תקבל ארגומנט todos, שימלא את המשתנה window.todos בדוגמת הקוד שלמעלה:
const html = (todos) => `
<!doctype html>
<html>
<!-- existing content -->
<script>
window.todos = ${todos}
var todoContainer = document.querySelector("#todos")
// ...
<script>
</html>
`;ב-fetch, השתמשו בנתוני ה-KV שאוחזרו כדי לקרוא לפונקציה html וליצור על פיה Response:
async fetch (request, env, ctx) {
const body = html(JSON.stringify(data.todos).replace(/</g, '\\u003c'));
return new Response(body, {
headers: { 'Content-Type': 'text/html' },
});
}#4. הוספת משימות מממשק המשתמש (UI)
בשלב הזה בניתם Cloudflare Worker שלוקח נתונים מ-Cloudflare KV ומציג דף סטטי על סמך ה-Worker הזה. הדף הסטטי קורא נתונים ומייצר רשימת משימות על סמך הנתונים האלה. המשימה שנותרה היא יצירת משימות מתוך ממשק האפליקציה. אפשר להוסיף משימות באמצעות KV API: עדכנו את המטמון על ידי הרצת env.TODOS.put(newData).
כדי לעדכן פריט משימה, תוסיפו מטפל שני בסקריפט ה-Workers, שנועד לעקוב אחרי בקשות PUT אל /. כאשר גוף בקשה מתקבל בכתובת הזו, ה-Worker ישלח את נתוני המשימה החדשים למאגר ה-KV.
הוסיפו את הפונקציונליות החדשה הזו ב-fetch: אם שיטת הבקשה היא PUT, היא תיקח את גוף הבקשה ותעדכן את המטמון.
export default {
async fetch(request, env, ctx) {
const setCache = (data) => env.TODOS.put("data", data);
if (request.method === "PUT") {
const body = await request.text();
try {
JSON.parse(body);
await setCache(body);
return new Response(body, { status: 200 });
} catch (err) {
return new Response(err, { status: 500 });
}
}
// previous code
},
};בדקו שהבקשה היא PUT ועטפו את שאר הקוד בבלוק try...catch. תחילה, נתחו את גוף הבקשה הנכנסת, וודאו שהוא JSON, לפני שתעדכנו את המטמון בנתונים החדשים ותחזירו אותם למשתמש. אם משהו משתבש, החזירו קוד מצב 500. אם הניתוב נפגע בשיטת HTTP שאינה PUT, לדוגמה POST או DELETE, החזירו שגיאת 404.
עם הסקריפט הזה אפשר עכשיו להוסיף פונקציונליות דינמית לדף ה-HTML כדי לפגוע בפועל בניתוב הזה. תחילה, צרו שדה קלט לשם המשימה וכפתור לשליחת המשימה.
const html = (todos) => `
<!doctype html>
<html>
<!-- existing content -->
<div>
<input type="text" name="name" placeholder="A new todo"></input>
<button id="create">Create</button>
</div>
<!-- existing script -->
</html>
`;בהינתן שדה הקלט והכפתור, הוסיפו פונקציית JavaScript מתאימה שתעקוב אחרי לחיצות על הכפתור. ברגע שלוחצים על הכפתור, הדפדפן יבצע PUT אל / וישלח את המשימה.
const html = (todos) => `
<!doctype html>
<html>
<!-- existing content -->
<script>
// Existing JavaScript code
var createTodo = function() {
var input = document.querySelector("input[name=name]")
if (input.value.length) {
todos = [].concat(todos, {
id: todos.length + 1,
name: input.value,
completed: false,
})
fetch("/", {
method: "PUT",
body: JSON.stringify({ todos: todos }),
})
}
}
document.querySelector("#create").addEventListener("click", createTodo)
</script>
</html>
`;הקוד הזה מעדכן את המטמון. זכרו שמטמון ה-KV עקבי בסופו של דבר: גם אם תעדכנו את ה-Worker כך שיקרא מהמטמון ויחזיר אותו, אין לכם הבטחה שהוא באמת יהיה מעודכן. במקום זאת, עדכנו את רשימת המשימות באופן מקומי, על ידי לקיחת הקוד המקורי להצגת רשימת המשימות, הפיכתו לפונקציה לשימוש חוזר בשם populateTodos, וקריאה אליה כשהדף נטען וכשבקשת המטמון הסתיימה:
const html = (todos) => `
<!doctype html>
<html>
<!-- existing content -->
<script>
var populateTodos = function() {
var todoContainer = document.querySelector("#todos")
todoContainer.innerHTML = null
window.todos.forEach(todo => {
var el = document.createElement("div")
el.textContent = todo.name
todoContainer.appendChild(el)
})
}
populateTodos()
var createTodo = function() {
var input = document.querySelector("input[name=name]")
if (input.value.length) {
todos = [].concat(todos, {
id: todos.length + 1,
name: input.value,
completed: false,
})
fetch("/", {
method: "PUT",
body: JSON.stringify({ todos: todos }),
})
populateTodos()
input.value = ""
}
}
document.querySelector("#create").addEventListener("click", createTodo)
</script>
`;כשקוד צד הלקוח במקום, פריסת הגרסה החדשה של הפונקציה אמורה לחבר את כל החלקים האלה. התוצאה היא רשימת משימות דינמית אמיתית.
#5. עדכון משימות מממשק המשתמש של היישום
לחלק האחרון של רשימת המשימות, צריך שתוכל לעדכן משימות, ובפרט לסמן אותן כהושלמו.
למזלנו, חלק גדול מהתשתית לעבודה הזו כבר קיים. אפשר לעדכן את נתוני רשימת המשימות במטמון, כפי שרואים בפונקציה createTodo. ביצוע עדכונים על משימה הוא יותר משימה בצד הלקוח מאשר בצד ה-Worker.
כדי להתחיל, אפשר לעדכן את הפונקציה populateTodos כך שתיצור div לכל משימה. בנוסף, העבר את שם המשימה לרכיב בן של אותו div:
const html = (todos) => `
<!doctype html>
<html>
<!-- existing content -->
<script>
var populateTodos = function() {
var todoContainer = document.querySelector("#todos")
todoContainer.innerHTML = null
window.todos.forEach(todo => {
var el = document.createElement("div")
var name = document.createElement("span")
name.textContent = todo.name
el.appendChild(name)
todoContainer.appendChild(el)
})
}
</script>
`;עיצבת את החלק בצד הלקוח של הקוד הזה כך שיטפל במערך של משימות ויציג רשימה של רכיבי HTML. יש כמה דברים שעשית ועדיין לא ממש היה להם שימוש: הכללת מזהים ועדכון מצב ההשלמה של המשימה. הדברים האלה עובדים היטב יחד כדי לתמוך בפועל בעדכון משימות בממשק המשתמש של היישום.
כדי להתחיל, כדאי לצרף את המזהה של כל משימה ב-HTML. כך תוכל אחר כך להפנות לרכיב כדי להתאים אותו למשימה בחלק ה-JavaScript של הקוד. תכונות data והמתודה המתאימה dataset ב-JavaScript הן דרך מצוינת ליישם את זה. כשאתה יוצר את רכיב ה-div לכל משימה, אפשר לצרף לכל div תכונת data בשם todo:
const html = (todos) => `
<!doctype html>
<html>
<!-- existing content -->
<script>
var populateTodos = function() {
var todoContainer = document.querySelector("#todos")
todoContainer.innerHTML = null
window.todos.forEach(todo => {
var el = document.createElement("div")
el.dataset.todo = todo.id
var name = document.createElement("span")
name.textContent = todo.name
el.appendChild(name)
todoContainer.appendChild(el)
})
}
</script>
`;בתוך ה-HTML, לכל div של משימה יש עכשיו תכונת data מצורפת, שנראית כך:
<div data-todo="1"></div>
<div data-todo="2"></div>עכשיו אפשר ליצור תיבת סימון לכל רכיב משימה. תיבת הסימון תהיה כברירת מחדל לא מסומנת למשימות חדשות, אבל אפשר לסמן אותה כמסומנת בזמן שהרכיב מוצג בחלון:
const html = (todos) => `
<!doctype html>
<html>
<!-- existing content -->
<script>
window.todos.forEach(todo => {
var el = document.createElement("div")
el.dataset.todo = todo.id
var name = document.createElement("span")
name.textContent = todo.name
var checkbox = document.createElement("input")
checkbox.type = "checkbox"
checkbox.checked = todo.completed ? 1 : 0
el.appendChild(checkbox)
el.appendChild(name)
todoContainer.appendChild(el)
})
</script>
`;תיבת הסימון מוגדרת כך שתשקף נכון את הערך של completed בכל משימה, אבל היא עדיין לא מתעדכנת כשמסמנים את התיבה בפועל. כדי לעשות זאת, צרף את הפונקציה completeTodo כמאזין אירועים לאירוע click. בתוך הפונקציה, בדוק את רכיב תיבת הסימון, מצא את ההורה שלו (ה-div של המשימה), והשתמש בתכונת ה-data בשם todo שלו כדי למצוא את המשימה המתאימה במערך הנתונים. אפשר להחליף את מצב ההשלמה, לעדכן את המאפיינים שלו, ולרנדר מחדש את ממשק המשתמש:
const html = (todos) => `
<!doctype html>
<html>
<!-- existing content -->
<script>
var populateTodos = function() {
window.todos.forEach(todo => {
// Existing todo element set up code
checkbox.addEventListener("click", completeTodo)
})
}
var completeTodo = function(evt) {
var checkbox = evt.target
var todoElement = checkbox.parentNode
var newTodoSet = [].concat(window.todos)
var todo = newTodoSet.find(t => t.id == todoElement.dataset.todo)
todo.completed = !todo.completed
todos = newTodoSet
updateTodos()
}
</script>
`;התוצאה הסופית של הקוד היא מערכת שבודקת את המשתנה todos, מעדכנת את מטמון Cloudflare KV בערך הזה, ואז מבצעת רינדור מחדש של ממשק המשתמש לפי הנתונים שיש לה באופן מקומי.
#6. סיכום והצעדים הבאים
עם השלמת המדריך הזה, בנית יישום סטטי של HTML, CSS ו-JavaScript שמופעל בשקיפות על ידי Workers ו-Workers KV, שמנצלים במלואם את הרשת הגלובלית של Cloudflare.
אם תרצה להמשיך לשפר את הפרויקט, אפשר ליישם עיצוב טוב יותר (אפשר להסתכל בגרסה חיה הזמינה ב-todos.signalnerve.workers.dev ↗︎), או לבצע שיפורים נוספים באבטחה ובמהירות.
אפשר גם להוסיף שמירה במטמון לפי משתמש. כרגע, מפתח המטמון הוא תמיד data. המשמעות היא שכל מבקר באתר ישתף את אותה רשימת משימות עם מבקרים אחרים. בתוך ה-Worker, אפשר להשתמש בערכים מבקשת הלקוח כדי ליצור ולתחזק רשימות לפי משתמש. לדוגמה, אפשר ליצור מפתח מטמון לפי כתובת ה-IP המבקשת:
export default {
async fetch(request, env, ctx) {
const defaultData = {
todos: [
{
id: 1,
name: "Finish the Cloudflare Workers blog post",
completed: false,
},
],
};
const setCache = (key, data) => env.TODOS.put(key, data);
const getCache = (key) => env.TODOS.get(key);
const ip = request.headers.get("CF-Connecting-IP");
const myKey = `data-${ip}`;
if (request.method === "PUT") {
const body = await request.text();
try {
JSON.parse(body);
await setCache(myKey, body);
return new Response(body, { status: 200 });
} catch (err) {
return new Response(err, { status: 500 });
}
}
let data;
const cache = await getCache();
if (!cache) {
await setCache(myKey, JSON.stringify(defaultData));
data = defaultData;
} else {
data = JSON.parse(cache);
}
const body = html(JSON.stringify(data.todos).replace(/</g, "\\u003c"));
return new Response(body, {
headers: {
"Content-Type": "text/html",
},
});
},
};אחרי ביצוע השינויים האלה ופריסה נוספת של ה-Worker, יישום רשימת המשימות כולל עכשיו פונקציונליות לפי משתמש, ועדיין מנצל במלואו את הרשת הגלובלית של Cloudflare.
הגרסה הסופית של סקריפט ה-Worker אמורה להיראות כך:
const html = (todos) => `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Todos</title>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss/dist/tailwind.min.css" rel="stylesheet"></link>
</head>
<body class="bg-blue-100">
<div class="w-full h-full flex content-center justify-center mt-8">
<div class="bg-white shadow-md rounded px-8 pt-6 py-8 mb-4">
<h1 class="block text-grey-800 text-md font-bold mb-2">Todos</h1>
<div class="flex">
<input class="shadow appearance-none border rounded w-full py-2 px-3 text-grey-800 leading-tight focus:outline-none focus:shadow-outline" type="text" name="name" placeholder="A new todo"></input>
<button class="bg-blue-500 hover:bg-blue-800 text-white font-bold ml-2 py-2 px-4 rounded focus:outline-none focus:shadow-outline" id="create" type="submit">Create</button>
</div>
<div class="mt-4" id="todos"></div>
</div>
</div>
</body>
<script>
window.todos = ${todos}
var updateTodos = function() {
fetch("/", { method: "PUT", body: JSON.stringify({ todos: window.todos }) })
populateTodos()
}
var completeTodo = function(evt) {
var checkbox = evt.target
var todoElement = checkbox.parentNode
var newTodoSet = [].concat(window.todos)
var todo = newTodoSet.find(t => t.id == todoElement.dataset.todo)
todo.completed = !todo.completed
window.todos = newTodoSet
updateTodos()
}
var populateTodos = function() {
var todoContainer = document.querySelector("#todos")
todoContainer.innerHTML = null
window.todos.forEach(todo => {
var el = document.createElement("div")
el.className = "border-t py-4"
el.dataset.todo = todo.id
var name = document.createElement("span")
name.className = todo.completed ? "line-through" : ""
name.textContent = todo.name
var checkbox = document.createElement("input")
checkbox.className = "mx-4"
checkbox.type = "checkbox"
checkbox.checked = todo.completed ? 1 : 0
checkbox.addEventListener("click", completeTodo)
el.appendChild(checkbox)
el.appendChild(name)
todoContainer.appendChild(el)
})
}
populateTodos()
var createTodo = function() {
var input = document.querySelector("input[name=name]")
if (input.value.length) {
window.todos = [].concat(todos, { id: window.todos.length + 1, name: input.value, completed: false })
input.value = ""
updateTodos()
}
}
document.querySelector("#create").addEventListener("click", createTodo)
</script>
</html>
`;
export default {
async fetch(request, env, ctx) {
const defaultData = {
todos: [
{
id: 1,
name: "Finish the Cloudflare Workers blog post",
completed: false,
},
],
};
const setCache = (key, data) => env.TODOS.put(key, data);
const getCache = (key) => env.TODOS.get(key);
const ip = request.headers.get("CF-Connecting-IP");
const myKey = `data-${ip}`;
if (request.method === "PUT") {
const body = await request.text();
try {
JSON.parse(body);
await setCache(myKey, body);
return new Response(body, { status: 200 });
} catch (err) {
return new Response(err, { status: 500 });
}
}
let data;
const cache = await getCache();
if (!cache) {
await setCache(myKey, JSON.stringify(defaultData));
data = defaultData;
} else {
data = JSON.parse(cache);
}
const body = html(JSON.stringify(data.todos).replace(/</g, "\\u003c"));
return new Response(body, {
headers: {
"Content-Type": "text/html",
},
});
},
};אפשר למצוא את קוד המקור של הפרויקט הזה, וגם README עם הוראות פריסה, ב-GitHub ↗︎.