لوحة المتصدرين
المحتوى الأكثر حصولًا على سمعة جيدة
المحتوى الأعلى تقييمًا في 10/20/22 في كل الموقع
-
2 نقاط
-
1 نقطة
-
وعليكم السلام ورحمة الله وبركاته بالفعل سوف تجد كل ما توفره اللوحة في ملف يسمى بالأغلبية index.html وربما يكون من هذا الملف عدّة نسخ ، ولكي تستخدم اللوحة بشكل سليم أحيانا توفر لك اللوحة ملف مبدئي مثل starter.html أو أي ملف باسم آخر وهذا يكون الشكل الأساسي للوحة وبإحتوائه على بعض الأزرار ويمكنك البدء من هذا الملف وبعدها تطلع على الملف الذي يحتوي على جميع العناصر التي توفرها اللوحة وتضيفها إلى الملف المبدئي وإنشاء اللوحة التي تحتاجها لأنه لن تحتاج جميع عناصر اللوحة . لا تقوم بحذف أي شيء من اللمف الأساسي للوحة لإنك سوف ترجع إليه لأخذ العناصر وإضافتها إلى الملف الذي سوف تبني عليه لوحة التحكم1 نقطة
-
السلام عليكم. أقدم لكم الكود التالي // show my favorite products const favorites = document.querySelector(".favorite-products") favorites.addEventListener('click', ()=>{ // create a popup overly = document.createElement('div') overly.setAttribute("class", "popup-overly") let body = document.getElementsByTagName('body') document.body.appendChild('overly') }) عند النقر العنصر المحدد تظهر لي رسالة الخطأ التالية: script.js:276 Uncaught TypeError: Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'. at HTMLDivElement.<anonymous> (script.js:276:19) شكرا عى المساعدة.1 نقطة
-
هذا لأن التابع appendChild يفترض أن يمرر عبره كمعامل أول مرجع العقدة التي نحاول حقنها في الوثيقة وليس سلسلة نصية. لا يجب عليك وضع overly بين علامتي تنصيص: // show my favorite products const favorites = document.querySelector(".favorite-products") favorites.addEventListener('click', ()=>{ // create a popup overly = document.createElement('div') overly.setAttribute("class", "popup-overly") let body = document.getElementsByTagName('body') document.body.appendChild(overly) }) ،body أيضا لا يبدوا هنالك أي حاجة من تعريف المتغير :يمكنك التخلص من هذا السطر: let body = document.getElementsByTagName('body') Document.body1 نقطة
-
تطرقنا في المقال السابق إلى الهياكل وبعض الهياكل الشائعة، مثل الأشجار والقوائم المرتبطة، وننتقل الآن إلى الاتحادات وحقول البتات والمعددات ونتكلم عن استعمال وخصائص كل منها. الاتحادات Unions لن تستغرق الاتحادات Unions وقتًا طويلًا لشرحها، فهي تشابه الهياكل بفرق أنك لا تستخدم الكلمة المفتاحية struct بل تستخدم union، وتعمل الاتحادات بالطريقة ذاتها التي تعمل بها الهياكل structures بفرق أن أعضائها مُخزنون على كتلة تخزينية واحدة بعكس أعضاء الهياكل التي تُخزن على كتل تخزينية متفرقة متعاقبة، ولكن ما الذي يفيدنا هذا الأمر؟ تدفعنا الحاجة في بعض الأحيان إلى استخدام الهياكل بهدف تخزين قيم مختلفة بأنواع مختلفة وبأوقاتٍ مختلفة مع المحافظة قدر الإمكان على مساحة التخزين وعدم هدر الموارد؛ في حين يمكننا باستخدام الاتحادات تحديد النوع الذي ندخله إليها والتأكد من استرجاع القيمة بنوعها المناسب فيما بعد. إليك مثالًا عن ذلك: #include <stdio.h> #include <stdlib.h> main(){ union { float u_f; int u_i; }var; var.u_f = 23.5; printf("value is %f\n", var.u_f); var.u_i = 5; printf("value is %d\n", var.u_i); exit(EXIT_SUCCESS); } مثال 1 إذا أضفنا قيمةً من نوع float إلى الاتحاد في مثالنا السابق، ثم استعدناه على أنه قيمةٌ من نوع int، فسنحصل على قيمة غير معروفة، لأن النوعان يُخزنان على نحوٍ مختلف وأضف على ذلك أنهما من أطوالٍ مختلفة؛ فالقيمة من نوع int ستكون غالبًا تمثيل الآلة (الحاسوب) لبتات float منخفضة الترتيب، ولربما ستشكل جزءًا من قيمة float العشرية (ما بعد الفاصلة). ينص المعيار على اعتماد النتيجة في هذه الحالة على تعريف التطبيق (وليست سلوكًا غير معرفًا)، والنتيجة معرفةٌ من المعيار في حالة واحدة، ألا وهي أن يكون لبعض أعضاء الاتحاد هياكل ذات "سلسلة مبدئية مشتركة common initial sequence"، أي أن لأول عضو من كل هيكل نوع متوافق compatible type، أو من الطول ذاته في حالة حقول البتات bitfields، ويوافق اتحادنا الشروط التي ذكرناها، وبالتالي يمكننا استخدام السلسلة المبدئية المشتركة على نحوٍ تبادلي، يا لحظنا الرائع. يعمل مصرّف لغة سي على حجز المساحة اللازمة لأكبر عضو ضمن الاتحاد لا أكثر (بعنوان مناسب إن أمكن)، أي لا يوجد هناك أي تفقد للتأكد من أن استخدام الأعضاء صائب فهذه مهمتك، وستكتشف عاجلًا أم آجلًا إذا فشلت في تحقيق هذه المهمة. تبدأ أعضاء الاتحاد من عنوان التخزين ذاته (من المضمون أنه لا يوجد هناك أي فراغات بين أيٍ من الأعضاء). يُعد تضمين الاتحاد في هيكل من أكثر الطرق شيوعًا لتذكر طريقة عمل الاتحاد، وذلك باستخدام عضو آخر من الهيكل ذاته ليدل على نوع الشيء الموجود في الاتحاد. إليك مثالًا عمّا سيبدو ذلك: #include <stdio.h> #include <stdlib.h> /* شيفرة للأنواع في الاتحاد */ #define FLOAT_TYPE 1 #define CHAR_TYPE 2 #define INT_TYPE 3 struct var_type{ int type_in_union; union{ float un_float; char un_char; int un_int; }vt_un; }var_type; void print_vt(void){ switch(var_type.type_in_union){ default: printf("Unknown type in union\n"); break; case FLOAT_TYPE: printf("%f\n", var_type.vt_un.un_float); break; case CHAR_TYPE: printf("%c\n", var_type.vt_un.un_char); break; case INT_TYPE: printf("%d\n", var_type.vt_un.un_int); break; } } main(){ var_type.type_in_union = FLOAT_TYPE; var_type.vt_un.un_float = 3.5; print_vt(); var_type.type_in_union = CHAR_TYPE; var_type.vt_un.un_char = 'a'; print_vt(); exit(EXIT_SUCCESS); } مثال 2 يوضح المثال السابق أيضًا استخدام عامل النقطة للوصول إلى ما داخل الهياكل أو الاتحادات التي تحتوي على هياكل أو اتحادات أخرى بدورها، تسمح لك بعض مصرفات لغة سي الحالية بإهمال بعض الأجزاء من أسماء الكائنات المُدمجة شرط ألا يتسبب ذلك بجعل الاسم غامض، فعلى سبيل المثال يسمح استخدام الاسم الواضح var_type.un_int للمصرف بمعرفة ما تقصده، إلا أن هذا غير مسموح في المعيار. لا يمكن مقارنة الهياكل بحثًا عن المساواة فيما بينها ويقع اللوم على الاتحادات، إذ أن احتمالية احتواء هيكل ما على اتحاد يجعل من مهمة المقارنة مهمةً صعبة، إذ لا يمكن للمصرّف أن يعرف ما الذي يحويه الاتحاد في الوقت الحالي مما لا يسمح له بإجراء عملية المقارنة. قد يبدو الكلام السابق صعب الفهم وغير دقيق بنسبة 100%، إذ أن معظم الهياكل لا تحتوي على اتحادات، ولكن هناك مشكلة فلسفية بخصوص القصد من كلمة "مساواة" عندما نُسقطها على الهياكل. بغض النظر، تمنح الاتحادات عذرًا مناسبًا للمعيار بتجنبه لأي مشاكل بواسطة عدم دعمه لمقارنة الهياكل. حقول البتات Bitfields دعنا نلقي نظرةً على حقول البتات بما أننا نتكلم عن موضوع هياكل البيانات، إذ يمكن تعريفها فقط بداخل هيكل أو اتحاد، وتسمح لك حقول البتات بتحديد بعض الكائنات الصغيرة بحسب طول بتات محدد، إلا أن فائدتها محدودةٌ ولا تُستخدم إلا في حالات نادرة، ولكننا سنتطرق إلى الموضوع بغض النظر عن ذلك. يوضح لك المثال استخدام حقول البتات: struct { /* كل حقل بسعة 4 بتات */ unsigned field1 :4; /* * حقل بسعة 3 بتات دون اسم * تسمح الحقول عديمة الاسم بالفراغات بين عناوين الذاكرة */ unsigned :3; /* * حقل بسعة بت واحد * تكون قيمته 0 أو 1- في نظام المتمم الثنائي */ signed field2 :1; /* محاذاة الحقل التالي مع وحدة التخزين */ unsigned :0; unsigned field3 :6; }full_of_fields; مثال 3 يمكن التلاعب والوصول إلى كل حقل بصورةٍ منفردة وكأنه عضو اعتيادي من هيكل ما، وتعني الكلمتان المفتاحيتان signed وunsigned ما هو متوقع، إلا أنه يجدر بالذكر أن حقلًا بحجم 1 بت ذا إشارة سيأخذ واحدةً من القيمتين 0 أو -1 وذلك في آلة تعمل بنظام المتمم الثنائي، ويُسمح للتصريحات بأن تحتوي المؤهلين const أو volatile. تُستخدم حقول البتات بشكل رئيس إما للسماح بتحزيم مجموعة من البيانات بأقل مساحة، أو لتحديد الحقول ضمن ملفات بيانات خارجية. لا تقدم لغة سي أي ضمانات بخصوص ترتيب الحقول بكلمات الآلة التي تعمل عليها، لذا إذا كنت تريد استخدام حقول البتات للهدف الثاني، فسيصبح برنامجك غير قابل للتنقل ومعتمدًا على المصرّف الذي يصرف البرنامج أيضًا. ينص المعيار على أن الحقول مُحزّمة بما يدعى "وحدات تخزين"، التي تكون عادةً كلمات آلة. يُحدّد ترتيب التحزيم وفيما إذا كان سيتجاوز حقل البتات حاجز التخزين أم لا بحسب تعريف التطبيق، ونستخدم حقلًا بعرض صفر قبل الحقل الذي تريد تطبيق الحد عنده لإجبار الحقل على البقاء ضمن حدود وحدة التخزين. كن حذرًا عند استخدام حقول البتات، إذ يتطلب الأمر شيفرة وقت تشغيل run-time طويلة للتلاعب بهذه الأشياء، وقد ينتج ذلك بتوفير الكثير من المساحة (أكثر من حاجتك). ليس لحقول البتات أي عناوين، وبالتالي لا يمكنك استخدام المؤشرات أو المصفوفات معها. المعددات enums تقع المُعدّدات enums تحت تصنيف "منجزة جزئيًا"، إذ ليست بأنواع مُعددة بصورٍ كاملة مثل لغة باسكال، ومهمتها الوحيدة هي مساعدتك في التخفيف من عدد تعليمات #define في برنامجك، إليك ما تبدو عليه: enum e_tag{ a, b, c, d=20, e, f, g=20, h }var; يمثل e_tag الوسم بصورةٍ مشابهة لما تكلمنا عنه في الهياكل والاتحادات، ويمثل var تعريفًا للمتغير. الأسماء المُعلنة بداخل المُعدد ثوابت من نوع int، إليك قيمها: a == 0 b == 1 c == 2 d == 20 e == 21 f == 22 g == 20 h == 21 تلاحظ أنه بغياب أي قيمة مُسندة للمتغيرات، تبدأ القيم من الصفر تصاعديًا، ويمكنك إسناد قيمة مخصصة إنذا أردت في البداية، إلا أن القيم التي ستتزايد بعدها ستكون من نوع عدد صحيح ثابت integral constant (كما سنرى لاحقًا)، وتُمثّل هذه القيمة بنوع int ومن الممكن أن تحمل عدة أسماء القيمة ذاتها. تُستخدم المُعدّدات للحصول على إصدار ملائم للنطاق Scope بدلًا من استخدام #define على النحو التالي: #define a 0 #define b 1 /* وهكذا دواليك */ إذ يتبع استخدام المعددات لقوانين نطاق لغة سي C، بينما تشمل تعليمات #define كامل نطاق الملف. قد لا تهمك هذه المعلومة، ولكن المعيار ينص على أن أنواع المعددات من نوع متوافق مع أنواع الأعداد الصحيحة بحسب تعريف التطبيق، لكن ما الذي يعنيه ذلك؟ لاحظ المثال التالي: enum ee{a,b,c}e_var, *ep; تسلك الأسماء a و b و c سلوك الأعداد الصحيحة الثابتة int عندما تستخدمها، و e_var من نوع enum ee و ep مؤشر يشير إلى المعدد ee. تعني متطلبات التوافقية بين الأنواع (بالإضافة لمشكلات أخرى) أن هناك نوع عدد صحيح ذو عنوان يمكن إسناده إلى ep من غير خرق أي من متطلبات التوافقية بين الأنواع للمؤشرات. المؤهلات والأنواع المشتقة تعد المصفوفات والهياكل والاتحادات "مشتقةٌ من derived from" (أي تحتوي) أنواعٍ أخرى، ولا يمكن لأي ممّا سبق أن تُشتق من أنواع غير مكتملة incomplete types، وهذا يعني أنه من غير الممكن للهيكل أو الاتحاد أن يحتوي مثالًا من نفسه، لأن نوعه غير مكتمل حتى ينتهي التصريح عنه، وبما أن المؤشر الذي يشير إلى نوع غير مكتمل ليس بنوع غير مكتمل بذات نفسه فمن الممكن استخدامه باشتقاق المصفوفات والهياكل والاتحادات. لا ينتقل التأهيل إلى النوع المُشتق إن كان أي من الأنواع التي اشتُق منها تحتوي على مؤهلات مثل const أو volatile، وهذا يعني أن الهيكل الذي يحتوي على كائن ذو مؤهل ثابت const لا يجعل من الهيكل بنفسه مؤهلًا بهذا المؤهل، ويمكن لأي عضو غير ثابت أن يُعدّل عليه بداخل الهيكل، وهذا ما هو متوقع، إلا أن المعيار ينص على أن أي نوع مشتق يحتوي على نوع مؤهل باستخدام const (أو أي نوع داخلي تعاودي) لا يمكن التعديل عليه، فالهيكل الذي يحتوي الثابت لا يمكن وضعه على الطرف الأيسر من عامل الإسناد. ترجمة -وبتصرف- لقسم من الفصل Structured Data Types من كتاب The C Book. اقرأ أيضًا المقال التالي: تهيئة المتغيرات وأنواع البيانات في لغة سي C المقال السابق: هياكل البيانات: القوائم المترابطة Linked lists والأشجار Trees في لغة سي C بنية برنامج لغة سي C العوامل في لغة سي C1 نقطة
-
1 نقطة
-
1 نقطة
-
لقد عدلته هل يمكنك رايته https://mohamed-montaser1.github.io/my_portfolio/public/1 نقطة
-
أنا طالب في دورة تطوير واجهات المستخدم أريد أنا أسأل ما هو نوع العمل الذي يمكنني الحصول عليه بعد الإنتهاء من الدورة هل مجرد عمل حر واحد أم يمكنني العمل أون لاين بشركة مثلًا ك جونير أرجوا تفاصيل أوضح1 نقطة
-
اذا كان لدي قيمة المتغير q تساوي 12345 مثلا كيف اقوم باخراجها الى الشاشة السوداء بمسافة بين كل رقم 1 2 3 4 51 نقطة
-
هو ليه وانا بطبق ورا حد css وhtml بلاقي مشاكل مع اني مقلده بظبت هل دي اصدارات مختلفه ؟1 نقطة
-
من الممكن أن تكون مشاكل اصدارات اذا كان يستخدم مكاتب مثل بوتستراب, ولكن اذا كان html و css بدون أي مكاتب فالمشكلة بالتأكيد ليس بسبب اختلاف اصدارات وربما يوجد مشكلة ما وأنت لم تنتبه لها, تأكد من الاكواد بشكل دقيق جدا واذا لم تجد أي اختلاف في الاكواد فحاول حل المشكلة بنفسك بطريقة ختلفة فهذا يشجع على الاجتهاد واكتساب الخبرة1 نقطة
-
أرجو التأكد من ضبط إعدادات الstatic ، ثم تشغيل هذا اﻷمر python manage.py collectstatic وإذا لم ينجح الأمر ، يرجى أخذ لقطة الشاشة من terminal الخاصة بمشروع django و أخرى ل console الخاصة بالمتصفح ( إضغط على المفتاح F12 وستٌفتح النافذة ) ليتسنى لنا معرفة الخطأ .1 نقطة
-
1 نقطة
-
قم بإعداد ASGI لتطبيقك قبل البدء ، قم بتثبيت التبعيات المدرجة أدناه والتي ستكون ضرورية لإعداد WebSockets. Django 3.0(or greater) channels 3.0.4 channels-redis 3.3.1 بمجرد تثبيت التبعيات المذكورة أعلاه ، سيتعين علينا إضافة "channels" إلى قائمة قسم "INSTALLED_APPS" في ملف settings.py. بشكل افتراضي ، تعمل تطبيقات Django على خوادم WSGI ولهذا السبب سنحتاج إلى إعداد تطبيقنا يدويًا للتشغيل على خادم ASGI. انتقل إلى ملف settings.py وأضف السطر التالي. ملاحظة: "your_project" في الكود أدناه هو اسم الدليل للمجلد الجذر الذي سيحتوي على ملف "settings.py" ASGI_APPLICATION = 'your_project.asgi.application' الآن لكي يعمل جزء الكود أعلاه ، سيتعين علينا التأكد من وجود ملف "asgi.py" في دليل التطبيق الجذر لدينا مثل: 2) إنشاء نموذج الإشعارات class notifications(models.Model): user_sender=models.ForeignKey(Users,null=True,blank=True,related_name='user_sender',on_delete=models.CASCADE) user_revoker=models.ForeignKey(Users,null=True,blank=True,related_name='user_revoker',on_delete=models.CASCADE) status=models.CharField(max_length=264,null=True,blank=True,default="unread") type_of_notification=models.CharField(max_length=264,null=True,blank=True) 3) قم بإعداد ملف customers.py الخاص بك المستهلكون إلى WebSockets كوجهات النظر إلى HTTP. ملف customers.py هو المكان الذي يتم فيه التعامل مع جميع طلبات WebSocket وإرسالها مرة أخرى إلى الواجهة الأمامية. هناك أنواع مختلفة من مستهلكي WebSocket مثل "AsynConsumer" و "WebSocketConsumer" و "AsyncWebSocketConsumers" على سبيل المثال لا الحصر. انتقل إلى أحد التطبيقات داخل مشروعك وأنشئ ملف "customers.py". قم باستيراد التبعيات التالية from channels.generic.websocket import WebsocketConsumer,AsyncWebsocketConsumer from channels.db import database_sync_to_async from asgiref.sync import async_to_sync,sync_to_async from channels.layers import get_channel_layer يحتاج كل "مستهلك" في قنوات Django إلى ثلاث طرق محددة. الاتصال والاستلام وقطع الاتصال. يتم استخدام الاتصال لإنشاء اتصالات بين الواجهة الأمامية والخلفية. يُستخدم الاستلام للتعامل مع الطلبات المرسلة من الواجهة الأمامية والتعامل معها وفقًا لذلك. يُستخدم فصل الاتصال للتعامل مع ما يجب أن يحدث إذا تم إغلاق WebSocket أو إيقاف تشغيله. إذن كيف ستعمل WebSockets لدينا؟ سيقوم WebSocket الخاص بنا أولاً "بإنشاء" اتصال. بمجرد إنشاء الاتصال ، فإن الواجهة الخلفية "تستمع" لأي حدث قد ترسله الواجهة الأمامية إليه وتعالج ذلك في طريقة "الاستلام". دعونا نلقي نظرة خاطفة على ذلك سننشئ أولاً وظيفة سيتم استخدامها لإنشاء كائن إعلام في كل حدث. @database_sync_to_async def create_notification(receiver,typeof="task_created",status="unread"): notification_to_create=notifications.objects.create(user_revoker=receiver,type_of_notification=typeof) print('تم') return (notification_to_create.user_revoker.username,notification_to_create.type_of_notification) بعد ذلك سنلقي نظرة على "المستهلك" الرئيسي لدينا. دعونا نلقي نظرة على طريقة الاتصال أولاً. class NotificationConsumer(AsyncWebsocketConsumer): async def websocket_connect(self,event): print('تم الإتصال',event) print('تم اﻷمر') print(self.scope['user'].id) await self.accept() await self.send(json.dumps({ "type":"websocket.send", "text":"مرحبا" })) 4) أدخل Redis الآن مع طريقة الاتصال بعيدًا عن الطريق. سنحتاج إلى إعداد "Redis". Redis هو مخزن بنية بيانات مفتوح المصدر (مرخص من BSD) ، يستخدم كقاعدة بيانات وذاكرة تخزين مؤقت ووسيط رسائل. إنه جزء لا يتجزأ من تشغيل WebSockets الخاص بنا. انتقل إلى ملف settings.py وأضف الكود التالي لإعداد Redis. CHANNEL_LAYERS = { "default": { "BACKEND": "channels_redis.core.RedisChannelLayer", "CONFIG": { "hosts": [("127.0.0.1", 6379)], }, }, } 5) إعداد جهاز التوجيه URL الخاص بنا import os from channels.routing import ProtocolTypeRouter,URLRouter from channels.auth import AuthMiddlewareStack from stories import consumers from django.urls import re_path,path from django.core.asgi import get_asgi_application os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mirror_project.settings') import stories.routing application = ProtocolTypeRouter({ "http": get_asgi_application(), "websocket":AuthMiddlewareStack( URLRouter( [path('stories/notification_testing/',consumers.NotificationConsumer.as_asgi())] )) }) إذا نظرت إلى جزء "ProtocolTypeRouter" داخل الكود ، فلدينا قسمان ، "HTTP" و "WebSocket". "HTTP" مخصص لـ ASGI لرعاية طلبات العرض التقليدية بينما يتم استخدام "WebSocket" للتعامل مع طلبات مأخذ التوصيل. ملاحظة ، لقد حددت "مسارًا" يحتوي على "مسار" وبجانبه يوجد شيء مشابه لطريقة عرض. رمز يرتبط بـ "فئة مستهلك الإشعارات". 6) جمع كل ذلك معًا السبب في قيامنا بدمج Redis في المقام الأول هو أننا نحتاج إلى طريقة "لبث" حدث إنشاء الإشعارات متى تم إنشاؤه في "غرفة". وبعد ذلك يمكن للواجهة الأمامية التحقق من المعلومات وتقرر إما عرضها أو رفضها. دعونا نلقي نظرة على الكود النهائي الخاص بنا لملف customers.py. @database_sync_to_async def get_user(user_id): try: return Users.objects.get(id=user_id) except: return AnonymousUser() @database_sync_to_async def create_notification(receiver,typeof="task_created",status="unread"): notification_to_create=notifications.objects.create(user_revoker=receiver,type_of_notification=typeof) print('تم') return (notification_to_create.user_revoker.username,notification_to_create.type_of_notification) class NotificationConsumer(AsyncWebsocketConsumer): async def websocket_connect(self,event): print(self.scope) await self.accept() await self.send(json.dumps({ "type":"websocket.send", "text":"مرحبا" })) self.room_name='test_consumer' self.room_group_name='test_consumer_group' await self.channel_layer.group_add(self.room_group_name,self.channel_name) self.send({ "type":"websocket.send", "text":"room made" }) async def websocket_receive(self,event): print(event) data_to_get=json.loads(event['text']) user_to_get=await get_user(int(data_to_get)) print(user_to_get) get_of=await create_notification(user_to_get) self.room_group_name='test_consumer_group' channel_layer=get_channel_layer() await (channel_layer.group_send)( self.room_group_name, { "type":"send_notification", "value":json.dumps(get_of) } ) print('receive',event) async def websocket_disconnect(self,event): print('disconnect',event) async def send_notification(self,event): await self.send(json.dumps({ "type":"websocket.send", "data":event })) print('تم') print(event) 6) اختبر اتصالات socket الخاص بك ws://127.0.0.1:8000/stories/notification_testing/ 7) قم بإنشاء إعلام عن طريق إرسال معرف المستخدم. أخيرًا ، نرسل معرف مستخدم إلى WebSocket الخاص بنا1 نقطة
-
تتضمن العديد من تطبيقات الويب نظام إشعارات مبني ضمن التطبيق والذي يُعلِم المستخدم فورًا عندما يحاول شخص ما تنفيذ فعل ما مرتبط به، إذ يتلقى المستخدم في شبكة التواصل الاجتماعي فيسبوك مثلًا إشعارًا عندما يُعجَبُ أحدهم بحالة ما قد نشرها المستخدم أو عندما يضع أحدهم تعليقًا على صفحة المستخدم الشخصية. ننشئ في هذه المقالة آليةً شبيهةً بذلك عن طريق تطوير منظومة إشعارات معتمدة على الويب باستخدام بيئة لارافيل ومكتبة Pusher. المتطلبات الأساسية يجب التأكد من تثبيت خادم PHP ولارافيل Laravel على الحاسوب لإكمال المشروع كما يجب امتلاك حساب على منصة بوشر Pusher. ما الذي سنبنيه؟ يتضمن العمل إنشاء تطبيق ويب بسيط يعرض الإشعارات باستخدام لارافيل وقنوات بوشر Pusher. يبين العرض التالي ما الذي نريد بناءه: إعداد تطبيق Pusher ننشئ بدايةً حسابًا على منصة Pusher ومن ثم نضبط التطبيق كما هو موضح بالصورة أدناه: إعداد تطبيق لارافيل ننشئ تطبيق لارافيل جديد بإجراء الأمر التالي في موجه الأوامر: laravel new laravel-web-notifications نثبت حزمة التطوير البرمجي الخاصة بمنصة بوشر Pusher والمعتمدة على لغة PHP بتنفيذ الأمر التالي: composer require pusher/pusher-php-server نوجّه لارافيل لاستخدام منصة بوشر على أنها وسيلة لإرسال رسائل بث broadcast تُستخدم لإعلام أجزاء البرنامج بوقوع حدثٍ ما، ولتحقيق ذلك نفتح الملف ".env" الموجود في المجلد الجذر Root الخاص بلارافيل، ثم نحدّث محتواه ليوافق الضبط التالي مع ضرورة استبدال الرموز X بالقيم الخاصة بالحساب الخاص بك: PUSHER_APP_ID=322700 BROADCAST_DRIVER=pusher // نضيف المعلومات المتعلقة بالحساب الخاص بنا من منصة بوشر PUSHER_APP_ID=XXXXX PUSHER_APP_KEY=XXXXXXX PUSHER_APP_SECRET=XXXXXXX ملاحظة مهمة: يتصل لارافيل افتراضيًا بخوادم الولايات المتحدة الأمريكية، وعند الحاجة لاستخدام خوادم أخرى، يجب تحديث مصفوفة الخيارات الموجودة ضمن الملف "config/broadcasting.php"، كما يُعدّل الملف "config/app.php" ويحذف التعليق الموجود في الجزء: App\Providers\BroadcastServiceProvider::class إنشاء التطبيق الذي يدمج لارافيل مع Pusher ننشئ التطبيق بعد الانتهاء من العمليات السابقة، حيث نبدأ بإنشاء صنف الحدث Event، الذي يبث الرسائل إلى قنوات بوشر Pusher انطلاقًا من تطبيق لارافيل. تُستدعى الأحداث من أي مكان نرغب به ضمن التطبيق. php artisan make:event StatusLiked ينشئ هذا السطر صنفًا باسم StatusLiked ضمن مجلد "app/Events". نفتح هذا الملف ونحدّث محتواه كما يلي: <?php namespace App\Events; use Illuminate\Queue\SerializesModels; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Contracts\Broadcasting\ShouldBroadcast; class StatusLiked implements ShouldBroadcast { use Dispatchable, InteractsWithSockets, SerializesModels; public $username; public $message; /** * إنشاء عينة جديدة من الحدث * * @return void */ public function __construct($username) { $this->username = $username; $this->message = "{$username} liked your status"; } /** * تحديد القنوات التي سيُبث الحدث عليها * * @return Channel|array */ public function broadcastOn() { return ['status-liked']; } } تخبر الواجهة ShouldBroadcast لارافيل أن الحدث الحالي يجب أن يُرسل وفقًا للطريقة المحدّدة في ملف الضبط الذي أعددناه مسبقًا. يوجد تابع باني يأخذ وسيطين، هما: اسم المستخدم وفعل verb، إذ تُسند قيم المتغيرات إلى خصائص الصنف التي تأخذ نفس الأسماء، ويجب أن تكون هذه الخصائص عامة public وإلا فسيجري تجاهلها. إنشاء واجهة عرض التطبيق نستخدم واجهة عرض وحيدة تحتوي شريط تنقّل وأيقونة للإشعارات، إذ تُحدّث الأيقونة عند وجود إشعارات جديدة دون الحاجة لإعادة تحميل الصفحة. لن تستمر الإشعارات طويلًا في التجربة الحالية ولكن يمكن زيادة المدة الزمنية لاستمرار الإشعار حسب الحاجة. نفتح الملف "welcome.blade.php" ونستبدل محتواه بما يلي: <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Demo Application</title> <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <link rel="stylesheet" type="text/css" href="/css/bootstrap-notifications.min.css"> <!--[if lt IE 9]> <script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script> <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script> <![endif]--> </head> <body> <nav class="navbar navbar-inverse"> <div class="container-fluid"> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-9" aria-expanded="false"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">Demo App</a> </div> <div class="collapse navbar-collapse"> <ul class="nav navbar-nav"> <li class="dropdown dropdown-notifications"> <a href="#notifications-panel" class="dropdown-toggle" data-toggle="dropdown"> <i data-count="0" class="glyphicon glyphicon-bell notification-icon"></i> </a> <div class="dropdown-container"> <div class="dropdown-toolbar"> <div class="dropdown-toolbar-actions"> <a href="#">Mark all as read</a> </div> <h3 class="dropdown-toolbar-title">Notifications (<span class="notif-count">0</span>)</h3> </div> <ul class="dropdown-menu"> </ul> <div class="dropdown-footer text-center"> <a href="#">View All</a> </div> </div> </li> <li><a href="#">Timeline</a></li> <li><a href="#">Friends</a></li> </ul> </div> </div> </nav> <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script> <script src="//js.pusher.com/3.1/pusher.min.js"></script> <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> <script type="text/javascript"> var notificationsWrapper = $('.dropdown-notifications'); var notificationsToggle = notificationsWrapper.find('a[data-toggle]'); var notificationsCountElem = notificationsToggle.find('i[data-count]'); var notificationsCount = parseInt(notificationsCountElem.data('count')); var notifications = notificationsWrapper.find('ul.dropdown-menu'); if (notificationsCount <= 0) { notificationsWrapper.hide(); } // تأهيل وظيفة السجلات الخاصة بمكتبة بوشر Pusher // لا نستخدم هذه الوظيفة في بيئة الإنتاج الفعلية // Pusher.logToConsole = true; var pusher = new Pusher('API_KEY_HERE', { encrypted: true }); // الإشتراك بالقناة التي حددناها في صنف الحدث المعرّف في لارافيل var channel = pusher.subscribe('status-liked'); // ربط تابع ما بالحدث الذي عرّفناه في الصنف channel.bind('App\\Events\\StatusLiked', function(data) { var existingNotifications = notifications.html(); var avatar = Math.floor(Math.random() * (71 - 20 + 1)) + 20; var newNotificationHtml = ` <li class="notification active"> <div class="media"> <div class="media-left"> <div class="media-object"> <img src="https://api.adorable.io/avatars/71/`+avatar+`.png" class="img-circle" alt="50x50" style="width: 50px; height: 50px;"> </div> </div> <div class="media-body"> <strong class="notification-title">`+data.message+`</strong> <!--p class="notification-desc">Extra description can go here</p--> <div class="notification-meta"> <small class="timestamp">about a minute ago</small> </div> </div> </div> </li> `; notifications.html(newNotificationHtml + existingNotifications); notificationsCount += 1; notificationsCountElem.attr('data-count', notificationsCount); notificationsWrapper.find('.notif-count').text(notificationsCount); notificationsWrapper.show(); }); </script> </body> </html> نركّز على الجزء الخاص بلغة جافا سكريبت JavaScript، الذي يتضمن الأجزاء الخاصة بمكتبة Pusher التي ترسل الإشعارات. وفيما يلي الجزء المهم من الكتلة البرمجية الخاصة بعملية الإرسال: // تأهيل وظيفة السجلات الخاصة بمكتبة بوشر //لا نستخدم هذه الوظيفة في بيئة الإنتاج الفعلية // Pusher.logToConsole = true; // نهيئ مكتبة جافا سكريبت الخاصة بقنوات بوشر var pusher = new Pusher('API_KEY_HERE', { encrypted: true }); // الإشتراك بالقناة التي حددناها في صنف الحدث المعرّف في لارافيل var channel = pusher.subscribe('status-liked'); // ربط تابع ما بالحدث الذي عرّفناه في الصنف channel.bind('App\\Events\\StatusLiked', function(data) { // يُستدْعى هذا التابع عند استلام إشعار بالحدث }); ملاحظة: ستبث لارافيل الحدث افتراضيًا تحت اسم الصنف الخاص بالحدث ولكن يمكن تخصيص هذا الاسم عن طريق تعريف طريقة خاصة للبث الخاص بالحدث: public function broadcastAs() { return 'event-name'; } تهيئ الشيفرة السابقة مكتبة الجافا سكريبت الخاصة بالتعامل مع بوشر Pusher وتؤسس قناة اتصال لإرسال واستقبال الأحداث المختلفة، وتتضمن أيضًا طريقةً خاصةً تُستَدعى عند استلام عملية بث تخص حدث ما عن طريق القناة. اختبار التطبيق ننشئ الاتجاه Route اللازم لتنفيذ الحدث والذي يسبب وصول الإشعار إلى المستخدم، إذ يظهر الإشعار عند الوصول إلى هذا الاتجاه ما لم يوجد خطأ ما في الخطوات السابقة. نضيف الاتجاه الجديد كما يلي: Route::get('test', function () { event(new App\Events\StatusLiked('Someone')); return "Event has been sent!"; }); نشغّل خادم PHP عن طريق لارافيل لاختبار نجاح عمل البرنامج الذي كتبناه: php artisan serve الخاتمة أنشأنا نظام إشعارات خاص بالويب باستخدام عددٍ قليل نسبيًا من السطور البرمجية بالاعتماد على قنوات بوشر Pusher، وما أنجزناه يُعد مثالًا بسيطًا على قدرات هذه المكتبة وأداةً يمكن تضمينها في عدة مشاريع، ولا شك أنه يمكن الاعتماد على مكتبة بوشر Pusher في تطوير كثيرٍ من الأدوات المفيدة أيضًا والتي يمكن تضمينها ضمن مختلف المشاريع البرمجية. ترجمة -وبتصرف- للمقال How To Create Web Notifications Using Laravel and Pusher Channels لصاحبه Neo Ighodaro. اقرأ أيضًا أساسيات بناء التطبيقات في إطار العمل Laravel 5 إنشاء تطبيق Todo List بسيط باستخدام Laravel 5 - الجزء الأول1 نقطة