full_stack_101 أساسيات بناء تطبيقات الويب


ابراهيم الخضور

قبل أن نبدأ البرمجة، سنستعرض بعض المبادئ المعتمدة في تطوير الويب من خلال الاطلاع على هذا التطبيق النموذجي عبر الرابط: fullstack-exampleapp.herokuapp.com.

صممت هذه التمارين لتوضيح بعض المفاهيم الأساسية في المنهاج، وليس لتقديم الطريقة التي ينبغي أن تبنى بها تطبيقات الويب. فهي على العكس تمامًا، ستشرح بعض التقنيات القديمة في تطوير الويب والتي يمكن أن تعتبر في أيامنا هذه عادات برمجية سيئة. مع هذا ستتعلم كتابة الشيفرة بالأسلوب الأنسب في القسم 1.

استعمل متصفح Chrome الآن ودائمًا خلال رحلتك في هذا المنهاج. ثم استخدمه في فتح التطبيق النموذجي، قد يستغرق ذلك وقتًا. القاعدة الأولى في تطوير الويب: ابق طرفية التطوير (Development Console) مفتوحةً دومًا في متصفحك. إن كنت تعمل على نظام التشغيل Windows اضغط زر F12 أو أزرار ctrl-shift-i معًا لفتحه. أما في نظام التشغيل MacOS فاضغط زر F12 أو الأزرار Option-cmd-i معًا.

قبل المتابعة، اكتشف طريقة فتح طرفية التطوير على حاسوبك، وتذكر أن تبقيها مفتوحةً دومًا عندما تعمل على تطوير تطبيقات الويب. ستظهر طرفية التطوير بالشكل التالي:

console_001.png

تأكد أن نافذة (Network) مفتوحة، ثم ألغ تفعيل خيار (Disable Cache).

إن حفظ سجل العمل (log) قد يكون مفيدًا، إذ سيحفظ سجلات تنفيذ التطبيق عند إعادة تحميل الصفحة.

لاحظ جيدًا: إن أكثر النوافذ أهميةً في الطرفية هي (Network)، و سنستخدمها بكثرة في البداية.

الطلبية من النوع HTTP GET

يتواصل الخادم (Server) مع المتصفح باستخدام بروتوكول HTTP، وتعرض نافذة (Network) تفاصيل هذا التواصل. فعندما نطلب إعادة تحميل الصفحة ( الزر F5 أو الرمز على المتصفح)، ستُظهر الطرفية أن حدثين قد وقعا:

  • جلب المتصفح محتوى صفحة التطبيق من الخادم.

  • نزّل المتصفح الصورة Kuva.png.

network_window_002.png]

عليك تكبير شاشة الطرفية لترى ذلك إن كانت شاشة العرض صغيرة. ستظهر لك معلومات أكثر عند النقر على الحدث الأول:

network_window_headers_003.png

يظهر القسم العلوي (General)، أن المتصفح قدم طلبًا إلى العنوان: https://fullstack-exampleapp.herokuapp.com مستخدما أسلوب GET، وأن الطلب كان ناجحًا لأن الخادم استجاب معيدًا رمز حالة (Status Code) قيمته 200.

توجد العديد من الترويسات (Headers) للطلب والاستجابة، يُظهر الشكل التالي بعضها:

headers_004.png

تعطينا ترويسات الاستجابة (Response headers) معلومات عدة، مثل حجم الاستجابة مقدرة بالبايت والوقت الدقيق لها. كذلك الأمر تخبرنا ترويسة نوع المحتوى (Content_Type) أن الاستجابة كانت على شكل ملف نصي بصيغة utf-8، وتحدد المحتوى الذي سيظهر بتنسيق HTML. وبهذا يعلم المتصفح أن الاستجابة ستكون على شكل صفحة HTML قياسية، ليعرضها كصفحة ويب.

تُظهر نافذة (Response) بيانات الاستجابة على شكل صفحة HTML. حيث يحدد القسم Body هيكل الصفحة التي ستُعرض على الشاشة. وتحوي صفحة HTML أيضًا العنصر Div الذي يضم عناصر أخرى مثل معرّف العنوان h1 والرابط a إلى صفحة الملاحظات ومعّرف الصورimg وأخيرًا معرّف الفقرة p الذي يُظهر في مثالنا عدد الملاحظات التي تم إنشاؤها.

response_window_005.png

ونظرًا لوجود معرّف الصور img، أرسل المتصفح طلب HTTP جديد إلى الخادم لإحضار الصورة التي عنوانها kuva.png. يُظهر الشكل التالي تفاصيل الطلب:

headers_image_fetch_006.png

أُرسل طلب HTTP-GET إلى العنوان: https://fullstack-exampleapp.herokuapp.com/kuva.png وتخبرنا ترويسات الاستجابة أن حجم الاستجابة يعادل 89350 بايت، ومحتواها صورة بصيغة png. يستخدم المتصفح هذه المعلومات لإظهار الصورة بشكل صحيح على الشاشة.

يمثل المخطط التتابعي التالي، سلسلة الأحداث التي بدأت بفتح الصفحة https://fullstack-exampleapp.herokuapp.com:

follow_diagram_007.png

أرسل المتصفح في البداية، طلبًا إلى الخادم من نوع HTTP-GET للحصول على شيفرة HTML للصفحة. يخبر معرّف الصور img الموجود في شيفرة HTML المتصفح أن يحضر صورة بعنوان kuva.png. ثم يعالج المتصفح شيفرة HTML والصورة ويظهرهما على الشاشة. مع ذلك يصعب ملاحظة أن معالجة صفحة HTML بدأت قبل أن يحضر المتصفح الصورة من الخادم.

تطبيقات الويب التقليدية

يماثل عمل صفحة التطبيق النموذجي السابق عمل تطبيقات الويب التقليدية. فعند الدخول إلى الصفحة، يحضر المتصفح من الخادم ملف HTML الذي يُنسِّق طريقة عرض الصفحة ومحتواها النصي.

لقد بنى الخادم هذا المستند بطريقة ما. فقد يكون المستند عبارة عن مستند نصي ذي محتوًى ثابت محفوظ على الخادم. كما يمكن للخادم أيضًا أن يبني مستند HTML ديناميكيًا وفقًا للتعليمات الموجودة في شيفرة التطبيق مستخدمًا على سبيل المثال، بيانات موجودة في قاعدة بيانات محددة. فشيفرة ملف HTML في التطبيق النموذجي السابق مثلًا، كُتبت ديناميكيًا من قبل الخادم، كونه احتوى معلومات تدل على عدد الملاحظات التي دونت في الصفحة.

تظهر شيفرة HTML لصفحة التطبيق النموذجي بالشكل التالي:

const getFrontPageHtml = (noteCount) => {
  return(`
    <!DOCTYPE html>
    <html>
      <head>
      </head>
      <body>
        <div class='container'>
          <h1>Full stack example app</h1>
          <p>number of notes created ${noteCount}</p>
          <a href='/notes'>notes</a>
          <img src='kuva.png' width='200' />
        </div>
      </body>
    </html>
`)
} 

app.get('/', (req, res) => {
  const page = getFrontPageHtml(notes.length)
  res.send(page)
})

بالطبع ليس عليك استيعاب الشيفرة المكتوبة حاليًا.

يُحفظ محتوى صفحة HTML على شكل قالب نصي (Template String) أو على شكل سلسلة نصية تسمح على سبيل المثال، بتحديد قيمة المتغيرات الموجودة ضمنها. فالقسم الديناميكي (القابل للتغيير) في صفحة التطبيق النموذجي السابق هو القسم الذي يشير إلى عدد الملاحظات المدونة و يدعى notecount. يُستبدَل هذا القسم بالعدد الفعلي للملاحظات notes.length ضمن القالب النصي للصفحة. لاحظ أن وجود شيفرة HTML ضمن الشيفرة السابقة هو أمر غير مُحبَّذ، لكن مبرمجي مدرسة PHP التقليدية يعتبرون ذلك شيئًا طبيعيًا.

يُعتبَر المتصفح في تطبيقات الويب التقليدية "محايدًا". فوظيفته فقط إحضار بيانات HTML من الخادم الذي يقع على عاتقه القيام بكل العمليات المنطقية التي يحتاجها التطبيق. يمكن بناء الخادم باستعمال لغة Java Spring كالخادم الذي استخدم في منهاج Web-palvelinohjelmointi لجامعة هلسنكي، أو استعمال لغة Python Flask كمنهاج tietokantasovellus ، أو باستعمال Ruby and Rails. سيستعمل التطبيق السابق Express من Node.j.s، وسيستخدم هذا المنهاج التقنيتين السابقتين لبناء الخادم.

تشغيل التطبيق من المتصفح

ابق طرفية التطوير مفتوحةً، واحذف ما فيها بالنقر على ?. عندما تنقر الآن على رابط notes، سيرسل المتصفح أربع طلبات HTTP:

request_types_008.png

لهذه الطلبات أنواع مختلفة. ويمثل النوع (مستند document)، شيفرة HTML للصفحة ويظهر بالشكل التالي:

request_type_document_009.png

الآن، عندما نقارن الصفحة الظاهرة على المتصفح مع شيفرة HTML التي يعيدها الخادم، سنلاحظ أن الشيفرة لا تحتوي على قائمة الملاحظات. تحتوي ترويسة القسم html من الشيفرة على العنصر script الذي يطلب من المتصفح إحضار ملف JavaScript يدعى main.js. تظهر شيفرة JavaScript كالتالي:

var xhttp = new XMLHttpRequest()

xhttp.onreadystatechange = function() {
  if (this.readyState == 4 && this.status == 200) {
    const data = JSON.parse(this.responseText)
    console.log(data)

    var ul = document.createElement('ul')
    ul.setAttribute('class', 'notes')

    data.forEach(function(note) {
      var li = document.createElement('li')

      ul.appendChild(li)
      li.appendChild(document.createTextNode(note.content))
    })

    document.getElementById('notes').appendChild(ul)
  }
}

xhttp.open('GET', '/data.json', true)
xhttp.send()

لا تهمنا حاليًا التفاصيل الواردة في الشيفرة السابقة، لكن جزء منها قد كتب لتجميل مظهر الصور والنصوص (من المرجح أن نبدأ كتابة شيفرات في القسم 1). ونلفت انتباهك أن كتابة الشيفرة السابقة لا تتناسب إطلاقًا وتقنيات كتابة الشيفرات التي ستتعلمها في هذا المنهاج.

قد يتساءل البعض عن مغزى استخدام كائن xhttp بدلًا من الأسلوب الحديث لجلب البيانات. إن السبب الرئيسي هو أننا لانريد الآن إظهار ما وعدنا به، و أيضًا للدور الثانوي الذي تلعبه هذه الشيفرة في هذا القسم من المنهاج. لكننا سنطبق في القسم الثاني الطرق الأحدث في إرسال الطلبات إلى الخادم.

بعد إحضار ماطلبه عنصرscript مباشرةً، يبدأ المتصفح بتنفيذ الشيفرة. حيث يشير السطرين الأخيرين إلى أن المتصفح سيرسل طلبا من نوع HTTP-GET إلى العنوان التالي data.json/ على الخادم:

xhttp.open('GET', '/data.json', true)
xhttp.send()

سنكون بذلك قد وصلنا إلى آخر طلب سيظهر ضمن النافذة (Network). يمكننا بطبيعة الحال الوصول إلى هذا العنوان مباشرة من نافذة المتصفح كما في الشكل التالي:

json_data_in_browser_010.png

سنحصل عند فتح العنوان السابق على الملاحظات مكتوبة بصيغة بيانات غير معالجة (raw data) كالتي يستخدمها JSON. لا يمكن للمتصفح في الحالة الطبيعية إظهار هذه البيانات بشكل واضح، لذلك يمكن تثبيب إضافة (Plugin) للتعامل مع هذه الصيغة من البيانات. ثبت على سبيل المثال JSONView على متصفح Chrome ثم أعد تحميل الصفحة. ستظهر البيانات الآن بشكل مفهوم.

rawdata_jsonview_011.png

إذًا، تُحمِّل شيفرة HTML في صفحة الملاحظات Notes بيانات JSON تحتوي على الملاحظات، وترتبها على شكل قائمة نقاط. ويتم ذلك بتنفيذ الشيفرة التالية:

const data = JSON.parse(this.responseText)
console.log(data)

var ul = document.createElement('ul')
ul.setAttribute('class', 'notes')

data.forEach(function(note) {
  var li = document.createElement('li')

  ul.appendChild(li)
  li.appendChild(document.createTextNode(note.content))
})

document.getElementById('notes').appendChild(ul)

تُنشأ الشيفرة بداية قائمة غير مرتبة ul كما يلي:

var ul = document.createElement('ul')
ul.setAttribute('class', 'notes')

ثم تضيف العنصرli (الذي يمثل عنصرًا من قائمة نقاط) لكل ملاحظة، وتضع بداخله محتويات الحقل content فقط من الملاحظة. أما القيم الزمنية الموجودة في الحقل date، فلن تستخدم هنا.

data.forEach(function(note) {
  var li = document.createElement('li')

  ul.appendChild(li)
  li.appendChild(document.createTextNode(note.content))
})

افتح الآن النافذة Console ضمن طرفية التطوير كالتالي:

console_window_012.png

سيظهر النص كاملًا عند النقر على المثلث الصغير في بداية السطر:

console_window_log_013.png

نتج النص السابق عن تعليمة console.log الموجودة في الشيفرة التالية:

const data = JSON.parse(this.responseText)
console.log(data)

وبالتالي ستطبع الشيفرة تلك البيانات في الطرفية بعد استقبالها من الخادم. ستعتاد التعامل مع نافذة Console ومع تعليمة Console.log خلال مسيرتك في هذا المنهاج.

معالج الأحداث (Event Handler) و دوال رد النداء (Callback functions)

ستبدو لك بنية هذه الشيفرة غريبةً نوعا ما:

var xhttp = new XMLHttpRequest()

xhttp.onreadystatechange = function() {
  الشيفرة التي ستعالج استجابة الخادم//
}

xhttp.open('GET', '/data.json', true)
xhttp.send()

لقد أُرسِل الطلب إلى الخادم في آخر سطر من الشيفرة، لكن التعليمات التي تعالج الاستجابة أتت في البداية. ما الذي يحدث؟ في هذا السطر:

xhttp.onreadystatechange = function () {

تم تحديد معالج (handler) للحدث onreadystatechange، الخاص بالكائن xhttp، أثناء إرسال الطلب إلى الخادم. فعندما تتغير حالة هذا الكائن، يستدعي المتصفح الدالة التي تمثل معالج هذا الحدث، ثم تتحق الدالة من أن قيمة الخاصة 'readystate' تساوي 4 (والتي تشير إلى أن العملية قد اكتملت)، وأن قيمة رمز الحالة (status code) لبروتوكول HTTP عند الاستجابة تساوي 200.

xhttp.onreadystatechange = function() {
  if (this.readyState == 4 && this.status == 200) {
       الشيفرة التي ستعالج استجابة الخادم// 
  }
}

إن هذا الأسلوب في استدعاء معالجات الأحداث شائع جدًا في لغة JavaScript، وتسمى دالة معالج الأحداث (نداء callback). لا تستدعي شيفرة التطبيق الدوال بنفسها، بل يستدعي المتصفح الذي يعتبر (بيئة التشغيل) في هذه الحالة، الدوال في الوقت المناسب وذلك عندما يقع الحدث.

نموذج كائن المستند (Document Object Model DOM)

يمكنك التفكير بصفحة HTML على أنها بنًى شجرية متداخلة.

html
  head
    link
    script
  body
    div
      h1
      div
        ul
          li
          li
          li
      form
        input
        input

يمكن مشاهدة بنًى كهذه ضمن الطرفية /نافذة Elements:

console_elements_014.png

تعتمد آلية عمل المتصفح على فكرة تمثيل عناصر HTML على شكل شجرة. وبما أن نموذج كائن المستند DOM هو واجهة تطبيقات برمجية API، قادرة على تعديل العناصر الشجرية لصفحة الويب برمجيًا، فقد استخدمت شيفرة JavaScript التي رأيناها سابقًا الواجهة DOM-API لإضافة لائحة بالملاحظات المدونة في الصفحة.

تضيف الشيفرة التالية عقدة جديدة للمتغيِّر ul، ثم تضيف عقدًا ضمنه (عقد أبناء):

var ul = document.createElement('ul')

data.forEach(function(note) {
  var li = document.createElement('li')

  ul.appendChild(li)
  li.appendChild(document.createTextNode(note.content))
})

وأخيرًا، بعد اكتمال فرع الشجرة الذي يمثله المتحول ul، سنضمه إلى شجرة HTML الممثلة لكامل صفحة الويب في مكانه المناسب.

document.getElementById('notes').appendChild(ul)

إجراء التعديلات على DOM من الطرفية

يمكننا القيام بالعديد من العمليات على صفحة الويب باستخدام واجهة DOM-API. تدعى العقدة الأعلى في شجرة ملف HTML، كائن document. وللوصول إلى كائن مستند صفحة الويب من الطرفية اكتب كلمة document في النافذة Console.

console_document_015.png

لنضف ملاحظة جديدة إلى الصفحة باستخدام الطرفية. دعونا نستعرض أولًا قائمة الملاحظات الموجودة في أول عنصر ul تحتويه الصفحة:

list = document.getElementsByTagName('ul')[0]

بعد ذلك سننشئ عنصر li جديد ونكتب نصًا ما داخله:

newElement = document.createElement('li')
newElement.textContent = 'Page manipulation from console is easy'

وأخيرًا سنضيف العنصر الجديد إلى القائمة:

list.appendChild(newElement)

console_document_016.png

على الرغم من أن محتوى الصفحة قد تغير على متصفحك، إلا أنَّ هذا التغيير مؤقت. فلو حاولت إعادة تحميل الصفحة ستختفي التغيرات التي أجريتها، ذلك أن التغيرات لم ترفع إلى الخادم. إذًا، ستنشئ شيفرة JavaScript التي أحضرها المتصفح، قائمة بالملاحظات الموجودة فقط في العنوان https://fullstack-exampleapp.herokuapp.com/data.json

صفحات الأنماط الانسيابية Cascading Style Sheets CSS

يحتوي عنصر head في ملف HTML المرتبط بالصفحة Notes، عنصر ارتباط Link، يخبر المتصفح ضرورة إحضار ملف CSS من العنوان main.css. وتعتبر ملفات CSS لغة وصفية (Markup) تستخدم بكثرة في التحكم بمظهر صفحات الويب.

يظهر ملف CSS الذي سيحضره المتصفح كالتالي:

css
.container {
  padding: 10px;
  border: 1px solid; 
}

.notes {
  color: blue;
}

يعرّف الملف محددي صنف (Class Selectors). يستخدم محدد الصنف لاختيار أجزاء من صفحة الويب، يطبق عليها قواعد محددة للتحكم بمظهرها. يبدأ سطر تعريف محدد الصنف بنقطة (.)، يليها اسم لهذا الصنف. وتعتبر هذه الأصناف (خاصيات attributes) يمكن إضافتها إلى عناصر HTML للتحكم بمظهر هذه العناصر.

يمكن تفحص خاصيات CSS ضمن نافذة Elements في الطرفية:

console_elements_css_017.png]

لاحظ أن العنصر div يمتلك الصنف container، بينما يمتلك العنصر ul الصنف notes. وبالعودة إلى ملف CSS، فإن أي عنصر يمتلك الصنف container سيُحاط بمربع مستمر سماكته 1 بكسل وسيُضبط بعد العنصر عن محيطه (padding) بمقدار 10 بكسل، وأي عنصر يمتلك الصنف notes سيكون النص فيه أزرق اللون.

يمكن لعناصر HTML امتلاك خاصيات أخرى غير classes. فالعنصر div الذي يحتوي الملاحظات notes، يمتلك خاصية تدعىid. تستخدم JavaScript هذه الخاصية لإيجاد العنصر.

يمكن استخدام النافذة Elements في الطرفية لتغيير مظهر العناصر. وكما هي الحال لن تكون التغييرات التي نجريها دائمة. إن أردت تغييرات دائمة، عليك أن تحفظها في مستند CSS الموجود على الخادم.

console_elements_css_change_018.png

تحميل صفحة تحتوي على شيفرة JavaScript (نظرة ثانية)

دعونا نعيد النظر إلى ما يجري عندما نفتح الصفحة https://fullstack-exampleapp.herokuapp.com/notes من خلال المتصفح.

  • يحضر المتصفح شيفرة HTML التي تحدد هيكل ومحتوى الصفحة من الخادم عبر طلب HTTP-GET.

  • تطلب العناصر link في شيفرة HTML من المتصفح إحضار ملف CSS عنوانه main.css وكذلك ملف JavaScript عنوانه main.js من الخادم.

  • ينفذ المتصفح شيفرة JavaScript.

  • ترسل الشيفرة طلب HTTP-GET إلى العنوان https://fullstackexampleapp.herokuapp.com/data.json الذي سيستجيب بإرسال الملاحظات على شكل بيانات JSON.

  • عندما تصل البيانات، سينفذ المتصفح الشيفرة الموجودة ضمن معالج حدث، وهذا الأخير سيظهر الملاحظات على صفحة الويب مستخدمًا الواجهة DOM-API.

follow_diagram_requests_019.png

الاستمارات Forms وطلبات HTTP-POST

دعونا نتفحص الآن كيف أُضيفت ملاحظة جديدة. تحتوي الصفحة Notes على عنصر استمارة Form، عندما ننقر على الزر الموجود في الاستمارة، سيرسل المتصفح المعلومات التي أدخلها المستخدم إلى الخادم.

form_020.png

لنفتح النافذة Network ولنرى كيف يتم إرسال الاستمارة:

form_send_021.png

نتج عن إرسال الاستمارة خمس طلبات HTTP. يمثل الأول حدث إرسال الاستمارة (Form Submit Event)، وهو طلب HTTP-POST إلى العنوان new_note الموجود على الخادم. سيستجيب الخادم برمز حالة رقمه 302، ويعني هذا أن عملية تحويل عنوان (URL-redirect) قد تمت. في هذه العملية يطلب الخادم من المتصفح إن يرفع طلب HTTP-GET جديد إلى العنوان notes الذي نجده ضمن الموقع Location الذي يمثل جزءًا من ترويسة الاستجابة التي أرسلها الخادم. سيعيد الخادم إذًا، تحميل صفحة الملاحظات. وتسبب تلك العملية ثلاثة طلبات HTTP:

  • إحضار ملف CSS باسم main.css

  • إحضار ملف JavaScript باسم main.js

  • إحضار ملف البيانات data.json

form_send_requests_022.png

يمكن أن نشاهد البيانات التي أرسلت أيضًا في نافذة Network:

form_send_requests_network_023.png

يمتلك معرّف الاستمارةForm خاصيتي action وmethod اللتان تشيرا إلى أن إرسال الاستمارة سيكون على شكل طلب HTTP-POST إلى العنوان new_notes.

form_attributes_024.png

تعتبر الشيفرة الموجودة على الخادم والمسؤولة عن الاستجابة إلى طلب المتصفح بسيطة (لاحظ: هذه الشيفرة موجودة على الخادم، وليست جزءًا من ملف JavaScript الذي يحضره المتصفح):

app.post('/new_note', (req, res) => {
  notes.push({
    content: req.body.note,
    date: new Date(),
  })

  return res.redirect('/notes')
})

ترسل البيانات ضمن جسم (Body) طلب الإرسال (Post-Request) حيث يتمكن الخادم عندها من الوصول إلى البيانات الموجودة في جسم كائن من النوع Request يدعى req باستعمال الطريقة req.body. ومن ثم ينشأ الخادم كائنًا جديدًا من النوع note، ويضيفه إلى مصفوفة تدعى notes:

notes.push({
  content: req.body.note,
  date: new Date(),
})

يضم الكائن note حقلين، يدعى الأول content يحوي نص الملاحظة، ويدعى الآخرdate ويحوي معلومات عن تاريخ ووقت إنشاء الملاحظة. طبعًا، لم يحفظ الخادم هذه الملاحظة ضمن قاعدة بيانات، وبالتالي ستختفي الملاحظة الجديدة بانتهاء جلسة العمل.

تقنية AJAX

اعتمدت صفحة Notes في التطبيق السابق أسلوبًا قديمًا في تطوير الويب، بالإضافة إلى استخدامه تقنية AJAX التي تصدرت قائمة تقنيات الويب بداية العقد الماضي.

تأتي التسمية AJAX من (Asynchronous JavaScript and XML). قدم هذا المصطلح في فبراير (شباط) عام 2005، على خلفية التطورات في تقنية المتصفح، لتقدم مقاربةً ثوريةً جديدةً تمكن المتصفح من إحضار محتوى صفحات الويب باستخدام شيفرة JavaScript مدمجة ضمن HTML، دون الحاجة لإعادة تكوين (Rerender) الصفحة من جديد.

استخدمت جميع تطبيقات الويب أسلوب التطبيق النموذجي الذي استعرضناه سابقًا قبل ظهور Ajax. فجميع البيانات التي تعرض في الصفحة، يتم إحضارها عند تنفيذ شيفرة HTML التي أنشأها الخادم. استخدمت صفحة Note تقنية AJAX لإحضار البيانات، بينما استخدمت الطريقة التقليدية في إرسال الاستمارة. وتعكس العناوين التي استخدمها التطبيق أسلوب الزمن القديم اللامبالي، حيث أحضرت بيانات JSON من العنوان: https://fullstack-exampleapp.herokuapp.com/data.json وأرسلت الملاحظات الجديدة إلى العنوان: https://fullstack-exampleapp.herokuapp.com/new_note. لاتعتبر هذه العناوين مقبولة حاليًا، كونها لا تتقيد بالتفاهمات العامة التي جرى الاتفاق عليها بخصوص واجهة التطبيقات RESTful التي سنراها في القسم 3. شاع مصطلح AJAX كثيرًا لدرجة أنه اعتبر من المسلمات، ثم تلاشى في عالم النسان ولم يعد يسمع به أحد من الأجيال الجديدة.

تطبيق من صفحة واحدة

تسلك الصفحة الرئيسية في المثال النموذجي الذي عملنا عليه سلوك صفحة الويب التقليدية. حيث تجري العمليات المنطقية للتطبيق على الخادم، بينما يكّون المتصفح ملف HTML بمقتضى الشيفرة المكتوبة. أما صفحة Notes، فتلقي على المتصفح بعض المسؤولية في توليد شيفرة HTML لعرض الملاحظات الموجودة. إذ ينفذ شيفرة JavaScript التي أحضرها من الخادم، وستحضر هذه الشيفرة بدورها الملاحظات على شكل بيانات JSON، ثم يضيف إليها المتصفح عناصر HTML لعرض الملاحظات على الصفحة مستخدمًا DOM-API.

سادت فكرة تصميم تطبيق من صفحة واحدة (Single-page application (SPA، في السنوات الأخيرة. فمواقع الويب التي تستخدم هذا الأسلوب، لاتحضر صفحاتها من الخادم بشكل منفصل كما فعل التطبيق النموذجي السابق، بل تضم جميع الصفحات في صفحة HTML واحدة وتحضرها، ويتم التعامل معها لاحقًا بواسطة شيفرة JavaScript التي تُنفَّذ ضمن المتصفح.

تحمل صفحة Notes في تطبيقنا السابق شبهًا بتطبيقات الويب ذات الصفحة الواحدة، لكنها لم تبلغ هذا المستوى بعد. فبالرغم من أن منطق تكوين الملاحظات ينفذه المتصفح، لاتزال الصفحة تستخدم الطريقة التقليدية في إضافة الملاحظات. حيث ترسل الاستمارة البيانات إلى الخادم، الذي يوجه المتصفح لإعادة تحميل الصفحة مع إعادة توجيه نحو عنوان جديد.

يحوي العنوان التالي https://fullstack-exampleapp.herokuapp.com/spa نسخة الصفحة الواحدة من تطبيقنا النموذجي. حيث نلاحظ للوهلة الأولى تطابق الطريقتين. فللطريقتين شيفرة HTML نفسها، لكن ملف JavaScript مختلف ويدعى (spa.js)، وكذلك حصل تغيير بسيط في كيفية استخدام معرّف الاستمارة Form، فلا تمتلك الاستمارة الآن خاصيات method و action لتحدد كيف وأين سترسل البيانات المدخلة ضمنها.

spa_form_025.png

افتح نافذة Network، وامسح محتواها بالنقر على الرمز ?. ستلاحظ أن طلبًا واحدًا فقط أرسل إلى الخادم عند إنشاء ملاحظة جديدة.

spa_form_request_026.png

يتضمن طلب الإرسال POST إلى العنوان new_note_spa ملاحظة جديدة بصيغة بيانات JSON، تضم محتوى الملاحظة content وتوقيتها date:

{
  content: "single page app does not reload the whole page",
  date: "2019-05-25T15:15:59.905Z"
}

يفهم الخادم أن البيانات الواردة إليه مكتوبة بصيغة JSON، عن طريق الخاصية Content-Type من ترويسة الطلب. ومن غير هذه الترويسة، لن يعرف المتصفح كيف يفسر البيانات بشكل صحيح.

spa_form_request_header_027.png

يستجيب الخادم معلنًا نجاح عملية إنشاء الملاحظة الجديدة معيدًا رمز الحالة 201. لن يطلب من المتصفح هذه المرة إعادة توجيه الصفحة إلى عنوان آخر، بل يبقى المتصفح على نفس الصفحة دون أن يرسل أية طلبات HTTP أخرى.

لاترسل نسخة SPA من التطبيق بيانات الاستمارة بالطريقة التقليدية، بل تستخدم شيفرة JavaScript التي تحضرها من الخادم. سنلقي نظرة سريعة على هذه الشيفرة، لكن ليس عليك الآن فهم تفاصيلها.

var form = document.getElementById('notes_form')
form.onsubmit = function(e) {
  e.preventDefault()

  var note = {
    content: e.target.elements[0].value,
    date: new Date(),
  }

  notes.push(note)
  e.target.elements[0].value = ''
  redrawNotes()
  sendToServer(note)
}

تطلب التعليمة ('document.getElementById('notes_form إحضار العنصر Form من الصفحة، وربطه بمعالج أحداث ليتعامل مع حدث إرسال الاستمارة. يستدعي معالج الأحداث مباشرة التابع ()e.preventDefault لإيقاف الاستجابة الافتراضية لحدث الإرسال. هذه الاستجابة التي لانريد حصولها، هي من ترسل البيانات إلى الخادم من خلال طلب GET جديد. بعدها ينشأ معالج الأحداث ملاحظة جديدة ويضيفها إلى قائمة الملاحظات باستعمال التعليمة (notes.push(note ثم يعيد تكوين هذه القائمة على الصفحة ويرسل الملاحظة إلى الخادم.

تمثل الشيفرة التالية تعليمات إرسال الملاحظة إلى الخادم:

var sendToServer = function(note) {
  var xhttpForPost = new XMLHttpRequest()
  // ...

  xhttpForPost.open('POST', '/new_note_spa', true)
  xhttpForPost.setRequestHeader(
    'Content-type', 'application/json'
  )
  xhttpForPost.send(JSON.stringify(note))
}

تحدد التعليمات السابقة طريقة إرسال البيانات على شكل طلب HTTP-POST وأنها بصيغة JSON، ومن ثم ترسل البيانات على شكل سلسلة JSON النصية.

تتوفر شيفرة التطبيق على العنوان https://github.com/mluukkai/example_app. وعليك أن تتذكر أن التطبيق قد صمم لإبراز مفاهيم المنهاج فقط، وأنه اعتمد أسلوبًا ضعيفًا نسبيًا لايجب عليك اتباعه عند كتابة تطبيقاتك الخاصة. وينطبق كلامنا أيضًا على طريقة استخدام العناوين التي لا تتماشى مع معايير البرمجة الأفضل المعتمدة حاليًا.

مكتبات JavaScript

صمم التطبيق السابق بما يسمى vanilla Javascript أي باستعمال جافاسكربت فقط، التي تستخدم DOM-API و JavaScript في تغيير بنية الصفحة. وبدلًا من استخدام التقنيتين السابقتين فقط، تستخدم مكتبات مختلفة تحتوي على أدوات يسهل التعامل معها مقارنة بتقنية DOM-API. وتعتبر JQuery أحد أشهر هذه المكتبات.طورت المكتبة JQuery في الوقت الذي استخدمت فيه تطبيقات الويب الأسلوب التقليدي المتمثل بإنشاء الخادم لصفحة HTML، وقد حسَّن ظهورها فعالية المتصفح باستخدام JavaScript المكتوبة اعتمادًا على هذه المكتبة. يعتبر أحد أسباب نجاح JQuery توافقها مع المتصفحات المختلفة (cross-browser). حيث تعمل المكتبة أيًّا كان المتصفح أو الشركة التي أنتجته، وبالتالي انتفت الحاجة إلى حلول خاصة بكل متصفح.

لا تُستخدَم JQuery اليوم بالشكل المتوقع، على الرغم من تطور أسلوب Vanilla JS -أي كما ذكرنا الاعتماد على جافاسكربت الصرفة- والدعم الجيد الذي تقدمه أشهر المتصفحات لوظائفها الأساسية. دفع انتشار التطبيق وحيد الصفحة بتقنيات أحدث من jQuery إلى الواجهة، حيث جذبت BackboneJS الموجة الأولى من المطورين. لكن سرعان ما أصبحت AngularJS التي قدمتها غوغل، المعيار الفعلي لتطوير تطبيقات الويب الحديثة، بعد إطلاقها عام 2012. لكن شعبيتها انخفضت بعد أن صرّح فريق العمل في أكتوبر (تشرين الأول) لعام 2014، أن دعم النسخة الأولى سينتهي، وأن النسخة الثانية لن تكون متوافقة معها. لذلك لم تلق Angular بنسختيها ترحيبًا حارًا جدًا.

تعتبر حاليًا مكتبة React التي أنتجها Facebook، الأكثر شعبية في مشاركة المتصفح عند تنفيذ منطق تطبيقات الويب. وسنتعلم خلال مسيرتنا في هذا المنهاج التعامل معها وكذلك التعامل مع مكتبة Redux التي سنستخدمها مع React بشكل متكرر. تبدو React حاليًا في موقع قوة، لكن عالم JavaScript دائم التغير. فالقادم الجديد لهذه العائلة VueJS يلقى اهتماما متزايدًا الآن (يمكنك الاطلاع على سلسلة "مقدمة إلى Vue.js" للتعرف على هذه المكتبة الرائعة).

التطوير الشامل لتطبيق الويب

ما الذي يعنيه مصطلح «التطوير الشامل لتطبيق الويب» (Full stack web development)؟. هذا المصطلح الذي يردده الجميع دون معرفة فعلية بمضمونه أو على الأقل ليس هناك اتفاق على تعريف محدد له.

تمتلك جميع تطبيقات الويب من الناحية العملية مستويين: الأول مستوى العمل مع المتصفح وهو الأقرب إلى المستخدم النهائي (End-User) ويعتبر المستوى الأعلى. والثاني هو مستوى الخادم وهو المستوى الأدنى. وتوضع أحيانًا قواعد البيانات في مستوًى أخفض من مستوى الخادم. وبالتالي يمكننا التفكير بهندسة تطبيقات الويب على أنها مستويات متراكمة.

يتم الحديث أحيانًا عن واجهة أمامية (frontend) وعن واجهة خلفية (backend). سيمثل المتصفح وشيفرة JavaScript التي ينفذها الواجهة الأمامية، بينما يمثل الخادم الواجهة الخلفية.

يُفهم من مصطلح التطوير الشامل في سياق هذا المنهاج، أن العمل سيكون على كل المستويات سواء الواجهة الأمامية أو الخلفية، وكذلك على قواعد البيانات. وقد تعتبر البرمجيات الخاصة بالخادم ونظام تشغيله جزءًا من مستويات التطوير، لكننا لن ندخل في هذا الموضوع. سنكتب شيفرة الواجهة الخلفية بلغة JavaScript مستخدمين بيئة التشغيل Node.js. وسيمنح استخدام نفس لغة البرمجة على مستويات التطوير جميعها بعدًا جديدًا لعملية التطوير الشامل، علمًا أن هذا الأمر ليس ملزمًا.

اعتاد المطورون على التخصص بأحد مستويي التطوير، ذلك أن التقنيات المستخدمة في المستويين مختلفة تمامًا. لكن في مضمار التطوير الشامل، لابد من وجود مطورين أكفاء في جميع المستويات بما فيها قواعد البيانات. كما ينبغي على المطورين في بعض الأحيان، امتلاك مهارات في التهيئة والإدارة لتشغيل تطبيقاتهم على منصات مختلفة كالمنصات السحابية (Cloud).

جافاسكربت المتعبة

يحمل التطوير الشامل للتطبيقات تحديات مختلفة. فأمور كثيرة تحدث معًا في أماكن مختلفة، وتنقيح المشاكل أصعب قليلًا مقارنة بتطبيقات سطح المكتب. فلا تتصرف JavaScript كما هو متوقع منها دائمًا (مقارنة بغيرها من اللغات)، كما أن الطريقة غير المتزامنة التي تعمل بها بيئة التشغيل ستبرز كل أنواع التحديات. المعرفة ببروتوكولHTTP ضروري جدًا لفهم الاتصال في الشبكة، وكذلك معالجة الأمور المتعلقة بقواعد البيانات وإدارة وتهيئة الخادم. لابد أيضا من امتلاك معرفة جيدة بتنسيق CSS لتقديم التطبيق بشكل لائق على الأقل. يتغير عالم JavaScript بسرعة، ولهذا الأمر حصته من التحديات. فكل الأدوات والمكتبات حتى اللغة نفسها عرضة للتطوير المستمر، حتى أن البعض سئم من التغيير المستمر، فأطلق مصطلح جافاسكربت المتعبة (أو Javascript fatigue). ستعاني من هذا التعب أنت أيضًا خلال مسيرتك في المنهاج، ولحسن حظنا، هناك طرق عدة لتسهيل الأمور، منها أننا سنبدأ بكتابة الشيفرة بدلًا من الغوص في عمليات التهيئة، والتي لا يمكننا تفاديها تمامًا، لكن سنحاول أن نتقدم خلال الأسابيع القليلة القادمة متجاوزين الأسوء في هذا الكابوس.

التمارين 0.1 - 0.6

ارفع حلول التمارين على منصة GitHub، وينبغي عليك الإشارة إلى إتمام عملية التحميل ضمن منظومة تسليم الملفات submission system.

يمكنك تحميل جميع التمارين في نفس المكان، أو استخدم أماكن مختلفة. اعط أسماء مناسبة للمجلدات إن رفعت التمارين التي تعود لأقسام مختلفة من المنهاج إلى نفس المكان، وأضف اسم mluukkai إلى قائمة المتعاونين، إن حمَّلت ملفاتك إلى مجلدات خاصة.

تعتبر الطريقة التالية جيدة في تسمية المجلدات:

part0
part1
     Courseinfo   
  unicafe
  anecdotes
part2
  phonebook
  countries

لاحظ أن لكل قسم مجلده الخاص الذي يضم مجلدات فرعية لكل تمرين (مثل تمرين unicafe في القسم1). وانتبه جيدًا لرفع كل التمارين العائدة لقسم محدد معًا، فلو رفعت تمرينًا واحدًا فقط، لن تتمكن من رفع أية تمارين أخرى تعود لنفس القسم.

1- التمرين 0.1 (HTML)

راجع أساسيات HTML بقراءة هذه الدورة التعليمية من Mozilla. لاترفع هذا التمرين إلى GitHub، يكفي أن تطلع على الدورة.

2- التمرين 0.2 (CSS)

راجع أساسيات CSS بقراءة هذه الدورة التعليمية من Mozilla. لاترفع هذا التمرين إلى GitHub، يكفي أن تطلع على الدورة.

3- التمرين 0.3 (HTML forms)

تعلم أساسيات استخدام استمارات HTML بقراءة هذه الدورة التعليمية من Mozilla. لاترفع هذا التمرين إلى GitHub، يكفي أن تطلع على الدورة.

4- التمرين 0.4 (ملاحظة جديدة new note)

في فقرة تحميل صفحة تحتوي على شيفرة JavaScript (نظرة ثانية)، عرضت سلسلة الأحداث الناتجة عن فتح هذه الصفحة على شكل مخطط تتابعي أُنجز باستخدام خدمة websequencediagrams كالتالي:

browser-`server: HTTP GET https://fullstack-exampleapp.herokuapp.com/notes
server--`browser: HTML-code
browser-`server: HTTP GET https://fullstack-exampleapp.herokuapp.com/main.css
server--`browser: main.css
browser-`server: HTTP GET https://fullstack-exampleapp.herokuapp.com/main.js
server--`browser: main.js

note over browser:
browser starts executing js-code
that requests JSON data from server 
end note

browser-`server: HTTP GET https://fullstack-exampleapp.herokuapp.com/data.json
server--`browser: [{ content: "HTML is easy"، date: "2019-05-23" }، ...]

note over browser:
browser executes the event handler
that renders notes to display
end note

أنشئ مخططًا مماثلًا لتوضيح الحالة التي ينشأ فيها المستخدم ملاحظة جديدة في الصفحة https://fullstack-exampleapp.herokuapp.com/notes، وذلك بكتابة شيء ما في حقل النص ونقر زر إرسال.

يمكنك إظهار العمليات على المخدم أو المتصفح كتعليقات على المخطط، إن وجدت هذا ضروريًا. ولا حاجة أن يكون المخطط تتابعيًّا، فلا بأس بأية طريقة مفهومة لعرض الموضوع.

ستجد كل المعلومات المتعلقة بحل هذا التمرين مع التمرينين القادمين في نص هذا القسم. فكرة هذه التمارين قراءة النص مرة أخرى والتفكر مليًا بما يحدث. ليس من الضروري قراءة شيفرة التطبيق على الرغم من أن ذلك ممكن.

5- تمرن 0.5 (تطبيق من صفحة واحدة SPA)

أنشئ مخططًا لتوضيح الحالة التي ينتقل فيها المستخدم إلى صفحة نسخة تطبيق من صفحة واحدة من تطبيق الملاحظات على الرابط https://fullstack-exampleapp.herokuapp.com/spa.

6- تمرين 0.6 (ملاحظة جديدة new note)

أنشئ مخططًا لتوضيح الحالة التي ينشئ فيها المستخدم ملاحظة جديدة مستخدمًا نسخة التطبيق ذو الصفحة الواحدة.

بهذا التمرين نصل إلى نهاية القسم. لقد حان الوقت لترفع إجاباتك على GitHub. لا تنسى أن تشير إلى إتمام عملية التحميل ضمن منظومة تسليم الملفات submission system.

يمكنك اقتراح تعديلات على القسم أيضًا إن أحببت في المقال الأصلي.

ترجمة -وبتصرف- للفصل Fundamentals of web apps من سلسلة Deep Dive Into Modern Web Development





تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن