صفحات HTML
جزء من قسم أساسيات Express في رحلة Node.js على Coddy. الدرس 10 من 23.
يحوّل Recipe Box الآن تمارين سلاسل HTML والإفلات إلى صفحة. أنشئ رابطًا واحدًا لكل وصفة. تحدد قيمة href للرابط وجهته، ويكون النص بين وسمَيه هو التسمية التي يراها القارئ.
const link = `<a href="/books/${book.id}">${escapeHtml(book.title)}</a>`;الدالة المساعدة متوفرة في html.js. طبّقها على كل عنوان، مع إبقاء وسوم الرابط الخاصة بك بوصفها ترميزًا. استخدم map لإنشاء الروابط وjoin لضمّها في سلسلة واحدة. إن أرسلت المصفوفة مباشرةً داخل سلسلة قالب، فستظهر فواصل بين الروابط. يرسل res.send الصفحة المكتملة إلى المتصفح.
يحتوي <ul> القائمة، ويحيط <li> بكل عنصر. يمكن للترميز غير المُفلت أن يغيّر الصفحة أو يشغّل نصًا برمجيًا في متصفح الزائر؛ لذا أفلت كل عنوان حتى لو كان مصدره ملف بياناتك الخاص.
التحدي
سهلتفتح هذه الخطوة مشروع Recipe Box المُعَدّ للدورة، بدلًا من ملفات مشروعك من رموز الحالة (بما في ذلك server.js). يبقى عملك السابق في درسه الخاص. اقرأ الملفات المرفقة قبل تعديل server.js. تحتوي هذه النسخة على مسارات Recipe Box ورموز الحالة والمساعد المشترك html.js.
تصبح الصفحة الرئيسية صفحة فعلية: قائمة بكل وصفة، وكل وصفة فيها رابط إلى صفحتها الخاصة.
- في
server.js، استورد المساعد المرفقescapeHtmlمن./html.js. - غيّر المسار
/بحيث يرسل صفحة HTML بدلًا من نص الترحيب. تحتوي الصفحة على رابط لكل وصفة، يؤدي إلى صفحة تلك الوصفة ويعرض عنوانها، كما في هذا المثال للوصفة 2:<a href="/recipes/2">Pancakes</a>. القائمة مناسبة لهذا الغرض: ضع كل رابط داخل عنصر<li>، واجمعها كلها داخل عنصر<ul>واحد. يمكنك البدء بعنوان،<h1>Recipe Box</h1>. - مرّر كل عنوان عبر
escapeHtmlقبل إضافته إلى الصفحة. عندها تصل Fish & Chips إلى الصفحة بالشكلFish & Chips، ويعرضها المتصفح على شكل Fish & Chips. - أنشئ العناصر باستخدام
mapواجمعها بسلسلة فارغة. إذا أدرجت مصفوفةً كما هي في سلسلة قالبية، فستظهر فواصل بين العناصر.
جرّب ذلك: اضغط «تشغيل» وأرسل /. ستظهر الاستجابة كصفحة فيها أربعة روابط. انقر على Fish & Chips: ستُفتح صفحة /recipes/4 وتعرض Fish & Chips.
جرّب بنفسك
import express from 'express';
import { recipes } from './data.js';
const app = express();
app.get('/', (req, res) => {
res.send('Welcome to Recipe Box');
});
// ترسل type('text') نصًا عاديًا، لذا يظهر كل عنوان في سطر مستقل.
app.get('/recipes', (req, res) => {
const { tag } = req.query;
const list = tag ? recipes.filter((recipe) => recipe.tags.includes(tag)) : recipes;
res.type('text').send(list.map((recipe) => recipe.title).join('\n'));
});
app.get('/recipes/:id', (req, res) => {
const recipe = recipes.find((r) => r.id === Number(req.params.id));
if (!recipe) {
res.status(404).send('No such recipe');
return;
}
res.send(recipe.title);
});
app.get('/api/recipes', (req, res) => {
res.json(recipes);
});
app.get('/api/recipes/:id', (req, res) => {
const recipe = recipes.find((r) => r.id === Number(req.params.id));
if (!recipe) {
res.status(404).json({ error: 'No such recipe' });
return;
}
res.json(recipe);
});
app.get('/about', (req, res) => {
res.send('Recipe Box collects family recipes.');
});
app.listen(3000, () => {
console.log('Recipe Box is running on port 3000');
});
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس أساسيات Express
تدرّب بنفسك: Playground لـ Express