المحتوى عن 'الأحداث'.



مزيد من الخيارات

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

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

نوع المُحتوى


التصنيفات

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

التصنيفات

  • PHP
    • Laravel
    • ووردبريس
  • جافاسكريبت
    • Node.js
    • jQuery
    • AngularJS
    • Cordova
  • HTML5
  • CSS
    • Sass
    • إطار عمل Bootstrap
  • SQL
  • سي شارب #C
    • منصة Xamarin
  • بايثون
    • Flask
    • Django
  • لغة روبي
    • إطار العمل Ruby on Rails
  • لغة Go
  • لغة جافا
  • لغة Kotlin
  • برمجة أندرويد
  • لغة Swift
  • لغة R
  • لغة TypeScript
  • سير العمل
    • Git
  • صناعة الألعاب
    • Unity3D
  • مقالات برمجة عامة

التصنيفات

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

التصنيفات

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

التصنيفات

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

التصنيفات

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

التصنيفات

  • الإنتاجية وسير العمل
    • مايكروسوفت أوفيس
    • ليبر أوفيس
    • جوجل درايف
    • شيربوينت
    • Evernote
    • Trello
  • تطبيقات الويب
    • ووردبريس
    • ماجنتو
  • أندرويد
  • iOS
  • macOS
  • ويندوز

التصنيفات

  • شهادات سيسكو
    • CCNA
  • شهادات مايكروسوفت
  • شهادات Amazon Web Services
  • شهادات ريدهات
    • RHCSA
  • شهادات CompTIA
  • مقالات عامة

أسئلة وأجوبة

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

التصنيفات

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

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

  1. عندما تعمل على حاسوبك الشخصي، أو حتى على هاتفك الذكيّ، أو ربّما ساعتك الذكيّة. فأنت تستخدم الأحداث آلاف المرّات. الحدث هو وسيلة جميلة للتعبير عن أمر طارئ حدث لكائن برمجيّ. قد يكون هذا الأمر الطارئ عبارة عن نقرة زر فأرة، أو عن لمسة على شاشة جهازك الذكي. أو ضغطة مفتاح من لوحة المفاتيح الخاصّة بي وأنا أكتب هذا المقال، أو أن يكون أمرًا طارئًا يُعبّر عن حالة داخليّة ضمن نظام التشغيل. باختصار، هناك عدد كبير جدًّا من المصادر المختلفة أو المحتملة للأحداث. العلاقة بين الأحداث Events والنوّاب Delegates تعتمد الأحداث بشكل كليّ على النوّاب. وفي الحقيقة هي وسيلة لتهذيبها! لنستعير الصنف Car من البرنامج Lesson13_02 من الدرس السابق: 1 public class Car 2 { 3 public delegate void SpeedNotificatoinDelegate(string message); 4 5 private SpeedNotificatoinDelegate speedNotificationHandler; 6 7 public void RegisterWithSpeedNotification(SpeedNotificatoinDelegate handler) 8 { 9 this.speedNotificationHandler = handler; 10 } 11 12 public int CurrentSpeed { get; set; } 13 public int MaxSpeed { get; set; } 14 15 public Car() 16 { 17 CurrentSpeed = 0; 18 MaxSpeed = 100; 19 } 20 21 public Car(int maxSpeed, int currentSpeed) 22 { 23 CurrentSpeed = currentSpeed; 24 MaxSpeed = maxSpeed; 25 } 26 27 public void Accelerate(int delta) 28 { 29 CurrentSpeed += delta; 30 31 if (CurrentSpeed > MaxSpeed) 32 { 33 if(this.speedNotificationHandler != null) 34 { 35 string msg = string.Format("You exceed the maximum speed! (Current = {0}, Max = {1})", 36 CurrentSpeed, MaxSpeed); 37 38 speedNotificationHandler(msg); 39 } 40 } 41 } 42 } كنت قد طلبت منك في التمرين الداعم رقم 2 من الدرس السابق أن تجري تعديلًا على الصنف Car بحيث تستغني عن التابع RegisterWithSpeedNotification، ولمّحت بأن تجعل الحقل speedNotificationHandler ذي محدّد وصول public. بعد إجراء هذا التعديل سيصبح الصنف Car على الشكل التالي: public class Car { public delegate void SpeedNotificatoinDelegate(string message); public SpeedNotificatoinDelegate speedNotificationHandler; public int CurrentSpeed { get; set; } public int MaxSpeed { get; set; } public Car() { CurrentSpeed = 0; MaxSpeed = 100; } public Car(int maxSpeed, int currentSpeed) { CurrentSpeed = currentSpeed; MaxSpeed = maxSpeed; } public void Accelerate(int delta) { CurrentSpeed += delta; if (CurrentSpeed > MaxSpeed) { if (this.speedNotificationHandler != null) { string msg = string.Format("You exceed the maximum speed! (Current = {0}, Max = {1})", CurrentSpeed, MaxSpeed); speedNotificationHandler(msg); } } } } سيعمل هذا الصنف بشكل ممتاز، حيث من الممكن أن نُسند نائبًا للحقل speedNotificationHandler من خارج الصنف Car بالشكل التالي: Car car = new Car(100, 0); car.speedNotificationHandler = new Car.SpeedNotificatoinDelegate(OnExceedMaxSpeedHandler); المشكلة هنا أنّ الحقل speedNotificationHandler أصبح مكشوفًا تمامًا، حتى أنّه من الممكن استدعاء التابع الذي يغلّفه هذا النائب، من خارج الصنف Car، وهذا يعدّ خرقًا لمبدأ مهم في البرمجة كائنيّة التوجّه ألا وهو التغليف Encapsulation. لا ينبغي أن يتمكّن أيّ أحدٍ من استدعاء التابع الذي يغلّفه الحقل السابق إلّا من داخل الصنف Car حصرًا، لأنّ النائب المُسند لذلك الحقل يُعبّر عن حالة داخليّة ضمن الصنف Car وهي تجاوز السرعة القصوى. توفّر لنا سي شارب حلًا عمليًّا وأنيقًا لهذه المشكلة تتمثّل في استخدام الأحداث. بالنسبة للصنف Car السابق (بعد التعديل) يكفيك أن تَسِم الحقل speedNotificationHandler بالكلمة المحجوزة event ليتحوّل إلى حدث لا يمكن استدعاؤه إلّا من داخل الصنف Car. لكن سيكون هناك اختلاف صغير في طريقة إسناد النوّاب إلى هذا الحقل. يحتوي البرنامج Lesson14_01 على النسخة الجديدة للصنف Car مع تعديل بسيط ضمن التابع Main لندعم استخدام الأحداث: 1 using System; 2 3 namespace Lesson14_01 4 { 5 public class Car 6 { 7 public delegate void SpeedNotificatoinDelegate(string message); 8 9 public event SpeedNotificatoinDelegate speedNotificationHandler; 10 11 public int CurrentSpeed { get; set; } 12 public int MaxSpeed { get; set; } 13 14 public Car() 15 { 16 CurrentSpeed = 0; 17 MaxSpeed = 100; 18 } 19 20 public Car(int maxSpeed, int currentSpeed) 21 { 22 CurrentSpeed = currentSpeed; 23 MaxSpeed = maxSpeed; 24 } 25 26 public void Accelerate(int delta) 27 { 28 CurrentSpeed += delta; 29 30 if (CurrentSpeed > MaxSpeed) 31 { 32 if (this.speedNotificationHandler != null) 33 { 34 string msg = string.Format("You exceed the maximum speed! (Current = {0}, Max = {1})", 35 CurrentSpeed, MaxSpeed); 36 37 speedNotificationHandler(msg); 38 } 39 } 40 } 41 } 42 43 class Program 44 { 45 static void Main(string[] args) 46 { 47 Car car = new Car(100, 0); 48 49 car.speedNotificationHandler += new Car.SpeedNotificatoinDelegate(OnExceedMaxSpeedHandler); 50 51 for (int i = 0; i < 5; i++) 52 { 53 Console.WriteLine("Increasing speed by 30"); 54 car.Accelerate(30); 55 } 56 } 57 58 static void OnExceedMaxSpeedHandler(string message) 59 { 60 Console.WriteLine(message); 61 } 62 } 63 } انظر كيف وضعنا الكلمة المحجوزة event بعد كلمة public في التصريح عن الحقل speedNotificationHandler في السطر 9. كما أرجو أن تلاحظ أيضًا التعديل الذي طرأ في السطر 49 على كيفيّة إسناد النائب الجديد إلى الحقل speedNotificationHandler: car.speedNotificationHandler += new Car.SpeedNotificatoinDelegate(OnExceedMaxSpeedHandler); لاحظ كيف استخدمنا العامل (=+) بدلًا من العامل (=). في الحقيقة سيؤدي استخدام العامل (=) في هذه الحالة إلى حدوث خطأ أثناء ترجمة البرنامج. ولكن ماهي الفائدة من العامل (=+)؟ لهذا العمل فائدة كبيرة، فمن خلاله يمكن تسجيل عدّة نوّاب (وبالتالي عدّة توابع) ضمن الحدث speedNotificationHandler بنفس الوقت. مما يعني أنّ عبارة مثل تلك الموجودة في السطر 37: speedNotificationHandler(msg); ستؤدّي إلى استدعاء أي نائب (وبالتالي أي تابع) مسجّل في الحدث speedNotificationHandler بشكل متسلسل يراعي الترتيب الذي سُجّلت ضمنه هذه النوّاب ضمن الحدث speedNotificationHandler باستخدام العامل =+. نسمّي التابع OnExceedMaxSpeedHandler اصطلاحًا بمعالج الحدث. الآن سنذهب أبعد من ذلك ونجري تغييرًا على عبارة التسجيل في الحدث في السطر 49 لتصبح على الشكل التالي: car.speedNotificationHandler += OnExceedMaxSpeedHandler; لاحظ هنا أنّنا قد أزلنا التعبير الذي ينشئ كائن من النائب SpeedNotificatoinDelegate، ووضعنا بدلًا من ذلك اسم التابع OnExceedMaxSpeedHandler مباشرةً بعد العامل (=+). في الحقيقة إنّ مترجم سي شارب ذكيّ كفاية ليعرف أنّه ينبغي عليه أن يُنشئ كائنًا جديدًا من النائب SpeedNotificatoinDelegate بشكل تلقائيّ يغلّف التابع OnExceedMaxSpeedHandler. إذا نفّذت البرنامج ستحصل على نفس الخرج المتوقّع. ملاحظة: تتمتّع النوّاب أيضًا بميّزة التسجيل المتعدّد باستخدام العامل =+. ولكن هذه الميّزة تُستخدم مع الأحداث بشكل أكبر. التوابع مجهولة الاسم Anonymous Methods التوابع مجهولة الاسم هي من المزايا التي تسمح باختصار الشيفرة إلى حدٍّ كبير. فمن اسمها، يظهر أنَّه لا يوجد لمثل هذه التوابع اسم، وإنّما جسم فقط يحوي الشيفرة المطلوب تنفيذها. كمثال بسيط على التوابع مجهولة الاسم سنجري تعديلًا على البرنامج Lesson14_01 السابق ليستخدم تابعًا عديم الاسم بدلًا من التابع OnExceedMaxSpeedHandler. سيكون هذا التعديل في السطر 49 ليصبح على الشكل التالي: car.speedNotificationHandler += delegate(string message) { Console.WriteLine(message); }; لاحظ هنا أنّنا قد استخدمنا الكلمة المحجوزة delegate بعد العامل =+ بعد ذلك الوسائط التي يقبلها هذا التابع ثم جسم التابع المحاط بالحاضنة. الشيء الوحيد الناقص هو اسم التابع. من الواضح أنّ تعريف هذا التابع عديم الاسم يجب أن يتطابق مع تعريف النائب الذي صُرّح الحدث speedNotificationHandler بناءً عليه. الآن يمكن التخلّص من التابع OnExceedMaxSpeedHandler. انظر إلى البرنامج Lesson14_02 الكامل: 1 using System; 2 3 namespace Lesson14_02 4 { 5 public class Car 6 { 7 public delegate void SpeedNotificatoinDelegate(string message); 8 9 public event SpeedNotificatoinDelegate speedNotificationHandler; 10 11 public int CurrentSpeed { get; set; } 12 public int MaxSpeed { get; set; } 13 14 public Car() 15 { 16 CurrentSpeed = 0; 17 MaxSpeed = 100; 18 } 19 20 public Car(int maxSpeed, int currentSpeed) 21 { 22 CurrentSpeed = currentSpeed; 23 MaxSpeed = maxSpeed; 24 } 25 26 public void Accelerate(int delta) 27 { 28 CurrentSpeed += delta; 29 30 if (CurrentSpeed > MaxSpeed) 31 { 32 if (this.speedNotificationHandler != null) 33 { 34 string msg = string.Format("You exceed the maximum speed! (Current = {0}, Max = {1})", 35 CurrentSpeed, MaxSpeed); 36 37 speedNotificationHandler(msg); 38 } 39 } 40 } 41 } 42 43 class Program 44 { 45 static void Main(string[] args) 46 { 47 Car car = new Car(100, 0); 48 49 car.speedNotificationHandler += delegate(string message) 50 { 51 Console.WriteLine(message); 52 }; 53 54 for (int i = 0; i < 5; i++) 55 { 56 Console.WriteLine("Increasing speed by 30"); 57 car.Accelerate(30); 58 } 59 } 60 } 61 } ملاحظة: يوجد نائب جاهز موجود ضمن مكتبة FCL اسمه EventHandler وظيفته توفير الدعم للأحداث الجديدة التي نعرّفها، بحيث لا نضطّر إلى التصريح عن نائب جديد في كلّ مرّة نريد فيها التصريح عن حدث جديد. يغلّف النائب EventHandler أي تابع يتطلّب وسيطين الأوّل من النوع object والذي يمثّل الكائن الذي أصدر الحدث، والثاني من النوع EventArgs وهو كائن يحتوي على بعض المعلومات الإضافيّة عن الحدث. تمارين داعمة تمرين 1 ليكن لدينا الصنف التالي: class Counter { private int currentValue = 0; public void Increase() { currentValue++; } public void Decrease() { currentValue--; } } أجرِ تعديلًا على هذا الصنف بحيث تصرّح عن الحدث Notification الذي يُفعَّل عندما تصبح قيمة الحقل currentValue من مضاعفات العدد 5 فقط. (تلميح: الأعداد السالبة ليست من مضاعفات 5. والعدد 5 هو مضاعف لنفسه). تمرين 2 أجرِ تعديلًا على البرنامج Lesson14_02 السابق لتستغني عن النائب SpeedNotificatoinDelegate تمامًا، بحيث تستخدم النائب الجاهز EventHandler عوضًا عنه. (تلميح: سيتطلّب الأمر تعديل الوسائط الممرّرة إلى معالج الحدث لتتطابق مع الوسائط التي يحتاجها النائب EventHandler) الخلاصة تعرّفنا في هذا الدرس على الأحداث Events. تلك التقنيّة المهمّة التي تعتمد عليها تطبيقات سطح المكتب desktop applications بشكل أساسيّ، فضلًا عن باقي أنواع التطبيقات مثل تطبيقات الويب، وتطبيقات الأجهزة المحمولة، وأي نوع من أنواع التطبيقات التي تتطلّب التفاعل الداخلي مع نظام التشغيل أو الخارجيّ مع المستخدم.
  2. الأحداث Events هي إجراءات يمكن لبرنامج التعرّف عليها ومن ثمّ التعامل معها. يمكن أن نأخذ مثالا على ذاك عملية تسجيل مستخدم في الموقع. عندما يكمل زائر ملء حقول الاستمارة الخاصّة بالتسجيل في موقع فإنه يكون قد فعّل حدثا يمكننا تسميّته "التسجيل في الموقع"؛ نستطيع بعد ذلك التعامل مع هذا الحدث لنقرّر ما التالي. كانت الطريقة القديمة تقضي بكتابة شفرة برمجية مباشرةً في المتحكّم Controller للتعامل مع الحدث؛ إلا أن توسّع التطبيق وزيادة الحاجة للتعامل مع أحداث جديدة يجعل الحاجة أكبر لطريقة جديدة أنظف وأكثر قابلية للتمديد. يستجيب Laravel لهذه الحاجة بالصنف Event والأدوات المساعدة المتعلّقة به. لا تقتصر الأحداث على إرسال البريد؛ إلا أن هذا الأخير وسيلة سهلة لشرح المبدأ. سنرى في هذا الدرس الخطوات اللازمة لتعريف حدث واستخدامه. نفترض أن لدينا جدولا ببيانات الاتصال ببعض الأشخاص؛ ونريد أن نرسل لأحدهم بريدا في كل مرة يُعرَض فيها الرابط contacthandler/id/ حيث id معرّف من نرسل له البريد. سنستخدم في هذا الجدول نفس المشروع الذي أنشأناه في الدرس السابق ونزيد عليه بإنشاء نموذج Model وجدول بيانات لحفظ جهات الاتّصال. تهيئة المشروع لا تدخل الخطوات الواردة في هذا العنوان في صميم إدارة الأحداث في Laravel؛ إلا أننا سنحتاجها للحصول على نتيجة مرئية للمثال الذي نبنيه. سنمرّ على الخطوات دون الكثير من التفاصيل لورودها في دروس سابقة. إنشاء النموذج Contact ننفذ الأمر التالي في مجلّد المشروع لإنشاء نموذج باسم Contact مع ملف التهجير الخاصّ به: php artisan make:model Contact -m نعدّل ملف التهجيرعلى النحو التالي: public function up() { Schema::create('contacts', function (Blueprint $table) { $table->increments('id'); $table->string('name'); $table->string('email')->unique(); $table->string('subject'); $table->text('body'); $table->timestamps(); }); } أضفنا الحقول التي نريد استخدامها في جدول البيانات. ننفذ أمر التهجير لإنشاء الجدول: php artisan migrate بذر البيانات سنحتاج لبيانات اتصّال للتجربة عليها. نستخدم معمل نماذج Model factory لبذر الجدول contacts. ننشئ ملفا باسم ContactFactory.php في المسار /app/factories ونضيف إليه المحتوى التالي: <?php $factory->define(App\Contact::class, function (Faker\Generator $faker){ return [ 'name' => $faker->name, 'email' => $faker->unique()->safeEmail, 'subject' => $faker->word, 'body' => $faker->text ]; }); يستدعي معمل النماذج مكتبة Faker لإدراج بيانات في جدول contacts عبر النموذج Contact. ثم ننشئ صنفا للبذر: php artisan make:seeder ContactTableSeeder نستورد النموذج Contact في صنف البذر ContactTableSeeder ونستخدم معمل النماذج لتوليد بيانات عشوائية: <?php use Illuminate\Database\Seeder; use App\Contact; class ContactTableSeeder extends Seeder { public function run() { Contact::truncate(); factory(Contact::class, 50)->create(); } } نأتي الآن للخطوة الأخيرة قبل تنفيذ أمر البذر وهي تحرير الملف DataBaseSeeder.php ليستدعي الصّنف ContactTableSeeder في الدالة run: $this->call(ContactTableSeeder::class); ثم ننفذ البذر: php artisan db:seed المسار والمتحكم نفتح ملف routes.php لإضافة مسار للرابط contacthandler/id/: Route::get('/contacthandler/{id}', 'HomeController@contactHandler'); يحيل ملف المسارات الطلب على الرابط إلى الدالة contacthandler في المتحكّم HomeController. نذهب للمتحكم ونضيف هذه الدالة: public function contactHandler($id) { } نترك الدالة خاوية لحد الساعة. التعامل مع الأحداث جهزنا المشروع ويمكننا الآن الانتقال إلى الأحداث والتعامل معها. نذكّر بأننا نريد إرسال بريد لصاحب جهة الاتصال عند طلب رابط به معرّفه. مثلا نرسل بريدا إلى جهة الاتصال ذات المعرّف 6 عند زيارة يُعرَض فيها الرابط contacthandler/6/. نحصُل على البريد الإلكتروني الذي سنرسل إليه الرسالة من خلال النموذج Contact؛ إضافة لاسمه وموضوع الرسالة وفحواها. ولدنا هذه البيانات تلقائيا أثناء البذر السابق. تعريف الحدث يتلخّص الأمر في أننا سننفّذ إجراءً عند وقوع الحدث "زيارة رابط المعرّف الخاص بجهة الاتصال". سنسميّ الصنف الموافق للحدث بـ ContactHandlerAction. نستعين بأداة artisan لإنشاء الحدث على النحو التالي: php artisan make:event ContactHandlerAction ستجد أن الأداة أنشأت الملف ContactHandlerAction.php على المسار app/Events وأضافت إليه المحتوى التالي (بعد نزع التعليقات): class ContactHandlerAction extends Event { use SerializesModels; public function __construct() { } public function broadcastOn() { return []; } } سنضيف خاصيّة إلى الصّنف ليصبح كالتالي: class ContactHandlerAction extends Event { use SerializesModels; public $contact; public function __construct($contact) { $this->contact = $contact; } public function broadcastOn() { return []; } } أضفنا متغيّرا جديدا لحفظ بيانات الاتصال واستخدامها عند الحاجة. نعود لدالّة contactHandler في المتحكّم HomeController ونعدّلها لتصبح كما يلي: public function contactHandler($id) { $contact = Contact::find($id); event (new ContactHandlerAction($contact)); return view('mail.success_view'); } نحتاج لاستيراد الصنفين المستخدمين في الدالة (النموذج والحدث) إلى المتحكّم: use App\Contact; use App\Events\ContactHandlerAction; عند استدعاء الدالة contactHandler نأخذ المعطى id المُمرَّر في الرابط ونحيله إلى الدالة find في النموذج Contact من أجل العثور عليه في قاعدة البيانات. في التعليمة التالية يبدأ عمل آلية إدارة الأحداث في Laravel: نستدعي الدالة المساعدة event ونمرّر لها كائنا من الحدث ContactHandlerAction الذي أنشأناه قبل قليل. نستخدم جهة الاتصال التي عثرنا عليها contact$ من أجل بناء الحدث. يعني هذا أننا أطلقنا الحدث “زيارة رابط المعرّف الخاص بجهة الاتصال” وزوّدناه بجهة الاتصال المعنيّة. مالذي يترتّب عن إطلاق الحدث؟ هذا هو ما سنراه الآن. التعامل مع الحدث توجد في Laravel ما تُعرَف بالمستمعات Listeners ومهمتها انتظار وقوع أحداث لتبدأ عملها. نعود لأداة artisan وننفّذ الأمر التالي: php artisan make:listener EmailContactListener --event="ContactHandlerAction" ينشئ الأمر صنفا باسم EmailContactListener للاستماع للأحداث ContactHandlerAction. يوجد الصّنف EmailContactListener في المجلّد /app/Listeners ومحتواه التالي: class EmailContactListener { public function __construct() { // } public function handle(ContactHandlerAction $event) { // } } لاحظ أن الدالة handle تنتظر معطى من نوع ContactHandlerAction. يشغّل المستمِع الدالة handle بعد تلقيه إخطارا بإطلاق الحدث ContactHandlerAction. سنعود بعد قليل للدالة handle بعد أن نعرّف الآلية التي سيُخطَر بها المستمِع بوقوع الحدث. ربط الحدث بآلية التعامل معه يوفّر Laravel مزوّد خدمة خاصًّا بالأحداث اسمه EventServiceProvider يوجد في المجلّد /app/Providers. إن فتحت الملف EventServiceProvider.php ستجد التالي (إن لم يسبق لأحد التعديل عليه): class EventServiceProvider extends ServiceProvider { protected $listen = [ 'App\Events\ContactHandlerAction' => [ 'App\Listeners\EmailContactListener', ], ]; public function boot(DispatcherContract $events) { parent::boot($events); // } } ما يهمّنا هنا هو الخاصيّة listen$. نعدّلها لتصبح على النحو التالي: protected $listen = [ 'App\Events\ContactHandlerAction' => [ 'App\Listeners\EmailContactListener', ], ]; يمكن تعريف أكثر من مستمِع لنفس الحدث (لاحظ أن قيمة App\Events\ContactHandlerAction في الخاصيّة هي مصفوفة Array). تمكننا الآن العودة إلى المنصِت بعد أن أعلمنا Laravel بالرابط بينه والحدث. تنفيذ آلية التعامل مع الحدث نعدّل الدالة handle لتصبح على النحو التالي: public function handle(ContactHandlerAction $event) { $data = [ 'name' => $event->contact->name, 'email' => $event->contact->email, 'subject' => $event->contact->subject, 'body' => $event->contact->body ]; Mail::send('mail.email_view',$data , function($message) use($data) { $message->from('hsoub@academy.com', 'Hsoub Academy'); $message->to($data['email'])->subject($data['subject']); }); if (Mail::failures()){ throw new EmailSendingException; } } نعرّف مصفوفةً باسم data$ تحوي اسم جهة الاتصال، بريدها الإلكتروني، موضوع الرسالة وفحواها. نعثُر على هذه البيانات من الكائن contact الذي مررناه للحدث event عند زيارة الرابط. نستخدم هذه البيانات لجدولة إرسال بريد عبر الدالة Mail::send. تأخذ الدالة Mail::send عرضا View تستخدمه لتنسيق الرسالة والبيانات التي أخذناها من جهة الاتصال. ثم نختبر أخطاء إرسال البريد الإلكتروني بالدالة Mail::failures؛ فإن وجدت أخطاء استخدمنا استثناءً Exception مخصّصًا لمعالجتها. يمكنك الحصول على هذا الاستثناء في الملف المرفق أو مراجعة الدرس الخاص بالاستثناءات. نحن الآن جاهزون تقريبا لتجربة الأحداث والتعامل معها في Laravel. بقي لنا إنشاء بضعة عروض لحاجات تطبيقنا المختلفة: mail.success_view في المتحكّم، mail.email_view لتنسيق الرسالة، إضافة لعروض أخرى قد تحتاجها أثناء تخصيص الاستثناء. يمكنك الحصول عليها في الملف المرفق. بقيت لنا خطوة أخيرة قبل اختبار التطبيق: إعداد Laravel لإرسال البريد. يوفّر Laravel وسيلة لمحاكاة إرسال البريد يمكننا استخدامها للتجربة. ابحث في ملف متغيّرات النظام env. عن المعطى MAIL_DRIVER وعيّن قيمته بـlog: MAIL_DRIVER=log نطلُب بهذه الطريقة من Laravel كتابة البريد في ملفّ السجل بدلا من إرساله. هذه الطريقة مفيدة أثناء الاختبار. افتح الرابط التالي في المتصفّح (ضع اسم المضيف المناسب بدلا من laraveltips): http://laraveltips.dev/contacthandler/45 ملحوظة: لا يتعدّى عدد جهات الاتصال التي أضفناها الخمسين؛ سيؤدي البحث عن جهة اتصال بمعرّف أكبر من 50 إلى خطأ يمكنك التقاطه في استثناء كما فعلنا في درس استخدام الاستثناءات Exceptions المخصَّصة في Laravel. ثم اذهب إلى ملف السجل storage/logs/laravel.log للتأكد من نجاح معالجة الحدث؛ ستجد في آخر الملف ما يشبه التالي: [2016-03-30 22:16:29] local.DEBUG: Message-ID: <8c79163440df2d3b5bbb3efd97ad549f@laraveltips.dev> Date: Wed, 30 Mar 2016 22:16:29 +0000 Subject: distinctio From: Hsoub Academy <hsoub@academy.com> To: fFranecki@example.net MIME-Version: 1.0 Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: quoted-printable يليه محتوى العرض المستخدم لتنسيق الرسالة (mail.email_view في المثال). إن حدثت مشكلة في إرسال البريد فسيتولّى الاستثناء -إن أعددتَه - إظهار العرض المناسب للزائر؛ وفي حال سريان الأمور على الوجه الأمثل فسيُظهر المتحكّم العرض mail.success_view للدلالة على نجاح الإرسال. الملف المرفق: الأحداث في Laravel.zip ترجمة -وبتصرّف- للمقال Eventing in Laravel 5.1 لصاحبه Bill Keck.
  3. أساسيات angularjs

    تقتصر بعض تطبيقات Angular على كونها أدوات مساعدة في صفحة ويب تقليدية، إلا أنّ الأغلبية العُظمى لتطبيقاتها هي التطبيقات وحيدة الصفحة (single-page applications)، التي تستبدل تصفح الويب المعتمد على المتصفّح بواجهاتٍ وانتقالاتٍ مشابهة في صفحةٍ واحدة، مقدمة للمستخدم تجربة تفاعليّة رائعة. إلّا أنّ كتابة تطبيقات وحيدة الصفحة بطريقةٍ بسيطة يجعلنا نخسر شيئًا قيّمًا في التطبيقات المعتمدة على المتصفّح وهو الرّابط URL. ففي تطبيقٍ بسيطٍ وحيد الصفحة، سيكون هناك رابط URL واحد فقط، ولن تكون هناك طريقة لمشاركة روابطَ مخصصة لكلّ مورد (resource) على حدة، كتعليق محدّد على تدوينة ما على سبيل المثال. وعلى العكس، فسيقوم تطبيق تقليدي في طرف المخدّم يتبع نمط REST (تبادل الحالة ضمن رابط HTML) بإدارة الواجهات كبنية هرميّة من الروابط المنظّمة سهلة القراءة والتي تظهر حالة التّطبيق الحاليّة بوضوح. تُقدّم هذه الروابط طريقةً للمستخدم ليعود إلى حالةٍ من حالات التطبيق في وقتٍ لاحق، أو ليشارك هذه الحالة مع الآخرين. يُعرَّف التوجيه في تطبيق ويب تقليديّ بأنّه صلة الوصل بين عمليّات HTTP مثل (GET وPOST وما إلى ذلك) وأنماط الروابط وبين المتحكّمات. إن لم تكن معتادًا على التوجيه من طرف المخدّم فسيقدّم لك دليل التوجيه السريع مقدّمةً سهلةً للتوجيه في JavaScript. على أي حال، فالتوجيه من طرف المستخدم يقلب الحالة رأسًا على عقب. فبدلًا من أن يتمّ التفاعل مع الأفعال التي ينقلها لنا المتصفّح، فسنحتاج إلى إبقاء المتصفّح متابعًا للتحديثات بينما يتفاعل التطبيق مباشرةً مع مدخلات المستخدم. كمثالٍ على ذلك، كيف يمكننا الإبقاء على شريط التّنقل محدّثًا بروابط تمثّل الحالة بدقّة، وفي نفس الوقت نستجيب لروابط تُدخل فيه أو تُحمّل من العلامات المرجعية؟ لحُسن الحظ، فقد تمّ إيجاد الوحدة ngRoute لتساعدنا في هذا العمل. لنتمكّن من استخدام هذه الوحدة، سنحتاج إلى تحميل ملف angular-route.js إضافةً إلى ملف مكتبة Angular الرئيسي. كما سنقوم بتحميل بيئة Bootstrap لاستخدام أنماط CSS الخاصة بها. <base href="/"> <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css"> <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.2/angular.js"></script> <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.2/angular-route.js"></script>لاحظ تعريف العنصر base في السطر الأول في المثال السابق. يُعرِّف هذا العنصر رابط URL القاعديّ لتطبيق Angular الحالي، وهذا العنصر يطلبه دعم Angular لـتوابع HTML5 الخاصة بالتاريخ (سنتحدث عنها أدناه). إن كنت تتساءل لمَ تم إسناد الخاصية href إلى مسار الجذر، بدلًا من أن تكون مسندةً إلى مسار الصفحة، فهذا لأنّ أمثلة هذه السلسلة تعمل داخل أُطُر iframe خاصة لكل واحد منها. <body ng-app="app"> <!-- كل الأمثلة توضع هنا --> </body>بعد ذلك، سيكون علينا جلب ngRoute عند تهيئة الوحدة الجذر، وقد قمنا بتغطية هذه الفكرة بعمق في فصل الوحدات. angular.module('app', ['ngRoute']);وهكذا نكون قد أتممنا تحميل الوحدة ngRoute في تطبيقنا. routeProvider$لنتمكن من استخدام ngRoute، سنحتاج إلى ربط مسار URL نسبي واحدٍ أو أكثر مع معالجاتٍ (handlers). يتيح لنا التابع module.config إعداد وحداتٍ مثل ngRoute بعد أن تقوم Angular بتحميلها. كل ما علينا هنا هو معرفة اسم الخدمة (service) أو المزوّد (provider) الذي سيقوم بإعداد الوحدة. في حالة ngRoute يُسمّى المزوِّد بـrouteProvider$. سنتمكن عند وضع هذا الاسم في تابع الاستدعاء الخلفي config من عمل سلسلةٍ من عمليّات ربط المسارات باستخدام التابع when. الوسيط الأول للتابع when هو المسار النسبي الخاص بوجهة التوجيه، أما الوسيط الثاني فهو كائن الإعدادات الذي يحدد الكيفية التي سيتم بها معالجة محتوى وجهة التوجيه. angular.module('app') .config(function($routeProvider) { $routeProvider .when('/', { template: "<a href='#/about'>About</a>" }) .when('/about', { template: "<a href='#/'>Home</a>" }); });يشبه الخيار template الذي يظهر في المثال السابق ذلك الذي استخدمناه في إعداد التوجيهات سابقًا. لا يحوي المثال السابق أي محتوىً متغيّر، فقط نص HTML ثابت. يُقدّم كلّ قالبٍ رابطًا إلى المسار النسبي للآخرين، لذا سنتمكن في المثال السابق من التبديل بين الاثنين. قد تتساءل أين سيتم وضع المحتوى المعالَج، حسنًا، تُقدّم الوحدة ngRoute توجيهًا مُميّزًا، هو ng-view، ويجب أن يكون هذا التوجيه موجودًا في وحدتنا لتعمل. تتكوّن القوالب الرئيسي في هذا الفصل كلّها من عنصرdiv خالٍ يوجد فيه التوجيه ng-view، وكلّ ما سوى ذلك سيقوم المعالج (handler) بمعالجته للمسار الحالي. <div ng-view></div>جرب النقر على الرابط الناتج لتتم معالجة الرابط للمورد الآخر. لم يعمل؟ حسنًا، لقد تجاهلنا أمرًا هامًا، إن نظرت إلى شريط التنقل الخاص بمتصفحك أثناء نقر الرابط فلن تراه قد تغير ليلائم المسار النسبي الصحيح، في الواقع، ليس هناك أي أمرٍ خاطئ في النص البرمجي الخاص بالمثال، وفي التطبيقات الحقيقية ستلاحظ التغيير، فكلّ ما في الأمر أن البيئة التفاعلية التي تمّ استضافة الأمثلة في داخلها ضمن هذه السلسلة لا تسمح لنا برؤية التغيير في الصفحة الرئيسية للفصل. الخدمة location$جميع أمثلة هذه السلسلة موضوعة داخل صناديق sandbox داخل أُطر iframe الخاصة بكل واحد منها. (يُمكنك إلقاء نظرة على الرابط التالي Codecademy/stuff.js إن كان لديك فضول لمعرفة طريقة القيام بذلك.) وهذا ممتازٌ لعزل البيئة البرمجية، إلا أنّه يمنعك من رؤية رابط الـURL الخاص بالأمثلة في شريط الانتقال. ومن حسن الحظ أنّه توجد طريقةٌ للتحايل على هذا الأمر، مع فائدةٍ إضافيّة في استكشاف خدمةٍ مفيدةٍ في Angular تسمح بمعاينة رابط URL الحالي، وهي الخدمة location$. angular.module('app') .controller('LocationController', function($scope, $location) { $scope.location = $location.absUrl(); });يتم في المثال السابق التصريح عن متحكم بسيطٍ يستقبل الخدمة location$ عن طريق حقن التبعية، ويستخدمها للدخول إلى رابط URL المُطلق الحالي للتطبيق، حيث تكون القيمة هي دمجًا للرابط الثابت للجذر الخاص بالصفحة المغلّفة للمثال مع رابط الامتداد الذي تُديره الوحدة ngRoute. المتحكميُمكننا أن نقوم بتحميل المتحكّم LocationController في المثال السابق بالطريقة المعيارية باستخدام التّوجيه ng-controller، إلّا أنّه بإمكاننا أيضًا أن نقوم بإعداد متحكّمٍ كخيار (option)، كما سنضيف خاصّيّة المجال location إلى قالبنا. angular.module('app') .config(function($routeProvider) { $routeProvider .when('/', { controller: 'LocationController', template: '<div class="well well-sm" ng-bind="location"></div>\ <a href="#/about">About</a>' }) .when('/about', { controller: 'LocationController', template: '<div class="well well-sm" ng-bind="location"></div>\ <a href="#/">Home</a>' }); });ها نحن ذا، صار لدينا الآن محاكٍ لشريط الانتقال لأمثلتنا، ولكن للأسف لن تتمكّن من تعديلها لرؤية الكيفية التي ستدير بها الوحدة ngRoute التغييرات بمعالجتها وإظهار محتوىً جديد، وسيكون عليك اختبار ذلك في تطبيقاتك الخاصة. رمز Hashbangتتوقّع وحدة ngRoute في الحالة الافتراضية أن تبدأ مسارات URL النسبيّة برمز hash (#)، ويُمكنك بسهولةٍ تغييره إلى البادئة hashbang متمثّلة بالرمز (!#) بإضافة إعداداتٍ إلى الخدمة locationProvider$ كما يبيّن المثال التالي. لاحظ أنّه يجب علينا أيضًا إضافة رمز ! إلى الروابط في قوالبنا. angular.module('app') .config(function($locationProvider) { $locationProvider.hashPrefix('!'); }) .config(function($routeProvider) { $routeProvider .when('/', { controller: 'LocationController', template: '<div class="well well-sm" ng-bind="location"></div>\ <a href="#!/about">About</a>' }) .when('/about', { controller: 'LocationController', template: '<div class="well well-sm" ng-bind="location"></div>\ <a href="#!/">Home</a>' }); });لا يزال رمز البادئة hashbang مصادقًا عليه من Google في دليل Webmasters الخاص بها، وتحديدًا في المقطع Making AJAX Applications Crawlable، الذي ينص على أنّ "أقسام الـhash يجب أن تبدأ بعلامة تعجّب"، على أيّ حالٍ، طالما أنّك تقوم بالخطوات اللازمة للتّأكّد من ملاءمة موقعك لقواعد SEO فقد تجد أنّه من الأفضل أن يكون تطبيقك مخدومًا بأسلوبٍ خالٍ من البادئة، وهذا الأسلوب مفعّلٌ في HTML5 الآن. الكائن History من HTML5يُقدّم الكائن window.history تحكّمًا بالتّنقّل في المتصفّحات الحديثة، مما يقدّم تجربة مستخدمٍ سلسة. angular.module('app') .config(function($locationProvider) { $locationProvider.html5Mode(true); });يُمكننا كتابة قيم href الخاصة بنا كمسارٍ بسيط، بدون البادئة # ولا البادئة !#. angular.module('app') .config(function($routeProvider) { $routeProvider .when('/', { controller: 'LocationController', template: '<div class="well well-sm" ng-bind="location"></div>\ <a href="/about">About</a>' }) .when('/about', { controller: 'LocationController', template: '<div class="well well-sm" ng-bind="location"></div>\ <a href="/">Home</a>' }); });استخدام الكائن history من HTML5 وسيلةٌ ممتازةٌ إن كنت غير مضطرٍّ لدعم متصفحاتٍ قديمة في تطبيقك. العنصر templateUrlلنقم الآن بتحليل تطبيقنا البسيط إلى مكوناته الأولية. كما قمنا سابقًا مع التوجيهات بجعل شيفراتنا البرمجية أكثر قابلية للإدارة عن طريق استخراج قوالبنا (حتى الصغيرة منها) إلى ملفاتٍ مستقلة، فسيكون علينا للقيام بذلك فقط استبدال العناصر template في إعداداتنا بالعناصر templateUrl. angular.module('app') .config(function($routeProvider) { $routeProvider .when('/', { controller: 'LocationController', templateUrl: '/views/index.html' }) .when('/about', { controller: 'LocationController', templateUrl: '/views/about.html' }); });دون تغييرٍ على صفحة About التي سنضعها في مجلد views. <div class="well well-sm" ng-bind="location"></div> <a href="/">Home</a> <h4>About</h4>سنتكلّم في الفقرات التالية عن فكرة التعامل مع روابط URL غير الصالحة، لذا لنقم بإضافة واحدٍ منها الآن. <div class="well well-sm" ng-bind="location"></div> <a href="/about">About</a> | <a href="/bad-path">Bad path</a> <h4>Home</h4>جرّب النقر على Bad path في المثال السابق، ستنتهي التّسلية للأسف، وستضطر لإعادة إنعاش الصفحة لاستعادتها. التابع otherwiseما الذي يمكننا فعله لتجنب النهايات الحزينة كما حدث قبل قليل؟ قد يكون أحد الإجابات هو تجنب تقديم روابط غير صالحة، ولكن في التطبيقات الحقيقيّة لا يمكننا إيقاف المستخدم عن الكتابة في شريط التنقل. يُمكننا أن نُعِدّ المُخدّم ليقوم بإعادة توجيهٍ للروابط غير الصالحة إلى رابط الجذر لتطبيق Angular، ولكن هذا يعني إعادة تحميل الصفحة من جديد، مما يعني إعادة تشغيل التطبيق من البداية. كيف يُمكننا بناء تطبيقٍ من طرف المستخدم ليقوم بإعادة توجيهٍ للروابط غير الصالحة؟ لو افترضنا بأننا نريد أن نُعلم المستخدم بأن الرابط غير صالح قبل أن نقوم بإعادة التوجيه، فالأمر الأول الذي نحتاجه هو الواجهة التي ستظهر لأي رابط غير صالح. سنسُمّي هذا القالب 404 ليتناسب مع معناه، وطبعًا يمكنك تسميته بأي اسمٍ آخر. <div class="well well-sm" ng-bind="location"></div> <a href="/">Home</a> <h4 class="text-danger">404 - Not Found</h4>كلّ ما نحتاجه الآن هو عبارة when أخرى لإضافة المسار 404/. ثم لربط أي رابط غير متوافق مع الروابط الموجودة بالمسار 404/، وسنقوم بذلك بإضافة استدعاءٍ للتابع otherwise الذي يقوم بإضافة المسار إلى العنصر redirectTo فيه. في المثال التالي، سيتم تشغيل الاستدعاء للتابع config إضافةً إلى كل الإعدادات السابقة. (يُمكنك تسجيل عددٍ غير محدودٍ من الاستدعاءات الخلفية (callbacks) للتابع config في المزوِّد (provider)) angular.module('app') .config(function($routeProvider) { $routeProvider .when('/404', { controller: 'LocationController', templateUrl: '/views/404.html' }) .otherwise({ redirectTo: '/404' }); });جّرب النقر الآن على Bad path. صحيحٌ أنّ بيئة الأمثلة لا تسمح لك بإدخال مسارٍ عشوائيّ في شريط التصفح، إلّا أنّه يمكنك تعديل "href="/bad-path في القالب السابق إلى أيّ قيمة تريدها لترى كيف سيتم التعامل معها. ربما يجب عليك دومًا إضافة معالجٍ (handler) ليتعامل مع كل الاحتمالات الباقية في إعدادات التوجيه الخاصة بك. الأحداث (Events)تُقدّم Angular تطبيقًا للرسائل من نمط (publish-subscribe) لأحداث التطبيق، وهي تعتمد على ثلاث توابع: emit$ وbroadcast$ وon$، حيث يقوم التابع emit$ و broadcast$ بتفعيل نشر أحداثٍ مخصصة، أما التابع on$ فيقوم بتسجيل معالجات (handlers) للأحداث التي تقوم بنشرها الوحدة ngRoute. فمثلًا، عندما يتمّ تغيير المسار بنجاح سينتج عن ذلك نشر الأحداث التالية: routeChangeStart$locationChangeStart$locationChangeSuccess$routeChangeSuccess$هذه الأحداث مرتبةٌ في القائمة بنفس ترتيب ظهورها أثناء دورة حياة عملية تغيير المسار، لنقُم بإثبات ذلك. تسجيل معالج للحدث (event handler)لنستخدم التابع on$ لتسجيل معالجٍ بسيط لأحداث المسار، حيث سيقوم هذا المعالج بجمع أسماء الأحداث في مصفوفة، ثم سنقوم لاحقًا بطباعة أسماء الأحداث في الصفحة بنفس الترتيب الذي حُفظت به. angular.module('app') .value('eventsLog', []) .factory('logEvent', function(eventsLog) { return function(event) { eventsLog.push(event.name); }; });في هذا المثال، سنقوم بتسجيل الأحداث أثناء انتقالنا من مسار الجذر (والمسؤول عنه هو المتحكم HomeController) إلى المسار events/ (والمسؤول عنه المتحكم EventsController). سنقوم أيضًا بجعل المتحكّم يضيف اسمه إلى القائمة أيضًا لنعرف وقت استدعاء هذا المتحكم. angular.module('app') .controller('HomeController', function($scope, $location, $rootScope, eventsLog, logEvent) { $scope.location = $location.absUrl(); $scope.link = {path: '/events', title: 'Events'}; eventsLog.push("HomeController: " + $location.path()); $rootScope.$on('$routeChangeStart', logEvent); $rootScope.$on('$locationChangeStart', logEvent); $rootScope.$on('$locationChangeSuccess', logEvent); $rootScope.$on('$routeChangeSuccess', logEvent); $scope.eventsLog = eventsLog; });يقوم المتحكم EventsController بإضافة اسمه إلى المصفوفة eventsLog ثم يقوم بكشف (expose) السجل للمجال بحيث يمكننا رؤيته في الصفحة. angular.module('app') .controller('EventsController', function($scope, eventsLog, $location) { $scope.location = $location.absUrl(); $scope.link = {path: '/', title: 'Home'}; eventsLog.push("EventsController: " + $location.path()); $scope.eventsLog = eventsLog; });نفس الواجهة ستعمل في كلا المتحكّمين. وسيتم عرض شريط انتقالٍ متغيّر إضافةً إلى محتويات السجل باستخدام التوجيه ng-repeat. <div class="well well-sm" ng-bind="location"></div> <a ng-href="{{link.path}}">{{link.title}}</a> <ol> <li ng-repeat="event in eventsLog track by $index"> {{event}} </li> </ol>كملاحظةٍ جانبية، يُعدّ المرشح json المبني في Angular مفيدًا في التنقيح وإنشاء السجلات، فإن أردنا رؤية ما هو أكثر من اسم الحدث، فيُمكننا استخدامه لعرض كامل المحتويات لكائن الحدث. سيكون علينا لإتمام المثال أن نكتب إعداداتٍ مباشرةً للمسارين. angular.module('app') .config(function($routeProvider) { $routeProvider .when('/', { controller: 'HomeController', templateUrl: '/views/events/index.html' }) .when('/events', { controller: 'EventsController', templateUrl: '/views/events/index.html' }); });انقر على الرابط في المثال السابق لرؤية الأحداث المسجلة في الصفحة. الموارد التي تتبع أسلوب RESTيُقدّم أسلوب REST في تطوير تطبيقات الويب مجموعةً من المعايير لتنظيم عمليات CRUD (إنشاء، قراءة، تحديث و حذف) على مجموعات الموارد والموارد المفردة أيضًا. لن تحتاج عند استخدامك لـ Angular أن تتّبع أسلوب REST في التوجيه إلا إذا قمت بتحميل الوحدة ngResource التي لم نقم بتغطيتها في السلسلة. على أيّ حال، سنختم هذا الفصل بالقليل من التوجيه المتّبع لأسلوب REST لتحصل على خبرةٍ في بعض الأمور العمليّة في التوجيه. سنحتاج إلى مجموعة موارد لنتمكّن من البدء، وسنعرض في الفصل التالي HTTP كيف نقوم بتحميل البيانات من مخدّمٍ في النهاية الخلفية (backend). أما الآن فسنقوم فقط بحقن مصفوفةٍ من كائناتٍ من نوع item في داخل المتحكّم. angular.module('app') .factory('Item', function(filterFilter) { var items = [ {id: 1, name: 'Item 1', color: 'red'}, {id: 2, name: 'Item 2', color: 'blue'}, {id: 3, name: 'Item 3', color: 'red'}, {id: 4, name: 'Item 4', color: 'white'} ]; return { query: function(params) { return filterFilter(items, params); }, get: function(params) { return this.query(params)[0]; } }; });لكلّ عنصرٍ في المصفوفة قيمة فريدة للخاصية id فيه، مما يسمح لنا بالتعامل معه كموردٍ منفرد. تقوم الخدمة التي يُنشئها التابع factory بتغليف الوصول إلى المصفوفة items بتابعين مفيدين هما: query لمجموعة الموارد، وget للموارد المنفردة. يستخدم التابع query المرشّح ذا الاسم سيء الحظ، filter (محقونٍ مع filterFilter) لتنفيذ جلبٍ حسب المثال باستخدام الوسيط params، أمّا التابع get فيقوم باستدعاء التابع query ليقوم بإعادة موردٍ واحد. (راجع فصل المرشحات إن كنت قد تجاوزته، لمعلوماتٍ إضافيّة عن المرشّحات في Angular.) أوّل شيءٍ نحتاجه في تطبيقٍ يتبع أسلوب REST هو دليل (index) أو مسارٌ للقائمة ليتم كشف المجموعة items كاملةً. وكلّ ما على المتحكم القيام به هو كشف كامل المحتويات للمصفوفة items باستدعاء التابع query دون وُسطاء. angular.module('app') .controller('ItemsController', function($scope, $location, Item) { $scope.location = $location.absUrl(); $scope.items = Item.query(); });واجهة هذا المتحكم سهلةٌ أيضًا، وسنستخدم التوجيه ng-repeat لإظهار العناصر. <div class="well well-sm" ng-bind="location"></div> <a ng-href="/">Home</a> <ol> <li ng-repeat="item in items"> {{item.name}} - {{item.color}} </li> </ol>في هذا المثال الأساسيّ، ستربط إعدادات التوجيه بين مسار الجذر (/) وبين واجهة المجموعة view. angular.module('app') .config(function($routeProvider) { $routeProvider .when('/', { controller: 'ItemsController', templateUrl: '/views/items/index.html' }); });لا يُحقّق المثال السّابق أسلوب REST فعلًا، وذلك لأنّ المسار ليس اسم مجموعة الموارد (items/). المشكلة هي أننا نحتاج إلى ننشئ شيئًا ما في مسار الجذر ليكون المكان الافتراضي الذي يتم تحميل تطبيق Angular منه. سنرى الحل في المثال التالي، وهو إعادة التوجيه الفوري من المسار الجذر إلى المسار items/. العنصر redirectToلنتمكّن من القيام بإعادة توجيهٍ تلقائيّة من مسارٍ لآخر، سيكون علينا استخدام العنصر redirectTo بدلًا من متحكّم وقالب. angular.module('app') .config(function($routeProvider) { $routeProvider .when('/', { redirectTo: '/items' }) .when('/items', { controller: 'ItemsController', templateUrl: '/views/items/index.html' }); });كما يُمكنك أن ترى في شريط الموضع في المثال السابق، فالمجموعة items يتم عرضها في المسار items/. ومهما كان عدد المرات التي تنقر بها على الرابط Home، فلن تتمكن من الانتقال إلى المسار الجذر / وسيتم دومًا إعادة توجيهك إلى المسار items/. هذا استخدامٌ بسيط للخيار redirectTo. إن كنت تريد تطبيق بعض العمليات على إعادة التوجيه، فيُمكنك توفير تابعٍ بدلًا من مسارٍ نصّيّ. التابع searchكما ناقشنا في بداية هذا الفصل، تقدّم روابط URL النصّية تمثيلًا لحالة التطبيق (مثل عملية ترشيحٍ للموارد) يُمكن أن يتم حفظها ومشاركتها بسهولة. كيف يُمكننا معالجة نصّ طلب (query) يطلب فقط الحصول على العناصر التي تكون قيمة العنصر color فيها هو لونًا معيّنًا؟ angular.module('app') .config(function($routeProvider) { $routeProvider .when('/', { controller: 'LocationController', template: '<div class="well well-sm" ng-bind="location"></div>\ <a ng-href="/items?color=red">Red items</a>' }) .when('/items', { controller: 'ItemsController', templateUrl: '/views/items/index.html' }); });في هذا المثال، تحتوي واجهة مسار الجذر الخاص بالتطبيق على رابط التّنقّل الحاوي على نصّ الطلب color=red. يُمكننا جلب قيمة هذا الوسيط بسهولةٍ باستخدام التابع search للخدمة location$. angular.module('app') .controller('ItemsController', function($scope, $location, Item) { $scope.location = $location.absUrl(); $scope.items = Item.query({color: $location.search().color}); });عند النّقر على الرابط Red items في المثال السابق، سترى كيف استخدم التابع query (الذي قُمنا بتعريفه سابقًا في الخدمة items) المُرشّح filter للقيام بعملية جلبٍ حسب المثال. الخدمة routeParams$يعتمد أسلوب REST نموذجيًّا على إلحاق معرّف المورد الفريد في نهاية المسار بدلًا من وضعه في نصّ الطلب. كيف يُمكننا القيام بمعالجةٍ صحيحة للمسار بحيث يحوي المعرّف الفريد؟ أو بعبارةٍ أدقّ، كيف يُمكننا استخراج المعرّف الفريد 3 من المسار items/3/ على سبيل المثال؟ لنقم بتحديث واجهة المجموعة لتتضمّن هذا الأسلوب من رابط التّنقّل لكلّ مورد مفرد على حدة. <div class="well well-sm" ng-bind="location"></div> <p class="lead">Items</p> <ol> <li ng-repeat="item in items"> <a ng-href="/items/{{item.id}}"> {{item.name}} </a> </li> </ol>تُقدّم الخدمة routeParams$ وصولًا ملائمًا للعناصر في المسار، حيث يقوم بكشفها (expose) كعناصر مُسمّاة. يُمثّل القسم الأخير من المسار المعرّف الفريد لموردٍ وحيد يتبع أسلوب REST. فمثلًا، يجب أن يعيد المسار items/3/ تمثيلًا للمورد Item بالمعرّف الفريد 3. في إعدادات التوجيه الخاصة بنا، يُمكننا استخدام البادئة الخاصّة : لتعريف متغيّراتٍ ذات أسماءٍ متغيرّة قابلةٍ للاستخراج من المسار. تمّ الاتّفاق على تسمية الوسيط المعرّف للمورد بالاسم id:، لذا سيكون نصّ المسار هو items/:id/. angular.module('app') .config(function($routeProvider) { $routeProvider .when('/', { redirectTo: '/items' }) .when('/items', { controller: 'ItemsController', templateUrl: '/views/items/linked-index.html' }) .when('/items/:id', { controller: 'ItemController', templateUrl: '/views/items/show.html' }); });تقوم الوحدة بوضع الوسطاء المستخرجة من المسار في الخدمة routeParams$ التي يجب أن نقوم بحقنها في المتحكّم ItemController جنبًا إلى جنبٍ مع الخدمة Item التي أنشأناها. angular.module('app') .controller('ItemController', function($scope, $location, Item, $routeParams) { $scope.location = $location.absUrl(); $scope.item = Item.get({id: $routeParams.id}); });سنقوم باستدعاء التّابع Item.get مستخدمين القيمة التي تمّ إسنادها في routeParams.id$، وهذا التّابع يستخدم المرشّح filter لإيجاد موقع النموذج الصحيح. (يُمكن للتابع ()Array.prototype.find الموجود في المكتبة المركزيّة أن يُقدّم طريقةً أفضل للقيام بذلك عندما ينتشر هذا التابع على نطاقٍ واسع.) طريقة عرض المورد item المفرد سهلةٌ ومباشرة، سنقوم بتضمين رابطٍ للعودة إلى المسار items/ بحيث نتمكّن من العودة إلى واجهة عرض القائمة. <div class="well well-sm" ng-bind="location"></div> <a ng-href="/items">Items</a> <p class="lead">{{item.name}}</p> <p>Color: {{item.color}}</p>صحيحٌ أنّ الخدمة routeParams$ والتابع ()location.search$ كلاهما سهل الاستخدام نوعًا ما، إلّا أنّهما معًا يقدّمان حجر بناءٍ هامًّا في أيّ عمليّة توجيه متضمّنةٍ لأسلوب REST. خاتمةتعلّمنا في هذا الفصل أساسيات طريقة Angular الخاصة بها في التوجيه. ورغم أنّ ngRoute لا تُقدّم بعض الميزات المعقّدة للتوجيه، كدعم الموارد المتداخلة، أو آلة الحالة من الدرجة الأولى، أو توليد مسارات URL، إلا أنّها تعطيك مقدّمةً ممتازةً للتوجيه في Angular. ستجد أنّ العديد من التطبيقات الحقيقية تستخدم UI-Router من مشروع AngularUI بدلًا من ذلك، ولن نتطرّق إليها في هذه السلسلة، بل سيكون الفصل الأخير مقدّمةً لتحميل البيانات من المخدّم في النهاية الخلفية (backend) باستخدام الخدمة http$ في Angular. ترجمة -وبتصرّف- للفصل الثاني عشر (Routing) من كتاب: Angular Basics لصاحبه: Chris Smith.