اذهب إلى المحتوى

البحث في الموقع

المحتوى عن 'ajax'.

  • ابحث بالكلمات المفتاحية

    أضف وسومًا وافصل بينها بفواصل ","
  • ابحث باسم الكاتب

نوع المحتوى


التصنيفات

  • الإدارة والقيادة
  • التخطيط وسير العمل
  • التمويل
  • فريق العمل
  • دراسة حالات
  • التعامل مع العملاء
  • التعهيد الخارجي
  • السلوك التنظيمي في المؤسسات
  • عالم الأعمال
  • التجارة والتجارة الإلكترونية
  • نصائح وإرشادات
  • مقالات ريادة أعمال عامة

التصنيفات

  • مقالات برمجة عامة
  • مقالات برمجة متقدمة
  • PHP
    • Laravel
    • ووردبريس
  • جافاسكربت
    • لغة TypeScript
    • Node.js
    • React
    • Vue.js
    • Angular
    • jQuery
    • Cordova
  • HTML
  • CSS
    • Sass
    • إطار عمل Bootstrap
  • SQL
  • لغة C#‎
    • ‎.NET
    • منصة Xamarin
  • لغة C++‎
  • لغة C
  • بايثون
    • Flask
    • Django
  • لغة روبي
    • إطار العمل Ruby on Rails
  • لغة Go
  • لغة جافا
  • لغة Kotlin
  • لغة Rust
  • برمجة أندرويد
  • لغة R
  • الذكاء الاصطناعي
  • صناعة الألعاب
  • سير العمل
    • Git
  • الأنظمة والأنظمة المدمجة

التصنيفات

  • تصميم تجربة المستخدم UX
  • تصميم واجهة المستخدم UI
  • الرسوميات
    • إنكسكيب
    • أدوبي إليستريتور
  • التصميم الجرافيكي
    • أدوبي فوتوشوب
    • أدوبي إن ديزاين
    • جيمب GIMP
    • كريتا Krita
  • التصميم ثلاثي الأبعاد
    • 3Ds Max
    • Blender
  • نصائح وإرشادات
  • مقالات تصميم عامة

التصنيفات

  • مقالات DevOps عامة
  • خوادم
    • الويب HTTP
    • البريد الإلكتروني
    • قواعد البيانات
    • DNS
    • Samba
  • الحوسبة السحابية
    • Docker
  • إدارة الإعدادات والنشر
    • Chef
    • Puppet
    • Ansible
  • لينكس
    • ريدهات (Red Hat)
  • خواديم ويندوز
  • FreeBSD
  • حماية
    • الجدران النارية
    • VPN
    • SSH
  • شبكات
    • سيسكو (Cisco)

التصنيفات

  • التسويق بالأداء
    • أدوات تحليل الزوار
  • تهيئة محركات البحث SEO
  • الشبكات الاجتماعية
  • التسويق بالبريد الالكتروني
  • التسويق الضمني
  • استسراع النمو
  • المبيعات
  • تجارب ونصائح
  • مبادئ علم التسويق

التصنيفات

  • مقالات عمل حر عامة
  • إدارة مالية
  • الإنتاجية
  • تجارب
  • مشاريع جانبية
  • التعامل مع العملاء
  • الحفاظ على الصحة
  • التسويق الذاتي
  • العمل الحر المهني
    • العمل بالترجمة
    • العمل كمساعد افتراضي
    • العمل بكتابة المحتوى

التصنيفات

  • الإنتاجية وسير العمل
    • مايكروسوفت أوفيس
    • ليبر أوفيس
    • جوجل درايف
    • شيربوينت
    • Evernote
    • Trello
  • تطبيقات الويب
    • ووردبريس
    • ماجنتو
    • بريستاشوب
    • أوبن كارت
    • دروبال
  • الترجمة بمساعدة الحاسوب
    • omegaT
    • memoQ
    • Trados
    • Memsource
  • برامج تخطيط موارد المؤسسات ERP
    • تطبيقات أودو odoo
  • أنظمة تشغيل الحواسيب والهواتف
    • ويندوز
    • لينكس
  • مقالات عامة

التصنيفات

  • آخر التحديثات

أسئلة وأجوبة

  • الأقسام
    • أسئلة البرمجة
    • أسئلة ريادة الأعمال
    • أسئلة العمل الحر
    • أسئلة التسويق والمبيعات
    • أسئلة التصميم
    • أسئلة DevOps
    • أسئلة البرامج والتطبيقات

التصنيفات

  • كتب ريادة الأعمال
  • كتب العمل الحر
  • كتب تسويق ومبيعات
  • كتب برمجة
  • كتب تصميم
  • كتب DevOps

ابحث في

ابحث عن


تاريخ الإنشاء

  • بداية

    نهاية


آخر تحديث

  • بداية

    نهاية


رشح النتائج حسب

تاريخ الانضمام

  • بداية

    نهاية


المجموعة


النبذة الشخصية

تم العثور على 6 نتائج

  1. إحدى أسوأ الأسرار التي تمَّ الاحتفاظ بها حول AJAX على الويب هو أنّ الواجهة البرمجية (API) الأساسية لها، XMLHttpRequest، لم توجد للغرض الذي نستخدمه الآن. لقد قمنا بعمل جيد في إنشاء واجهة برمجية جيّدة باستخدام الكائن XHR ولكننا نعرف أنه يمكننا القيام بعمل أفضل. نحن نبذل الجهود لتحقيق الأفضل الذي هو الواجهة البرمجية fetch. لنأخذ فكرة عامة عن التابع window.fetch الجديد، المتوفر الآن في Firefox و Chrome Canary. الكائن XMLHttpRequest إنَّ كائن XHR معقد قليلًا برأيي ولا أريد أن أبدأ بشرح لماذا "XML" تُكتب بأحرف كبيرة بينما "Http" تُكتب بأسلوب سنام الجمل. على أيّة حال، لنلاحظ كيف نستخدم XHR. // فوضى XHR فقط الحصول على if (window.XMLHttpRequest) { // موزيلا، سفاري... request = new XMLHttpRequest(); } else if (window.ActiveXObject) { // إنترنت إكسبلورر try { request = new ActiveXObject('Msxml2.XMLHTTP'); } catch (e) { try { request = new ActiveXObject('Microsoft.XMLHTTP'); } catch (e) {} } } // فتح، إرسال request.open('GET', 'https://davidwalsh.name/ajax-endpoint', true); request.send(null); بالطبع إنَّ أطر عمل جافاسكربت الخاصة بنا تجعل XHR أكثر متعة للعمل، ولكن ما نراه في الأعلى هو مثال بسيط على فوضى XHR. استخدام fetch الأساسي تم توفير دالة fetch في نطاق window العام، الوسيط الأول له هو الرابط URL: // الرابط (إلزامي)، الخيارات (اختيارية) fetch('https://davidwalsh.name/some/url', { method: 'get' }).then(function(response) { }).catch(function(err) { // خطأ :( }); وهذا يشبه إلى حد بعيد الواجهة البرمجية Battery المحدّثة، إذ تستخدم الواجهة البرمجية fetch الوعود في جافاسكربت لمعالجة النتائج/ردود النداء: // معالجة رد بسيط fetch('https://davidwalsh.name/some/url').then(function(response) { }).catch(function(err) { // خطأ :( }); // "تسلسل لمعالجة أكثر "تطورًا fetch('https://davidwalsh.name/some/url').then(function(response) { return //... }).then(function(returnedValue) { // ... }).catch(function(err) { // خطأ :( }); إذا لم تكن معتادًا على استخدام then، يجب أن تعتاد عليه لأنها ستنتشر قريبًا في كلّ مكان. ترويسات الطلب القدرة على ضبط ترويسات الطلب هي أمر مهم في مرونة الطلب. يمكنك العمل مع ترويسات الطلب بتنفيذ new Headers()‎: // إنشاء كائن ترويسة فارغ var headers = new Headers(); // إضافة بعض الترويسات headers.append('Content-Type', 'text/plain'); headers.append('X-My-Custom-Header', 'CustomValue'); // للترويسة set و get و check قيم headers.has('Content-Type'); // true headers.get('Content-Type'); // "text/plain" headers.set('Content-Type', 'application/json'); // حذف ترويسة headers.delete('X-My-Custom-Header'); // إضافة قيم ابتدائية var headers = new Headers({ 'Content-Type': 'text/plain', 'X-My-Custom-Header': 'CustomValue' }); يمكنك استخدام التوابع append و has و get و set و delete لتعديل ترويسات الطلب. لاستخدام ترويسات الطلب أنشئ الكائن Request: var request = new Request('https://davidwalsh.name/some-url', { headers: new Headers({ 'Content-Type': 'text/plain' }) }); fetch(request).then(function() { /* معالجة الرد*/ }); لنطّلع على عمل الكائنين Response و Request. الكائن Request يمثّل الكائن Request جزء الطلب عند استدعاء التابع fetch، يمكنك إنشاء طلبات مخصصة ومتطورة بتمرير الكائن Request للتابع fetch: method (الطريقة): يمكن أن تكون GET أو POST أو PUT أو DELETE أو HEAD url (الرابط): رابط الطلب headers (الترويسة): ترتبط مع الكائن Headers referrer (المرجع): مرجع الطلب mode (النمط): يكون cors أو no-cors أو same-origin credentials (بيانات الاعتماد): هل تعمل ملفات تعريف الارتباط (cookies) مع الطلب؟ وتأخذ إحدى القيمتين omit، أو same-origin. redirect (إعادة التوجيه): يمكن أن يأخذ القيمة follow أو error أو manual. integrity (التكامل): قيمة تكامل المصدر الفرعي. cache (التخزين المؤقت): وضع التخزين المؤقت ويمكن أن يأخذ إحدى القيم default أو reload أو no-cache عينة عن استخدام الكائن Request: var request = new Request('https://davidwalsh.name/users.json', { method: 'POST', mode: 'cors', redirect: 'follow', headers: new Headers({ 'Content-Type': 'text/plain' }) }); // الآن استخدمه fetch(request).then(function() { /* معالجة الرد */ }); الوسيط الأول URL هو فقط الوسيط الإلزامي، وكل خاصية تصبح للقراءة فقط حالما يتم إنشاء نسخة من الكائن Request، ومن المهم أن نلاحظ أن الكائن Request يملك التابع clone المهم عند استخدام fetch ضمن الواجهة البرمجية Service Worker -- يعد الكائن Request مجرًى ولهذا يجب أن يتم نسخه عند تمريره إلى استدعاء آخر للتابع fetch. بصمة التابع fetch ولكن نفس استخدام الكائن Request لذا يمكنك القيام بما يلي: fetch('https://davidwalsh.name/users.json', { method: 'POST', mode: 'cors', redirect: 'follow', headers: new Headers({ 'Content-Type': 'text/plain' }) }).then(function() { /* معالجة الرد */ }); من المحتمل أن تستخدم نسخًا من الكائن Request فقط ضمن Service Workers بما أن الكائن Request والتابع fetch يؤديان نفس الوظيفة. الكائن Response التابع then الذي يخص fetch مزود بالكائن Response ولكن يمكنك أيضًا إنشاء كائنات Response بنفسك -- حالة أخرى قد تواجهها عند استخدام service workers. يمكنك ضبط ما يلي عند استخدام الكائن Response: type (النوع): يمكن أن يكون basic أو cors url (الرابط) useFinalURL (استخدام الرابط النهائي): قيمة منطقية للرابط url إذا كان رابطًا نهائيًا أم لا status (الحالة): رمز الحالة (مثلًا 200، 400) ok: قيمة منطقية للاستجابة الناجحة (الحالة في المجال بين 200- 299) statusText (نص الحالة): نص يعبّر عن الحالة وفقًا للرمز (مثلًا: OK) headers (الترويسة): كائن Headers المرتبط بالاستجابة // service worker أنشئ ردك لاختبار // جديد (الجسم، الخيارات) Response كائن var response = new Response('.....', { ok: false, status: 404, url: '/' }); // Response يجلب مجدَّدًا نسخة من الكائن fetch الذي يخص then التابع fetch('https://davidwalsh.name/') .then(function(responseObj) { console.log('status: ', responseObj.status); }); يوفر الكائن Response أيضًا التوابع التالية: clone()‎: تُنشئ نسخة من الكائن Response ()error: تعيد كائن Response جديد مرتبط مع خطأ في الشبكة ()redirect: تنشئ استجابة جديدة مع رابط URL مختلف ()arrayBuffer: تعيد وعدًا يُقبل (resolve) مع ArrayBuffer ()blob: تعيد وعدًا يُقبل (resolve) مع Blob ()formData: تعيد وعدًا يُقبل (resolve) مع كائن FormData ()json: تعيد وعدًا يُقبل (resolve) مع كائن JSON ()text: تعيد وعدًا يُقبل (resolve) مع القيمة النصية USVString التعامل مع JSON بفرض أنّك أنشأت طلب لـJSON -- معلومات نتيجة ردود النداء لديها التابع json لتحويل البيانات الخام إلى كائن جافاسكربت: fetch('https://davidwalsh.name/demo/arsenal.json').then(function(response) { // JSON التحويل إلى return response.json(); }).then(function(j) { // هو كائن جافاسكربت j console.log(j); }); بالطبع هذا أسهل من (JSON.parse(jsonString لكن طريقة json تعد اختصارًا سهلًا أيضًا. التعامل مع استجابات Text/HTML الأساسية ليست دائما JSON هي صيغة رد الطلب المرغوبة لذا إليك كيف نجعل الاستجابة على شكل نص أو HTML: fetch('/next/page') .then(function(response) { return response.text(); }).then(function(text) { // <!DOCTYPE .... console.log(text); }); بإمكانك الحصول على استجابة نصية عبر تسلسل طريقة then للوعد مع طريقة text()‎. التعامل مع استجابات بشكل بيانات ثنائية إذا كنت ترغب مثلًا بتحميل صورة باستخدام التابع fetch فإن هذا سيكون مختلفًا. fetch('https://davidwalsh.name/flowers.jpg') .then(function(response) { return response.blob(); }) .then(function(imageBlob) { document.querySelector('img').src = URL.createObjectURL(imageBlob); }); يأخذ التابع blob()‎ تدفق الاستجابة وتقرأه حتى يكتمل. الحصول على بيانات النموذج استخدام آخر شائع للـAJAX وهو إرسال بيانات نموذج، وإليك كيف يمكن أن نستخدم التابع fetch للحصول على بيانات نموذج مرسلة بالطريقة POST: fetch('https://davidwalsh.name/submit', { method: 'post', body: new FormData(document.getElementById('comment-form')) }); وإذا أردت إرسال البيانات بصيغة JSON إلى الخادم: fetch('https://davidwalsh.name/submit-json', { method: 'post', body: JSON.stringify({ email: document.getElementById('email').value, answer: document.getElementById('answer').value }) }); سهل جدًا ومريح للعينين أيضًا. قصة لم تُكتب رغم أن fetch هي واجهة برمجية سهلة الاستخدام، إلا أنّ الواجهة البرمجية الحالية -تاريخ ترجمة المقال- لا تسمح بإلغاء الطلب مما يجعلها غير مستخدمة لكثير من المطورين. إن الواجهة البرمجية fetch الجديدة تبدو أفضل وأسهل استخدامًا من XHR. وبعد كل ذلك تمّ إنشاؤها حتى نتمكّن من استخدام AJAX بالطريقة الصحيحة. تملك fetch فوائد الخبرات السابقة، ولا أطيق الانتظار حتى يتم استخدامها على نطاق واسع. ترجمة -وبتصرف- للمقال ‎fetch API لصاحبه David Walsh
  2. الواجهة البرمجية Promise رائعة ويمكنك جعلها مذهلة باستخدام async و await. إنّ الشيفرة المتزامنة سهلة التتبع والتنقيح إلا أنّ الشيفرة غير المتزامنة أفضل بشكل عام من حيث الأداء والمرونة، فلماذا "توقف العرض" بينما بإمكانك استقبال الكثير من الطلبات في وقت واحد ثمّ معالجة كلٍّ منها عندما يصبح جاهزًا؟ مع العديد من الواجهات البرمجية API الجديدة التي تم تحقيقها مع مبدأ الوعد، إذ أصبحت الوعود جزءًا كبيرًا في عالم الجافاسكربت. لنلقِ نظرة على كيفية استخدام الواجهة البرمجية للوعود. الوعود قيد التطبيق الواجهة البرمجية XMLHttpRequest غير متزامنة ولكنها لا تستخدم الواجهة البرمجية Promises. هناك عدة واجهات برمجية أصلية تستخدم الوعود الآن، مثل: Battery API. fetch API (بديل XHR) الواجهة البرمجية ServiceWorker ستصبح الوعود أكثر شيوعًا، لذا من المهم أن يعتاد عليها جميع مطوري الواجهات الأمامية، وتجدر الإشارة إلى أنّ Node.js هي منصة أخرى للوعود. (يبدو هذا واضحًا، كما الواجهة البرمجية Promise ميزة أساسية في اللغة). من المحتمل أن يكون اختبار الوعود أسهل مما تعتقد لأنه يمكنك استخدام setTimeout كـ "مهمة" لك غير متزامنة. استخدام Promise الأساسي يجب أن يستخدم الباني new Promise()‎ فقط للمهام الغير متزامنة الموروثة، مثل استخدام setTimeout أو XMLHttpRequest. يتم إنشاء وعدًا جديدًا باستخدام الكلمة المفتاحية new ويتم تمرير توابع resolve و reject لردّ النداء المزوَّد: var p = new Promise(function(resolve, reject) { // ...القيام بمهمة غير متزامنة وثم if(/* شرط جيد */) { resolve('Success!'); } else { reject('Failure!'); } }); p.then(function(result) { /* القيام بفعل ما مع النتيجة */ }).catch(function() { /* خطأ :( */ }).finally(function() { /* تنفّذ بغض النظر عن النجاح أو الفشل */ }); يعود الأمر للمطور فيما إذا كان يريد استدعاء resolve أو reject يدويًا ضمن جسم رد النداء اعتمادًا على نتيجة المهمة المعطاة. مثال واقعي لتحويل XMLHttpRequest إلى مهمة تعتمد على الوعد: // من وعود جاك أرشيبالد والعودة // http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promisifying-xmlhttprequest function get(url) { //إعادة وعد جديد. return new Promise(function(resolve, reject) { // XHR القيام بالعمل الاعتيادي لـ var req = new XMLHttpRequest(); req.open('GET', url); req.onload = function() { // هذا يتم استدعاؤه حتى في حالة 404 // لذا اختبر الحالة if (req.status == 200) { // قم بإنهاء الوعد مع نص الرد resolve(req.response); } else { // وإلا ارفض مع نص الحالة // والذي نأمل أن يكون خطأ ذو معنى reject(Error(req.statusText)); } }; // معالجة أخطاء الشبكة req.onerror = function() { reject(Error("Network Error")); }; // القيام بالطلب req.send(); }); } // استخدمه! get('story.json').then(function(response) { console.log("Success!", response); }, function(error) { console.error("Failed!", error); }); إذا كان من الممكن اتخاذ إجراء غير متزامن قد لا تحتاج إلى إكمال المهام غير المتزامنة ضمن الوعد، لكن سيكون الأفضل هو أن تكون القيمة المعادة وعدًا لذا يمكنك أن تعدّ كم عدد الوعود التي حصلت عليها من تابع معطى. في تلك الحالة يمكنك ببساطة استدعاء Promise.resolve()‎ أو Promise.reject()‎ بدون استخدام الكلمة المفتاحية new. مثلًا: var userCache = {}; function getUserDetail(username) { // في كلتا الحالتين، تم إضافته إلى الذاكرة أو لا، سيتم إعادة وعد if (userCache[username]) { // new إعادة وعد بدون الكلمة المفتاحية return Promise.resolve(userCache[username]); } // لتحصل على المعلومات fetch استخدم الواجهة البرمجية // وعدًا fetch تعيد return fetch('users/' + username + '.json') .then(function(result) { userCache[username] = result; return result; }) .catch(function() { throw new Error('Could not find user: ' + username); }); } بما أن القيمة المعادة هي وعد لذا يمكنك استخدام التوابع then و catch عليها. then كل كائنات الوعد تملك التابع then الذي يسمح لك بالتفاعل مع الوعد. رد النداء الأول لتابع then يستدعي النتيجة المعطاة له عن طريق استدعاء ()resolve: new Promise(function(resolve, reject) { // setTimeout حدث زائف غير متزامن باستخدام setTimeout(function() { resolve(10); }, 3000); }) .then(function(result) { console.log(result); }); // console من الطرفية // 10 رد النداء لـ then يتم تشغيله عندما ينتهي الوعد. يمكنك أيضًا أن تسلسل ردود النداء للتابع then: new Promise(function(resolve, reject) { // setTimeout حدث زائف غير متزامن باستخدام setTimeout(function() { resolve(10); }, 3000); }) .then(function(num) { console.log('first then: ', num); return num * 2; }) .then(function(num) { console.log('second then: ', num); return num * 2; }) .then(function(num) { console.log('last then: ', num);}); // من الـ console // first then: 10 // second then: 20 // last then: 40 كل then تستقبل القيمة المعادة من استدعاء then السابق. إذا أُنهي الوعد قبل أن يتم استدعاء التابع then مجددًا، يتم إيقاف رد النداء مباشرةً. إذا تم رفض الوعد ثم استدعاء التابع then بعد الرفض، لا يتم استدعاء رد النداء أبدًا. catch يتم استدعاء رد النداء catch عندما يُرفض الوعد: new Promise(function(resolve, reject) { // setTimeout حدث زائف غير متزامن باستخدام setTimeout(function() { reject('Done!'); }, 3000); }) .then(function(e) { console.log('done', e); }) .catch(function(e) { console.log('catch: ', e); }); // console من الطرفية // 'catch: Done!' ما تمرره لتابع reject يعود لك ولكن النمط المعتاد هو إرسال Error لـ catch: reject(Error('Data could not be found')); finally رد النداء المعرّف حديثًا finally يتم استدعاؤه بغض النظر عن النجاح أو الفشل: (new Promise((resolve, reject) => { reject("Nope"); })) .then(() => { console.log("success") }) .catch(() => { console.log("fail") }) .finally(res => { console.log("finally") }); // >> fail // >> finally Promise.all فكر بمحمّلات الجافاسكربت: يوجد أوقات يتم فيها تشغيل عدة تفاعلات غير متزامنة وتريد الاستجابة عندما تكتمل جميعها، هنا يأتي دور التابع Promise.all، هذا التابع يأخذ مصفوفة من الوعود ويعطيك رد نداء واحد فقط عند إنهاء جميع الوعود. Promise.all([promise1, promise2]).then(function(results) { // كلا الوعدين تم إنهاؤهما }) .catch(function(error) { // تم رفض وعد واحد أو أكثر }); الطريقة المثالية للتفكير بـ Promise.all هي إطلاق عدة طلبات AJAX (باستخدام fetch) في نفس الوقت. var request1 = fetch('/users.json'); var request2 = fetch('/articles.json'); Promise.all([request1, request2]).then(function(results) { // كلا الوعدين تم تنفيذهما }); يمكنك أن تدمج عدة واجهات برمجية مثل fetch والواجهة البرمجية Battery بما أنّها تعيد وعودًا. Promise.all([fetch('/users.json'), navigator.getBattery()]).then(function(results) { // كلا الوعدين تم تنفيذهما }); التعامل مع الرفض صعب بالطبع. إذا تم رفض أيّ وعد سيتم إطلاق catch للرفض الأول: var req1 = new Promise(function(resolve, reject) { // setTimeout حدث زائف غير متزامن باستخدام setTimeout(function() { resolve('First!'); }, 4000); }); var req2 = new Promise(function(resolve, reject) { // setTimeout حدث زائف غير متزامن باستخدام setTimeout(function() { reject('Second!'); }, 3000); }); Promise.all([req1, req2]).then(function(results) { console.log('Then: ', results); }).catch(function(err) { console.log('Catch: ', err); }); // console من الـ // Catch: Second! ستكون Promise.all مفيدة أكثر للواجهات البرمجية التي تتجه لاستخدام الوعود. Promise.race تعدّ الدالة Promise.race مفيدة فبدلًا من أن يتم الانتظار حتى إنهاء جميع الوعود أو رفضها تقوم بتشغيل أيّ وعد في المصفوفة تم إنهاؤه أو رفضه. var req1 = new Promise(function(resolve, reject) { // setTimeout حدث زائف غير متزامن باستخدام setTimeout(function() { resolve('First!'); }, 8000); }); var req2 = new Promise(function(resolve, reject) { // setTimeout حدث زائف غير متزامن باستخدام setTimeout(function() { resolve('Second!'); }, 3000); }); Promise.race([req1, req2]).then(function(one) { console.log('Then: ', one); }).catch(function(one, two) { console.log('Catch: ', one); }); // console من الـ // Then: Second! يمكن أن تشغّل حالة الاستخدام طلبًا إلى مصدر أوليّ ومصدر ثانوي (في حال عدم توفر الأوليّ والثانوي). اعتد على الوعود كانت الوعود موضوعًا مثيرًا للاهتمام خلال السنوات القليلة الماضية (أو خلال السنوات العشرة الأخيرة إذا كنت مستخدم مجموعة أدوات Dojo) وانتقلت من كونها نمط في إطار عمل جافاسكربت إلى كونها من أساس اللغة. وربما من الأفضل افتراض أنك ستشاهد معظم الواجهات البرمجية الجديدة في جافاسكربت يتم تنفيذها بنمط يعتمد على الوعد، وهذا أمر ممتاز. إنّ المطورين قادرون على تجنب جحيم رد النداء وتمرير التفاعلات غير المتزامنة مثل أيّ متغير آخر. تستغرق الوعود بعض الوقت لتكون الأدوات الأصلية وقد حان الوقت لتعلّمها. يمكنك في أي وقت الرجوع إلى توثيق الكائن Promise في موسوعة حسوب كما ننصحك أيضًا بقراءة صفحة «استخدام الوعود» بعد هذه المقالة مباشرةً لوضع ما تعلمته موضع التطبيق. ترجمة -وبتصرف- للمقال JavaScript Promise API لصاحبه David Walsh
  3. SSE اختصار لـ Server Sent Events وتسمى أيضًا EventSource تُستخدم لِبنّاء تطبيقات ويب تفاعلية. حيث يُرسل الخادوم أحداثا في الوقت الحقيقي إلى المستخدم، قد يتضمن الحدث تنبيهات للمستخدم أو بيانات يمكن استغلالها في عمل شيء ما وتحديث صفحة الويب. في السّابق، لِعمل تحديث تلقائي للبيانات كل ثانية كانت تُستَخدم هذه الطريقة: setInterval(function(){ // إستدعاء الدالة بشكل تكراري xhr = new XMLHttpRequest(); // إستخدام طلب ajax xhr.onreadystatechange = function(){// عند أي تغير في حالة الطلب if(this.readyState == 4){// تم إستقبال النص بنجاح // إفعل بعض الاوامر } } xhr.open("get","http://localhost"); // تحديد نوع الطلب ورابط الموقع xhr.send("mydata"); // إرسال البيانات إلى الخادم }, 1000); // تحديد مدّة التكرار إلى ثانية أو هذه الطريقة الأكثر تطورًا: function getAjax(){ // تعريف الدالة التي ستدعى بشكل تكراري xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(){ if(this.readyState == 4) setTimeout(function(){ // استدعاء ألدالة مجددًا بعد ثانية من الإنتهاء لِتكرار طلب الajax getAjax(); },1000); } xhr.open("get","http://localhost"); xhr.send("mydata"); } getAjax();// إستدعاء الدالة للمرة الأولىهذه الطرق صحيحة لكنها ليست الأفضل، حيث تقوم بإرسال طلب كل ثانية للخادم مما يُسبٍّب ضغط عليه وغالبًا لا يكون التحديث في الوقت الحقيقي لظهوره. فكرة عمل SSE أن الطلب يظل مَفتوحا في إنتظار محتوى جديد والخادم هو من يُرسل المحتوى الجديد وليس المتصفح من يطلبه. SSE ليست الطريقة الوحيدة لِبناء تطبيقات ويب تفاعلية، يوجد العديد من الطرق الأخرى من أشهرها WebSocket. مُقارنة بين SSE و WebSocketالسهولة والتوافقيةمعظم الإستضافات المشتركة لا تَدعم WebSocket وهو يعمل على بروتوكل ws أو wss (في حالة إستخدام شهادة أمان ssl) وهما مختلفان عن الذي عليه واجهة المستخدم مما يُسبِّب مشاكل في مصادفة الاثنين معًا (يوجد مكتبات لفعل ذلك) على خلاف SSE التي تُعتبر طلب عادي على نفس بروتوكول واجهة المستخدم النهائية. تزامن المتسخدمينWebSocket يُمكنها عمل تزامن بين المستخدمين بطريقة مباشرة حيث أن الإتصلات تُعالج كلها معًا. في SSE لايوجد طريقة مباشرة لِتزامن المسخدمين معًا لأن كل طلب مُنفصل عن الآخر لذا تُستخدم قاعدة بيانات وتحفظ أخر التغيرات للمستخدم الأول وعند المستخدم الآخر يَتم البحث بشكل دوري عن التغيرات في قاعدة البيانات ثم إرسالها إلى المستخدم الآخر. إرسال وإستقبال البياناتWebSocket و SSE يدعمان إستقبال البيانات لكن لا يُمكن إرسال البيانات إلى الخادم بإستخدام SSE، أي عند بناء تطبيق يعتمد على إرسال وإستقبال البيانات من الخادوم ستضطر إلى بناء صفحة منفصلة تقوم بإستقبال الطلبات من المستخدم بواسطة ajax مثلًا. تطبيقات قد يكون إستخدام WebSocket فيها أفضلفي تطبيق سيحتاج إلى تفاعل المستخدمين معًا (تطبيق تواصل مثلًا) سيكون إستخدام WebSocket أفضل من SSE لأن الأخيرة لا يُمكنها إستقبال الطلبات من الخادوم.إرسال إشعارات أو رسائل للمستخدم أو أي تطبيق لن يَحتاج المستخدم فيه إلى إرسال رسائل إلى الخادوم. هنا ممكن أن يكون إستخدام SSE أفضل وأسهل.اهلًا بالعالمقبل البدء يجب أن تَعرف المتصفحات الداعمة لـِ SSE وأن متصفح أنترنت أكسبلور لايَدعمه لكن يُمكنك إستخدام مكتبة خارجية لدعمه مثل EventSource.js (يتم تضمين ملف javascript في الصفحة) هذا مثال لنص مصدري لإستجابة طلب SSE: http/1.1 200 OK ## الصفحة موجودة ! Content-Type: text/event-stream ## تعريف المتصفح أن الصفحة هي محتوى لطلب SSE event: delete## تعريف الحدث الذي سيُستقبل من javascript data: "some data" ## البيانات التي سترسل إلى الحدث event: add## تعريف حدث آخر data: "some data" data: "some data"## إرسال بيانات بدون حدث مُعرف id: 3 ## غير مهم وغير مطلوب بشكل ضروري يُستخدم لتحديد مُعرف lastEventId للرسائلالأمثلة مكتوبة بلغة php ويُمكنك فعلها في أي لغة ويب أخرى هذا تطبيق بسيط لجلب الساعة من الخادم لحظة بلحظة. ملف server.php ( الذي سيعالج طلبات SSE احفظه بجوار واجهة المستخدم أو أي مكان آخر مع تغير المسار في شفرة javascript ): <?php set_time_limit(0);// تحديد أقصى مدة للطلب غير نهائية لان هناك خوادم تقوم بتحديدها $time = 1;// متغير بقيمة الوقت الذي سيرسل فيه تحديث جديد header("Content-Type: text/event-stream");// إرسال ترويسة أن الصفحة هي محتوى sse header('Cache-Control: no-cache'); // لمنع حفظ الكاش للصفحة while(true){// تكرار /* date("s-i-h") تُخرج الوقت على التَنسيق ساعة-دقيقة-ثانية PHP_EOL تعني سطر جديد يُمكن إستخدام \n لكن سَتختلف من نظام تَشغيل الى آخر */ echo "data: ".date("s-i-h").PHP_EOL; echo PHP_EOL; // كتابة نص جديد آخر للتفريق بين التنبيهات // إرسال المحتوى الذي كتب دون الإنتظار إلى انتهاء الصفحة ob_flush(); flush(); // إيقاف الاسكربت إلى المدة التي تم تعريفها في المتغير time لمدة ثانية أن لم تَقف الاسكربت سَيعمل بدون توقف sleep($time); } ?>إن فتحت الصفحة ستجد أنها تقوم بإرسال الساعة الحالية كل ثانية. الآن الواجهة التي ستتعامل معها: <!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> </head> <body style="direction: rtl"> <label>الساعة الآن : <span>.......</span></label> <script type="text/javascript"> if(!EventSource) alert("مٌتصفحك لايَدعم SSE :("); else{ SSE = new EventSource("server.php"); // انشاء الطلب SSE.onmessage = function(ms){ // عند وجود رسالة جديدة document.querySelector("span").innerHTML = ms.data;// استبدال النص بالجديد } SSE.onerror = function(){//عند وجود خطأ alert("يوجد خطأ في الاتصال ..."); mkcon(); // إعادة الطلب } } </script> </body> </html> الأحداث وطرق انشاءهايُمكن في SSE إنشاء أحداث مخصصة لكي تَكون الشفرة أكثر ذكاءا، مثلًا إرسال حدث لحذف عنصر وحدث لإنشاء عنصر آخر. مثال: تعريف مصفوفة بإسم الألوان أحمر أخضر أزرق. $colors = array("red", "green", "blue");ثم تعريف اسم الحدث بإسم عشوائي من هذه الألوان: $colors[rand(0,2)];ليكون النص الكامل لملف server.php: <?php set_time_limit(0);// تحديد أقصى مدة للطلب غير نهائية لان هناك خوادم تقوم بتحديدها $time = 1;// متغير بقيمة الوقت الذي سيرسل فيه تحديث جديد header("Content-Type: text/event-stream");// إرسال ترويسة أن الصفحة هي محتوى sse $colors = array( // مصفوفة الألوان من النوع الرقمي "red", "green", "blue" ); while(true){// تكرار /* date("s-i-h") تُخرج الوقت على التَنسيق ساعة-دقيقة-ثانية PHP_EOL تعني سطر جديد يُمكن إستخدام \n لكن سَتختلف من نظام تَشغيل إلى آخر */ echo "data: ".date("s-i-h").PHP_EOL; echo "event: ". $colors[rand(0,2)] // توليد لون عشوائي بإسم الحدث .PHP_EOL; echo PHP_EOL; // كتابة نص جديد آخر للتفريق بين التنبيهات // إرسال المحتوى وعدم الإنتظار إلى إكتمال الطلب إلى النهاية ob_flush(); flush(); // إيقاف الاسكربت إلى المدة التي تم تعريفها في المتغير time لمدة ثانية أن لم تَقف الاسكربت سَيعمل بدون توقف sleep($time); } ?>وفي واجهة المستخدم إنشاء بعض العناوين: <style> .red{ color: red } .blue{ color: blue } .green{ color: green } </style> <label class="red"> ....... </label> <label class="green"> ....... </label> <label class="blue"> ....... </label>الآن إستقبال الأحداث: SSE.addEventListener("red",function(ms){ //اللّون الأحمر red اسم الحدث الذي عرف من php document.querySelector(".red").innerHTML = ms.data; });لتصبح شفرة الواجهة الكاملة: <!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <style> .red{ color: red } .blue{ color: blue } .green{ color: green } </style> </head> <body> <label class="red"> ....... </label> <label class="green"> ....... </label> <label class="blue"> ....... </label> <script type="text/javascript"> if(!EventSource) alert("مٌتصفحك لايَدعم SSE :("); else{ function mkcon(){ SSE = new EventSource("server.php"); // إنشاء الطلب SSE.addEventListener("red",function(ms){ //اللون الأحمر document.querySelector(".red").innerHTML = ms.data; }); SSE.addEventListener("green",function(ms){ //اللون الأخضر document.querySelector(".green").innerHTML = ms.data; }); SSE.addEventListener("blue",function(ms){ //اللون الأزرق document.querySelector(".blue").innerHTML = ms.data; }); SSE.onerror = function(){//عند وجود خطأ alert("يوجد خطأ في الاتصال ..."); mkcon(); // اعادة الطلب } } mkcon(); } </script> </body> </html>ستجد أن أحدى الساعات من الثلاثة تزيد بشكل عشوائي كل ثانية لكن بالطبع هذا مثال، لن تقوم بإستخدام هذه التقنية في هذا. إستخدام بيانات jsonإن أردت إرسال مقال بإستخدام SSE فستحتاج إلى إرسال العنوان والمحتوى ولن تُرسل حدثين لذلك يُمكنك إستخدام json كما يلي: { "title": "العنوان", "content": "المحتوى" }اذًا سَترسل هذه البيانات: data: {"title": "ألعنوان","content":"ألمحتوى"}ثم سيكون إستقبال البيانات من خلال javascript بهذا الشكل: SSE.onmessage = function(ms){ var JsonData = JSON.parse(ms.data); // قراءة نص json alert(JsonData.title + "\n" + JsonData.content); }مثال عملي عن إرسال بيانات json بتوقيت السيرفر المحلي: ملف server.php <?php set_time_limit(0);//تُحدد أقصى مدة للطلب غير نهائية لان هناك خوادم تقوم بتحديدها $time = 1;// متغير بقيمة الوقت الذي سيرسل فيه تحديث جديد header("Content-Type: text/event-stream");// إرسال ترويسة أن الصفحة هي محتوى sse while(true){// تكرار /* date("s-i-h") تُخرج الوقت على التَنسيق ساعة-دقيقة-ثانية PHP_EOL تعني سطر جديد يُمكن إستخدام \n لكن سَتختلف من نظام تَشغيل الى آخر */ echo 'data: '. /** مصفوفة بالوقت. دالة json_encode تقوم بتحويل البيانات الى ترميز json */ json_encode(array( "hour" => date("h"), // الساعة "minute" => date("i"), // الدقيقة "second" => date("s") // الثانية )) .PHP_EOL; echo PHP_EOL; // كتابة نص جديد آخر للتفريق بين التنبيهات // إرسال المحتوى وعدم الإنتظار إلى إكتمال الطلب إلى النهاية ob_flush(); flush(); // إيقاف الاسكربت إلى المدة التي تم تعريفها في ألمتغير time لمدة ثانية ان لم تَقف الاسكربت سَيعمل بدون توقف sleep($time); } ?>وملف الواجهة: <!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> </head> <body style="direction: rtl"> <p>الساعة الآن <b id="hour">0</b> و <b id="minute">0</b> دقيقة و <b id="second">0</b> ثانية.</p> <script type="text/javascript"> if(!EventSource) alert("مٌتصفحك لايَدعم SSE :("); else{ function mkcon(){ SSE = new EventSource("server.php"); // إنشاء الطلب SSE.onmessage = function(ms){ var JsonData = JSON.parse(ms.data); // قراءة نص json document.querySelector("#hour").innerHTML = JsonData.hour; // إستبدال الساعة document.querySelector("#minute").innerHTML = JsonData.minute;// إستبدال الدقائق document.querySelector("#second").innerHTML = JsonData.second;// إستبدال الثواني } SSE.onerror = function(){//عند وجود خطأ alert("يوجد خطأ في الاتصال ..."); mkcon(); // إعادة الطلب } } mkcon(); } </script> </body> </html>في هذا المثال والذي سبقه لن تَحتاج إلى إستحدام SSE ويمكن عَمل ذلك بإستخدام javascript بسهولة. تطبيق على ما شُرح: بناء تطبيق تواصللم تُستخدم قاعدة بيانات لتسهيل تشغيل السكربت (لكن بالطبع إستخدام قاعدة بيانات أفضل) سيولد لون لكل مستخدم وسيحفظ في cookies للتعرف عليه في الرسائل، ستحفظ البيانات في ملف ChatData.json على هذا الشكل (كان يُمكن إستخدام قاعدة بيانات sqlite): [// مصفوفة الرسائل { "author": "rgb(54,48,98)", // لون السمتخدم "message": "رسالة", }, { "author": "rgb(54,48,98)", // لون السمتخدم "message": "رسالة 2", }// ....,...,.. ]تصميم الواجهةهذه هيكلة الصفحة: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>تجربة</title> <meta name="viewport" content="width=device-width"> </head> <body> <div class="messages"> </div> <div class="form"> <div class="author me"></div> <textarea placeholder="كتابة رسالة"></textarea> <button id="send">أرسل</button> </div> </body> </html>إضافة الحيوية للصفحة: body,html{ width: 100%;/** جعل عرض الصفحة بكامل الشاشة **/ direction: rtl; height: 100%; background: #f0f0f0;/** لون الخلفية **/ padding: 0; margin: 0 } /** جعل حجم بعض العناصر يحسب بالحواشي الخارجة **/ .messages, .messages > .message, .message > .content, .form, .form > textarea{ box-sizing: border-box } /** الرسائل **/ .messages{ padding: 40px; /** عمل فراغ بقيمة 40px من كل جهة **/ height: 80%; overflow-y: auto/** جعل الرسائل الزائدة تتحول الى اسكرول **/ } /** عناصر لن تكون بكامل عرض الحافظة **/ .message > .author, .message > .content{ display: inline-block;/** لن يأخذ العنصر كامل العرض **/ vertical-align: top /** العنصر سيكون في محاذة الأعلى **/ } /** هوية المستخدم **/ .message > .author{ width: 64px; height: 64px; background: #ddd; /** اللون الافتراضي سيغير بعد ذلك **/ border-radius: 100% /** تدوير العنصر **/ } /** جسم الرسالة **/ .message > .content{ background: white; box-shadow: 0 0 1px #ccc; /** تأثير الظل **/ padding: 20px; margin-right: 30px; /** لجعل الصفحة أكثر إستجابة على الشاشات المختلفة **/ width: 100%; max-width: 550px } /** عناصرالتحكم لإرسال رسالة **/ .form{ padding: 10px; height: 20% } /** صندوق النصوص **/ .form > textarea{ width: 100%; max-width: 678px; height: 100% } /** زر الإرسال **/ .form > button{ background: rgb(31, 158, 238); padding: 4px 20px; border: 0; cursor: pointer; /** مؤشر الفارة **/ color: white; border-radius: 3px /** عمل بعض الحواف **/ } .form > button:active{ background: rgb(31, 158, 200) } /** تصميم الجوال إصلاح بعض العلل **/ @media screen and (max-width:640px){ .messages{ padding: 0 } .message > .content{ margin: 0; margin-bottom: 20px } }إستقبال الرسائلSEE لاتدعم إرسال البيانات إلى الخادوم لذا سَتستخدم ajax لذلك. هذه شفرة بسيطة لإرسال الرسائل إلى الخادوم (يُضاف بين الوسم script في أخر الصفحة قبل </body> أو إن كنت ستضيفه الى head، يجب أن يكون في دالة: window.onload = function(){ //هنا }; document.querySelector("#send").addEventListener("click",function(){// عند الضغط على زر إرسال var mss = document.querySelector(".form > textarea").value; // محتوى الرسالة if(mss && mss.length){// التأكد من أن الرسالة مكتوبة var ajax = new XMLHttpRequest(); ajax.onreadystatechange = function(){ if(this.readyState == 4) alert("تم إرسال الرسالة"); } ajax.open("get","server.php"); // نوع الطلب ومسار الإرسال ajax.send("?ms="+mss);// إرسال الرسالة } });ستحدث مشكلة إن أرسل المستخدم أكثر من رسالة في وقت واحد، من الممكن أن تصل رسالة قبل الأخرى. لذا سنستعمل قائمة بالرسائل المرسلة ونقوم بمعالجتها واحدة ثم الأخرى مع تحسين تجربة المستخدم قليلًا: messagesList = []; // مصفوفة بالرسائل InProgress = false;// يوجد طلب يُعالج أم لا function sendFirstMessage(){ if(!messagesList.length || InProgress) // لايوجد رسائل أو هناك رسالة تُعالج return false; var ajax = new XMLHttpRequest(); ajax.onreadystatechange = function(){ // الرسالة ارسلت if(this.readyState == 4){ messagesList.splice(0, 1);// حذف الرسالة المرسلة اول رسالة InProgress = false; // تم الإنتهاء من العمل sendFirstMessage(); // إرسال الرسالة الأخرى } } ajax.onerror = function(){ alert("خطأ في الإتصال جاري المحاولة مجددًا"); sendFirstMessage(); } InProgress = true; ajax.open("get","send.php?message="+messagesList[0]); // نوع الطلب ومسار الإرسال ajax.send();// إرسال الرسالة } document.querySelector("#send").addEventListener("click",function(){// عند الضغط على زر إرسال var mss = document.querySelector(".form > textarea").value; // محتوى الرسالة if(mss && mss.length){// التأكد من أن هناك رسالة مكتوبة messagesList.push(mss); // إضافة رسالة إلى المصفوفة sendFirstMessage(); // إرسال الرسالة الأولى document.querySelector(".form > textarea").value = "";// افراغ حقل إرسال الرسائل } });ملف الخادوم الذي سيستقبل الرسائل، لن تكتب [] في الملف لتسهيل الكتابة في نهايته دون قِرائته: ملف send.php (الذي سيستقبل الرسائل) <?php if(empty($_GET["message"])) die(); /* التأكد من المستخدم */ if(!isset($_COOKIE["author"]) || !preg_match("/^rgb\([0-9]{0,3}\,[0-9]{0,3}\,[0-9]{0,3}\)$/i",$_COOKIE["author"])){ // توليد لون عشوائي أن لم يُعرف من قبل $R = rand(0,250); // اللّون الأحر $G = rand(0,250); // اللّون الأخضر $B = rand(0,250); // اللّون الأزرق setcookie("author","rgb($R,$G,$B)"); // نمط الألوان rgb } $message = str_replace(array("<",">"),array("&lt;","&gt;"),$_GET["message"]); // الوقاية من ثغرة xss $data = array( // البيانات التي ستكتب "content" => $message, "author" => $_COOKIE["author"] ); $FILE = fopen("./ChatData.json","a"); //فتح الملف للكتابة فيه $star = (!filesize("./ChatData.json")) ? "" :",";// إضافة فاصلة أن تم الكتابة بالملف من قبل fwrite($FILE,$star.json_encode($data)); // كتابة بيانات json في الملف fclose($FILE); ?>ملاحظة: سبب إستخدام الألوان هو لكي تتمكن من التعامل مع المستخدمين وحفظ البيانات في قاعدة البيانات للمستخدم. ملف server.php (الذي سيراقب تغير الملف ويرسل التحديث) <?php set_time_limit(0); // لايوجد اقصى مدة للطلب header("Content-Type: text/event-stream"); $viewd = 0; // عدد الرسائل التي عرضت لمنع إرسال الرسالة اكثر من مرة $lastFileSize = 0; // حجم الملف عندما يتغير فان هناك رسالة ارسلت while(true){ if(filesize("./ChatData.json") >= $lastFileSize){// التحديث فقط عند تغير حجم الملف $messages = json_decode("[".file_get_contents("./ChatData.json")."]"); // قراءة الرسائل for($i=$viewd;$i<=sizeof($messages)-1;$i++){ // حلقة تكرار من أخر رسالة مُرسلة echo "data: ".json_encode($messages[$i]).PHP_EOL; echo PHP_EOL; } $viewd = sizeof($messages); // تحديث عدد الرسائل المرسلة $lastFileSize = filesize("./ChatData.json"); // تحديث مُتغير أخر حجم للملف } ob_flush(); flush(); sleep(1); } ?>ثم في واجهة المستخدم شفرة javascript لجلب التحديثات: sse = new EventSource("server.php"); // انشاء الاتصال sse.onmessage = function(ms){ var data = JSON.parse(ms.data); // قراءة بيانات json var message = document.createElement("div"); // عنصر الرسائل message.setAttribute("class","message"); // اضافة كلاسس message var author = document.createElement("div"); // عنصر مُرسل الرسائل author.setAttribute("class","author"); // اضافة كلاسس author author.style.backgroundColor = data.author; // تغير لون الخلفية لمعرف المستخدم message.appendChild(author); // اضافة عنصر مُعرف مُعرف المستخدم الى عنصر الرسالة var content = document.createElement("p"); // محتوى الرسالة content.setAttribute("class","content"); content.innerHTML = data.content;// محتوى الرسالة message.appendChild(content); messages = document.querySelector(".messages"); messages.appendChild(message);// اضافة عنصر الرسالة الى حافظة الرسائل messages.scrollTop = messages.scrollHeight; // انزال الاسكرول الى الاعلى } ليعمل على انترنت اكسبلور يجب تضمين ملف javscript هذا في الـ header: <script src="https://rawgithub.com/remy/polyfills/master/EventSource.js"></script> يجب ان يكون العمل مُطابق لهذا: إن لم يعمل معك بشكل صحيح يُمكنك تحميل ملفات الأمثلة وقراءتها حتى تتمكن من معرفة كيف يعمل هذا المثال . الخاتمةيُمكنك رؤية المزيد من المصادر لمعرفة المزيد حول SSE: eventsource من طرف W3C.Server Sent Events من طرف مدونة موزيلا للمبرمجين.
  4. تمنح تقنيّة Ajax مواقع الويب إمكانيّةَ تحميل محتوى إلى عناصر مختلفة ضمن الصّفحة دون الحاجة لإنعاش Refresh الصّفحة. قد لا تبدو - للوهلة الأولى - ميزةً مؤثّرة، إلّا أنّها تمكّن من فعل الكثير من الأمور. تستخدم الكثير من الإجراءات مثل التّصويت، إبداء الإعجاب، التّعليقات على Disqus والتّغريدات على تويتر؛ وإجراءات أخرى كثيرة Ajax للحصول على تجربة مستخدم User experience لا مثيل لها. ذاع صيت Ajax سنة 2005 عندما استخدمته Google في ميزة اقتراحات البحث، الّتي تُظهر اقتراحات أثناء الكتابة على محرّك بحث Google. لم تكن هذه الميزة لترى النّور لولا وجود تقنيّة Ajax. سنعرض في هذا المقال لكيفيّة استخدام Ajax في ووردبريس. سيكون الأمر أكثر تعقيدًا قليلًا ممّا تعوّدت عليه، إلّا أنّ الأمور ستجري على ما يُرام إن كان لديك فهم جيّد CSS، HTML وPHP؛ إضافةً لفهم لأساسيّات Javascript. كيف يعمل Ajaxليس Ajax في الواقع سوى Javascript. تأتي كلمة Ajax اختصارًا لAsynchronous Javascript And XML (أي: Javascript وXML لا تزامنيّان). تهدف تقنيّة Ajax إلى أن تكون جسرًا بين موقع الويب والخادوم، وهو ما يعني أنّك ستسخدمها بالتّزامن مع Javascript، CSS، HTML وربّما PHP أيضًا؛ الأمر الّذي قد يعقّد الأمور قليلًا. يعدّ الكائن XMLHttpRequest الّذي يتبادل البيانات مع الخادوم الأساس الّذي تقوم عليه تقنيّة Ajax. يشبه عمل Ajax عمومًا إرسال استمارة في صفحة ويب. في ما يلي الخطوات الأساسيّة: تحدّد البيانات الّتي تريد إرسالهاتضبُط نداء Ajaxتستخدم كائن XMLHttpRequest لإرسال البيانات إلى الخادومتحصُل على إجابة من الخادوم. يُمكن استخدام ردّ الخادوم ضمن شفرة Javascript.يكثُر استخدام Ajax عبر دوالّ التّغليف Wrapper functions في jQuery، لذا سنركّز على هذه الطّريقة في هذا المقال. أوّل نداء Ajaxسنحتاج قبل البدء في كتابة شفرة Ajax إلى قالب يُمكن التّعديل عليه، قالب فرعيّ أو إضافة. أنصح باستخدام إضافة. إنشاء إضافة سهل: أنشئ مجلَّدًا فرعيًّا ضمن wp-content/plugins (اخترتُ اسم ajax-text). أنشئ ملفّ PHP ضمن المجلَّد وأعطِه نفس اسم المجلَّد (ajax-text.php) وألصِق الشفرة التّاليّة في الملفّ: <?php /** * Plugin Name: Ajax Test * Plugin URI: <a href="http://danielpataki.com">http://danielpataki.com</a> * Description: This is a plugin that allows us to test Ajax functionality in WordPress * Version: 1.0.0 * Author: Daniel Pataki * Author URI: <a href="http://danielpataki.com">http://danielpataki.com</a> * License: GPL2 */ ستظهر الإضافة، بعد حفظ الملفّ، في ركن الإضافات في لوحة التّحكّم بووردبريس. فعّلها. للمزيد حول تطوير إضافات ووردبريس راجع دليل مدخل إلى برمجة إضافات ووردبريس. 1- صفّ Enqueueing ملفّ Javascriptسنحتاج لماستخدام ملفّ JavaScript في الإضافة، لذا سنصُفّ الملفّ. نستخدم الشّفرة التّاليّة لهذا الغرض: add_action( 'wp_enqueue_scripts', 'ajax_test_enqueue_scripts' ); function ajax_test_enqueue_scripts() { wp_enqueue_script( 'test', plugins_url( '/test.js', __FILE__ ), array('jquery'), '1.0', true ); }تُضيف الشّفرة أعلاه ملفّ test.js إلى الموقع. أخبرنا ووردبريس أنّ jQuery اعتماديّة يجب تحميلها في التّذييل Footer. الخطوة التّاليّة هي إنشاء نداء Ajax بسيط داخل ملفّ Javascript. 2- إنشاء نداء Ajaxيحوي نداء Ajax على العديد من المعطيات، سنتعرّف على الأساسيّة منها. jQuery(document).ready( function($) { $.ajax({ url: "http://yourwebsite.com", success: function( data ) { alert('يبلُغ عدد عناصر div في الصّفحة الرّئيسيّة لديك:' + $(data).find('div').length); } }) })كلّ ما فعلناه هو استخدام دالّة ()ajax.$ وتحديد بعض الخيّارات. يحدّد المُعطى url لنداء Ajax أين يجب أن يُرسل الطّلب الّذي تستقبل الدّالةُ الموجودة في المعطى success الإجابةَ عليه. يجب أن يُحيل المُعطى url إلى رابط الصّفحة الرّئيسيّة لموقعك. ستظهر لك، إذا نفّذت الخطوات بطريقة صحيحة، نافذةٌ منبثقة بعدد عناصر div الموجودة في الصّفحة الرئيسيّة فورَ إنعاش الصّفحة (ﻷخذ التّغييرات على الإضافة في الحسبان). لا يبدو المظهر أنيقًا؛ إلّا أنّ ما فعلناه في الواقع هو تحميل صفحة مختلفة تمامًا دون إعادة تحميل تلك الّتي نوجد فيها. مثال كامل لاستخدام Ajax في ووردبريسيوفّر ووردبريس دعمًا أكبر للمساعدة في استخدام نداءات Ajax ويقدّم آليّة معياريّة لتأديّتها. سنُبدِل عنوان url بآخر أكثر ديناميكيّة وننشئ طريقةً أفضل للتّخاطب مع الخادوم. سنعرض في المثال التّالي كيفيّةَ إضافة ميزة “أعجبني هذا المقال” إلى موقعنا. أنشأتُ إضافةً جديدة لهذا الغرض باسم “post-love”، يمكن الملفّ المضغوط للإضافة في آخر الدّرس. ملحوظة: يجب تعطيل وحذف الإضافة السّابقة ajax-text حتى يمكن للإضافة الجديدة post-love العمل بطريقة صحيحة. 1- تخزين وعرض البياناتسنستخدم حقلًا مُخصَّصًا Custom field لحفظ عدد الإعجابات الّتي تلقّاها المنشور. سنسمّي هذا الحقل post_love وسنعرض قيمته تحت كلّ مقال في صفحة منفردة Single page. في ما يلي كيفيّة ذلك (ملفّ content-filter.php): add_filter( 'the_content', 'post_love_display', 99 ); function post_love_display( $content ) { $love_text = ''; if ( is_single() ) { $love = get_post_meta( get_the_ID(), 'post_love', true ); $love = ( empty( $love ) ) ? 0 : $love; $love_text = '<p class="love-received"><a class="love-button" href="#" data-id="' . get_the_ID() . '">أبدِ(ي) إعجابك</a><span id="love-count">' . $love . '</span></p>'; } return $content . $love_text; } يمكننا عبر استخدام content-filter.php عرض أيّ شيء نُريده أسفل محتوى المقال مباشرةً. نستخدم دالّة ()is_singl للتّأكّد من أنّ الإعجابات تظهر على الصّفحات المنفردة. نحصُل على عدد الإعجابات من الحقل المخصَّص post_love ونتأكّد من أنّ العدد يساوي صفرًا إن لم توجد إعجابات حتى اللّحظة. استخدمنا HTML لعرض رابط لإبداء الإعجاب وعدد الإعجابات. نحصُل على معرّف المقال عبر الدّالّة ()get_the_ID ونمرّره لدالّة get_post_meta الّتي نحصُل من خلالها على قيمة الحقل المخصَّص post_love؛ ثمّ في اﻷخير نُرجِع المحتوى متبوعًا بالنّص الّذي أنشأناه للتّو. نستخدم CSS لإضافة بعض التّحسينات على طريقة العرض عبر صفّ ملفّ CSSكان تفهم جيّدًا على النّحو التّاليّ: add_action( 'wp_enqueue_scripts', 'post_love_assets' ); function post_love_assets() { if( is_single() ) { wp_enqueue_style( 'love', plugins_url( '/love.css', __FILE__ ) ); }لاحظ استخدام الشّرط ()is_single قبل تحميل ملفّ CSS، يعود السّبب في ذلك إلى أنّه لا معنى لتحميل النّمط في صفحات غير منفردة، حيثُ إنّه لا يُستخدَم إلّا فيها؛ الأمر الّذي يُساعد في التّقليل من تأثير الإضافة على زمن تحميل الموقع. أنشئ ملفّ love.css في مجلَّد الإضافة وضمّن فيه النّمط التّالي: .entry-content .love-button { background: #f14864; color: #fff; padding:11px 22px; display:inline-block; border:0px; text-decoration: none; box-shadow: 0px 6px #d2234c; position:relative; } .entry-content .love-button:hover{ top:3px; box-shadow: 0px 3px #d2234c; } .entry-content .love-button:active{ top:6px; box-shadow: none; } #love-count { background: #eee; box-shadow: 0px 6px #ddd; color: #666; padding:11px 22px; display:inline-block; }تظهر النّتيجة بعد إكمال الخطوات السّابقة على هيئة زرّ بتأثير ثلاثيّ الأبعاد يوجد أمامه عدّاد. 2- نداء Ajax الخطوة التّاليّة هي تجميع نداء Ajax. يستدعي ذلك كتابة السكربت وصَفَّه. سنناقش، قبل البدْء في كتابة السّكربت، معطَى url الموجود في نداء Ajax. في المثال السّابق (ajax-text) كتبنا عنوان الموقع مباشرةً ضمن الشّفرة البرمجيّة، وهي طريقة غير صحيحة أثناء إنشاء الإضافات؛ فكلّ موقع لديه عنوانه الخاصّ. ملحوظة: سنحذف الشّفرة التّاليّة ونعيد كتابتها من جديد مع إضافة نداء Ajax: add_action( 'wp_enqueue_scripts', 'post_love_assets' ); function post_love_assets() { if( is_single() ) { wp_enqueue_style( 'love', plugins_url( '/love.css', __FILE__ ) ); }يوفّر ووردبريس ملفًّا موّحدًا للاستخدام (wp-admin/admin-ajax.php) ووسائلَ للحصول على مسار الملفّ كامِلًا. يستدعي الملفّ بعض الحيّل أثناء صفّه، نعرض لها في الشّفرة التّاليّة: add_action( 'wp_enqueue_scripts', 'ajax_test_enqueue_scripts' ); function ajax_test_enqueue_scripts() { if( is_single() ) { wp_enqueue_style( 'love', plugins_url( '/love.css', __FILE__ ) ); } wp_enqueue_script( 'love', plugins_url( '/love.js', __FILE__ ), array('jquery'), '1.0', true ); wp_localize_script( 'love', 'postlove', array( 'ajax_url' => admin_url( 'admin-ajax.php' ) )); }تُسجّل الشّفرة أعلاه ملفّ Javascript بالطّريقة المُعتادة، ثمّ تمرّر سلسلة محارف String إلى السّكربت عبر الدّالّة ()wp_localize_script (تُستخدَم هذه الدّالّة عادةً لترجمة سلاسل المحارف الموجودة في Javascript). يُمكنك إضافة سلاسل محارف إلى المصفوفة Array بحسب حاجتك. يمكننا الآن استخدام المتغيّر postlove.ajax_url في ملفّ love.js للحصول على مسار admin-ajax.php، على النّحو التّاليّ: jQuery( document ).on( 'click', '.love-button', function() { var post_id = jQuery(this).data('id'); jQuery.ajax({ url : postlove.ajax_url, type : 'post', data : { action : 'post_love_add_love', post_id : post_id }, success : function( response ) { alert(response) } }); })ليست هذه النّسخةَ النّهائيّة، لكنّنا وضعنا أساس التّخاطب مع الخادوم. ننفّذ دالّة في كلّ مرة يُضغَط فيها على الزّرّ أبد(ي) إعجابك، ثمّ نأخذ معرّف المقال ونخزّنه في المتغيّر post_id. يُنشئ سكربت love.js نداء Ajax يأخذ في معطى url قيمةَ المتغيّر postlove.ajax_url. بالنّسبة لمعطى type فقد أعطيناه القيمة post أيّ أنّ التّخاطب مع الخادوم سيكون عبر طلبات Post وهي ما يعني أنّنا سنستخدِم المتغيّر POST_$، من جانب الخادوم، للحصول على البيانات. توجد في قسم data كلّ المعطيات الّتي تودّ إرسالها. نحتاج لإرسال معرّف المقال (post_id) لنعرِف المقال الّذي أبدى الزّائر إعجابه به. يطلُب ووردبريس تحديد إجراء Action عند استخدام الملفّ admin-ajax.php. حدّدنا الإجراء post_love_add_love. أخيرًا نعرض النّتيجة في نافذة منبثقة. إذا جربّت التّعديلات الجديدة وضغط على زرّ إبداء الإعجاب فستظهر رسالة منبثقة تحوي عدد مرّات إبداء الإعجاب (0). لم نعرّف خطّافًا Hook للإجراء post_love_add_love في الشّفرة أعلاه وهو ما يؤدّي بadmin-ajax إلى إرجاع القيمة 0. سنعرّف في الفقرة المواليّة الخطّاف النّاقص. 3- المعالجة من جانب الخادوموصلنا في هذه المرحلة إلى إرسال بيانات إلى الخادوم دون أن نخبره مالّذي يجب عليه فعله بها. يجب، عند الضّغط على الزّرّ، زيّادة عدد الإعجابات ب1 وإرجاع القيمة الجديدة. سنحتاج لإنشاء خطّافَين لهذا الغرض. add_action( 'wp_ajax_nopriv_post_love_add_love', 'post_love_add_love' ); add_action( 'wp_ajax_post_love_add_love', 'post_love_add_love' ); function post_love_add_love() { }يُنفَّذ الخطّاف الأوّل بالنّسبة للزّوّار الضّيوف، والثّاني لمُسجَّلي الدّخول. بالمناسبة، هذه طريقة جيّدة للتّحكّم في الوصول. الاتّفاق هو كالتّالي: هل تتذكّر كيف عرّفنا معطى الإجراء في نداء Ajax؟ نحتاج إلى إلحاق هذا المعطى ب_wp_ajax و/أو _wp_ajax_nopriv. بالنّسبة للدّالّة يُمكن استخدام أيّ تسميّة، استخدمنا نفس سلسلة المحارف الموجودة في اسم الإجراء للحفاظ على التّجانس. نُضيف الآن وظيفة الزّيّادة بالتّدرّج Incrementing add_action( 'wp_ajax_nopriv_post_love_add_love', 'post_love_add_love' ); add_action( 'wp_ajax_post_love_add_love', 'post_love_add_love' ); function post_love_add_love() { $love = get_post_meta( $_POST['post_id'], 'post_love', true ); $love++; update_post_meta( $_POST['post_id'], 'post_love', $love ); echo $love; die(); }نعثُر على القيمة الحاليّة، نزيدها بواحد ثمّ نخزّن القيمة الجديدة ونعرضها. يجب تنفيذ ()die في نهاية الدّالّة وإلّا فإنّ ملفّ admin-ajax.php سينفّذ (0)die الخاصّة به ممّا ينتج عنه عرض 0 إضافيّ في الإجابة. تظهرعند الضّغط على زر إبداء الإعجاب الآن نافذة منبثقة بها عددُ الإعجابات، وعند إنعاش الصّفحة يظهر العدد إلى جانب الزّرّ المذكور. لم يتبقّ لنا سوى جعل العدد يتغيّر دون الحاجة لإنعاش الصّفحة. تتكفّل شفرة Javascript التّاليّة بهذه المهمّة: jQuery( document ).on( 'click', '.love-button', function() { var post_id = jQuery(this).data('id'); jQuery.ajax({ url : postlove.ajax_url, type : 'post', data : { action : 'post_love_add_love', post_id : post_id }, success : function( response ) { jQuery('#love-count').html( response ); } }); })يؤدّي الضّغط على الزّرّ الآن إلى زيّادة عدد الإعجابات دون الحاجة لإنعاش الصّفحة. 4- لمسات أخيرةربّما لاحظت أنّنا أضفنا في زرّ الإعجاب رابطًا إلى #: <a class="love-button" href="#" data-id="' . get_the_ID() . '">أبدِ(ي) إعجابك</a>يعود السّبب في ذلك إلى أخذ الحالة الّتي يكون Javascript فيها غير مفعَّل لدى الزّائر. في هذه الحالة يؤدّي الضّغط على الزّرّ إلى إعادة تنزيل الصّفحة. إلّا أنّ استخدام # وحده ضمن وسم Tag الرّابط يجعل هذا الأخير غير صالح Invalid. فلنعالج هذا الأمر. ملحوظة: يُستخدَم ماسك المكان Placeholder # في خاصيّة href ضمن وسم <a> للدّلالة على أنّ الرّابط يُحيل إلى عنصر من الصّفحة يُعطَى معرّفه بعد ماسك المكان (مثلًا:"a href="#id5). استخدام # لوحده يعني أنّ الرّابط يُحيل إلى معرّف غير موجود في الصّفحة. في المحصّلة يُحيل الرّابط إلى الصّفحة لكنّه يبقى غير صالح دلاليًّا Semantic. سنعدّل على الشّفرة بحيث يُحيل الرّابط إلى نداء Ajax، مثلًا: http://yourwebsite.com/wp-admin/admin-ajax.php?action=post_love_add_love&post_id=23 بالنّسبة للزّوّار الّذين يفعّلون Ajax. add_filter( 'the_content', 'post_love_display', 99 ); function post_love_display( $content ) { $love_text = ''; if ( is_single() ) { $love = get_post_meta( get_the_ID(), 'post_love', true ); $love = ( empty( $love ) ) ? 0 : $love; $love_text = '<p class="love-received"><a class="love-button" href="' . admin_url( 'admin-ajax.php?action=post_love_add_love&post_id=' . get_the_ID() ) . '" data-id="' . get_the_ID() . '">give love</a><span id="love-count">' . $love . '</span></p>'; } return $content . $love_text; }إذا كان Javascript مفعَّلًا في المتصفّح فيجب تعطيل إمكانيّة تتبّع رابط URL. لذا نُضيف return false إلى الدّالّة في حدث click كما يلي: jQuery( document ).on( 'click', '.love-button', function() { var post_id = jQuery(this).data('id'); jQuery.ajax({ url : postlove.ajax_url, type : 'post', data : { action : 'post_love_add_love', post_id : post_id }, success : function( response ) { jQuery('#love-count').html( response ); } }); // نُرجع القيمة false هنا return false; })الخطوة الأخيرة هيّ التّفريق بين عمليّات Ajax والعمليّات الأخرى ضمن دالّة ()post_love_add_love. عند استخدام Ajax نعرض القيمة الجديدة، ثمّ يتوقّف السكربت. إن لم يُستخدَم Ajax (يعني هذا أنّ الزّائر سيُوجَّه إلى admin-ajax) فكلّ ما نفعله هو إعادة توجيه المتصفّح إلى المقال بعد تنفيذ السّكربت. في ما يلي النّسخة النّهائيّة لدالّة ()post_love_add_love: function post_love_add_love() { $love = get_post_meta( $_REQUEST['post_id'], 'post_love', true ); $love++; update_post_meta( $_REQUEST['post_id'], 'post_love', $love ); if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { echo $love; die(); } else { wp_redirect( get_permalink( $_REQUEST['post_id'] ) ); exit(); } }أبدلنا المتغيّر POST_$ بREQUEST_$ إذ أنّ الزوّارّ بAjax مفعَّل سيستخدمون طلب POST في ما يستخدم بقيّة الزّوّار طلب GET. يمكّن متغيّر REQUEST_$ من الحصول على البيانات في الحالتيْن. جوانب إضافية تجب مراعاتها عند استخدام AjaxAjax ليس صعبًا، إلّا أنّه يوجد الكثير من الأمور السّهلة الّتي تجب عليك معرفتها. في ما يلي أهمّ جانبيْن ينبغي عليك الاهتمام بهما. 1- الأمانيُمكن أن تؤدّي قلّة الحذر أثناء استخدام Ajax إلى مشاكل أمنيّة عديدة. لا تتحقّق الإضافة التّي أنشأناها أعلاه من الضّغطات المتعدّدة الّتي يُمكن أن تؤدّي - فضلًا عن تشويه النّتائج - إلى زيّادة الحمل على الخادوم، خصوصًا إن واصل الكثيرون الضّغط على الزّرّ في نفس الوقت. علاوةً على ذلك فإنّ تغيير العدّاد لا يحتاج للضّغط على الزّر؛ مجرّد الدّخول إلى الرّابط (مثلًا http://yourwebsite.com/wp-admin/admin-ajax.php?action=post_love_add_love&post_id=23) يفي بالمهمّة. خذ في الحسبان أنّ بعض الإضافات تسمح بحذف المقالات من الواجهة اﻷماميّة دون الحاجة للدّخول إلى لوحة التّحكّم؛ ماذا لو وُجِد رابط تؤدّي زيّارته إلى نفس التّأثير؟ ليست وضعيّة مثاليّة. استخدام الأرقام الخاصّة Nonces هو إحدى وسائل تأمين الاستمارات والرّوابط ضدّ المحاولات الخبيثة. 2- تجربة المستخدميعود انتشار استخدام Ajax إلى أنّ المستخدم يحصُل على تفاعليّة أكبر مع التّقليل من وقت التّحميل. يعني هذا أيضًا أنّه يجب عليك العمل جاهدًا للتّأكّد من أنّ استخدامك لAjax يرفع حقًا من نوعيّة تجربة المستخدم: يجب أن يعرف الزّائر دائمًا مالّذي يحدُث ولماذا. لو كانت إضافة post-love موجَّهةً للنّشر ﻷضفتُ أيقونة لحالة التّحميل تعطّل زرّ الإعجاب عند الضّغط عليه، ثمّ بعد نجاح العمليّة يعود الزّرّ لحالته الطّبيعيّة. تُمكن إضافة هذه الميزة عن طريق معطى beforeSend ضمن دالّة ()ajax.$. يُتيح beforeSend إمكانيّة تنفيذ بعض التّعليمات قبل إرسال البيانات إلى الخادوم. إضافة إشارة مرئيّة أثناء تغيّر عدد الإعجابات فكرة جيّدة؛ إن لم يكن المستخدم يعرف مالّذي ينتظره عند الّضغط على الزّر فربّما لا يُلاحظ تغيّر عدد الإعجابات. في المقابل، لا تبالِغ باستخدام التّأثيرات المرئيّة أثناء التّعامل مع نداءات Ajax، كما حصل للكثيرين مع انتشار هذه التّقنيّة فجعلوا كلّ شيء يتحرّك وينشط في كلّ اتّجاه. لا تضف تأثيرات تلفت النّظر إلّا إذا كان لذلك معنى، ولا تجعل المستخدم ينتظر حتّى نهاية التّحريكة Animation؛ وإلّا فإنّ جودة تجربة المستخدم ستنقُص ولن تزيد. خاتمةتعدّ تقنيّة Ajax من الأدوات القويّة المتاحة للمطوّرين من أجل إضافة التّفاعليّة والتّقليل من الضّغط على الخادوم. لن يُمكن إحصاء الميزات الممكن تقديمها باستخدام Ajax: عرض التّعليقات دون الحاجة لإعادة تنزيل الصّفحة، التّمرير اللّامتناهيّ Infinite scrolling للمقالات، التّحميل الخامل Lazy-loading للصوّر، وغيرها الكثير. سيجعل Ajax تطبيقك أفضل بكثير شرطَ الانتباه إلى تفاصيل تجربة المستخدم وعدم استخدامه لغير ضرورة. رابط تنزيل إضافة `post-love` في صورتها النّهائيّة. post-love.zip ترجمة بتصرّف لمقال Using AJAX With PHP on Your WordPress Site Without a Plugin لكاتبه Daniel Pataki.
  5. أصبحت تقنيّة أجاكس موضة العصر في السنوات الأخيرة وعن جدارة واستحقاق، إن تقنيّة أجاكس، والتي هي بمسماها الكامل Asynchronous JavaScript and XML، هي طريقة لإجراء "محادثة" مع الخادوم، وعرض النتائج بدون إعادة تحميل الصّفحة. يُقدّم هذا الأسلوب للمطورين أمور عدّة: تحديث عدّاد الإعجاب (زر أعجبني).إضافة عناصر إلى سلّة التسوّق.إنشاء نماذج (forms) ديناميكيّة.وإليه من هذه الأمور، وكل ذلك بدون إعادة تحميل الصّفحةستتطرّق هذه المقالة إلى كيفيّة تحميل المنشورات (posts) مع تقنيّة أجاكس وباستخدام القالب الافتراضي Twenty Fifteen كمثال وأساس للشرح. سيتمّ التطرّق إلى لماذا يجب استخدام أجاكس من خلال مثالٍ مُبسّط، ومن ثُمّ سيتمّ العمل على مثال آخر وتحميل المنشورات باستخدام أجاكس وذلك باستخدام القالب Twenty Fifteen. لماذا يجب استخدام تقنية AJAX؟يَطلب سكريبت ووردبريس المنشورات من قاعدة البيانات عندما يتمّ تحميل الصفحة الأولى للمقالات، ويعرضهم مستخدمًا التوصيف (markup) المحدّد، وبجانب ذلك، سيتم تحميل قوائم التنقّل (navigation menus)، والإضافات المُصغّرة ودجت (widgets)، والرسومات، وملفّات جافا سكريبت، والعديد من الأمور الأخرى، وكما توضّح الصورة عدد الطلبات الّتي تمّ تطلبها في الصفحة الواحدة. يتّضح من الصورة السابقة (المأخوذة من أدوات المطورين الخاصّة بالمُتصفّح كروم) عدد لا بأس به من الأصول الّتي يتمّ تحميلها، وعلى الرغم من أنّه سيتمّ استخدام بعض التقنيات في تحسين الأداء، واستخدام التخبئة (cache) لبعض الأصول (assets) كما هو الأمر مع ملفّات جافا سكريبت، سيبقى عدد الطلبات عددًا لا بأس به. وإن تحميل الصّفحة الثّانية من المنشورات سيُحمّل كل ما سبق مرّة أُخرى، حيثُ سيجلب ووردبريس المنشورات ويعرضها باستخدام التوصيف (markup) المحدّد، ويتم تحميل عناصر الصّفحة مرّة أخرى أيضًا، ولذلك يُعتبر هذا الأسلوب مضيعة للموارد في أغلب الحالات، ولا يخدم تجربة المُستخدم أيضًا، فلا أحد يرغب بالانتظار بينما يتمّ استكمال تحميل الصّفحة بطبيعة الحال. إنشاء قالب فرعي Creating Child Themeسيتمّ التعديل على القالب Twenty Fifteen، ولكن قبل ذلك سيتمّ إنشاء قالب (theme) فرعي، وذلك للحفاظ على التعديلات في حال تحديث القالب. البداية مع تقنية AJAXسيتمّ البدء بمثال مُبسّط يشرح آلية عمل تقنيّة أجاكس، وذلك من خلال روابط شريط التصفيح (pagination)، في أسفل الصّفحة بحيثُ عندما يتمّ الضغط على رقم الصّفحة، يتم تحميل الصّفحة ديناميكيًّا، فعندما يتمّ الضغط على أحد هذه الروابط سيتمّ إرسال طلب (request) إلى الخادوم وتنبيه (alert) النتيجة. صف ملفات JavaScriptستكون الخطوة الأولى هي إنشاء ملفّ جافا سكريبت وصفّه باستخدام ملفّ القالب functions.php. تمّ إنشاء المجلّد js و الملفّ ajax-pagination.js بداخله، ومن ثُمّ إضافة الشيفرة التّالية إلى الملفّ functions.php. function my_enqueue_assets() { wp_enqueue_style( 'parent-style', get_template_directory_uri().'/style.css' ); wp_enqueue_script( 'ajax-pagination', get_stylesheet_directory_uri() . '/js/ajax-pagination.js', array( 'jquery' ), '1.0', true ); }يُمكن الرجوع إلى المقال صفّ وتسجيل ملفات Javascript و CSS في قوالب ووردبريس للمزيد من التفصيل حول صف ملفات جافا سكريبت، ولكن بالمُختصر ما سيتمّ عمله هو إخبار ووردبريس باسم السكريبت (كما في المُعامل الأوّل)، ومكانه (المُعامل الثّاني)، والمُتطلّبات (المُعامل الثّالث)، الإصدار (المعامل الرابع)، والتحميل سيكون في ذيل الصّفحة (المعامل الخامس). يجدر الانتباه هنا إلى إنه عند صفّ ملفّ التنسيق، تمّ استخدام ()get_template_directory_uri، وهذه الدالة دائمًا تُشير إلى مسار القالب الرئيسي (parent theme)، وعند صف السكريبت تمّ استخدام ()get_stylesheet_directory_uri، والّذي يُشير إلى مسار القالب الفرعي (chiled theme). بعد أن تمّ تحميل السكريبت في ذيل الصّفحة، يُمكن ببساطة كتابة: alert( ‘Script Is Enqueued’ ) داخل الملفّ ajax-pagination.js، ومع إعادة تحميل الصّفحة سيتمّ معرفة فيما إذا كان السكريبت يعمل أم لا. إنشاء حدث Creating an Eventستكون الخطوة التّالية هي إنشاء حدث (event) مهمته هي بدء استدعاء (call) أجاكس، وسيكون الحدث في هذه الحالة هو الضغط على رابط مُعيّن، ولاستهداف الرابط يجب معرفة أصناف (classes) العناصر والمعرّفات (IDs) المحيطة به. يُمكن الوصول إلى الشيفرة السابقة باستخدام أدوات المطورين المُضمّنة ضمن المُتصفّح Chrome. توضّح الصورة السابقة كيف أنّ روابط التصفيح تملك الصنف page-numbers، ورابط الصّفحة التّالية يملك الصنف السابق بالإضافة إلى الصنف next، وجميعها داخل الوسم nav الّذي يملك الصنف nav-links، يوجد أيضًا رابط الصّفحة السابقة، ولكنه لا يظهر في الصورة السابقة، والّذي يحمل الصنف prev بالإضافة إلى الصنف page-numbers. سيتمّ الآن استهداف أحد الروابط داخل حاوية التصفيح (pagination)، ومن ثم إنشاء تنبيه (alert) وبالرسالة Clicked Link: (function($) { $(document).on( 'click', '.nav-links a', function( event ) { event.preventDefault(); alert( 'Clicked Link' ); }) })(jQuery);يُلاحظ كيف أنّ كل شيء محتوى داخل دالّة مجهولة (anonymous function)، وهو ما يُنصح به، بعد ذلك تمّ إنشاء حدث الضغط، ومن ثُمّ منع الوظيفة الافتراضيّة للحدث (تحميل الصّفحة)، ومن ثُمّ عرض رسالة نصيّة باستخدام دالّة التنبيه (alert). إنشاء استدعاء AJAXسيتمّ الآن العمل على جلب بيانات ديناميكيّة من الخادوم بدلًا من التعامل مع واجهة الموقع فقط (تنبه نص معدّ مُسبقًا) كما في المثال السابق، ولذلك يجب إتمام بعض الإعداد المُسبق، وذلك للأسباب التّالية: يجب إعطاء استدعاء أجاكس رابطًا لاستخدامه هذا أوّلًا.ثانيًا، لا يَعلم ملفّ الجافا سكريبت المُنشئ ببيئة العمل الخاصّة بسكريبت ووردبريس. لذلك لا يُمكن استخدام دالة على الشكل ()get_stylesheet_directory_uri فيه، ولكن من المُمكن استخدام أسلوب المَوضَعة (localization) لتمرير المُتغيّرات إلى جافا سكريبت، وذلك في الملفّ functions.php: wp_localize_script( 'ajax-pagination', 'ajaxpagination', array( 'ajaxurl' => admin_url( 'admin-ajax.php' ) ));إن إضافة الشيفرة السابقة داخل الدالة ()my_enqueue_assets، سيقوم بتعريف الكائن ajaxpagination (المُعامل الثاني)، حيثُ سيَستلم هذا الكائن الـ members الخاصّة به تبعًا إلى المصفوفة المزوّدة كمُعامل ثالث في الدالة ()wp-localize_script، بمعنى آخر، عندما يتمّ إضافة هذه الشيفرة سيصبح من المُمكن استخدام ajaxpagination.ajaxurl لتعيين الرابط URL إلى admin-ajax.php، والّذي سيُستخدم لتولّي استدعاءات أجاكس. <script type='text/javascript'> /* <![CDATA[ */ var ajaxpagination = {"ajaxurl":"http:\/\/wordpress.local\/wp-admin\/admin-ajax.php"}; /* ]]> */ </script> أصبح كل شيء جاهزًا لبناء استدعاء أجاكس في ملفّ جافا سكريبت كما هو موضّح في الشيفرة التالية: $(document).on( 'click', '.nav-links a', function( event ) { event.preventDefault(); $.ajax({ url: ajaxpagination.ajaxurl, type: 'post', data: { action: 'ajax_pagination' }, success: function(result ) { alert( result ); } }) })تمّ استخدام الدالة ()ajax.$، مع العلم أنّه يوجد دوال خاصّة من أجل post و get، ولكن يُفضّل البعض استخدام هذه الدالة بسبب مرونتها، والّتي يُمكن القراءة عن كافّة مُعاملاتها من خلال التوثيق الرسمي لمكتبة jQuery. تمّ استخدام المُعامل url لتمرير مسار السكريبت المُراد إرسال البيانات إليه، وهو الملفّ admin-ajax.php، والّذي سيكون في المسار wp-admin، بعد أن تمّ تعريف ذلك سابقًا في الدالة ()wp_localize_script. يقوم أخيرًا المُعامل success، والّذي هو دالّة، بتنبيه (alert) نتيجة استدعاء أجاكس، وبذلك يكون قد تمّ اختبار هذه الدالة، والّتي سيتمّ التعديل عليها لاحقًا، ولكن الآن سيعمل الرابط إن تمّ الضغط عليه، ولكنه ليس بذو نفعٍ في الوقت الحالي، بما أنّه لم يتمّ إعداد الشيفرة من جهة الخادوم بعد، مع ذلك ستكون النتيجة هي الرقم 0، وهي النتيجة الافتراضيّة عندما لا تكون شيفرة الخادوم قد كتبت. يَكمن السبب في ظهور النتيجة السابقة رغم عدم كتابة شيفرة من طرف جهة الخادم إلى وجود شيفرة مكتوبة بالفعل من قِبل ووردبريس، وذلك بالملف admin-ajax.php المُستخدم في المثال، والّذي يحتوي في طياته على الدالة ('die('0. سيَخمد السكريبت admin-ajax.php ويعود من الدالة (return) بالقيمة 0، في حال عدم تزويد أي إجراء (action)، ولكن إن تمّ تزويد إجراء ولم يتمّ تزويد الخطافات (hooks) المطلوبة من قِبل ووردبريس فلن يحدث شيء، وفي نهاية الملفّ سيتمّ الخمود (die) مرّة أُخرى والعودة بالقيمة 0. الاتصال مع ووردبريسيجب تعريف بعضًا من إجراءات ووردبريس للحصول على إجابة ذو معنى من ووردبريس، ويتم ذلك من خلال استخدام نمط محدّد: add_action( 'wp_ajax_nopriv_ajax_pagination', 'my_ajax_pagination' ); add_action( 'wp_ajax_ajax_pagination', 'my_ajax_pagination' ); function my_ajax_pagination() { echo get_bloginfo( 'title' ); die(); }تمّ ربط دالّة مع خطافين (hooks)، فالخطافات الّتي تأخذ الشكل [wp_ajax_[action_nam تُنفّذ للمُستخدمين أصحاب العضويّة، والخطافات الّتي تأخذ الشكل [wp_ajax_nopiv_[action_name تُنفّذ للمُستخدمين الزوّار، وهذا أمر جيّد لفصل الوظائف عن بعضها بسهولة. إن أسماء الإجراءات (actions) الّتي تمّ ذكرها في الأعلى تُشير إلى الإجراء المعرّف في استدعاء أجاكس في ملفّ جافا سكريبت المُنشئ سابقًا (action: ajax_pagination)، أي من المُفترض أنّ يتطابقا، أما اسم الدالة فمن الممكن أنّ يكون أي اسم، وتم اختيار الاسم my_ajax_pagination للوضوح. يُمكن للدالة أنّ تحتوي على أي شيء، فمن المُمكن تسجيل خروج المُستخدمين، جلب بياناتهم، أو نشر منشور، ومهما كان المطلوب عودته (return) إلى جافا سكريبت فمن الضروري استخدام echo، وفي المثال الحالي تمّ استخدام echo مع عنوان المُدوّنة، والّذي يُمكن الوصول إليه عن طريق الدالة ()get_bloginfo. ستكون الخطوة الأخيرة هي استخدام ()die، فبعدم تعريفها، فإن الدالة (dir(0 المعرّفة في نهاية الملف admin-ajax.php سيتمّ تنفيذها ليتم طباعة 0 بالإضافة إلى ما سيتمّ طباعته في الدالة الّتي يتمّ كتابتها حاليًّا، والآن إن تمّت تجربة الشيفرة السابقة فمن المفترض رؤية عنوان الموقع. تلخيصتمّ إلى هنا الوصول إلى نهاية المثال، وقبل الانتقال إلى كيفيّة استعراض المنشورات باستخدام أجاكس، سيتمّ مراجعة الخطوات الضروريّة لإتمام استدعاء أجاكس: صف (enqueue) ملفّ جافا سكريبت، إن لم يكن متوفرًّا بالأساس.استخدام ()wp_localize_script لتمرير URL للملف admin-ajax.php.إنشاء استدعاء أجاكس في جافا سكريبت.ربط دالة باستخدام اسم خطّاف (hook) مناسب.كتابة شيفرة الدّالة والّتي ستعود بالبيانات إلى جافا سكريبت.تحميل المنشورات باستخدام AJAXسيتمّ البدء بكتابة شيفرة جافا سكريبت، والّتي ستكون مبدئيًّا بالشكل التّالي، والتعديل عليها لاحقًا. (function($) { function find_page_number( element ) { element.find('span').remove(); return parseInt( element.html() ); } $(document).on( 'click', '.nav-links a', function( event ) { event.preventDefault(); page = find_page_number( $(this).clone() ); $.ajax({ url: ajaxpagination.ajaxurl, type: 'post', data: { action: 'ajax_pagination', query_vars: ajaxpagination.query_vars, page: page }, success: function( html ) { $('#main').find( 'article' ).remove(); $('#main nav').remove(); $('#main').append( html ); } }) }) }) (jQuery);إن الشيفرة السابقة مُشابهة إلى المثال المُبسّط السابق، مع ملاحظة أنه تمّ إضافة طريقة لمعرفة أي صفحة أراد المُستخدم طلبها، وكل رابط لديه عنصر span بداخله وهو في حالة عدم الظهور، كما تمّ استنساخ (clone) العنصر لكيلا يتمّ التعديل على العنصر الأصلي، ومن ثم حذف العنصر span وتحليل (parse) الباقي كعدد صحيح (integer) باستخدام الدالة parseInt، ليكون الناتج رقم الصّفحة. سيكون من الضروري أيضًا معرفة مُعاملات الاستعلام (query parameters) المُستخدمة، وسيكون الأمر سهلًا في الصفحة الرئيسيّة، باستخدام المُعامل paged، وذلك لأنّه يتمّ التعامل مع الاستعلام الافتراضي، إن تمّ البدء على صفحة أرشفة (archive page)، مثل أرشيف التصنيفات (category)، فعندها يجب معرفة اسم الصنف أيضًا. سيتمّ تمرير مُتغيّرات الاستعلام باستخدام طريقة الموضعة (localization) المُستخدمة سابقًا، ولكن الآن سيتمّ استخدام ajaxpagination.query_vars على الرغم من عدم تعريفها بعد، وأخيرًا وفي success سيتمّ حذف جميع العناصر article من الحاوية الرئيسية، وحذف عنصر التصفيح (pagination) وإلحاق (append) القيمة المُعادة من (return) استدعاء أجاكس المُنشئ إلى الحاوية الرئيسية. ستحتوي القيمة المُعادة (return) على المنشورات وعنصر التنقل الجديد، مع ملاحظة تغيير اسم المُعامل من response إلى html ليصبح الاسم معبّرًا أكثر، أخيرًا تمّ استخدام مصفوفة الموضعة لتمرير معاملات الاستعلام الأصليّة. يجب على الدالة التّالية أنّ توضع في الدالة ()my_enqueue_assets لتبديل الموضعة (localization) الّتي تمت سابقًا. global $wp_query; wp_localize_script( 'ajax-pagination', 'ajaxpagination', array( 'ajaxurl' => admin_url( 'admin-ajax.php' ), 'query_vars' => json_encode( $wp_query->query ) ));ستكون الدالة ()my_ajax_pagination في صورتها النهائيّة على الشكل التّالي: add_action( 'wp_ajax_nopriv_ajax_pagination', 'my_ajax_pagination' ); add_action( 'wp_ajax_ajax_pagination', 'my_ajax_pagination' ); function my_ajax_pagination() { $query_vars = json_decode( stripslashes( $_POST['query_vars'] ), true ); $query_vars['paged'] = $_POST['page']; $posts = new WP_Query( $query_vars ); $GLOBALS['wp_query'] = $posts; add_filter( 'editor_max_image_size', 'my_image_size_override' ); if( ! $posts->have_posts() ) { get_template_part( 'content', 'none' ); } else { while ( $posts->have_posts() ) { $posts->the_post(); get_template_part( 'content', get_post_format() ); } } remove_filter( 'editor_max_image_size', 'my_image_size_override' ); the_posts_pagination( array( 'prev_text' => __( 'Previous page', 'twentyfifteen' ), 'next_text' => __( 'Next page', 'twentyfifteen' ), 'before_page_number' => '<span class="meta-nav screen-reader-text">' . __( 'Page', 'twentyfifteen' ) . ' </span>', ) ); die(); } function my_image_size_override() { return array( 825, 510 ); }إن استخدام المُعاملات المُمرّرة مكّن من بناء استعلام مُخصّص (custom query) وذلك عن طريق استخدام مُتغيّرات الاستعلام الّتي تمّ تمريرها والتأكّد أنّ رقم الصّفحة الّذي تمّ تمريره يستبدل المُعامل paged، ومن ثُمّ تمّ استخدام المصفوفة query_vars لإنشاء الاستعلام الجديد. يجب جعل المُتغيّر ['wp_query']اGLOBALS$ مساويًا إلى كائن المنشورات الجديد، والسبب في ذلك هو أنّ الدالة ()the_posts_pagination تَستخدم هذا المُتغيّر العام (global variable). يُلاحظ أنّه تمّ إضافة دالة إلى المُرشح editor_max_image_size، ومن ثُمّ وبعد أسطر قليلة تمّ إزالتها، وذلك لمشكلة في سكريبت ووردبريس نفسه، وفي الحقيقة تمّ تسجيل هذا المُشكلة في مُتتبّع المشاكل الخاصّ بووردبريس حيث أن المشكلة هي كالتالي: ستتحمل الصور بشكل مناسب عندما يتم تحميلها في صفحة المنشور، ولكن بدون هذه المُرشحات فإن الصور ستكون ضيقة، أي ستكون 660px عرضًا بدلًا من 825px، والسبب في ذلك هو أنّ الدالة الّتي تحمّل الصور ستستدعي دالة بالاسم ()image_constrain_size_for_editor، وهذه الدالة تتأكّد من أنّ الصور في محرّر المنشور ليست عريضة بشكل كبير، ولتحديد فيما إذا كان يجب تقلّيص الحجم أم لا، تُستخدم الدالة ()is_admin في ذلك، وبما أنّ شيفرة المثال تعمل عبر admin-ajax.php، والّذي يعتبر admin في نهاية الأمر، سيُقلّص ووردبريس من حجم الصور، معتقدًا أنّه يتمّ استخدامهم في المُحرّر. يُمكن استخدام المرشح editor_max_image_size لتحديد الحجم الأعظمي للصور في المُحرّر، وبما أنّه المطلوب هو ترك كل شيء على ما هو عليه، باستثناء استدعاء أجاكس، فقد تمّ إضافة المرشح باستخدام القيمة المخصّصة (array( 828, 510 ومن ثُمّ إزالتها مباشرة لتأكّد من أنها لا تسبب مشاكل في أي مكان آخر. ستكون الخطوة التّالية استخدام الاستعلام في عرض المنشورات، حيثُ تمّ نسخ الكثير من الملفّ index.php في القالب الرئيسي (parent theme)، وفي حال عدم توفّر منشورات سيتمّ استخدام عارضة (template) مُخصّصة لهذا الأمر، وأخيرًا سيتمّ استخدام شكل التصفيح (pagination) كما هو في الملفّ index.php. تجربة مُستخدِم أفضليجب التركيز على تجربة المُستخدم دائمًا، حيثُ أنّه عند العمل في بيئة التطوير يبدو التصفّح سريعًا للغاية، ولكن تأخذ الصور وبقيّة الأصول (assets) وقتًا أطول في التحميل في بيئة العمل الحقيقيّة. يُفترض إضافة مُحمّل (loader) أو على الأقل نصّ يُشير إلى جريان تحميل المنشورات وذلك في سبيل تحسين تجربة المُستخدم، وبالإضافة إلى ذلك من المُمكن تعطيل أي نقرات (clicks) إضافيّة على عناصر شريط التنقل، وهذا ما سيتمّ في المثال الحالي: إخفاء المنشورات وشريط التنقل مباشرةً بعد عملية الضغط من قبل المُستخدِم ومن ثم عرض رسالة فحواها هو "loading new posts" (جاري تحميل المنشورات)، وعند الحدث success سيتمّ إزالة نص الرسالة وعرض المنشورات، وسيكون استدعاء أجاكس بصيغته النهائيّة على الشكل التّالي: $.ajax({ url: ajaxpagination.ajaxurl, type: 'post', data: { action: 'ajax_pagination', query_vars: ajaxpagination.query_vars, page: page }, beforeSend: function() { $('#main').find( 'article' ).remove(); $('#main nav').remove(); $(document).scrollTop(); $('#main').append( '<div class="page-content" id="loader">Loading New Posts...</div>' ); }, success: function( html ) { $('#main #loader').remove(); $('#main').append( html ); } })أصبح هناك دالتين منفصلتين وهما beforeSend و الدالة success، الأولى يتمّ تنفيذها حالما يتمّ الضغط على الرابط، قبل أنّ يُرسل استدعاء أجاكس إلى الخادوم، أما الثانية فيتم تنفيذها حالما يتمّ استقبال البيانات من الخادوم. سيتمّ إزالة المقالات وشريط التنقّل قبل إرسال الاستدعاء، ويفيد هذا الأمر في منع المُستخدم من الضغط باستمرار على روابط شريط التنقّل إلى حين انتهاء التحميل، ومن ثُمّ سيتمّ التدرّج إلى أعلى الصّفحة، وإضافة تنبيه يوضّح جريان عملية التحميل لتوضيح الأمور للمُستخدم، وفي الدالّة success تمّ إزالة المُحمّل loader ومن ثُمّ تحميل المُحتوى. مشاكل أجاكسإن أجاكس تقنيّة متقدّمة وقويّة بلا شك، فبجانب المثال المٌقدّم في هذا الدليل، يُمكن إجراء العديد من الأمور باستخدام استدعاءات أجاكس، ولكن يجب الحذر في بعض المواضع عند تطبيق هذه التقنيّة، سيتمّ التطرّق إلى بعضها: الحماية: تُعبر الحماية من الأمور الأساسيّة والهامّة الّتي يجب التركيز عليها، فعند الرغبة في حذف منشور باستخدام أجاكس يجب التأكد من عزم المُستخدم على ذلك، وأيضًا يجب التأكّد من صلاحياته (باستخدام nonces)، خاصّة بعد العلم أنّ ووردبريس مُجهز بحماية مُضمّنة عند العمل بشكله الافتراضي، ولكن عند تطبيق تقنيّة أجاكس فإن مسألة الحماية تقع على المطوّر.أجاكس بدون جافا سكريبت: تعمد تقنيّة أجاكس على جافا سكريبت، بمعنى آخر عدم توفّر جافا سكريبت يعني عدم توفّر أجاكس، فإن تمّ الاعتماد على أجاكس بشكل كبير في الموقع، فلن يستطيع المستخدمين الذين قاموا بتعطيل جافا سكريبت على متصفحاتهم من استخدام الموقع، على الرغم من أن جافا سكريبت أصبحت متوفّرة دائمًا، ولكن لا يخلو الأمر من هذه الحالات، وعليه من الجيّد ضمان عمل التطبيق في حال عدم توفّرها.تجربة المُستخدم: تُهمل تجربة المُستخدم غالبًا، وما يقدّمه أجاكس بالتأكيد أمر في غاية الأهميّة، ولكن الأهم هو موقع يعمل كما هو مفروضٌ له أنّ يَعمل، فالمُستخدمين معتادين على تحميل الصّفحة من جديد عند الضغط على الروابط، لذا من مهمّة المُطوّر جعل كل شيء واضح بالنسبة لهم، وعلى المُستخدمين معرفة ما الذي يحدث ولماذا، بمعنى آخر يجب استخدام أجاكس لتحسين الموقع، وعدم الإفراط في الاستخدام لكيلا ينقلب السحر على الساحر.تلخيصكما هو مُلاحظ إن تطبيق تقنيّة أجاكس يتطلّب القليل من التحضير والتدريب، ولكن مع التكرار ستصبح الأمور سهلة وميسرة، وربّما قراءة هذا الدرس قد استغرقت بعض الوقت، وسيستغرق تطبيق المثال أيضًا وقتًا أطول وخاصّة في المرّة الأولى، ولكن مع المُمارسة ستغدو الأمور أوضح والتطبيق أسرع. ترجمة –وبتصرّف- للمقال Loading WordPress Posts Dynamically With AJAX لصاحبه Daniel Pataki.
  6. ‏AJAX‏AJAX هي اختصار للعبارة "asynchronous JavaScript and XML"، وهي وسيلة لجلب البيانات من الخادوم دون الحاجة لإعادة تحميل الصّفحة، وهي تقوم على استخدام كائن مُتاح في المتصفّح اسمه XMLHttpRequest (أو XHR اختصارًا) لإرسال الطّلب إلى الخادوم ثمّ التّعامل مع البيانات الّتي يُجيب بها الخادوم. تُوفّر jQuery الوظيفة ‎$.ajax‎ (ووظائف أخرى مرافقة مُختصرة) لتسهيل العمل مع طلبات XHR في جميع المتصفّحات. ‏‎$.ajax‎بإمكاننا استخدام الوظيفة ‎$.ajax()‎ المُرفقة مع jQuery بعدّة أساليب: إحداها أن نُمرّر إليها كائنًا يحوي الإعدادات فقط، أو أن نُمرّر الرّابط مع أو بدون كائن الإعدادات. لنُلقِ نظرة على الأسلوب الأول: // أنشئ دالّة الاستدعاء الرّاجع الّتي ستُنفّذ عندما ينجح طلب AJAX var updatePage = function( resp ) { $( '#target').html( resp.people[0].name ); }; // وعندما يفشل var printError = function( req, status, err ) { console.log( 'something went wrong', status, err ); }; // أنشئ كائن الإعدادات الذي يصف الطّلب var ajaxOptions = { url: '/data/people.json', dataType: 'json', success: updatePage, error: printError }; // أرسل الطّلب $.ajax(ajaxOptions);بإمكانك طبعًا أن تُمرّر كائنًا حرفيًّا مباشرةً إلى الوظيفة‎$.ajax() ‎ وأن تستخدم دالّة مجهولة محلّ success وerror، هذا الأسلوب كتابته أسهل، وصيانته في المستقبل أسهل: $.ajax({ url: '/data/people.json', dataType: 'json', success: function( resp ) { $( '#target').html( resp.people[0].name ); }, error: function( req, status, err ) { console.log( 'something went wrong', status, err ); } });كما قلنا، بإمكانك استخدام الوظيفة‎$.ajax() ‎ بأسلوب ثانٍ، وذلك بتمرير الرّابط أوّلًا ثمّ كائن الإعدادات ثانيًا (ليس إلزاميًّا). يُفيدك هذا في حال رغبت في استخدام الإعدادات المبدئيّة للوظيفة أو في حال رغبت في استخدام كائن الإعدادات نفسه لأكثر من رابط: $.ajax( '/data/people.json', { type: 'GET', dataType: 'json', success: function( resp ) { console.log( resp.people ); }, error: function( req, status, err ) { console.log( 'something went wrong', status, err ); } });في المثال السّابق، لا تشترط الوظيفة سوى الرّابط، ولكنّ إضافة كائن الإعدادات تسمح لنا بإخبار jQuery بنوع البيانات الّتي نُرسلها، وأي فعل HTTP نستخدمه (POST، GET، إلخ...‏‏)، وما نوع البيانات الّتي نتوقّع استقبالها من الخادوم، وما الّذي يجب فعله إن نجح الطّلب أو فشل... اطّلع على وثائق الوظيفة‎$.ajax() ‎ لقراءة كامل الخيارات الّتي يمكن إضافتها إلى كائن الإعدادات. ‏A في AJAX تعني "لامتزامن"تجري طلبات AJAX بصورة لا متزامنة، وهذا يعني أنّ الوظيفة‎$.ajax ‎ تنتهي قبل انتهاء الطّلب، وقبل أن تُستدعى دّالة success، أيّ أنّ جملة return تُنفّذ قبل أن يصل جواب الطّلب. فالدّالة getSomeData في المثال التّالي ستُعيد قيمة data قبل أن تُعرّف، مما يؤدّي إلى وقوع خطأ: تحذير: نصّ برمجيّ غير سليم var getSomeData = function() { var data; $.ajax({ url: '/data/people.json', dataType: 'json', success: function(resp) { data = resp.people; } }); return data; } $( '#target' ).html( getSomeData().people[0].name );‏X في AJAX تعني JSON!وضع المصطلح AJAX عام 2005 ليصف طريقة لجلب البيانات من الخادوم دون الحاجة لإعادة تحميل كامل الصّفحة. في ذلك الوقت، كانت الصّيغة الأكثر شيوعًا للبيانات الّتي تُرسلها الخوادم هي XML، أمّا اليوم، فإنّ JSON هي الصّيغة الّتي تعتمدها أكثر التّطبيقات الحديثة. صيغة JSON في أساسها هي سلسلة نصّيّة (string) تُمثّل البيانات، وتبدو مُشابهة كثيرًا لكائن JavaScript عاديّ، ولكنّها لا تستطيع تمثيل كلّ أنواع البيانات الّتي يستطيع كائن JavaScript تمثيلها. فمثلًا: لا يمكن لـJSON تمثيل كائنات التّاريخ (Date) ولا الدّوال (functions). فيما يلي مثال عن نصّ JSON، لاحظ كيف تُحاط كلّ أسماء الخصائص بعلامتي اقتباس مُضاعفتين: { "people" : [ { "name" : "Ben", "url" : "http://benalman.com/", "bio" : "I create groovy websites, useful jQuery plugins, and play a mean funk bass. I'm also Director of Pluginization at @bocoup." }, { "name" : "Rebecca", "url" : "http://rmurphey.com", "bio" : "Senior JS dev at Bocoup" }, { "name" : "Jory", "url" : "http://joryburson.com", "bio" : "super-enthusiastic about open web education @bocoup. lover of media, art, and fake mustaches." } ] }تذكّر أنّ JSON هو تمثيل نصّيّ لكائن، ما يعني أنّه يجب تفسير السّلسلة النّصيّة لتحويلها إلى كائن JavaScript عاديّ قبل التّعامل معها. عندما تعمل مع جواب ورد من الخادوم بصيغة JSON، فإنّ jQuery تتولّى هذه المهمّة عنك. ولكن من المهمّ التمييز بين الكائنات الفعليّة، وطريقة تمثيلها في JSON. ملاحظة: إن أردت إنشاء سلسلة JSON نصّيّة من كائن JavaScript أو تفسير سلسلة JSON نصّيّة لتحويلها إلى كائن JavaScript دون الاستعانة بـjQuery، فإنّ المُتصفّحات الحديثة تُقدّم الوظيفتين ‎JSON.stringify()‎ و‎JSON.parse()‎، ويمكن إضافة هذه الخصائص إلى المُتصفّحات القديمة باستخدام المكتبة json2.js. توفّر jQuery أيضًا وظيفة ‎jQuery.parseJSON()‎، الّتي توافق الوظيفة ‎JSON.parse()‎ في المتصفّحات، إلّا أنّها لا توفّر وظيفة تُقابل ‎JSON.stringify()‎. وظائف مُختصرةإن كان كلّ ما نريده إرسال طلب بسيط، دون الاهتمام بالتّعامل مع الأخطاء الّتي قد تقع، فإنّ jQuery تُوفّر وظائف مُختصرة تسمح لنا بفعل ذلك. تستقبل كل وظيفة مُختصرة رابطًا وكائن إعدادات غير إلزاميّ، ودالّة تُستدعى عند نجاح الطّلب فقط: $.get( '/data/people.html', function( html ){ $( '#target' ).html( html ); }); $.post( '/data/save', { name: 'Rebecca' }, function( resp ) { console.log( resp ); });إرسال البيانات والعمل مع النّماذجبإمكاننا إرسال بيانات مع طلبنا بتعيين قيمة للخاصة data في كائن الإعدادات، أو تمرير كائن كمُعامل ثانٍ للوظائف المُختصرة. ستُضاف هذه البيانات إلى الرّابط في طلبات GET بصورة "جملة استعلام" (query string)، أمّا في طلبات POST فإنّها ستُرسل كبيانات نموذج. توفّر jQuery وظيفة مُفيدة ‎.serialize()‎ الّتي تستقبل مُدخلات نموذج وتُحوّلها إلى صيغة "جملة استعلام" (مثل field1name=field1value&field2name=field2value...): $( 'form' ).submit(function( event ) { event.preventDefault(); var form = $( this ); $.ajax({ type: 'POST', url: '/data/save', data: form.serialize(), dataType: 'json', success: function( resp ) { console.log( resp ); } }); });‏jqXHRتُعيد‎$.ajax() ‎ والوظائف المُختصرة المرافقة لها، كائن jqXHR (اختصارًا لـjQuery XML HTTP Request) والّذي يتضمّن وظائف مُفيدةً كثيرة. بإمكاننا إرسال طلب باستخدام ‎$.ajax()‎ ثمّ حفظ كائن jqXHR في مُتغيّر: var req = $.ajax({ url: '/data/people.json', dataType: 'json' });بإمكاننا استخدام هذا العنصر لربط الاستدعاءات الرّاجعة بالطّلب، حتّى بعد أن يكتمل الطّلب. بإمكاننا مثلًا استخدام الوظيفة ‎.then()‎ (ثُمَّ) لإرفاق استدعاءي نجاح الطّلب وفشله، إذ تقبل ‎.then()‎ دالّة أو اثنتين، تستدعى الأولى عند نجاح الطّلب، والثّانية إن فشل: var success = function( resp ) { $( '#target' ).append( '<p>people: ' + resp.people.length + '</p>' ); console.log( resp.people ); }; var err = function( req, status, err ) { $( '#target' ).append( '<p>something went wrong</p>' ); }; req.then( success, err ); req.then(function() { $( '#target' ).append( '<p>it worked</p>' ); });بإمكاننا استدعاء ‎.then()‎ على كائن الطّلب قدر ما نشاء، وستُنفّذ الاستدعاءات الرّاجعة بالتّرتيب ذاته الّتي أرفقت وفقه. إن لم نُرد إرفاق استدعاءي النّجاح والفشل معًا، فبإمكاننا استخدام الوظيفتين ‎.done()‎ و‎.fail()‎ على كائن الطّلب: req.done( success ); req.fail( err );لو أردنا إرفاق استدعاء راجعٍ يُنفَّذ دومًا، بغض النّظر عن نجاح الطّلب أو فشله، فيمكننا استخدام الوظيفة ‎.always()‎ على كائن الطّلب: req.always(function() { $( '#target' ) .append( '<p>one way or another, it is done now</p>' ); });‏JSONPيستغرب كثيرٌ من المبتدئين في JavaScript فشل طلبات XHR الّتي يرسلونها إلى نطاق آخر على الإنترنت، فمثلاً: يحاول بعض المُطوّرين جلب بيانات من واجهة برمجيّة من طرف ثالث (third-party API)، ليفاجؤوا بفشل الطّلب باستمرار. السّبب وراء ذلك أنّ المُتصفّحات لا تسمح بإرسال طلبات XHR إلى نطاقات إنترنت أخرى لأسباب أمنيّة، ولكنّ بعض الواجهات البرمجيّة تُعيد البيانات بصيغة JSONP (اختصارًا لـJSON with Padding)، الّتي تسمح للمُطوّرين بجلب البيانات متجاوزين حظر المُتصفّح. الحقيقة أن JSONP ليس طلب AJAX فعليًّا، فهو لا يستخدم طلب XHR الّذي يوفّره المُتصفّح، بل يعمل بإدراج وسم <script> في صفحة الويب، الّذي يحوي بدوره البيانات المطلوبة، مُحاطة بدالّة تُعيد هذه البيانات عند استدعائها. ليس هذه التّفاصيل مهمّة الآن، لأنّ jQuery تسمح لك بطلب JSONP كما لو كان XHR باستخدام الوظيفة ‎$.ajax()‎ بتعيين نوع البيانات dataType إلى 'jsonp' في كائن الإعدادات. $.ajax({ url: '/data/search.jsonp', data: { q: 'a' }, dataType: 'jsonp', success: function( resp ) { $( '#target' ).html( 'Results: ' + resp.results.length ); } });ملاحظة: عادةً ما توفّر الواجهات البرمجيّة خيارًا لتعيين اسم الدّالّة الّتي تُحيط بالبيانات والّتي ستُستدعى في عنوان الرّابط. عادةً ما يكون هذا اسم مُعامل الرّابط callback، وهذا ما تتوقّعه jQuery مبدئيًّا، إلّا أن بإمكانك تغييره بتعيين قيمة للخاصة jsonp في كائن الإعدادات الّذي تُمرّره لـ ‎$.ajax()‎. بإمكانك أيضًا استخدام الوظيفة المُختصرة ‎$.getJSON()‎ لإرسال طلب JSONP، حيث تستطيع jQuery تمييزه من خلال وجود ‎callback=?‎ أو ما يشبهها في الرّابط: $.getJSON( '/data/search.jsonp?q=a&callback=?', function( resp ) { $( '#target' ).html( 'Results: ' + resp.results.length ); } );مشاركة الموارد عبر الأصول (cross-origin resource sharing أو CORS اختصارًا) هي خيارٌ آخر للسّماح بالطّلبات العابرة للأصول. ولكنّها غير مدعومة في المتصفّحات القديمة، كما أنّها تحتاج تهيئة خاصّة على الخادوم وتعديل ترويسات الطّلبات في XHR لتعمل. الكائنات المُؤجّلة (Deferreds)ليست كائنات jqXHR الّتي تعرّفنا عليها إلا "نكهة" خاصّة ممّا يُعرف "بالكائنات المؤجّلة". تسمح jQuery لك بإنشاء كائنات مؤجّلة بنفسك، والّتي يمكن الاستفادة منها في تسهيل التّعامل مع الأوامر اللامتزامنة، فهي توفّر طريقة للاستجابة لعمليّة تجري بصورة غير متزامنة بعد نجاحها أو فشلها، وتجنّبك الحاجة لكتابة استدعاءات راجعة مُتداخلة فيما بينها. ‏‎$.Deferred‎بإمكانك إنشاء كائنٍ مؤجّل باستخدام ‏‎$.Deferred()‎. في المثال التّالي نُنفّذ دالة داخل setTimeout، ثمّ "نفي" (resolve) بوعدنا بإعادة القيمة الّتي تُرجعها الدّالة هذه. نُعيد الوعد (promise)، وهو كائن يمكن ربط الاستدعاءات الرّاجعة به، ولكنّه لا يؤثّر في نتيجة الكائن المؤجّل بحدّ ذاته. بإمكاننا "الإخلاف" (reject) بالوعد إذا وقع خطأ ما أثناء عمل الدّالّة: function doSomethingLater( fn, time ) { var dfd = $.Deferred(); setTimeout(function() { dfd.resolve( fn() ); }, time || 0); return dfd.promise(); } var promise = doSomethingLater(function() { console.log( 'This function will be called in 100ms' ); }, 100);‏‎.then()‎ و‎.done()‎ و‎.fail()‎ و‎.always()‎يمكننا ربط دوالّ تتولّى حالات الخطأ والنّجاح بالوعود، تمامًا كما في كائنات jqXHR: function doSomethingLater( fn, time ) { var dfd = $.Deferred(); setTimeout(function() { dfd.resolve( fn() ); }, time || 0); return dfd.promise(); } var success = function( resp ) { $( '#target' ).html( 'it worked' ); }; var err = function( req, status, err ) { $( '#target' ).html( 'it failed' ); }; var dfd = doSomethingLater(function() { /* ... */ }, 100); dfd.then( success, err );‏‎.pipe()‎بإمكاننا استخدام الوظيفة ‏‎.pipe()‎ للوعود للاستجابة إلى القيمة الّتي تُوفى وذلك بتعديلها ثمّ إعادة كائن مؤجّل جديد. تعمل الوظيفة ‏‎.then()‎ بدءًا من الإصدارة 1.8 من jQuery كما تعمل الوظيفة ‏‎.pipe()‎. function doSomethingLater( fn, time ) { var dfd = $.Deferred(); setTimeout(function() { dfd.resolve( fn() ); }, time || 0); return dfd.promise(); } var dfd = doSomethingLater(function() { return 1; }, 100); dfd .pipe(function(resp) { return resp + ' ' + resp; }) .done(function(upperCaseResp) { $( '#target' ).html( upperCaseResp ); });التّعامل مع العمليّات الّتي قد تكون لامتزامنةأحيانًا تكون لدينا وظيفة قد تعمل بصورة متزامنة أو لا متزامنة وفق ظروف مُعيّنة، فمثلًا: دالّة تقوم بعمليّة لا متزامنة أوّل مرّة تُستدعى فيها، ثمّ تُخزّن القيمة الّتي أنتجتها العمليّة لتُعيدها مباشرةً عند استدعاءها مُستقبلًا. في هذه الحالة يمكننا الاستفادة من ‎$.when()‎ للاستجابة لكلا الحالتين: function maybeAsync( num ) { var dfd = $.Deferred(); // أعِد وعدًا مؤجّلًا عندما num === 1 if ( num === 1 ) { setTimeout(function() { dfd.resolve( num ); }, 100); return dfd.promise(); } // أنهِ مباشرة فيما سوى ذلك، مُعيدًا num return num; } // هذا سيُجرى بصورة غير متزامنة ويعِد بإعادة 1 $.when( maybeAsync( 1 ) ).then(function( resp ) { $( '#target' ).append( '<p>' + resp + '</p>' ); }); // هذا سُيعيد 0 مُباشرةً $.when( maybeAsync( 0 ) ).then(function( resp ) { $( '#target' ).append( '<p>' + resp + '</p>' ); });بإمكانك أيضًا تمرير أكثر من معامل إلى ‎$.when()‎، الأمر الّذي يسمح لك بدمج عمليّات متزامنة ولا متزامنة معًا ثمّ الحصول على نتائج تنفيذها كلّها كُمعاملات للاستدعاء الرّاجع: function maybeAsync( num ) { var dfd = $.Deferred(); // أعد وعدًا مؤجّلًا عندما num === 1 if ( num === 1 ) { setTimeout(function() { dfd.resolve( num ); }, 100); return dfd.promise(); } // أنهِ مباشرةً فيما سوى ذلك، مُعيدًا num return num; } $.when( maybeAsync( 0 ), maybeAsync( 1 ) ) .then(function( resp1, resp2 ) { var target = $( '#target' ); target.append( '<p>' + resp1 + '</p>' ); target.append( '<p>' + resp2 + '</p>' ); });عندما يكون إحدى مُعاملات ‎$.when()‎ كائن jqXHR، فإنّنا نحصل على مصفوفة من المُعاملات تُمرّر إلى استدعائنا الرّاجع: function maybeAsync( num ) { var dfd = $.Deferred(); // أعد وعدًا مؤجّلًا عندما num === 1 if ( num === 1 ) { setTimeout(function() { dfd.resolve( num ); }, 100); return dfd.promise(); } // أنهِ مباشرةً فيما سوى ذلك، مُعيدًا num return num; } $.when( maybeAsync( 0 ), $.get( '/data/people.json' ) ) .then(function( resp1, resp2 ) { console.log( "Both operations are done", resp1, resp2 ); });مصادر إضافيةتوثيق AJAXكائن jqXHRالكائنات المؤجّلة في jQueryترجمة (بشيء من التصرف) للجزء السادس من سلسلة  jQuery Fundamentals لمؤلّفتها Rebecca Murphey.
×
×
  • أضف...