لوحة المتصدرين
المحتوى الأكثر حصولًا على سمعة جيدة
المحتوى الأعلى تقييمًا في 01/14/16 in مقالات البرمجة
-
مقدمة من أهم أسباب انتشار ووردبريس وسيطرته على سوق أنظمة إدارة المحتوى هو الكم الهائل من إضافاته المجانية والمدفوعة والتي تقدم العديد من الخصائص والمميزات والتحسينات الإضافية التي لا تتوفر بصورة افتراضية في قلب ووردبريس WordPress Core. في هذا المقال سنتعرف سوّيًا على أساسيات إنشاء إضافة لووردبريس لكن قبل الدخول في لب الموضوع دعنا نجب على سؤال مُهمّ وكذلك تعريف إضافات ووردبريس. لماذا ننشئ إضافات ووردبريس هنالك قاعدة مهمة في تطوير ووردبريس، وهي: إياك أن تلمس الشيفرات Codes الأساسية لووردبريس أي لا تُعدّل على قلب ووردبريس WordPress Core وذلك لأنه عند التحديث سيتمّ استبدال بعض أو كل ملفات ووردبريس الأساسية، وبالتّالي ستفقد التعديلات التي قمت بها، ولهذا السبب نلجأ إلى تطوير إضافات عند الحاجة لإضافة التحسينات والخصائص الجديدة على النّظام. تعريف إضافات ووردبريس إضافة ووردبريس عبارة عن دالة أو مجموعة دوال (حزمة شيفرات code package) مكتوبة بلغة php تقوم بإضافة بعض الخصائص لنظام إدارة المُحتوى. وتندمج شيفرات الإضافة معه من خلال واجهة برمجية API يوفرها له. بالإضافة لملفات php يمكن أن تحتوي الإضافة على ملفات أخرى مثل ملفات JavaScript, CSS وبعض الصور. إنشاء إضافة ووردبريس هذه بعض الخطوات المهمة عند إنشاء أي إضافة ووردبريس وبعض الأمور التي يجب مراعاتها. اختيار اسم الإضافة في البدء عليك أن تقوم باختيار اسم فريد للإضافة التي تود إنشائها بحيث لا يكون الاسم مستخدما من قبل من إحدى إضافات ووردبريس حتى لا يحدث تعارض بين الإضافات مستقبلا. إنشاء مجلد الإضافة بعد اختيار اسم الإضافة تقوم بإنشاء مجلد لها بالاسم الذي اخترناه ويكون هذا المجلد داخل مجلد plugins الخاص بالإضافات والموجود داخل مجلد wp-content كما موضح بالصّورة. يمكنك إنشاء ملف php بنفس الاسم بدلًا عن إنشاء مُجلّد كامل لكن يُفضّل أن تنشئ مُجلّدًا خاصًّا حتى يستوعب بقية ملفات الإضافة، ما لم تكن الإضافة تحتوي على ملف php واحد فقط. إنشاء الملف الأساسي للإضافة بعد إنشائك للمُجلّد في الخطوة أعلاه عليك أن تقوم بإنشاء ملف php له نفس اسم المجلد السابق، وهذا الملف هو الملف الأساسي للإضافة والذي سيستخدمه ووردبريس للتّعرف على بعض الأمور المُتعلّقة بالإضافة مثل اسمها واسم المُبرمج ورخصة الإضافة وغيرها، وهذا يتم من خلال استخدام ترويسة للملف الأساسي للإضافة وهذه هي الصّيغة العامة لها: <?php /** * Plugin Name: هنا تكتب اسم الإضافة والذي يجب أن يكون فريدًا * Plugin URI: هنا تكتب رابط موقع الإضافة * Description: هنا نبذة مختصرة عن الإضافة * Version: رقم نسخة الإضافة، مثلا 1.0.0 * Author: اسم مطور الإضافة * Author URI: رابط موقع مطور الإضافة * License: اسم رخصة الإضافة */ السّطر الإجباري هو سطر اسم الإضافة فقط، ويتمّ استخدام بقية السطور (في حال ورودها) في إنشاء معلومات الإضافة التي تظهر في لوحة تحكم الإضافات عند استعراض الإضافة. برمجة الإضافة قبل أن تبدأ في برمجة الإضافة تحتاج لمعرفة بعض الأمور العامة في برمجة ووردبريس بالإضافة لبعض التسهيلات التي يقدمها ووردبريس للمبرمجين مثل الواجهات البرمجية الجاهزة WordPress API's. الخطافات في ووردبريس WordPress Hooks ( الدخول الى عمق ووردبريس) من أهم المفاهيم في ووردبريس مفهوم الخطافات hooks والتي تنقسم إلى قسمين: الأحداث actions والمُرشّحات filters. 1- الإجراءات في ووردبريس WordPress Actions فلنفترض أنه لدينا دالة معينة نريد أن يتم تنفيذها في لحظة معينة أثناء دورة حياة ووردبريس أو عند حدوث حدث معين، كيف يمكننا إنجاز هذا الأمر؟ أحد الخيارات أن نقوم بمناداة هذه الدالة بالطريقة العادية في المكان الذي نريدها أن تُنفّذ فيه، لكن هذا الأمر يتطلّب التّعديل على ملفات ووردبريس الأساسية. لك القاعدة التي ذكرناها مسبقا: إياك أن تلمس الشيفرات الأساسية لووردبريس تمنعنا من القيام بذلك. الحل الآخر هو أن نستخدم الإجراءات actions وهي عبارة عن آليّة رائعة يوفرها ووردبريس تمكّنك من إضافة الدّوال الخاصّة بك لتُنفّذ في وقت معين أثناء تنفيذ الشيفرات الخاصة بووردبريس إذًا، يمكننا أن نعرف الإجراءات بأنها طريقة تمكننا من إخبار ووردبريس بتنفيذ دالة معينة عند حصول حدث معين وبهذه الطريقة نستطيع الدخول إلى عمق ووردبريس من دون التعديل المباشر على ملفاته الأساسية Core files. يتم هذا كله من خلال ربط الدّوال التي تريدها مع الأحداث الجاهزة التي يوفرها ووردبريس والتي يقوم بتنفيذها أثناء دورة حياته وهذا ما يجعلك قادرا على تنفيذ الدّوال الخاصة بك في أي مكان تريده تقريبا. إذا كنّا نريد مثلا تنفيذ الدّالة my_function عندما ينفذ ووردبريس الحدث my_action علينا ببساطة أن نخبره بذلك من خلال إضافة هذه الدالة إلى هذا الحدث. ويتم ذلك من خلال الدالة add_actions بالطريقة التالية add_action('my_action', 'my_function'); 1-1مثال للإجراءات في ووردبريس فلنأخذ هذا المثال الذي يقوم بإرسال بريد إلى مدير الموقع (الدالة المراد تنفيذها) كلما نشر مقالًا جديدًا (الحدث الذي تنفذ عنده الدالة) add_action('publish_post', 'email_admin'); function email_admin(){ // هنا يتم إرسال البريد الإلكتروني إلى مدير الموقع } 2-المرشحات في ووردبريس WordPress Filters المُرشّحات هي النوع الثاني من الخطّافات التي يوفرها ووردبريس وهي مختصّة أكثر بالبيانات حيث أنها تتيح لنا إمكانية التعديل على قيم المتغيرات (اسم المقال مثلا) قبل حفظها في قاعدة البيانات أو قبل عرضها للمستخدم، وكما هو ظاهر من اسمها فهي تتيح لنا إمكانية ترشيح البيانات، بمعنى إدخالها إلى filter مُعيّن يُعدّل عليها قبل إخراجها من الجانب الآخر من هذا المُرشّح . بطبيعة الأمر وكما هو الحال بالنّسبة للأحداث فإن ووردبريس يُوفّر لك إمكانية إدخال أغلب مُتغيّراته في المُرشّحات التي تريدها من خلال اسم المتغير، ويمكنك الاطّلاع على المُتغيّرات التي يمكنك تمريرها عبر المُرشّحات بزيارة التوثيق الرسمي للمرشحات. يتم إضافة فلتر معين my_filter لبيانات معينة my_data من خلال الدّالة add_filter بالطريقة التالية add_filter( 'my_data', 'my_filter' ); حيث أن my_filter هي الدّالة التي ستقوم بالتعديل على البيانات ويتم تمرير البيانات لها من قبل ووردبريس أي أنها يجب أن تستقبل my_data كمعامل parameter. 2-1مثال للمرشحات في ووردبريس هذا المثال يقوم بتعديل محتوى المقال the_content حيث يقوم بإضافة التّوقيع لمُحتويات كل مقال في الموقع add_filter ( 'the_content', 'add_signiture' ); function add_signiture($the_content){ // هنا نقوم بإضافة الشيفرات التي تضيف التّوقيع لمحتويات المقال return $the_content; // بعد ذلك تقوم الدّالة بإرجاع القيم الجديدة لمحتويات المقال } هذه نظرة سريعة فقط على الخطافات في ووردبريس ولكن ما زال هنالك العديد من الأشياء المتعلقة بهذه الآلية الرّائعة التي يوفرها ووردبريس منها: إمكانية إضافة الـhooks الخاصة بك. تعديل أولوية تنفيذ الدّوال التابعة لنفس الـhook. ويمكنك التعرف على المزيد حول موضوع الخطافات في التّوثيق الرسمي لPlugin API وسوم القالب Template Tags قد تبدو الترجمة الحرفية مُضلّلة نوعًا ما خصوصا إذا علمت أن المقصود من وسوم القالب هو مجموعة من الدّوال الجاهزة التي يوفرها ووردبريس والتي تختصر لك الكثير من العمل، لذلك من الجيد أن تلقي نظرة عليها قبل البدء في برمجة الإضافة الخاصة بك. الجدير بالذّكر أن يتم استخدام كثير من هذه الدّوال داخل الحلقة الخاصّة بجلب المقالات في ووردبريس WordPress Loop والتي تستخدم بكثرة في القوالب، ولعل هذا هو سرّ تسميتها بوسوم القالب. حفظ بيانات/ الإضافة في قاعدة البيانات الكثير من إضافات ووردبريس إن لم يكن معظمها تحتاج لاستقبال بعض البيانات من مدير الموقع (خيارات الإضافة مثلا) وحفظها في قاعدة البيانات لاسترجاعها واستخدامها لاحقا، ولهذا السبب يتيح لنا ووردبريس عدّة طرق لحفظ البيانات في قاعدة بيانات الموقع واختيار الطريقة المناسبة يعتمد على نوع البيانات المراد حفظها. هذه الطرق هي: 1- استخدام آلية "الخيارات options" التي يوفرها ووردبريس وهذه الطريقة مناسبة في حالة كون البيانات المراد حفظها صغيرة نسبيا ولا تتغير كثيرا مثل البيانات التي تتوقع من مدير الموقع إدخالها بعد تنصيب الإضافة مباشرة (خيارات الإضافة) والتي نادرا ما تتغير. في هذه الطريقة يتم حفظ بيانات الإضافة في جدول wp_options والذي يستخدمه ووردبريس لحفظ خياراته الخاصة. 2-استخدام البيانات الوصفية الخاصة بالمقالات Post Meta وهذه الطريقة مناسبة في حالة البيانات المتعلقة بمقال أو صفحة مفردة (مثلا مزاج الكاتب عند كتابة المقال) 3-استخدام الفئات المخصّصة وهذه مناسبة لتصنيف البيانات مثل تصنيف المقالات أو المستخدمين أو التعليقات. 4- إنشاء جدول جديد في قاعدة البيانات هذه الطريقة مناسبة مع البيانات التي لا يصلح معها أي نوع من الأنواع الثلاثة السابقة والتي تتوقع أن تتزايد مع الزمن والتي لا يمكن حصرها في اسم محدد. الاستفادة من الواجهات البرمجية API's التي يوفرها ووردبريس يحتوي ووردبريس على مجموعة ضخمة من الدّوال والأصناف Classes المفيدة التي تمثل بوّابات تربطك بقلب ووردبريس WordPress Core بالإضافة إلى توفيرها لدوال مفيدة في كثيرا من الأمور البرمجية العامة في مجال الويب، وهذه أمثلة لبعض الواجهات البرمجية التي يوفرها ووربريس Plugin API: تتيح لك الدّوال المستخدمة في الاستفادة من ميكانيكية الخطافات وكذلك الدّوال المساعدة لإنشاء الخطافات الخاصة بك. Database API : واجهة برمجية تسهل التعامل مع قاعدة بيانات ووردبريس. Rewrite API: تتيح لك إمكانية تغير صيغة روابط المقالات والموقع عموما والتحكم فيها. Filesystem API: للتعامل مع نظام الملفات في نظام التشغيل. وغيرها الكثير من الواجهات البرمجية ويمكنك الإطلاع عليها من هنا أمور عامة عليك مراعاتها عند برمجة الإضافة 1- الاهتمام بأمن الشيفرة Code Security كما هو الحال عند برمجة أي سكربت من خلال لغة php عليك مراعاة أن يكون خاليا من الثغرات التي قد تعرض الموقع لخطر الاختراق، نفس الأمر ينطبق على إضافات ووردبريس فعليك الاهتمام بإغلاق الثّغرات المعروفة وحماية المدخلات وكذلك ملفات الإضافة بمنع الوصول المباشر لها عن طريق استخدام الشيفرة التالية مثلا defined( 'ABSPATH' ) or die( 'ممنوع الوصول المباشر' ); هنالك أشياء أخرى يجب مراعاتها خصوصا إذا أردت نشر إضافتك في مستودع إضافات ووردبريس، بعض هذه الأمور إجباري وتحتاج لعمله حتى يتم قبول إضافتك ونشرها في المستودع، من هذه الأمور: 2- ملف اقرأني Readme File هو ملف تقوم بتضمينه في مجلد الإضافة باسم readme.txt ويستخدمه مستودع الإضافات لأخذ بعض المعلومات بالإضافة للمعلومات التي تظهر في صفحة الإضافة في المستودع، يمكنك استخدام هذا المُوّلد لإنشائه 3- جعل الإضافة قابلة للترجمة من الأمور الأخرى التي عليك مراعاتها هو جعل الإضافة قابلة للترجمة لعدة لغات localized حيث يمكنك استخدام مكتبة gettext التي يوفرها ووردبريس والتي تجعل من عملية جعل الكلمات قابلة للترجمة أمر سهل، وبعد ذلك يتم إرفاق ملفات اللغة مع الإضافة، ويمكنك قراءة المزيد عن هذا الأمر من هنا 4- استخدام معايير تطوير وودبريس معايير التّطويرCoding Standard هي بعض القواعد العامة التي عليك مراعاتها عند كتابة الشيفرة لتجنب أخطاء التشفير Coding وتسهيل عملية كتابة الشيفرات ومراجعتها وقراءتها خصوصا في حالة تعاون أكثر من شخص في برمجة شيء ما. لووردبريس قواعده الخاصة أيضا والتي يحاول من خلالها المحافظة على قواعد ثابتة في شيفرات ووردبريس وكذلك شيفرات الإضافات والقوالب لذلك يفضل أن تلتزم بهذه القواعد عند كتابة شيفرات الإضافة الخاصة بك. يمكنك قراءة المزيد عن هذه القواعد من هنا. 5- تحديث الإضافة يستخدم مستودع إضافات ووردبريس نظام Subversion لتحديث ولمعرفة آلية القيام بذلك، زر هذه الصّفحة كانت هذه كانت مقدمة سريعة ونبذة مختصرة حاولنا أن نجعلها كمدخل لبرمجة إضافات ووردبريس وما زال هنالك المزيد لمعرفته عن برمجة إضافات ووردبريس في مقالات قادمة.1 نقطة
-
تعاملنا حتّى الآن مع نوعٍ واحد من مكوّنات JavaScript المعدّلة في Angular، وهو المتحكّم. المتحكّمات هي نوعٌ مخصّص من المكوّنات، وكذلك سنجد المرشحات والتوجيهات، التي سنغطّيها قريبًا. تدعم Angular مكوّنًا غير مخصّص، يُسمّى بالخدمات، تعريف الخدمات في Angular فضفاضٌ نوعًا ما، ولكنّ السّمة المميّزة لها هي عدم اقترانها مباشرةً بالقالب، مما يفترض قرابةً بينها وبين نمط طبقة الخدمة في الهيكليّة التّقليديّة للمشاريع. تتضمّن وحدة القلب ng في Angular عددًا من الخدمات المدمجة، ونذكر الخدمات location$ وlog$ وq$ وwindow$ على سبيل المثال، وسنستكشف الخدمة http$ في الفصل الأخير من هذه السلسلة، فصل HTTP. في تطبيقٍ نموذجيٍّ ستحتاج إلى تعريف خدماتك الخاصّة لأيّ سلوكٍ مشترك بين مكوّنات JavaScript المخصّصة التي تنشئها، مثل المتحكّمات. يُمكن للخدمات أن تكون متابعةً للحالة (stateful) إن احتجت إلى ذلك، فمثلًا، إن أردت مكانًا لتخزين نقاط اللاعب في لعبةٍ ما، يُمكنك إنشاء خدمة score لتتمكّن من جلب وعرض النّقاط الحاليّة في عدّة أماكن ضمن تطبيقك. جميع الخدمات متفرّدة (singletons) أي أنه لا يوجد غير نسخةٍ واحدةٍ من خدمةٍ معيّنة طوال دورة حياة تطبيق Angular. الخدمة في Angular يُمكن أن تكون كائنًا، تابعًا، أو حتّى قيمةً أوّليّة (كالأعداد مثلًا)، أنت من يحدّد ذلك. في الواقع، هذا الفصل لن يشرح إلا القليل من الأمثلة عن الخدمات، وذلك لأنّ الخدمات يمكن أن تحوي أيّ شيفرة من شيفرات JavaScript العاديّة. ما سيُغطّيه هذا الفصل هو كيفيّة تسجيل (register) الخدمات في Angular. نقوم بذلك عن طريق توابع معرّفة بواسطة الخدمة provide$، ومغلّفة بواسطة angular.Module. لنتمكّن من تسجيل الخدمة نحتاج إلى مرجعٍ (reference) إلى وحدةٍ ما. لنقُم بتعريف وحدةٍ جذريّة لتطبيقنا الآن. angular.module('app', []);كما تمّ الشّرح في فصل الوحدات، نقوم بتحميل وحدة الجذر في تطبيقنا عن طريق تمرير اسمها إلى التّوجيه ng-app ضمن مستند HTML. <body ng-app="app"> <!-- الأمثلة توضع هنا --> </body>جيّد، أصبحت وحدتنا جاهزة. تُقدّم الخدمة provide$ خمس توابع مختلفةً للقيام بتسجيل الخدمة، يُمكننا تصنيف هذه التّوابع إلى صنفين، أوّلهما سهلٌ جدًّا والثّاني أصعبُ قليلًا. إنشاء خدمات دون استخدام حقن التبعيةقد لا تحتاج إلى حقن أيّ تبعيّاتٍ إلى خدمتك. (قُمنا بتغطية حقن التّبعيّة في الفصل الماضي. وكمثالٍ على تبعيّةٍ محقونة، فالخدمة المدمجة http$ يُمكن لخدمتك أن تستخدمها لجلب بياناتٍ من النّهاية الخلفيّة (backend) البعيدة.) ولكن حتّى لو لم تكن تريد حقن تبعيّاتٍ في خدمتك، ستحتاج إلى وضعها ضمن وحدةٍ لتجعلها متاحةً للحقن ضمن مكوِّناتٍ أخرى. إنّ جعل الخدمة متاحةً للحقن هو ما يجعلها خدمةً في Angular، وإلّا ستكون مجرّد شيفرة JavaScript عاديّة. هناك تابعان خدميّان يسمحان بتسجيل الخدمات من دون تبعيّات: التّابع value والتّابع constant. الفرق بينهما في Angular معقّدٌ قليلًا ويخُصّ المحترفين، فـالثّوابت تكون متاحًةً لتطبيق Angular أثناء الـbootstrapping، أما القِيمة فلا. لن نقوم بتغطية constant في هذه السلسلة. التابع valueلنفترض أنّنا نريد كتابة شيفرةٍ للعبة حيث يحصل فيها اللاعب على نقاطٍ تبدأ من الصفر. يمكننا باستخدام التّابع value من الخدمة provide$ أن نقوم بتسجيل (register) قيمةٍ عدديّةٍ أوّليّة باسم 'score' وستكون متاحةً للمتحكّمات، الخدمات، ومكوّناتٍ أخرى. angular.module('app') .value('score', 0);يُمكننا حقن خدمتنا score عن طريق وضعها ضمن قائمة الوسطاء للمتحكّم، وقد شرحنا ذلك بالتّفصيل في فصل حقن التّبعيّة. angular.module('app') .controller('ScoreController', function($scope, score) { $scope.score = score; $scope.increment = function() { $scope.score++; }; });يستخدم قالب هذا المثال نُسختين من المتحكّم ScoreController لإثبات الطّبيعيّة المتفرّدة (singleton) للخدمة score. <p ng-controller="ScoreController"> Score: {{score}} </p> <p ng-controller="ScoreController"> <button ng-click="increment()">Increment</button> </p>أعتذر، لقد تعمّدت إفشال المثال السّابق، فعند النّقر على زرّ Increment لن يتمّ تغيير قيمة المتغيّر score المعروضة في النّسخة الأولى من المتحكّم. هذه ليست مشكلة مجالات، ولكنّها بسبب الطّبيعة الثّابتة (immutable) للخدمات التي تمثّل قيمًا أوّليّة. يُمكننا إصلاح المثال السّابق بتغيير الخدمة من قيمةٍ أوّليّة إلى كائنٍ يحوي العنصر points. angular.module('app') .value('score', {points: 0});سنستخدم نفس جسم المتحكّم تقريبًا، إلّا أنّنا سنغيّر ++scope.score$ إلى ++scope.score.points$. angular.module('app') .controller('ScoreController', function($scope, score) { $scope.score = score; $scope.increment = function() { $scope.score.points++; }; });بطريقةٍ مماثلة، سنغيّر في القالب العبارة score إلى score.points. <p ng-controller="ScoreController"> Score: {{score.points}} </p> <p ng-controller="ScoreController"> <button ng-click="increment()">Increment</button> </p>لقد استخدمنا للتّو خدمةً تشارك بياناتٍ متغيّرة (mutable) ضمن تطبيقنا، هذا رائع. إضافةً إلى القيمة الأوّليّة والكائنات، يمكن لخدمات Angular أن تمثّل توابعًا. لنفترض أنّنا بحاجةٍ إلى خدمةٍ ثابتةٍ (stateless) تُعيد عدًدا عشوائيًّا بين 1 و 10. angular.module('app') .value('randomScore', function() { return Math.ceil(Math.random() * 10); });إنّ حقن هذه الخدمة أمرٌ سهل، سنقوم ببساطةٍ بإضافة اسمها randomScore إلى قائمة الوسطاء في متحكّمنا. angular.module('app') .controller('ScoreController', function($scope, score, randomScore) { $scope.score = score; $scope.increment = function() { $scope.score.points += randomScore(); }; });لقد تعلّمنا الآن كيفيّة تعريف الخدمات وحقنها ضمن المتحكّم كتبعيّات. ولكن ماذا لو أردنا حقن تبعيّاتٍ إلى خدماتنا؟ إنشاء خدمات باستخدام حقن التبعيةلعبتنا تبدأ دومًا بنقاطٍ قيمتها صفر، هذا منطقيٌّ، ولكن لنفترض أننا نريد أن تكون القيمة البدائيّة عددًا عشوائيًّا. كيف يمكننا جلب مرجعٍ للخدمة randomScore عندما ننشئ الكائن score للمرّة الأولى؟ إنّ التّابع value الذي كُنّا نستخدمه بسيط جدًّا، فأيّ شيءٍ نقوم بتمريره إليه سيكون هو القيمة أو التّابع أو الكائن الكامل والنّهائي الذي ستقوم Angular بحقنه لاحقًا، وهذا يعني بأنّنا لن نحظى بأيّ فرصةٍ لحقن أيّ تبعيّاتٍ لاحقًا. تُقدّم Angular عدّة حلول لهذه المشكلة، وسنبدأ بالحلّ الأوّل، التّابع service كائنيُّ التّوجّه. التابع serviceتدعم JavaScript أسلوب البرمجة كائنيّة التّوجّه، ولذلك يمكننا كتابة خدمة في Angular تقبل التّبعيّات عن طريق الحقن بواسطة الباني (constructor injection). كُلّ ما نحتاج إليه هو كتابة التّابع الباني لخدمتنا score، بدلّا من تهيئتها بقيمةٍ بدائيّة عن طريق مهيِّئ الكائن ({}) الذي استخدمناه قبل قليل. يجب أن يكون أوّل حرفٍ من اسم الباني في JavaScript حرفًا كبيرًا، ولذلك سنُسمّي الباني بالاسم Score، وسنقوم بوضع الخدمة randomScore ضمن قائمة الوسطاء لهذا الباني. function Score(randomScore) { this.points = randomScore(); }التّابع service يحتاج إلى تمرير الباني الخاصّ بالخدمة بدلًا من تمرير الخدمة ذاتها، وعندما تقوم Angular باستدعاء الباني عن طريق العمليّة new ستقوم آليّة حقن التّابعيّة بإسناد الخدمة randomScore إلى وسيط الباني. angular.module('app') .service('score', Score);توصف طريقة إنشاء نُسخة الخدمة في Angular بأنّها كسولة، أي أنّ النّسخة ستُنشأ فقط عندما تُشكِّل تبعيّةً لأحد المكوّنات التي يتمّ إخراجها في القالب. التابع factoryإن كانت خدمتك شيئًا آخر غير الكائن، أو إن أردت الحصول على مرونةٍ أكبر من طريقة إنشاء بانٍ للكائن، عندها يُمكنك استخدام التّابع factory بدلًا من service، حيث نقوم بتمرير تابع استدعاءٍ خلفيٍّ (callback) وسيتمّ حقنه لاحقًا بالتّبعيّات التي نكتب أسماءها في قائمة الوسطاء. يُمكنك كتابة ما تشاء داخل هذا التّابع، ولكن يجب عليك في النّهاية أن تعيد قيمةً، تابعًا أو كائنًا يُمثّل الخدمة. يُطلق على هذا النّوع من الإنشاء عن طريق الاستدعاء الخلفيّ في المرجع الرّسميّ اسمُ الوصفة(recipe). angular.module('app') .factory('score', function(randomScore) { return {points: randomScore()}; });المثال السّابق مكافئٌ للمثال الذي يستخدم تركيبة الباني مع التّابع service الذي رأيناه في الفقرة الماضية. التابع decoratorإن كنت تريد تعديل خدمةٍ موجودةٍ سابقًا، يُمكنك استخدام التّابع decorator الّذي تُوفّره الخدمة provide$. هذا التّابع ليس مُغلّفًا باستخدام angular.Module كحال بقيّة التّوابع، لذا يجب أن يتمّ استدعاؤه مباشرةً من الخدمة provide$. يُمكنك الحصول على مرجعٍ للخدمة provide$ عن طريق تسجيل تابع استدعاءٍ خلفيٍّ (callback) باستخدام التّابع config. angular.module('app') .config(function($provide) { $provide.decorator('score', function($delegate) { $delegate.points = 1000000; return $delegate; }); });يُمكن لتابع decorator أن يقوم بإعادة الخدمة الأصليّة التي تمّ تمريها ضمن الوسيط ذي علامات الاقتباس ' ' أو أن يقوم بإعادة نُسخةٍ (instance) من خدمةٍ جديدةٍ كُلّيًّا. ولكن عليك بالحذر من التّأثيرات الجانبيّة غير المرغوبة. التغليف (Encapsulation)في الفقرة السّابقة أسأنا استخدام التّابع decorator، ونتيجةً لذلك تمّ تقييد الوصول إلى العنصر points. لحسن الحظّ، يُقدّم التّغليف الخاصّ بحقن التّابع factory مجالًا مُغلقًا (closure)، ممّا يسمح لنا بإخفاء بعض المعلومات، أو بعبارةٍ أخرى، التّغليف (encapsulation). سنقوم باستبدال العنصر points المرئيّ في المجال العام بالمتغيّر المحلّي points المحدود الرؤية ضمن التّابع الّذي يغلّفه، وهذا سيسمح لنا بحمايته من التّعديل خارج التّابع. والآن سنقوم بجعل كائن الخدمة يكشف عن تابعٍ للوصول إلى قيمة المتغيّر، getPoints، وعن تابعٍ يقيَّد التعديل فيه فقط، increment. angular.module('app') .factory('score', function(randomScore) { var points = randomScore(); return { increment: function() { return ++points; }, getPoints: function() { return points; } }; });سنحتاج إلى تغيير بسيطٍ في المتحكّم كي نسمح له باستدعاء التّابع increment من الخدمة. angular.module('app') .controller('ScoreController', function($scope, score) { $scope.score = score; $scope.increment = function() { $scope.score.increment(); }; });وسنغيّر أيضًا القالب، ليكون مرتبطًا بتابع الوصول ()score.getPoints بدلًا من الوصول إلى العنصر points مباشرةً. <p ng-controller="ScoreController"> Score: {{score.getPoints()}} </p> <p ng-controller="ScoreController"> <button ng-click="increment()">Increment</button> </p>بما أنّ التابع increment يقوم أيضًا بإعادة قيمة المتغيّر points بعد التّعديل، إذًا بإمكاننا الكشف (expose) عنها ضمن العرض (view) في عبارة. قد تُفاجئك النّتيجة، قُم باستبدال الاستدعاء في السّطر الثاني، وضع ()score.increment بدلًا من ()score.getPoints، ثُمّ قُم بنقر الزّر عدّة مرّات. هل يُمكنك معرفة سبب زيادة القيمة بسرعةٍ كبيرة؟ هذا صحيح: تقوم Angular غالبًا باستدعاء العناصر والتّوابع المرتبطة بالقالب عدّة مرّات قبل أن تنتهي دورة الإخراج. هذه معلومةٌ هامّة يجب معرفتها لنفهم التّأثير الجانبيّ لها كالمثال السّابق، وأيضًا لتحسين كفاءة التّطبيق. خاتمةوصلنا إلى ختام الفصول الثّلاثة التي تتحدّث عن دعم Angular للبرمجة باستخدام الوحدات. بدأنا مع فصل الوحدات، وتابعنا مع الفصل القصير عن حقن التّبعيّة، وختمنا هذه الثّلاثيّة بهذا الفصل عن الخدمات. قد تستغرب إفراد الخدمات بفصلٍ مستقلٍّ عندما ترى أنّها ليست سوى JavaScript المعتادة ليس إلّا، ولكنّنا لم نقم بتغطيةٍ كاملة لما يجب معرفته، فإضافةً إلى التّوابع constant وvalue و service وfactory و decorator لا يزال هناك تابعٌ منخفض المستوى هو التّابع provider الذي يقدّم تعلّقات دورة الحياة (lifecycle hooks) لإعداد خدماتك بطريقةٍ متقدّمة. إن كنت تتساءل فيما إذا كنت تحتاج إلى مساعدة Angular في هذا المجال بالفعل، فلتبقِ في ذهنك بأنّك لست مضطرًّا لإدارة كلّ شيفراتك باستخدام Angular، ورغم ذلك فإنّ Angular 2.0 ستنتقل إلى نظام وحدات ES6 كما أشار العرض التّقديمي "RIP angular.module" الّذي قُدّم في خطاب فريق التّطوير في ng-europe 2014. وإلى أن يتمّ ذلك فإنّك تحتاج بالفعل إلى أن تضع شيفراتك داخل وحدات Angular عندما تحتاج إلى استخدام مكوّناتٍ مدمجة في Angular (أو مكوّناتٍ طوّرها طرفٌ ثالث) يتمُّ الوصول إليها عن طريق حقن التّبعيّة. لقد قمتُ بعرض بديلٍ لحقن التّبعيّة في نهاية الفصل الماضي. فقط أبقِ في ذهنك أنّ دعم اختبار الوحدة (unit testing support) في Angular قد عزّز من أهمّيّة استخدام حقن التّبعيّة، لذا عليك أن تهتمّ باستخدامك له أيضًا. هناك أيضًا بعض المكوِّنات المخصّصة التي يجب عليك تسجيلها باستخدام نظام الوحدات في Angular، لتجعلها متوفّرة ضمن القالب. أحد هذه المكوّنات هو المُرشّحات، وسنُغطّيها في الفصل القادم. ترجمة وبتصرّف للفصل الثامن من كتاب: Angular Basics لصاحبه: Chris Smith.1 نقطة
-
تدير Angular الموارد باستخدام نظام الوحدات، ولهذا السّبب فهي تحتاج أيضًا إلى تقديم طريقةٍ للوصول إلى هذه الموارد المنظّمة ضمن حاويات (container-managed)، حيث تقوم Angular بذلك باستخدام نمطٍ إنشائيّ يُدعى حقن التبعية. إن لم يكن لديك معرفةٌ مسبقةٌ بهذا المفهوم فسأقوم هنا بأفضل ما لديّ لتلخيصه لك: لنفترض أنّك تحتاج إلى الحصول على مَوردٍ (resource) من Angular، المَورد هو الحاوية. للحصول عليه يجب أن تكتب تابعًا يُعرّف وسيطًا له نفس اسم هذا المَورد بالضّبط. تقوم الحاوية باكتشاف التّابع الذي كتبته، وتتعرّف على توافق الوسيط مع المَورد المطلوب، فتقوم بإنشاء نُسخةٍ (instance) من هذا المَورد (أو تقوم بأخذ النُّسخة المنفردة (singleton) من هذا المَورد، إن كان من النّوع المنفرد) ثُمّ تقوم باستدعاء التّابع الخاص بك وجعل هذه النّسخة هي قيمة الوسيط الخاص بالتابع. بما أنّ الحاوية هنا تلعب الدّور الفعّال (على عكس الطلب الذي تقوم به للحصول على نسخة من المَورد)، لذلك يُعتبر حقن التّابعيّة عكسًا للتّحكّم (inversion of control)، ويُقدّم على أنّه تطبيق لمبدأ Hollywood، "لا تكلّمنا، نحن سوف نكلِّمُك". أعتقد بأنّ مخترعي Angular قد اختاروا طريقة حقن التّابعيّة بسبب تاريخ Google الطّويل مع منصّة Java، بيئة برمجة ساكنة يقوم فيها حقنُ التّابعيّة بدورٍ فعّال في عمليّات تغيير الأنواع أثناء وقت التّشغيل. (لقد تخطّت Google الحدود في ذلك إلى درجة أنّها أنشأت إطار عمل Java خاصٍّ بها، Guice، الذي يعتمد على حقن التّابعيّة كُلّيًّا.) أمّا في JavaScript، وهي البيئة المرنة والديناميكيّة، حيث يمكن أن تتغيّر الأنواع أثناء وقت التّشغيل، فقد حظِيَ فيها أسلوب محدّد موضع الخدمة (service locator) البسيط والمباشَر بشعبيّة هائلة عن طريق الواجهة البرمجيّة لـCommonJS، وهي عبارة require المستخدمة في Node.js وbrowserify. والأكثر من ذلك، أنّ الطريقة البدائيّة في القيام بحقن التّابعيّة قد تبيّن أنّ لها نقطة ضعفٍ عند استخدامها في JavaScript من طرف العميل، وذلك بسبب تعديل أسماء الوُسطاء باستخدام المُقصّرات (minifiers). ولنستطيع التّعامل مع هذه المشكلة، من الضّروريّ أن توجد أداة بناءٍ بديلة، أو أن نتعلّم ونستخدم حواشي التّبعيّات، وسنصل إلى ذلك بعد قليل، ولكن لنتعرّف أوّلًا على النّموذج البدائيّ. التبعيات الضمنيةلن نستخدم خارج هذا الفصل للقيام بحقن التّبعيّة غير طريقة التّبعيّات الضّمنيّة لأنّها الأسهل بين جميع الطُّرق. كما ذكرت سابقًا، يمكننا جلب مرجعٍ لأحد الموارد المتاحة عن طريق وضعه كوسيطٍ في تابعنا الإنشائيّ، ولقد قمنا بذلك للمرّة الأولى عندما أضفنا الوسيط scope$ الذي يعطي تابع التّحكّم مرجعًا إلى كائن المجال المنظّمٍ ضمن حاوية (container-managed). يقوم نظام حقن التّبعيّة في Angular بالعثور على نسخةٍ للمَورد المطلوب (أو بإنشاء نسخةٍ لهذا المَورد) وتمريره إلى المتحكّم. لنرى مرّةً أخرى كيف نقوم بذلك على المَورد المدمج في Angular، الخدمة locale$. نحتاج إلى تعريف وحدتنا app لأنّ بعض أمثلة هذا الفصل تستخدم نظام الوحدات. angular.module('app', []);وبعد ذلك سنقوم بتحميل الوحدة عن طريق تمرير اسمها إلى التّوجيه ng-app. (أكرّر التّنبيه إلى أنّ الاسم app ليس إجباريًّا ويمكن استخدام أيّ اسم آخر.) <body ng-app="app"> <!-- الأمثلة توضع هنا --> </body>نحن الآن جاهزون للانطلاق مع مثالنا الأوّل، وبطريقةٍ مماثلةٍ لإضافة الوسيط scope$ سنضيف الوسيط locale$، وستلاحظ Angular هذين الوسيطين وتقوم بحقنهما. تقوم Angular بحقن الموارد داخل التّابع لأنّ هذا التّابع يمثّلُ متحكّمًا. angular.module('app') .controller('LocaleController', function($scope, $locale) { $scope.locale = $locale.id; });داخل جسم المتحكّم قمنا ببساطةٍ بإسناد المتغيّر locale الذي يمكننا تغيير اسمه إلى أيّ اسمٍ آخر، إلى العنصر id من الوسيط locale$ الذي لا يمكننا استخدام اسمٍ آخر له. <p ng-controller="LocaleController"> Your locale is <strong ng-bind="locale"></strong> </p>الناتج: Your locale is en-usيُفترض أن ترى شيئًا مماثلًا لعبارة "Your locale is en-us" في المخرجات. ما الذي سيحدث لو أنّك أضفت الوسيط myResource إلى تابع التّحكّم؟ هل عطّلت عمل المثال؟ هل يمكنك قراءة الخطأ في الـconsole الخاصة بـJavaScript في متصفّحك؟ تعرض أخطاء Angular غالبًا رابطا مفيدا، يمكنك عن طريق هذا الرابط أن تحصل على درسٍ رائع وقصير عن حلّ مشاكل حقن التّبعيّة، انقر على الرّابط السّابق واقرأ الشرح إن أردت. الحواشي (Annotations)تمثّلُ الحاشية في لغة برمجةٍ مثل Java، معلوماتٍ إضافيّة تُضاف إلى الشيفرة المصدريّة ويتجاهلها المترجم. أمّا في سياق الحديث عن حقن التّبعيّة في Angular، فتعني كلمة الحاشية التّصريح عن الموارد التي نريد حقنها بطريقةٍ صريحةٍ تحافظ على أسماء الوسطاء قصيرةً باستخدام أسلوب التّصغير (minification). (استخدام أداة البناء UglifyJS مثالٌ على أسلوب التّصغير.) لنتابع الآن طريقة Angular التي تسمح لنا فيها بإضافة الحواشي إلى التّبعيّات. ng-annotateمن الطرق الملائمة للتخلص من إزعاج مهمّة بناء ما، استخدام مهمّة بناءٍ أخرى. ستقوم أداة البناء ng-annotate بإضافة الحواشي للتّبعيّات، ومن ثمّ ستسمح لنا بمواصلة استخدام طريقة التّبعيّات الضّمنيّة التي شرحناها في الفقرة السّابقة. ستحتاج فقط إلى تشغيل ng-annotate قبل أن تُشغّل المقصّر (minifier) الخاصّ بك. هذه الأداة المركزيّة صُمّمت لتُستخدم عن طريق سطر الأوامر، إلّا أنّه يوجد غلافٌ لها لتفادي التّعامل مع سطر الأوامر، كما يوجد غلافٌ لمعظم أدوات البناء وحزم الأدوات، من ضمنها grunt-ng-annotate وgulp-ng-annotate وbrowserify-ngannotate وng-annotate-webpack-plugin. الحاشية السطريةهناك أسلوبان لكتابة الحواشي، وأكثرهما شُهرةً يُعرف باسم الحاشية السّطريّة (inline annotation)، وهي تتضمّن وضع تابع التّحكّم داخل مصفوفةٍ كما يلي. angular.module('app') .controller('LocaleController', ['$scope', '$locale', function(s, l) { s.locale = l.id; }]);الناتج: Your locale is en-usفي المثال السّابق، قُمت بمحاكاة تأثير التّقصير عن طريق تغيير أسماء الوُسطاء من scope$ و locale$ إلى s وl بالترتيب. بالطّبع لا يمكن لـAngular أن تجد مَوردين لهما الأسماء s وl لو اعتمدنا على أسلوب إضافة الحواشي الضّمنيّ، ولكن هنا، ولأنّ المصفوفة تحوي أسماء الوسطاء الحقيقيّين ستتمكّن من حقنهم بنجاح. إضافة الحاشية بواسطة inject$إن كان قد انتابك شعورٌ بأن طريقة إضافة الحواشي السّطريّة، قبيحة، فالأرجح أنك ستجد الطريقة البديلة هذه أكثر قبحًا. فهي تعتمد على إنشاء عنصرٍ ذا اسمٍ مخصّص، inject$، في التّابع الإنشائيّ، وهذا سبب تسمية الطّريقة بهذا الاسم. var LocaleController = function(s, l) { s.locale = l.id; }; LocaleController['$inject'] = ['$scope', '$locale']; angular.module('app') .controller('LocaleController', LocaleController);الناتج: Your locale is en-usالمهمّ أنّك الآن تعرف الخيارات المتاحة للقيام بإضافة الحواشي. angular.injectorأرجو أن تكون عند هذه النّقطة قد قمت بإضافة ng-annotate إلى بُنيتك، لتتمكّن من استخدام الأسلوب البدائيّ في التّبعيّات الضّمنيّة التي ستراها في هذه السلسلة دون مشاكل. على أيّ حال، إن لم تكن قد قمت بذلك، لديّ اقتراح لك، إلّا أنّه ليس اقتراحًا جادًّا: قم بكتابة تابع require الخاصّ بك، وأفصح عن التّبعيّات الّتي تريد ضمن عمليّة البحث المباشر عن الخدمات، وذلك بدلًا من استخدام أسلوب حقن التّبعيّة. يمكنك القيام بذلك عن طريق استدعاء تابع angular.injector مباشرةً، وعليك تمرير وسيط وحيدٍ إليه هو مصفوفةٌ فيها أسماء الوحدات التي ترغب بالبحث ضمنها، ويجب عليك تمرير اسم الوحدة ng دومًا وجعلها في بداية القائمة. var require = function(name) { return angular.injector(['ng','app']).get(name); }; angular.module('app') .controller('LocaleController', ['$scope', function($scope) { var $locale = require('$locale'); $scope.locale = $locale.id; }]);الناتج: Your locale is en-usلاحظ أنّ المثال لايزال يعتمد على حقن التّبعيّة في تمرير كائن المجال الصّحيح للمتحكّم، سأسعى في الفقرة القادمة لأبيّن لك أحد أهمّ المشاكل العمليّة مع الحواشي في الأنظمة الضّخمة، وذلك خشية أن يتبادر إلى ذهنك بأنّ الاستثناء الّذي واجهناه للتّو يلغي الحاجة إلى تبنّي طريقة محدّد موضع الخدمة. مخاطرالمثال التّالي سيستبق الأحداث، ويأخذنا إلى الفصل القادم، الخدمات. سنستخدم في هذا المثال التّابع factory، ورغم أنّك لم تألف استخدامه من قبل إلّا أنّه بإمكانك تخمين وظيفته، فهو يقوم بإعداد مصنعٍ للمكوّن باستخدام نظام الوحدات. لاحظ أيضًا أنّ الخدمة http$ (التي سنناقشها في فصلٍ لاحق HTTP) ستكون خدمةً نموذجيّةً ومثالًا من الحياة العمليّة لاستخدام حقن التّبعيّة داخل خدماتٍ كهذه. angular.module('app') .factory('fullPrice', ['$http', function($http) { return function() { // Use $http to fetch remote data. return 100; } }]) .factory('discountPrice', ['$http', function($http) { return function() { // Use $http to fetch remote data. return 40; } }]);استلهمتُ المثال التّالي من تطبيق Angular حقيقيّ، حيث استُبدلت التّبعيّات بقائمةً طويلةً من الخدمات المدمجة في Angular، ولكنّ طول القائمة هو نفسه تقريبًا. angular.module('app') .controller('PriceController', ['$scope', '$anchorScroll', '$animate', '$cacheFactory', '$compile', '$controller', '$document', '$exceptionHandler', '$filter', '$http', '$httpBackend', '$interpolate', '$interval', 'fullPrice', 'discountPrice', '$locale', '$location', '$log', '$parse', '$q', '$rootElement', '$rootScope', '$sce', '$sceDelegate', '$templateCache', '$timeout', '$window', function( $scope, $anchorScroll, $animate, $cacheFactory, $compile, $controller, $document, $exceptionHandler, $filter, $http, $httpBackend, $interpolate, $interval, discountPrice, fullPrice, $locale, $location, $log, $parse, $q, $rootElement, $rootScope, $sce, $sceDelegate, $templateCache, $timeout, $window) { $scope.fullPrice = fullPrice(); $scope.discountPrice = discountPrice(); }]);من الواضح أنني اختصرت جسم المتحكّم PriceController وكذلك اختصرت شيفرة العرض التّالية. <table ng-controller="PriceController"> <tr> <td>Full price:</td> <td>{{fullPrice}}</td> </tr> <tr> <td>Discount price:</td> <td>{{discountPrice}}</td> </tr> </table>الناتج: Full price: 40 Discount price: 100لقد تعمّدت القيام بخطأٍ في المثال السابق، فالمفترض أن يكون السعر الكلّي أكبر دومًا من السّعر المخفّض، ولكنّهما انعكسا لسببٍ ما، والمشكلة ليست في شيفرة العرض أو في جسم المتحكّم، هل يمكنك إيجادها؟ ماذا تستنتج من ذلك بخصوص كتابة حاشية التّبعيّات يدويًّا في المشاريع الحقيقيّة؟ خاتمةهذا هو الفصل الثّاني من أصل ثلاثة فصول في هذه السلسلة تغطّي البنية التّحتيّة لـAngular الخاصّة بإدارة شيفرة التّطبيق. الفصل الأوّل، الوحدات، بيّنَ كيف سنقوم بتهيئة خشبة المسرح الّتي سيقف عليها الممثّلون ليقوم كلٌّ منهم بدوره. وفي هذا الفصل شرحنا ببعض التّفصيل الآليّات المتاحة لجعل كلِّ ممثّلٍ يقفُ في مكانه الصّحيح. أمّا الفصل الثّالث ضمن هذا المجال، الخدمات، سيشرح كيفيّة إنشاء الممثّلين أنفسهم. ترجمة وبتصرّف للفصل السابع من كتاب: Angular Basics لصاحبه: Chris Smith.1 نقطة
