تاريخ التسجيل: Feb 2019
الدولة: قلب ابي
العمر: 31
المشاركات: 1,006
التقييم: 10
|
تاريخ التسجيل: Feb 2019
الدولة: قلب ابي
العمر: 31
المشاركات: 1,006
التقييم: 10
إنشاء مدونة باستخدام Express (الجزء 2): توجيه الرّوابط في Express
إنشاء مدونة باستخدام Express (الجزء 2): توجيه الرّوابط في Express
في الدّرس السّابق قمنا بثتبيت Node.js وخادوم MySQL وبقيّة متطلّبات المشروع، حان الوقت لنبدأ العمل الحقيقيّ!
إنشاء صفحة المدوّنة الرئيسيّة
أهمّ ما تعرضه الصّفحة الرئيسيّة لكلّ مدوّنة عادةً آخر التّدوينات بتاريخ كتابتها من الأحدث للأقدم، وسنركّز الآن على تطبيق هذا الجزء على أنّ نتوسّع في إضافة الميّزات في وقتٍ لاحق.
أنشئ الملفّ index.js الّذي يُمثّل نقطة انطلاق مشروعنا، ولنبدأ باستيراد Express ضمنه:
var Express = require("express");
لنُنشئ الآن تطبيق Express جديد، وهو يمثّل الخادوم الذي يُدير مدوّنتنا بالكامل، يتمّ إنشاء تطبيق Express ببساطة باستدعاء دالّة Express الّتي أنشأناها لتوّنا:
var Express = require("express");
var app = express();
تكون الصّفحة الرئيسيّة للمدوّنة على الرّابط الجذر للموقع عادةً، وهو ما نُعبّر عنه بـ/، سنطلب من تطبيقنا الاستجابة للطّلبات التي تصل إلى هذا الرّابط بعرض صفحة HTML تحوي آخر 10 تدوينات مرتّبة وفق تاريخ كتابتها من الأحدث إلى الأقدم:
var Express = require("express");
var app = express();
app.get("/", function(request, response) {
// أرسل HTML
});
لندع إرسال الصّفحة جانبًا ولنفهم أسلوب استخدام Express، لكلّ تطبيق Express وظائف أربعة تُستخدم في استقبال وتوجيه الطّلبات، وهي get وpost وput وdel، وهذه الوظائف توافق أفعال HTTP الشّائعة. ولكن ما هي أفعال HTTP؟
كيف يعمل HTTP؟
في كلّ مرّة تزور صفحة على الويب فإنّ متصفّحك يرسل للخادوم الذي يستضيف الموقع طلبًا بالحصول (GET) على المحتوى في الرابط الذي كتبته، يكون طلب HTTP هذا مشابهًا للمثال التّالي (المُبسَّط عمدًا):
Accept: text/html
Accept-Language: ar-sy,ar;
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:34.0) Gecko/20100101 Firefox/34.0
تُسمّى الحقول Accept وAccept-Language... بترويسات الطّلب (Request Headings)، ولكلّ ترويسة معنىً بالنّسبة للخادوم الذي يستقبل الطّلب، فمثلاً يقوم المتصفّح في الحقل User-Agent بالتّعريف عن نفسه، وهو ما يسمح للخادوم بإرسال جواب مخصّص لكلّ متصفّح مثلاً (إن شاء)، وفي الحقل Accept-Language يُرسِل المتصفّح اللّغات الّتي يرغب المستخدم برؤية الجواب بها، فيقوم الخادوم بإرسالة الصّفحة بالعربيّة (سوريا) ar-sy في حالتنا إن توفّرت لديه، أو بالعربية ar كخيار ثانٍ... وهكذا. يردّ الخادوم على الطّلب بجواب HTTP (HTTP Response) الذي يُشبه مثالنا هذا:
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: 3918
<!DOCTYPE html>
<html lang="ar">
<head>
<title>مُدوّنتي - مرحبًا بالعالم!</title>
</head>
<body>
مرحبًا بكم في مدوّنتي المتواضعة!
</body>
</html>
السّطر الأوّل في الجواب يُسمّى سطر الحالة، ويتضمّن حالة الطّلب (حيث الرّقم 200 يعني أن الخادوم تلقّى الطّلب وردّ عليه بما هو متوقّع)، بقيّة السّطور هي ترويسات الجواب (Response Headings) الّتي تعني كلّ واحدة منها شيئًا ما لمستقبل الجواب (المتصفّح). يلي الترويسات متن الجواب (Response Body) الذي يحوي في حالتنا صفحة HTML الّتي سيقوم المتصفّح بعرضها على المستخدم.
فعل GET الذي استخدمناه ليس وحيدًا، فهناك أفعال أخرى مثل POST الذي يُستخدم في المتصفّح لإرسال الحقول التي يُعبّئها المستخدم (كتعبئة حقل تسجيل الدّخول)، والفعل DELETE الذّي يستخدم ليطلب من الخادوم حذف محتوى ما (مثل حذف تدوينة من قبل المستخدم). الجدير بالذّكر أن الخادوم حرّ التّصرّف بالطّلبات التي يتلقّاها، والطّريقة التي شرحناها بهذه الأفعال مبنيّة على التّقاليد الشّائعة لاستخدامها، فلا شيء في الحقيقة يمنع الخادوم من حذف تدوينة عندما يتلقّى طلب GET بدلاً من DELETE وإنّما هو عُرف متّفق عليه.
لنعد الآن إلى مثالنا السّابق، تقبل الوظيفة get مُعاملين أولهما الرّابط المطلوب التّعامل معه، والأخرى دالّة تقرأ الطّلب وتعدّل جوابه قبل إرسال الجواب للمُتصفّح، يمكن إرسال متن الجواب للمتصفّح من خلال الوظيفة send() للكائن response:
var Express = require("express");
var app = express();
app.get("/", function(request, response) {
var html = "<!DOCTYPE html><html lang='ar'>" +
"<head><title>مُدوّنتي!</title></head>" +
"<body>" + posts.map(function(post) { return "<li>" + post.title + "</li>"; }).join("") + "</body></html>"
response.send(html);
});
في الحالة الافتراضية سيكون جواب هذا الطّلب بالرّمز 200 OK مع متن يطابق محتوى المُتغيّر html. سنتعرّف فيما بعد على كيفيّة تغيير رموز الحالة بحيث نُرسل الرّمز الشّهير 404 Not Found عندما لا نجد تدوينة على الرّابط المطلوب.
سيتوقّف البرنامج في هذه الحالة مُعطيًا خطأ بسبب كون posts غير معرّف، كلّ ما علينا الآن هو جلب التّدوينات من خلال قاعدة البيانات وتخزينها ضمن المُتغيّر posts، نحتاج إذًا لتنفيذ استعلام MySQL لجلب أحدث التدوينات، ولهذا سنقوم باستيراد وحدة mysql التي قمنا بتثبيتها وتأمين الاتصال بقاعدة البيانات:
var Express = require("express");
var mysql = require("mysql");
var connection = mysql.createConnection({ host: "localhost", user: "root", password: "", database: "myblog" });
connection.connect();
var app = express();
app.get("/", function(request, response) {
connection.query( "SELECT * from `posts` ORDER BY date DESC LIMIT 10;", function(err, posts) {
if (err) throw err;
var html = "<!DOCTYPE html><html lang='ar'>" +
"<head><title>مُدوّنتي!</title></head>" +
"<body>" + posts.map(function(post) { return "<li>" + post.title + "</li>"; }).join("") + "</body></html>";
response.send(html);
});
});
ملاحظة: لا تنسَ تغيير اسم المستخدم وكلمة المرور ليتوافقا مع ما اخترته أثناء تثبيت MySQL. تمتلك وحدة mysql وظيفة createConnection() الّتي تُعيد لنا نسخة من اتّصال بقاعدة البيانات الّتي حدّدناها، والذي يمكن بدؤه باستدعاء الوظيفة connect() ثم تّنفيذ الاستعلامات query() الّتي تتمّ بأسلوب غير متزامن (asynchronous) لتُعيد لنا الصّفوف النّاتجة عن الاستعلام ضمن المعامل الثّاني للدّالة (function(err, posts) { ... }) الّتي تُستدعى بعد انتهاء الاستعلام.
بهذه السّطور القليلة التي يمكن فهمها بالقليل من الجهد تمكنّنا من إنشاء مدوّنة بسيطة، وهنا يبرز جمال Node.js الذي يسمح للمبتدئين بتطبيق أفكار قد تبدو بعيدة المنال وجعلها واقعًا ملموسًا!
الآن حان وقت تجربة المشروع، نحتاج لإخبار Express بالإنصات إلى الطّلبات الّتي ترد على منفذ معيّن على جهازنا (localhost):
var Express = require("express");
var mysql = require("mysql");
var connection = mysql.createConnection({ host: "localhost", user: "root", password: "", database: "myblog" });
connection.connect();
var app = express();
app.get("/", function(request, response) {
connection.query( "SELECT * from `posts` ORDER BY date DESC LIMIT 10;", function(err, posts) {
if (err) throw err;
var html = "<!DOCTYPE html><html lang='ar'>" +
"<head><title>مُدوّنتي!</title></head>" +
"<body>" + posts.map(function(post) { return "<li>" + post.title + "</li>"; }).join("") + "</body></html>";
response.send(html);
});
});
app.listen(3000);
لبدء البرنامج، افتح الطّرفيّة وانتقل إلى مجلّد المشروع، ثم نفّذ الأمر التّالي:
node index.js
home-page.thumb.jpg.e3e3fe8ba940095f97c0
قد تبدو الصّفحة غاية في البساطة وخالية من أي عُنصر جماليّ، لكنّ ما يهمّنا الآن هو أنّنا قمنا بإنشاء خادوم يتواصل مع قاعدة بيانات ويعرض النّتائج على المستخدم... كلّ هذا في 16 سطرًا من JavaScript!
لإنهاء البرنامج عُد إلى الطّرفيّة ذاتها واضغط Ctrl C.
تعرّف على لغة القوالب Jade
بعد أن تأكدنا من تنفيذ المكوّن الرئيسيّ لمشروعنا، سنعمل على تحسين شيفرتنا لجعلها أكثر بساطة وقابلة للتّطوير بسهولة فيما بعد. إذا ألقينا نظرةً على آخر ما كتبناه، سرعان ما نكتشف التّعقيد الذي ستصل إليه شيفرتنا إن أردنا إضافة المزيد من المزايا ضمن HTML، لأنّ هذا يعني إضافة المزيد من النّصّ إلى المتغيّر html بحيث يصبح طويلاً جدًّا وصعب القراءة؛ لا بدّ أن توجد طريقة أفضل من هذه!
تتوفّر في كلّ اللّغات طريقة لتوليد صفحات HTML ديناميكيّة على الخادوم، بمعنى أنّه يمكن تغيير بعض محتوياتها وإدخال محتوى مُتغيّر فيها قبل إرسالها إلى المستخدم، هل تساءلت يومًا كيف يعرض فيس بوك لكلّ مستخدم صفحةً خاصّة به؟ بحيث يكون هيكلها متماثلاً لكلّ المستخدمين ولكن محتواها من الأخبار مختلف من مستخدم لآخر، الجواب هو باستخدام القوالب؛ لن نقوم بإنشاء فيس بوك جديد الآن، لكنّنا سنستفيد من ميزات القوالب الدّيناميكيّة لتوليد HTML بدلًا من كتابتها يدويًّا ضمن شيفرتنا!
في عالم Node.js ستجد الكثير من لغات القولبة، لكنّ الامتداد الطّبيعيّ لاستخدام Express يكون باعتماد Jade كلغة قولبة كونها بدأت من المُطوّر ذاته، لنُعد كتابة HTML الصّفحة الرّئيسيّة لمدوّنتنا باستخدام Jade:
doctype html
html(lang="ar")
head
title "مُدوّنتي!"
body
for post in posts
li #{ post.title }
قارن بين نصّ HTML ونصّ Jade الأخير، أوّل ما نلاحظه في Jade هو بساطة صياغتها، فهي تلغي الوسوم النّهائيّة (مثل </head> و</body>) وتستعيض عن ذلك بكونها حسّاسة للمحاذاة، فكون الوسم title مُزاحًا إلى يمين head يعني أنّه محتوىً ضمنه، وكذلك الأمر بالنّسبة لـbody، نلاحظ كذلك دعم Jade للحلقات والمُتغيّرات، وهي من أبرز مزايا لغات القوالب، لأنها تسمح بتوليد عناصر متكرّرة دون الحاجة لكتابتها يدويًّا.
سنحتاج أوّلًا لتثبيت Jade وحفظه في متطلّبات المشروع:
npm install jade --save
احفظ شيفرة Jade السابقة في ملفّ home.jade ضمن مجلّد جديد سمّه views داخل مُجلّد المشروع، ثمّ عُد للملفّ index.js، ولنقم باستخدام Jade عوضًا عن الأسلوب السابق:
var Express = require("express");
var mysql = require("mysql");
var connection = mysql.createConnection({ host: "localhost", user: "root", password: "", database: "myblog" });
connection.connect();
var app = express();
app.set("view engine", "jade");
app.get("/", function(request, response) {
connection.query( "SELECT * from `posts` ORDER BY date DESC LIMIT 10;", function(err, posts) {
response.render("home", { posts: posts });
});
});
app.listen(3000);
ضبطنا الإعداد view engine في Express إلى القيمة "jade"، يستخدم Express هذا الإعداد عندما يُطلب منه عرض ملفّ ما باستخدام الوظيفة render التّابعة لكائن الجواب response، بحيث يبحث عن مُفسّر لغة القوالب (jade في حالتنا) ويطلب منه تحويل الملفّ "home" إلى HTML، مُمرّرًا له الكائن الذي يحوي المتغيّرات الّتي يحتاجها ({ posts: posts }). يبحث Express عن ملفّات العرض في المجلّد views بشكل افتراضيّ، وهو ما قمنا بإنشاءه للتّوّ.
لنقم الآن بتعديل القالب home.jade ليبدو بشكل أجمل:
doctype html
html(lang="ar", dir="rtl")
head
title "مُدوّنتي!"
body
style
:css
body {
font-family: Arial, sans-serif;
}
h1 مُدوّنتي
hr
for post in posts
h2 #{ post.title }
p #{ post.body }
small بتاريخ #{ post.date }
قمنا بتغيير اتّجاه النّص لجعله من اليمين إلى اليسار عبر الخاصة "dir"، ثمّ أدخلنا بعض التنسيق من خلال الوسم "<style>" في HTML، تسمح Jade بكتابة لغات أخرى ضمن القالب مثل كتابة CSS وCoffeeScript أو Markdown أو Sass عبر الصّياغة :language ليتم تحويلها إلى اللّغة المناسبة للمتصفّح إن تطلّب الأمر، وفي هذه الحالة أدخلنا CSS بسيط (الذي لا يحتاج للتّحويل) بكتابة :css قبل الشّيفرة. سنتعرّف على مزيد من مزايا Jade خلال عملنا.
home-page-jade.jpg.31a7f07087531af44685d
تبدو مدوّنتنا بشكل أجمل الآن، لكنّها بالتأكيد تحتاج المزيد من العمل! يمكننا تحسين عرض صيغة التّاريخ باستخدام مكتبة moment للتّعامل مع التّواريخ والوقت، سنحتاج أولاً إلى تثبيتها وحفظها في متطلّبات المشروع:
npm install --save moment
سنُدخل التّعديلات اللّازمة على الملفّين index.js وhome.jade:
var Express = require("express");
var mysql = require("mysql");
var moment = require("moment");
moment.locale("ar");
var formatDate = function(date) {
return moment(new Date(date)).fromNow();
}
var connection = mysql.createConnection({ host: "localhost", user: "root", password: "", database: "myblog" });
connection.connect();
var app = express();
app.set("view engine", "jade");
app.get("/", function(request, response) {
connection.query( "SELECT * from `posts` ORDER BY date DESC LIMIT 10;", function(err, posts) {
response.render("home", { posts: posts, formatDate: formatDate });
});
});
app.listen(3000);
doctype html
html(lang="ar", dir="rtl")
head
title "مُدوّنتي!"
body
style
:css
body {
font-family: Arial, sans-serif;
}
h1 مُدوّنتي
hr
for post in posts
h2 #{ post.title }
p #{ post.body }
small كُتِبَت #{ formatDate(post.date) }
يمكن تمرير الدّوال (functions) إلى Jade كما نُمرّر المتغيّرات، وفي حالتنا قمنا بتعريف دالّة تقوم بتنسيق التّاريخ الذي تتلقاه بصياغة نسبيّة (منذ كذا يومًا، منذ ساعتين...) وذلك بالاستفادة من مكتبة moment التي استوردناها وعيّنّا لغة التّاريخ فيها إلى العربيّة. أجرينا التغييرات اللازمة في Jade مستخدمين الدّالة التي فرضناها وأصبحت متوفّرة ضمن القالب:
home-page-jade-moment.jpg.fd577f4d6006b5
إنشاء صفحة التدوينة
/posts/hello-world
/posts/quotes-1
/posts/quotes-2
/posts/quotes-3
الثّابت بين هذه الرّوابط هو اعتمادها على الحقل slug الّذي أدخلناه في كلّ سطر في جدول التّدوينات. من غير المنطقيّ أن نُسجّل رابطًا لكلّ تدوينة على حدة في Express، وسيصبح هذا مستحيلاً مع إنشاء تدوينات جديدة. يوفّر Express آليّة للإجابة على الطّلبات الواردة على الروابط التي تطابق نمطًا معيّنًا، وهو في حالتنا /posts/ متبوعًا بحقل متغيّر slug، أو /posts/:slug بصياغة Express، سنضيف الشيفرة التالية إلى برنامجنا (قبل آخر سطر):
app.get("/posts/:slug", function(request, response) {
var slug = request.params.slug;
connection.query("SELECT * from `posts` WHERE slug = ?", [ slug ], function(err, rows) {
var post = rows[];
response.render("post", { post: post, formatDate: formatDate });
});
})
التعديل الأخير تم بواسطة مهرة النجدية ; 24-02-2019 الساعة 11:35 PM
|