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

محمد فواز عرابي

الأعضاء
  • المساهمات

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

  • تاريخ آخر زيارة

  • عدد الأيام التي تصدر بها

    14

كل منشورات العضو محمد فواز عرابي

  1. في عالم اليوم، لم يعد بالإمكان قصر اهتمامنا على الهواتف المحمولة والحواسيب، فلدينا أجهزة لوحية وأخرى "تُرتدى" كالسّاعات والنّظارات الذكيّة. سيكون موضوعنا اليوم عن التصميم لمختلف أنواع الأجهزة. فهرس سلسلة مدخل إلى تجربة المستخدم: مدخل إلى تجربة المستخدم User Experience فهم ودراسة المستخدمين في مجال تجربة المستخدم دراسة الشريحة المستهدفة في مجال تجربة المستخدم كيفية التصميم للأجهزة المختلفة (هذا الدرس) هندسة المعلومات في تجربة المستخدم تعرف على أنماط التصميم في مجال تجربة المستخدم أشياء لا يمكن اعتبارها رسوما تخطيطية (Wireframes) في مجال تجربة المستخدم تعرف على الرسوم التخطيطية (Wireframes) في مجال تجربة المستخدم مفهوم الثقل المرئي (Visual Weight) والألوان في مجال تجربة المستخدم التكرار ومخالفة الأنماط في مجال تجربة المستخدم المحاذاة والقرب في مجال تجربة المستخدم تعرف على أساليب مسح الواجهة والتراتب المرئي في مجال تجربة المستخدم أساليب الإطلاع في مجال تجربة المستخدم: التصفح، البحث والاكتشاف تصميم هيكل صفحة الويب والعناصر الأساسية في مجال تجربة المستخدم الأزرار، النماذج والدعوات إلى الإجراء في مجال تجربة المستخدم استخدام علم النفس في مجال تجربة المستخدم لتكييف المستخدم وإقناعه كيف تغير الخبرة من تجربة المستخدم؟ تصميم تجربة المستخدم من خلال بيانات وإحصائيات المستخدمين تعرف على أنواع المخططات الإحصائية في مجال تجربة المستخدم اختبارات أ/ب (A/B Test) في مجال تجربة المستخدم الخطوة الأولى: كيف سيكون التعامل مع الواجهة؟ باللمس بالإصبع أم بمؤشّر الفأرة؟ الخطوة الثانية: ابدأ بالأجهزة الصغيرة يعتقد البعض أن عبارة "mobile first" الشّائعة تأتي من صعود شعبيّة الهواتف الذّكية، وهذا جزء من الحقيقة، أمّا الجزء الآخر فهو قائم على أن التّصميم للأجهزة الصّغيرة محدودة القدرات يُجبر المصمّم على التّركيز على المحتوى والوظيفة الأساسيّة للمشروع، مؤدّيًا بدوره إلى تطبيقات بسيطة وجميلة؛ أمّا العكس (أي البدء بالأجهزة القويّة) فهو أشبه بإقحام قطّ في قفص عصفور، أمر ليس بسيطًا ولا جميلًا! الخطوة الثالثة: ما الإمكانيات المميزة لهذا الجهاز؟ تتنقّل الهواتف الذّكية معنا طيلة اليوم، وهذا يعني أنّنا نقضي وقتًا طويلًا في استخدامها، وأن باستطاعتنا استخدام الموقع في تطبيقاتنا، كما أنّها أجهزة صغيرة الحجم ويسهل نقلها، أمّا الحواسيب المحمولة فهي أقل سهولةً في النّقل ولكنّها أكثر قدرةً، وشاشاتها أكبر حجمًا، وفيها لوحة مفاتيح مُريحة، ومؤشّر يسمح بتحديد أكثر دقّة ووظائف أكثر. لا تُصرَّ كثيرًا على فكرة "وحدة الواجهة" بين الأجهزة المختلفة، بل فكّر بأسلوب مختلف لكلّ جهاز. الخطوة الرابعة: لا تنس البيئة التي يعمل فيها التطبيق هناك اختلاف في الخطوط العامّة لتجربة المستخدم بين Mac OS X وWindows، وكذلك يختلف Windows Vista عن Windows 8، وiOS 7 عن iOS 6، وقد تُضطّر لاختيار إصدارات محدودة لاستهدافها، وأخرى تتجاهلها، ففي كلّ مرّة توفّر تطبيقك لإصدار جديد، يتضاعف جهد التصّميم والتّطوير والصّيانة في المستقبل. كن بعيد النّظر! الخطوة الخامسة: كن مستجيبا هل ستوفّر تطبيقك على الويب؟ هل يدعم بضعة أنواع من الهواتف فقط؟ كيف سيعمل على الأجهزة القادمة؟ كل الأجهزة تستطيع التّواصل مع الإنترنت اليوم، لذا احرص على أن باستطاعة تطبيقك التلاؤم مع مختلف الأجهزة التي قد يرغب مُستخدمو تطبيقك باستعمالها. الخطوة السادسة: فكر بأكثر من شاشة واحدة في الوقت نفسه قد يكون هذا الموضوع متقدّمًا، ولكنّ بإمكانك بشيء من الجهد تحقيقه. هل يمكن استخدام هاتفك وحاسوبك سويّة كما يمكن التّحكم بالتّلفاز عن بُعد؟ هل يمكن لمجموعة من الهواتف أن تتحكّم بلعبة على حاسوب لوحيّ في غرفة واحدة؟ وإذا كنت تستخدم جهازين في وقت واحد، فهل يمكن نقل البيانات بينهما؟ ماذا عن مزامنة البيانات؟ هل ستؤدّي إلى مشاكل في الاستعمال؟ فكّر في الأمر! سنتعرّف في الدّرس القادم على أنماط التّصميم، وهي مجموعة من الأساليب الشّائعة لحلّ المشكلات المُتكّررة في تصميم تجربة المُستخدم. ترجمة بتصرّف للدرس Designing For Devices من سلسلة Daily UX Crash Course لصاحبها Joel Marsh. اقرأ أيضًا النسخة العربية الكاملة لكتاب مدخل إلى تجربة المستخدم (User Experience - UX) 1.0.0 كيف تعيد تصميم موقع إلكتروني قائم بالشكل الصحيح عشرة أمور أساسية يجب أخذها في الحسبان لدى تصميم النوافذ المنبثقة عشر مبادئ رئيسية لتصميم تجربة مستخدم على الهواتف الذكية التصميم للهواتف: التصميم البصري
  2. لمصمّمي تجربة المُستخدم شريحة مُستهدفة من المستخدمين ذات مواصفات مُحدّدة، تمامًا كخبراء التسويق، تُبنى هذه المواصفات على الأبحاث كذلك، إلّا أن التّشابه بين الأمرين ينتهي هنا. فهرس سلسلة مدخل إلى تجربة المستخدم: مدخل إلى تجربة المستخدم User Experience فهم ودراسة المستخدمين في مجال تجربة المستخدم دراسة الشريحة المستهدفة في مجال تجربة المستخدم (هذا الدرس) كيفية التصميم للأجهزة المختلفة هندسة المعلومات في تجربة المستخدم تعرف على أنماط التصميم في مجال تجربة المستخدم أشياء لا يمكن اعتبارها رسوما تخطيطية (Wireframes) في مجال تجربة المستخدم تعرف على الرسوم التخطيطية (Wireframes) في مجال تجربة المستخدم مفهوم الثقل المرئي (Visual Weight) والألوان في مجال تجربة المستخدم التكرار ومخالفة الأنماط في مجال تجربة المستخدم المحاذاة والقرب في مجال تجربة المستخدم تعرف على أساليب مسح الواجهة والتراتب المرئي في مجال تجربة المستخدم أساليب الإطلاع في مجال تجربة المستخدم: التصفح، البحث والاكتشاف تصميم هيكل صفحة الويب والعناصر الأساسية في مجال تجربة المستخدم الأزرار، النماذج والدعوات إلى الإجراء في مجال تجربة المستخدم استخدام علم النفس في مجال تجربة المستخدم لتكييف المستخدم وإقناعه كيف تغير الخبرة من تجربة المستخدم؟ تصميم تجربة المستخدم من خلال بيانات وإحصائيات المستخدمين تعرف على أنواع المخططات الإحصائية في مجال تجربة المستخدم اختبارات أ/ب (A/B Test) في مجال تجربة المستخدم تحديد الشريحة المستهدفة لكي نفهم الشّخصيّة المُستهدفة (نُسمّيها Persona أو User Profile)، يجب أن نُدرك أنّها لا تعني أيًّا ممّا يلي: أنماط الشّخصيّة الصفات الديموغرافية الشخصيات الظاهرة في "قصّة علامتك التّجاريّة" الآراء المُسبقة المبنيّة على خبرتك الشّخصيّة المفاهيم التّنبّؤات إذًا ما المقصود بالشّخصيّة المُستهدفة؟ المقصود هو أهداف المُستخدمين الحقيقيّة، وتوقّعاتهم، ودوافعهم، وسلوكهم: لماذا يزورون موقعك؟ ما الذي يبحثون عنه؟ ما الّذي يُزعجهم فيه؟ وهلمّ جرًّا... يُفترض أن تكون كلّ المعلومات الّتي تحتاجها مُستقاة من الأبحاث والبيانات الّتي جمعتها، فإنّ لم يكن لمعلومات ما يدعمها من البيانات والأبحاث، فإنّ استنتاجاتك مُختلقَة، وعليك أن تتوقّف! مواصفات شخصية غير مفيدة الشّخصيّة أ تُمثَّل أنثى بين 35-45 من العمر، متوسّطة الدخل والتّعليم، لديها طفل واحد على الأقل ولديها سيّارة جديدة واحدة على الأقل، تحبّ الاختلاط بالنّاس والعمل، وتميل إلى تغليب النّصف الأيمن من دماغها. لماذا لا تُعتبر هذه المواصفات مفيدة؟ قد تكون مفيدة إن كنت تبيع الإعلانات، ولكنّها غير مُجدية لنا كمصمّمي تجربة المُستخدمين، لأنّها لا تسمح لنا باستبعاد أيّة أفكار أو ميّزات في المشروع، إذ أنّنا لا نعلم ما الميّزات الّتي تحتاجها أنثى عمرها 35-45! فقد تكون أيّ شيء! مواصفات شخصية مفيدة الشّخصيّة أ تُمثّل مُديرًا ذا خبرة، مهتمًّا في الغالب في مجال أو اثنين، يزور الموقع بصورة متكرّرة، ولكنّ وقته ضيّق، لذا يحاول جمع المحتوى الّذي يرغب في قراءته في أيّام العطلات، ويميل إلى مشاركة أكثر ما يقرأه على الشّبكات الاجتماعيّة (غالبًا تويتر وLinkedIn)، ويعتبر نفسه من قادة الفكر، ويعتني بصورته أمام المجتمع بالغ العناية. لما تُعتبر هذه المواصفات مُفيدةً؟ لأنّ لدينا الآن معلومات كثيرة يمكن استخدامها، فنحن نعلم أن المحتوى السّخيف لن يكون شائعًا، وأن تجميع المحتوى سيكون مُهمًّا، ونحتاج لآليّة لتنصيف المحتوى في فئات، كما أنّه من الضّروريّ أن تكون عمليّة المشاركة على الشبكات الاجتماعيّة سهلة وسريعة. يمكن أيضًا تجنّب عمل حملات دعائيّة على فيسبوك، لأنّ معظم المستخدمين لا يزورونه، وسيكون إرسال رسائل بالبريد الإلكتروني تتضمّن مُختارات من المحتوى أفضل من إرسال إشعارات مُتكرّرة لأنّ المُستخدمين غير متفرّغين. فكر في المستخدمين "المثاليين" عندما تفكّر في ميّزات المشروع، احرص على وضع صورة المُستخدم المثاليّ نُصب عينيك، فمهمّتك ليست تكريس السّلوك الحاليّ للمستخدم، بل دفعهم نحو هذه الصّورة المثاليّة. تذكّر أن النّاس يختلفون، وسيكون لديك مجموعات ذات سلوكيّات مختلفة، فاحرص على تمثيل كلّ مجموعة بشخصيّة مُناسبة. ترجمة بتصرّف للدرس Creating User Profiles من سلسلة Daily UX Crash Course لصاحبها Joel Marsh. اقرأ أيضًا النسخة العربية الكاملة من كتاب مدخل إلى تجربة المستخدم (User Experience - UX) 1.0.0 كيف تصبح مرشحًا مميزًا لوظيفة تجربة المستخدم تلك، الجزء الأول
  3. لكتابة تنسيقات CSS يمكن قراءتها بسهولة بإمكانك إضافة مسافات فارغة وتعليقات في ورقة الأنماط لجعلها أسهل قراءة. بالإمكان أيضًا جمع المُحدّدات معًا، عندما تريد تطبيق نفس القاعدة على أكثر من هدف. هذا الدرس جزء من سلسلة تعلم CSS: مدخل إلى أوراق الأنماط المتتالية (CSS). آلية عمل تعليمات CSS داخل المتصفحات. المحددات (Selectors) في CSS. كيفية كتابة تعليمات CSS يسهل قراءتها. (هذا الدرس) تنسيق نصوص صفحات الويب باستخدام CSS. التعامل مع الألوان في CSS. إضافة محتوى إلى صفحة ويب باستخدام CSS. تنسيق القوائم (Lists) في CSS. تعرف على الصناديق (Boxes) في CSS. رصف العناصر (Layout) في CSS. الجداول (Tables) في CSS. التعامل مع أجهزة العرض المختلفة والمطبوعات في CSS. المسافات الفارغة (Whitespace) مصطلح مسافة فارغة (whitespace) يعني المسافة المعروفة وعلامات الجدولة (Tab) ومحرف السّطر الجديد. بإمكانك استخدام هذه المحارف لتسهيل قراءة ورقة الأنماط. لاحظ أن مصطلح المسافة الفارغة قد يستخدم في سياق تخطيط الواجهات للإشارة إلى الجزء من الصفحة الّذي يُترك دون عناصر: كالحواف (margins) والحشوات (gutters) والمسافات بين الأعمدة والسّطور. الملف الّذي تتدرّب عليه يحوي قاعدة واحدة في كلّ سطر، وأقل ما يمكن من المسافات الفارغة، وعندما تصبح ورقة الأنماط أكبر، فإنّ هذا الأسلوب سيجعل قراءة الورقة (ومن ثمّ صيانتها) أصعب. لك كامل الحرّيّة في اختيار أسلوب تنسيق الورقة الّذي ترغب فيه، ولكن قد يكون لبعض المشاريع الّتي يعمل عليها أكثر من شخص أسلوب مُتّفق عليه. أمثلة يُفضّل بعض المُصمّمين أسلوبًا وجيزًا في الكتابة، بحيث لا يفصلون السّطر إلا عندما يصبح طويًلا للغاية: .carrot {color: orange; text-decoration: underline; font-style: italic;} ويُفضّل آخرون جعل كل زوج من الخواص وقيمها في سطر منفرد: .carrot { color: orange; text-decoration: underline; font-style: italic; } وبعضهم يحاذي هذه السطور بمسافتين أو أربع أو بمحرف Tab: .carrot { color: orange; text-decoration: underline; font-style: italic; } والبعض يحبّ محاذاة كلّ شيء شاقوليًّا (ولكن هذا صعب الصّيانة): .carrot { color : orange; text-decoration : underline; font-style : italic; } والبعض يفضّل استخدام عدد مختلف من المسافات في كلّ مرّة لتسهيل القراءة: .vegetable { color: green; min-height: 5px; min-width: 5px } .vegetable.carrot { color: orange; height: 90px; width: 10px } .vegetable.spinach { color: darkgreen; height: 30px; width: 30px } البعض يستخدم محارف Tab، والبعض يستخدم المسافات فقط. التعليقات (Comments) تبدأ التعليقات في CSS بـ‎/*‎ وتنتهي بـ‎*/‎. بإمكانك استخدام التعليقات للشرح أو توضيح جزء معيّن في ورقة الأنماط، وأيضًا لحجب (comment out) أجزاء من الورقة مؤقّتًا بغاية تجريب التغيّرات. لحجب جزء من الورقة، أحِط هذا الجزء بتعليق بحيث يتجاهله المتصفّح. توخَّ الحذر في موضعي بداية التّعليق ونهايته، إذ يجب أن تكون بقيّة الورقة سليمة الصّياغة: /* style for initial letter C in first paragraph */ .carrot { color: orange; text-decoration: underline; font-style: italic; } تجميع المحددات عندما تتشارك عدّة عناصر بقاعدة واحدة، يمكن تحديد مجموعة من المحدّدات مفصولة بفاصلة لاتينية ","، وعندها فإنّ القاعدة ستُطبّق على كلّ العناصر المعنيّة. لتطبيق قواعد لكلّ عنصر من العناصر السّابقة على حدة، يمكن تعريف قاعدة منفصلة في موضع آخر من ورقة الأنماط تستهدف العناصر المنفردة. مثال تجعل هذه القاعدة لون <h1> و<h2> و<h3> واحدًا. وهذا يجعل من السّهل تغيير اللون من مكان واحد لكلّ العناصر مرّة واحدة: /* color for headings */ h1, h2, h3 {color: navy;} تمرين: إضافة التعليقات وتحسين هيئة ورقة الأنماط حرّر ملف CSS الّذي تتدرّب عليه، وتأكد من احتوائه على هذه القواعد (بغضّ النظر عن الترتيب): strong {color: red;} .carrot {color: orange;} .spinach {color: green;} #first {font-style: italic;} p {color: blue;} اجعل قراءته أسهل بإعادة ترتيبه بطريقة تراها منطقيّة، وبإضافة مسافات فارغة وتعليقات في أماكن مناسبة. احفظ الملفّ وحدّث الصّفحة في المتصفّح لتتأكّد من أن تعديلاتك لم تؤثّر على النّتيجة: تمرين احجب جزءًا من ورقة أنماطك، دون تعديل أيّ شيء آخر، بحيث تجعل الحرف الأوّل من المستند أحمر اللّون: (هناك أكثر من طريقة لتحقيق هذه النّتيجة) شاهد الحل إحدى طرق فعل ذلك هي احتواء قاعدة .carrot بتعليق: /* .carrot { color: orange; } */ ما التالي؟ استخدمنا تنسيقي الخطّ المائل والمُسطّر في ورقة أنماطنا. سنتعرّف على كيفية إضافة تنسيقات أخرى للنّصوص في الدرس القادم: تنسيق نصوص صفحات الويب باستخدام CSS ترجمة -وبتصرف- للمقال Readable CSS من سلسلة Getting started with CSS على شبكة مطوّري Mozilla.
  4. في هذا الدرس من سلسلة تعلّم CSS، سنتطرق إلى كيفيّة تطبيق الأنماط على العناصر بطريقة انتقائية، وكيف يكون للمحدّدات المختلفة درجات مختلفة من الأولويّة. سنضيف بعض الخواص (attributes) إلى المستند ثم نستخدمها في ورقة الأنماط. فهرس السلسلة: مدخل إلى أوراق الأنماط المتتالية (CSS). آلية عمل تعليمات CSS داخل المتصفحات. المحددات (Selectors) في CSS. (هذا الدرس) كيفية كتابة تعليمات CSS يسهل قراءتها. تنسيق نصوص صفحات الويب باستخدام CSS. التعامل مع الألوان في CSS. إضافة محتوى إلى صفحة ويب باستخدام CSS. تنسيق القوائم (Lists) في CSS. تعرف على الصناديق (Boxes) في CSS. رصف العناصر (Layout) في CSS. الجداول (Tables) في CSS. التعامل مع أجهزة العرض المختلفة والمطبوعات في CSS. ما هي المحددات؟ لـCSS مجموعة من المصطلحات الخاصة الّتي تصف اللّغة. كنّا قد أنشأنا سطرًا في ورقة الأنماط محتواه: strong { color: red; } وفي مصطلحات CSS نقول عن هذا السّطر أنّه قاعدة (rule). تبدأ هذه القاعدة بالكلمة strong والّتي نُسمّيها المحدِّد (selector)، لأنّه يحدّد العناصر في DOM الّتي تُطبّق عليها القاعدة المعنيّة. تفاصيل أكثر نُسمّي الجزء ضمن القوسين من القاعدة التّصريح (declaration). الكلمة color تُسمّى خاصّة (property)، وقيمتها (value) هي red. تفصل الفاصلة اللاتينية المنقوطة ; بين أزواجٍ من القيم والخواص ضمن التّصريح ذاته. سنطلق على المُحدّد strong في هذه السلسلة وصف محدّد لوسم (tag selector)، إلّا أنّه في مصطلحات CSS يُسمّى محدّد لنوع (type selector). يُفصِّل هذا الدرس من السلسلة المحدّدات التي يمكن استخدامها في قواعد CSS، فبالإضافة إلى أسماء الوسوم، يمكن استخدام قيم الخواص (attributes) في المُحدّدات، وهذا ما يسمح لقواعد CSS بتحديد أكثر انتقائيّة للعناصر. للخاصّتين class وid وضع خاصّ في CSS. محددات الأصناف (Class selectors) استخدم الخاصّة class في HTML لإسناد صنف إلى العنصر، بإمكانك اختيار أي اسم للصنف، ويمكن للصّنف الواحد أن يُسند إلى عدّة عناصر في الصّفحة. لاستخدام محدّد الصنف في CSS اكتب اسم الصّنف مسبوقًا بنقطة (.). محددات المعرفات (ID selectors) استخدم الخاصّة id في عنصر لإسناد مُعرّف إلى هذا العنصر، بإمكانك اختيار أي اسم للمُعرّف، ولكن ينبغي أن لا يكون لعنصرين في الصّفحة المُعرّف ذاته. لاستخدام مُحدّد المعرّف في CSS، اكتب اسم المُعرّف مسبوقًا برمز #. مثال: لوسم HTML التالي خاصّتا الصّنف والمعرّف معًا: <p class="key" id="principal"> قيمة الخاصّة id، وهي principal يجب أن تكون فريدة لا تتكرّر في كامل المستند، ولكن يمكن استخدام الصّنف key لأي عدد من العناصر. القاعدة التالية في CSS تجعل كل العناصر في المستند خضراء اللون، حتى وإن تكن جميعها عناصر <p>: .key { color: green; } القاعدة التالية تجعل العنصر الوحيد ذا المعرّف principal عريض الخطّ: #principal { font-weight: bolder; } محددات الخواص (Attribute Selectors) لا تقتصر CSS على الخاصتين class وid، بل يمكنك تحديد خواصّ أخرى باستخدام الأقواس المربّعة []. ضع اسم الخاصة ضمن القوسين وأتبعه بعامل ثم قيمة (غير إلزاميّين). يمكن أيضًا جعل المطابقة غير حسّاسة لحالة الحروف اللّاتينية بإضافة "i" بعد القيمة، ولكن هذه الميزة غير مدعومة في معظم المتصفّحات. أمثلة: ‏‎[disabled]‎: تحدّد كل العناصر التي تملك الخاصّة "disabled". ‏‎[type='button']‎: تحدد كل العناصر من النوع "button". ‏‎[class~=key]‎: تحدّد العناص ذات الصّنف "key" بالضّبط وليس أي كلمة تحوي ضمنها "key" مثل "keyed" أو "monkey". تطابق من حيث الوظيفة المُحدّد .key. ‏‎:[lang|=es]‎ تحدّد العناصر المحدّدة على أنّها باللّغة الإسبانية. هذا يتضمن العناصر "es" و"es-MX" ولكن ليس "eu-ES". ‏‎[title*="example" i]‎ يحدّد العناصر التي يحوي عنوانها "example"، متجاهلًا حالة الحروف اللاتينيّة، قد لا يطابق هذا العنصر أي شيء في المتصفّحات الّتى لا تدعم الإشارة "i". ‏‎a[href^="https://"]‎ يحدّد الرّوابط الآمنة. ‏‎img[src$=".png"]‎ يحدّد الصور بصيغة PNG بطريقة غير مباشرة، إلّا أنه لا يحدد الصور بصيغة PNG إن كانت روابطها لا تنتهي بالحروف ".png". محددات الأصناف الزائفة (pseudo-classes) تعني كلمة "الصّنف الزائف" في CSS حالة خاصّة للعناصر التي يُراد تحديدها، فمثلًا إضافة ‎:hover‎ إلى المحدّد يؤدّي إلى تطبيق القاعدة عندما يُمرّر المستخدم مؤشّر الفأرة فوق العنصر الذي يستهدفه المُحدّد. تسمح الأصناف الزائفة (والعناصر الزائفة) بتطبيق تنسيق على عنصر عندما يخضع لعوامل خارجيّة كتاريخ التّصفّح (‎:visited‎ مثلًا)، أو حالة محتواه (مثل ‎:checked‎)، أو موضع الفأرة (مثل ‎:hover‎)، يمكنك مراجعة القائمة الكاملة للمحدّدات على صفحة توصيف مُحدّدات CSS3. الصيغة selector:pseudo-class { property: value; } قائمة بالأصناف الزائفة :link :visited :active :hover :focus :first-child :last-child :nth-child :nth-last-child :nth-of-type :first-of-type :last-of-type :empty :target :checked :enabled :disabled عمق التحديد (Specificity) قد يكون لعدّة قواعد مُحدّدات تستهدف العنصر نفسه، فإن كانت الخاصّة غير مكرّرة ضمن هذه القواعد، لن يحدث تعارض، وستُعيّن هذه الخاصة على العنصر، أما إن كانت الخاصة واردة في أكثر من قاعدة، فإن CSS تطبّق الخاصّة الأكثر عمقًا في التّحديد، فالمُحدّد الّذي يستهدف العنصر بُمعرّفه أكثر عمقًا من مُحدّدات الصّنف والأصناف الزائفة والخواصّ الأخرى، وهذه بدورها أكثر عمقًا من مُحدّدات الوسوم والعناصر الزائفة. تفاصيل أكثر يمكنك أيضًا جمع المُحدّدات، مُنشِئًا مُحدّدًا أكثر عمقًا، فمثلًا يحدّد key. كل العناصر من الصّنف key، والمُحدّد p.key يحدّد فقط العناصر <p> ذات الصّنف key. إن تساوت المُحدّدات بدرجة العمق، كانت الأولوية للقاعدة الّتي ترد آخرًا في ورقة الأنماط. التحديد تبعا للعلاقات بين العناصر يمكن في CSS تحديد العناصر بناءً على علاقتها بعناصر أخرى، ويمكن بذلك إنشاء مُحدِّدات أكثر عمقًا. المحددات الشائع استخدامها بناء على علاقة العناصر المُحدّد يُحدِّد A E أي عنصر E يكون من سلالة A (أي ابنه أو ابن ابنه...) A > E أي عنصر E يكون ابنًا مباشرًا للعنصر A E:first-child أي عنصر E يكون أوّل ابن لأبيه B + E أي عنصر E يكون أول أخٍ لعنصر B (أي الابن التالي لنفس الأب) يمكن أيضًا جمع أيّ من هذه المُحدّدات للتّعبير عن علاقات أكثر تعقيدًا. يمكن استخدام الرّمز * للإشارة إلى "أي عنصر". مثال لجدول HTML التالي خاصّة id، ولكن ليس لسطوره وخلاياه أيّة مُعرّفات: <table id="data-table-1"> ... <tr> <td>Prefix</td> <td>0001</td> <td>default</td> </tr> ... القواعد التالية تجعل الخليّة الأولى في كل سطر عريضة الخطّ، والثّانية ذات خطّ ثابت العرض، ولا تنطبق هذه القواعد إلا على الجدول السّابق المستند: #data-table-1 td:first-child {text-decoration: underline;} #data-table-1 td:first-child + td {text-decoration: line-through;} هذه هي النّتيجة: تفاصيل أكثر في الحالة العاديّة، كلّما زاد عمق المُحدّد، زادت أولويّته، وبهذا يمكنك تجنب إضافة العديد من الأصناف والمعرّفات على أوسم كثيرة في المستند. عندما يصبح حجم المستند ضخمًا وتكون السّرعة مطلوبة، يمكنك تحسين أداء التنسيقات بتجنبّ القواعد المعقدة المبنيّة على العلاقات بين العناصر. لمزيد من الأمثلة عن الجداول، راجع صفحة الجداول في مرجع CSS. تدريب: استخدام محددات الأصناف والمعرفات حرّر ملفّ HTML الّذي تتدرّب عليه، كرّر الفقرة بنسخها ولصقها. أضف الخاصّتين id وclass إلى النّسخة الأولى من الفقرة، وأضف id إلى الثّانية كما في المثال أدناه، أو يمكنك نسخ كامل المثال: <!doctype html> <html> <head> <meta charset="UTF-8"> <title>Sample document</title> <link rel="stylesheet" href="style1.css"> </head> <body> <p id="first"> <strong class="carrot">C</strong>ascading <strong class="spinach">S</strong>tyle <strong class="spinach">S</strong>heets </p> <p id="second"> <strong>C</strong>ascading <strong>S</strong>tyle <strong>S</strong>heets </p> </body> </html> عدّل CSS مُستبدلًا كامل محتوى الملفّ: strong { color: red; } .carrot { color: orange; } .spinach { color: green; } #first { font-style: italic; } احفظ الملفّات وحدّث المتصفّح لترى النّتيجة: بإمكانك تغير ترتيب الأسطر في CSS للتأكد من أن ترتيبها لا يؤثّر على النّتيجة، لأن مُحدّدات الأصناف carrot. و spinach. مُقدَّمَة على مُحدّد الوسم strong، ومُحدّد المُعرّف first# مُقدّم عليها جميعًا. تمرين بدون تعديل ملف HTML، أضف قاعدة وحيدة إلى CSS تبقي كل الحروف الأولى بنفس لونها الحالي، ولكن تجعل الحروف الأخرى في الفقرة الثّانية زرقاء: عدّل القاعدة الّتي أضفتها للتوّ بحيث تجعل الفقرة الأولى زرقاء أيضًا (بدون تعديل أي شيء آخر): شاهد الحل جعل الفقرة الثانية زرقاء اللون أضف قاعدة تستهدف #second وتصريحًا color: blue; كما يلي: #second { color: blue; } يمكن أيضًا استخدام مُحدّد أكثر عُمقًا مثل p#second. جعل الفقرتين زرقاوين غيّر مُحدّد القاعدة الجديدة ليصبح مُحدّد وسم <p>: p { color: blue; } قواعد الألوان الأخرى لها مُحدّدات أكثر عمقًا، لذا فهي تطغى على لون الفقرات الأزرق. مثال عملي: استخدام محددات الأصناف الزائفة أنشئ ملف HTML بالمحتوى التالي: <!doctype html> <html> <head> <meta charset="UTF-8"> <title>Sample document</title> <link rel="stylesheet" href="style1.css"> </head> <body> <p>Go to our <a class="homepage" href="http://www.example.com/" title="Home page">Home page</a>.</p> </body> </html> عدّل ملف CSS مستبدلًا كلّ محتواه: a.homepage:link, a.homepage:visited { padding: 1px 10px 1px 10px; color: #fff; background: #555; border-radius: 3px; border: 1px outset rgba(50,50,50,.5); font-family: georgia, serif; font-size: 14px; font-style: italic; text-decoration: none; } a.homepage:hover, a.homepage:focus, a.homepage:active { background-color: #666; } احفظ الملفّين وحدّث المتصفّح لمشاهدة النّتيجة (ضع مؤشر الفأرة فوق الرّابط لمشاهدة التأثير): Go to our Home page تدريب: استخدام المحددات بناء على العلاقات بين العناصر والأصناف الزائفة بالإمكان الوصول إلى طرق معقدة لاستهداف العناصر بناء على العلاقات بين عناصرها والأصناف الزائفة، تشيع هذه التقنيات مثلًا بهدف إنشاء قوائم مُنسدلة باستخدام CSS فقط دون JavaScript. أساس هذه التقنية يعتمد على قواعد مشابهة لهذه: div.menu-bar ul ul { display: none; } div.menu-bar li:hover > ul { display: block; } بحيث تطبّق على بنية HTML مثل هذه: <div class="menu-bar"> <ul> <li> <a href="example.html">Menu</a> <ul> <li> <a href="example.html">Link</a> </li> <li> <a class="menu-nav" href="example.html">Submenu</a> <ul> <li> <a class="menu-nav" href="example.html">Submenu</a> <ul> <li><a href="example.html">Link</a></li> <li><a href="example.html">Link</a></li> <li><a href="example.html">Link</a></li> <li><a href="example.html">Link</a></li> </ul> </li> <li><a href="example.html">Link</a></li> </ul> </li> </ul> </li> </ul> </div> اطّلع على مثالنا الكامل كمساعدة. ما التالي؟ بدأت ورقة الأنماط تصبح معقّدة ومتشابكة، سنناقش كيف نُبسّطها ونجعلها سهلة القراءة في الدّرس القادم: كيفية كتابة تعليمات CSS يسهل قراءتها. ترجمة -وبتصرّف- للمقال Selectors من سلسلة Getting started with CSS على شبكة مطوّري Mozilla.
  5. المستخدمون، محور عالم تجربة المستخدم، وهمّنا الأوّل والأخير. أحد القوانين المقدّسة في تجربة المستخدم: "لا تلم المستخدم مهما يكن"، على الرغم من أنّ لومه قد يكون مُغريًا أحيانًا. فهرس سلسلة مدخل إلى تجربة المستخدم: مدخل إلى تجربة المستخدم User Experience فهم ودراسة المستخدمين في مجال تجربة المستخدم (هذا الدرس) دراسة الشريحة المستهدفة في مجال تجربة المستخدم كيفية التصميم للأجهزة المختلفة هندسة المعلومات في تجربة المستخدم تعرف على أنماط التصميم في مجال تجربة المستخدم أشياء لا يمكن اعتبارها رسوما تخطيطية (Wireframes) في مجال تجربة المستخدم تعرف على الرسوم التخطيطية (Wireframes) في مجال تجربة المستخدم مفهوم الثقل المرئي (Visual Weight) والألوان في مجال تجربة المستخدم التكرار ومخالفة الأنماط في مجال تجربة المستخدم المحاذاة والقرب في مجال تجربة المستخدم تعرف على أساليب مسح الواجهة والتراتب المرئي في مجال تجربة المستخدم أساليب الإطلاع في مجال تجربة المستخدم: التصفح، البحث والاكتشاف تصميم هيكل صفحة الويب والعناصر الأساسية في مجال تجربة المستخدم الأزرار، النماذج والدعوات إلى الإجراء في مجال تجربة المستخدم استخدام علم النفس في مجال تجربة المستخدم لتكييف المستخدم وإقناعه كيف تغير الخبرة من تجربة المستخدم؟ تصميم تجربة المستخدم من خلال بيانات وإحصائيات المستخدمين تعرف على أنواع المخططات الإحصائية في مجال تجربة المستخدم اختبارات أ/ب (A/B Test) في مجال تجربة المستخدم دراسة المستخدمين (User Research) تختلف الآراء حول ترتيب دراسة المستخدمين في تجربة المستخدم، فالبعض يقول بالبدء بها، والبعض يقول بالدراسة بعد التخطيط، والبعض يقول بعد الوصول إلى نسخة عاملة من المنتج. كل هذا الآراء صائبة. فما من توقيت خاطئ لدراسة المستخدمين. ونصيحتي أن تبدأ بها في المراحل الأولى، وتعيدها مرارًا. السؤال الأهم ليس "متى نقوم بدراسة المستخدمين؟". بل "ما الغرض منها؟". ما الذي تحاول فهمه عن مستخدميك؟ للمعلومات التي يمكن استخلاصها من دراسة المستخدمين نوعان رئيسيّان: موضوعيّ وشخصيّ. دراسة شخصية كلمة "شخصيّ" (subjective) تعني أنّ الأمر متعلّق برأي معيّن، أو بذكرى معيّنة، أو بانطباع معيّن عن شيء ما. المقصود هو الشعور الذي ينتج عن هذا الشيء، والتوقّعات التي يخلقها، وليس الحقيقة المجرّدة. "ما لونك المفضّل؟" "هل تثق بهذه الشّركة؟" "هل أبدو سمينًا بهذا السّروال؟" ما أقصده من هذه الأسئلة هو أنه ما من إجابة "صحيحة" عليها. للقيام بهذا النوع من الدراسة ينبغي على من يقوم بالدراسة طرح أسئلة على المستخدمين. دراسة موضوعية الكلمة "موضوعيّ" (objective) تعني حقيقة الأمر، وهي شيء يمكن إثباته، لا تغيّره الآراء مهما تمنّينا عكس ذلك. "كم أمضيت من الوقت ضمن تطبيقنا؟" "أين وجدت رابط موقعنا؟" "كم مستخدمًا زار موقعنا اليوم؟" لو أن للناس ذاكرة سليمة تمامًا، ولو أنّهم يصدقون دومًا، لكنّا سألنا المستخدمين هذه الأسئلة (لو وجدت شخصًا بهذه المواصفات، أخبرني!) الحقيقة أنّنا نصل إلى المعلومات الموضوعيّة من خلال القياسات والإحصاءات. ولكن إحصاء شيء ما لا يعني مباشرةً الوصول إلى الحقيقية المجرّدة. إليك مثالًا: لو قال 102 من الناس أن شيئًا ما جيّد، وقال 50 أنّه سيّئ، فهذا لا يعني أنّ أحدهما على صواب، الحقيقة الموضوعيّة الوحيدة التي يمكن استخلاصها هنا هي عدد المصوّتين. حجم العينة كقاعدة عامّة، فإن زيادة حجم العينة تؤدّي إلى معلومات أكثر موثوقيّة، وإن كانت شخصيّة، فقد يكون رأي واحد خاطئًا تمامًا، ولكن إن وافقه مليون من الناس فهذا يعني أنّه تمثيل دقيق لمُعتقدات الجمهور (ولكن قد يكون خاطئًا من وجهة نظر موضوعيّة). نصيحتي: اجمع أكبر قدر من المعلومات لبحثك. هل تعني ضخامة المعلومات الشّخصيّة معلومات أقرب ما تكون للموضوعيّة؟ إن طلبت من أناسٍ كثيرين أن يُخمّنوا الجواب لأمر موضوعيّ (مثلًا عدد حبّات الحلوى في علبة) فإن متوسّط التّخمينات سيكون قريبًا جدًّا من الإجابة الحقيقيّة الموضوعيّة. ولكن "حكمة الجماهير" في أمر موضوعيّ قد تؤدّي إلى الفوضى أحيانًا وأحيانًا أخرى إلى انتخاب جورج بوش! لذا كن حذرًا! كيف تطرح الأسئلة على العينة؟ هناك 3 أنواع أساسيّة للأسئلة: أسئلة مفتوحة: "لو طلبت منك أن تصفني، فكيف ستصفني؟" وهذا النّوع يفتح الباب لنطاق واسعٍ من الإجابات، ويكون مناسبًا عندما تريد أكبر قدر من الآراء والتّعليقات. أسئلة تبدأ بافتراضات: "ما أهي أجمل ملامحي؟" وهذا النّوع يحصر الإجابات ضمن فئة محدودة، فالسؤال ذاته يفترض أنّني جميل (وهذا قد لا يكون صحيحًا!). توخَّ الحذر! فقد يحجب هذا النّوع من الأسئلة إجابات قد ترغب بسماعها! أسئلة مغلقة أو مباشرة: "أيّهما أجمل: مرفقي أم ركبتي؟". هذا النّوع يوفّر خيارًا، نعم أو لا، هذا أو ذاك، ولكن تذكّر: إن كانت الخيارات غبيّة، فستكون النّتائج غبيّة كذلك. بعض الأمثلة على الأبحاث الموضوعية مقابلات: اسأل شخصًا ما مجموعة من الأسئلة المتتالية. المُلاحظة: أسنِد مجموعة من المهمّات أو التّعليمات إلى العيّنة وراقب كيف يستخدمون تصميمك دون مساعدة، بعد ذلك يمكنك طرح أسئلتك. مجموعات التّركيز: اجمع بعض النّاس في غرفة واطلب منهم مناقشة أسئلتك. ملاحظة: عادةً ما يُقنع الأفراد الواثقون من أنفسهم غيرهم ممّن في الغرفة برأيهم، وعلى العكس يكون البعض غير أهلٍ للمناقشة، ولذا أفضّل عادةً أن ألقي بنفسي في النّار على أن أختار هذا الأسلوب. الاستبيانات: نموذجٌ تملأه العّينة على الورق أو على الويب، من ميّزاته عشوائيّة العيّنة، وهذا قد يكون مُفيدًا. ترتيب البطاقات: يحصل كل فرد في العيّنة على مجموعة من الأفكار أو الفئات (مكتوبة على بطاقات) ويُطلب منه ترتيبها في مجموعات ذات معنى مُفيد. تُعطي هذه العمليّة بعد إجراء عدّة أشخاص لها فكرة عن التوزيع المرغوب للعناصر في قوائم الواجهة. (ملاحظة: لا تستخدم زملائك في العمل لهذا الغرض، اجلب عيّنة حقيقيّة). Google: يمكنك العثور على آراء مُفيدة للغاية على الويب، مجّانًا وعند الطّلب. ملاحظات هامة اطرح نفس الأسئلة وبنفس الأسلوب على جميع أفراد العّينة. لا تحاول تأويل الأسئلة أو التّلميح بالإجابات. قد يكذب البعض لتجنّب الإحراج أو ظنًّا منهم أنّك تفضّل إجابة معيّنة. سجّل المقابلة، ودوّن ملاحظاتك. لا تعتمد على ذاكرتك مُطلقًا. سنتعلّم في الدرس القادم كيف يمكن تصنيف شريحة المستخدمين المستهدفة في المشروع. ترجمة بتصرّف للدّرسين What is User Research و How to Ask People Questions من سلسلة Daily UX Crash Course لصاحبها Joel Marsh. حقوق الصورة البارزة: Designed by Freepik. اقرأ أيضًا النسخة العربية الكاملة من كتاب مدخل إلى تجربة المستخدم (User Experience - UX) 1.0.0 أفضل خمس طرق اختبار للمستخدم كيف تتصرف في جلسة بحث عن تجربة المستخدم UX
  6. يشرح هذا الجزء من سلسلة تعليم CSS كيف تعمل CSS في المتصفّحات وما الهدف من وجود DOM. سنتعلّم أيضًا كيف نُحلّل مستندًا بسيطًا. فهرس السلسلة: مدخل إلى أوراق الأنماط المتتالية (CSS). آلية عمل تعليمات CSS داخل المتصفحات. (هذا الدرس) المحددات (Selectors) في CSS. كيفية كتابة تعليمات CSS يسهل قراءتها. تنسيق نصوص صفحات الويب باستخدام CSS. التعامل مع الألوان في CSS. إضافة محتوى إلى صفحة ويب باستخدام CSS. تنسيق القوائم (Lists) في CSS. تعرف على الصناديق (Boxes) في CSS. رصف العناصر (Layout) في CSS. الجداول (Tables) في CSS. التعامل مع أجهزة العرض المختلفة والمطبوعات في CSS. كيفية عمل CSS عندما يعرض المتصفّح المستند، فإنّه يجمع محتواه مع معلومات التنسيق، ويُعالج المستند على مرحلتين: يحوّل المتصفّح لغة الرّماز وCSS إلى DOM (اختصارًا لـDocument Object Model). يُمثّل DOM المستند في ذاكرة الحاسوب، ويجمع محتوى المستند مع تنسيقه. يعرض المتصفّح محتويات DOM. تستخدم لغات الرّماز عناصر (elements) لتعريف هيكل المستند. يُحدّد العنصر بوسم (tag)، وهو نص يبدأ بالرّمز ‎<‎ وينتهي بالرّمز ‎>‎. معظم العناصر لها زوجان من الوسوم، وسم في البداية وآخر في النهاية. لكتابة وسم البداية أدرج اسم العنصر بين ‎<>‎، أما وسم النّهاية فيكتب بإضافة ‎/‎ بعد ‎<‎ وقبل اسم العنصر. قد يكون لبعض العناصر وسم بادئ فقط، أو وسم وحيد فيه تأتي ‎/‎ بعد اسم العنصر، وذلك تبعًا للغة الرّماز. قد يكون العنصر أيضًا حاويًا على عناصر آخرى ضمن وسْمَيه الأول والأخير. عليك التأكد من إغلاق الوسوم ضمن العنصر الحاوي. لـDOM بنية تشبه الشجرة، حيث يصبح كل عنصر أو خاصّة أو سلسلة من النصوص في لغة الرماز عقدة (node) في البنية الشجريّة. تُحدّد العُقد بعلاقتها بالعقد الأخرى، فبعض العناصر هي آباء (parent nodes) لعناصر آخرى (children nodes)، وقد يكون للأبناء إخوة (sibling nodes). يُعينك فهم DOM على تصميم وتنقيح وصيانة CSS، لأنّه يمثّل نقطة التقاء CSS مع محتوى المستند. مثال يُنشئ الوسم ‎<p>‎ ونظيره ‎</p>‎ عنصرًا حاويًا: <p> <strong>C</strong>ascading <strong>S</strong>tyle <strong>S</strong>heets </p> في هيكل DOM، تُعتير العقدة P أبًا، أبناؤه هي العقد STRONG والعقد النصيّة، وبالمثل فإنّ العقد STRONG آباء للعقد النّصيّة. ├─STRONG │ └─"C" ├─"ascading" ├─STRONG │ └─"S" ├─"tyle" ├─STRONG │ └─"S" └─"heets" تدريب: تحليل DOM باستخدام أداة فحص DOM لتحليل هيكل DOM، نحتاج إلى برنامج خاصّ، بإمكانك استخدام إضافة DOM Inspector من Mozilla لتحقيق ذلك. ما عليك إلا تثبيت الإضافة (المزيد من التفاصيل أدناه). استخدم متصفح Mozilla لفتح مستند HTML الذي أنشأته. من قائمة المتصفّح اختر: أدوات > DOM Inspector أو أدوات > تطوير الويب > DOM Inspector. تفاصيل أكثر إن لم يحوِ متصفح Mozilla عندك على أداة فحص DOM، فبإمكانك تثبيت الإضافة من موقع الإضافات وإعادة تشغيل المتصفح ثم العودة لهذا الدّرس. إن لم ترغب بتثبيت هذه الإضافة (أو لم تكن تستعمل متصفح من Mozilla)، بإمكانك استخدام Web X-Ray Goggles كما هو مشروح في القسم التالي. بإمكانك أيضًا تجاوز هذا القسم والمتابعة. في أداة فحص DOM، وسّع عقد المستند بالنقر على الأسهم. ملاحظة: قد تؤدي المسافات في ملف HTML إلى عرض عقد نصية فارغة، يمكنك تجاهلها. يبدو جزء من النتيجة مشابهًا لما يلي، تبعًا للعقد الّتي وسّعتها: │ ▼╴P │ │ │ ▼╴STRONG │ │ └#text │ ├╴#text │ ►╴STRONG │ │ عندما تختار أيّة عقدة، بإمكانك استخدام اللوحة على يمين DOM Inspector للاطّلاع على معلومات إضافية عنها، فمثلًا: يعرض DOM Inspector النص في اللوحة على اليمين عندما تحتار عقدة نصيّة. عندما تختار عقدة تمثّل عنصرًا، تُحلّل الأداة هذه العقدة وتقدّم معلومات كثيرة في اللّوحة على اليمين، يكون من ضمنها معلومات التنسيق. تمرين انقر على عقدة من نوع STRONG في أداة فحص DOM، ثم استخدم اللوحة على اليمين لإيجاد الموضع الّذي يُعيّن فيه لون العقدة إلى أحمر، والموضع الّذي يُجعل فيه خطّها أعرض من النصّ العاديّ. شاهد الحل في القائمة فوق اللوحة على اليمين، اختر CSS Rules، سترى عنصرين مُدرجين، أحدهما هو المصدر الداخلي الّذي ي يُعرّف font-width على أنّها bolder، والآخر آتٍ من ورقة الأنماط الّتي تُحرّرها والّذي يُعرّف color على أنّه red. استخدام Web X-Ray Goggles تعرض الأداة Web X-Ray Goggles معلومات أقل من DOM Inspector، ولكنّها أبسط في التثبيت والاستعمال: اذهب إلى الصّفحة الرئيسيّة للأداة. اسحب رابط العلامة المرجعيّة إلى شريط المتصفّح. افتح مستند HTML الّذي أنشأته. فعّل الأداة بالنقر على العلامة المرجعيّة في الشّريط. اسحب مؤشّر الفأرة فوق العناصر المختلفة في المستند. تتالي الأنماط ووراثتها سنتعلّم الآن كيف تتفاعل الأنماط المتتالية، وكيف ترث العناصر الأنماط عن آبائها، وكيف تضيف الأنماط إلى ورقة الأنماط مستفيدًا من فكرة الوراثة بحيث تغيّر تنسيق عدة أجزاء من المستند مرّة واحدة. قد يُحدّد النّمط النّهائي للعنصر في عدّة مواضع، تتفاعل فيما بينها بطريقة مُعقّدة، وهذا التّفاعل المعقّد يُعطي CSS قدراتها المُميّزة، ولكنّه قد يُعقّد الأمور ويجعل تتبّع الأخطاء عمليّة صعبة. تجتمع المصادر الثلاثة الأساسية لمعلومات التنسيق مشكّلة ما نسمّيه تعاقب الأنماط (cascade)، وهذه الأنماط هي: التنسيقات المبدئيّة الّتي يحدّدها المتصفّح للعناصر في لغة الرّماز. التنسيقات الّتي يحدّدها المستخدم الّذي يقرأ المستند. التنسيقات المرتبطة بالمستند الّتي يحدّدها مؤلّفه، والّتي قد تعيّن في ثلاثة مواضع: في ملفّ خارجيّ: وهذا الموضوع الأساسيّ لهذه الدّروس. في تعريف في بداية المستند: للتنسيقات الخاصّة بصفحة واحدة فقط. في عنصر معيّن ضمن متن المستند: وهي الطريقة الأقل سهولة في الصيانة، ويمكن استخدامها على سبيل التّجربة. تُغيّر أنماط المستخدم الأنماط الّتي يعيّنها المتصفّح، ثمّ تغيّر أنماط مؤلّف المستند تلك الّتي عيّنها المستخدم. في هذه السّلسلة أنت مؤلّف المستند، ولن نتعامل إلّا مع هذا النّوع من الأنماط. مثال عندما تقرأ هذا المستند في متصفّح، يأتي جزء من التنسيق الّذي تراه من الإعدادات المبدئيّة لـHTML في المتصفّح. وقد يأتي جزء آخر من الإعدادات المخصّصة للمتصفّح، إذ يمكن تخصيصها في مربع التفضيلات في Firefox مثلًا، أو يمكن تعيينها في ملف userContent.css في ملفّ المتصفّح الشّخصيّ. ويأتي الجزء الأخير من التنسيق من خادوم موقع الأكاديميّة. عندما تفتح المستند الّذي تتدرّب عليه في المتصفّح، تكون العناصر <strong> أعرض خطًّا من بقيّة النّصّ، هذا التنسيق يأتي من إعدادات المتصفّح المبدئيّة. كما أن العناصر نفسها ذات لون أحمر، وهذا التنسيق يأتي من ورقة الأنماط الّتي كتبناها بأنفسنا. ترث عناصر <strong> أيضًا معظم تنسيقها عن عناصر <p> لأنّها أبناؤها، وبالمثل ترث العناصر <p> معظم تنسيق عنصر <body>. يكون للتنسيقات الّتي يحدّدها مؤلّف المستند الأولويّة في تعاقب الأنماط على تلك الّتي يحدّدها القارئ، وتكون الأولويّة الأقل لتنسيقات المتصفّح المبدئيّة. بالنّسبة للتنسيقات الموروثة، يكون للتنسيقات المعرّفة على العقدة ذاتها الأولويّة على تلك الّتي ترثها عن آبائها. هناك قواعد أخرى لهذه الأولويّات، سنتعرّف عليها لاحقًا. تفاصيل أكثر توفّر CSS أيضًا طريقة للقارئ لحجب التنسيق الّذي يحدّده مؤلّف المستند باستخدام الكلمة !important. وهذا يعني أنّه لا يمكن توقّع الشّكل الّذي سيكون عليه المستند لكلّ قارئ. إذا أردت معرفة المزيد عن موضوع التعاقب والوراثة، انظر فقرة Assigning property values, Cascading and inheritance في تعريف CSS. تمرين: استخدام الوراثة حرّر ملفّ CSS الّذي تتدرّب عليه. أضف هذا السّطر بنسخه ولصقه. لا يهمّ إن كان فوق أو تحت السّطر الّذي أضفته من قبل. إلّا أن إضافته فوقه أكثر منطقيّة لأن العنصر <p> أب للعنصر <strong> في مستندك. p {color: blue; text-decoration: underline;} احفظ الملفّ وحدّث متصفّحك لتشاهد تأثير ذلك على المستند. يؤدّي ذلك إلى تسطير النّص في كامل الفقرة، بما في ذلك الحروف الأولى من كلّ كلمة، وهذا يعني أن العناصر <strong> ورثت عن أبيها نمط تسطير النّصّ. ولكّن هذه العناصر ما تزال حمراء اللّون، لأنّ اللون الأحمر هو نمطها الخاص، ولذا يكون له الأولويّة على اللّون الأزرق لأبيها العنصر <p>. قبل <p> <strong>C</strong>ascading <strong>S</strong>tyle <strong>S</strong>heets </p> strong {color:red} بعد <p> <strong>C</strong>ascading <strong>S</strong>tyle <strong>S</strong>heets </p> p {color:blue; text-decoration:underline} strong {color:red} تمرين عدّل ورقة الأنماط بحيث تكون الحروف الأولى فقط مُسطَّرة: شاهد الحل انقل التصريح الذي يُعنى بتسطير النّص من قاعدة <p> إلى <strong>، كما يلي: p {color: blue; } strong {color: orange; text-decoration: underline;} ما التالي؟ سنتعرّف في الدّرس التالي كيف نستهدف العناصر بطريقة أكثر انتقائيّة. ترجمة -وبتصرف- للمقال How CSS works من سلسلة Getting started with CSS على شبكة مطوّري Mozilla.
  7. أوراق الأنماط المتتالية (Cascading Style Sheets أو CSS اختصارًا) هي لغة تُستخدم لوصف كيفيّة عرض المستندات للمستخدمين. تُكتب هذه المستندات في لغة رُماز مثل HTML. المستند هو مجموعة من المعلومات التي تهيكل باستخدام لغة رُماز (markup language). يُقصّد بعرض المستند للمستخدم تحويله إلى صيغة مُفيدة للجمهور، فالمتصفحات مثل Firefox وChrome وInternet Explorer مًصمّمة بحيث تعرض المستندات للمستخدمين مرئيًّا، مثلًا: على شاشة حاسوب أو جهاز إسقاط (projector) أو طابعة. سنتعرض خلال هذه السلسلة إلى المواضيع التالية: مدخل إلى أوراق الأنماط المتتالية (CSS) (هذا الدرس). آلية عمل تعليمات CSS داخل المتصفحات. المحددات (Selectors) في CSS. كيفية كتابة تعليمات CSS يسهل قراءتها. تنسيق نصوص صفحات الويب باستخدام CSS. التعامل مع الألوان في CSS. إضافة محتوى إلى صفحة ويب باستخدام CSS. تنسيق القوائم (Lists) في CSS. تعرف على الصناديق (Boxes) في CSS. رصف العناصر (Layout) في CSS. الجداول (Tables) في CSS. التعامل مع أجهزة العرض المختلفة والمطبوعات في CSS. أمثلة صفحة الويب هذه الّتي تقرأها هي مُستند. تُهيكل المعلومات التي تشاهدها في صفحة ويب باستخدام لغة HTML (اختصارًا لـHyperText Markup Language). مرُبعات الحوار في التطبيقات هي أيضًا مُستندات، وقد تكون مُهيكلة باستخدام لغة رماز مثل XUL (اختصارًا لـXML User Interface Language)، والتي قد تجدها في بعض برامج Mozilla. في هذه السلسلة، ستُقرأ معلومات مُدرجة تحت عنوان تفاصيل أكثر كالفقرة التالية، تحوي هذه الفقرات على معلومات وروابط لمصادر إضافية عن مفهوم أو موضوع تغطّيه هذه الأقسام. اقرأ هذه المعلومات حالما تُشاهدها وتابع الروابط، أو تجاوزها وعُد لقراءتها لاحقًا. تفاصيل أكثر كلمة المستند لا تعني ما تعنيه كلمة "ملفّ"، إلّا أنّه يمكن حفظ المستندات ضمن ملفّات. المستند الذي تقرأه الآن لم يُحفَظ في ملفّ، بل يُجلب من قاعدة بيانات على الخادم والذي بدوره يولّد المستند بجمع أجزائه من عدّة ملفّات. مهما يكن الأمر، فإنّك في هذه السّلسلة ستتعامل مع مستندات محفوظة في ملفّات. بإمكانك إيجاد معلومات أكثر عن المستندات ولغات الرُماز في مواقع أخرى من شبكة مطوّري Mozilla: اللغة الوظيفة HTML لصفحات الويب XML للصفحات المُهيكلة بشكل عامّ SVG للرسوم XUL لواجهات برامج Mozilla سنطّلع على أمثلة من هذه اللغات في الجزء الثّاني من السلسلة. تفاصيل أكثر يُدعى البرنامج الذي يعرض المستندات على المستخدم في المصطلحات الرسميّة لـCSS بوكيل المستخدم (user agent أو UA). المتصفّح ليس إلّا واحدًا من وكلاء المستخدم، فلا تقتصر CSS على المتصفّحات أو العرض المرئيّ، ولكنّنا في الجزء الأوّل من السّلسلة لن نتعامل إلّا مع CSS في المتصفّح. للاطّلاع على التعاريف الرسمية للمصطلحات في CSS، انظر Definitions في وصف CSS من قبل جمعيّة W3C المسؤولة عن تعيين المعايير القياسيّة للويب. لنبدأ العمل: إنشاء مستند أنشئ مجلّدًا جديدًا في جهازك لحفظ وتنظيم تمارين هذه السّلسلة. افتح محرّر النصوص وأنشئ ملفًّا نصيًّا جديدًا، سنقوم بكتابة المستند في هذا الملف خلال التمارين القليلة التالية. انسخ والصق HTML أدناه. احفظ الملف باسم doc1.html. <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Sample document</title> </head> <body> <p> <strong>C</strong>ascading <strong>S</strong>tyle <strong>S</strong>heets </p> </body> </html> افتح علامة تبويب أو نافذة جديدة في متصفّحك، ثم افتح الملف الذي أنشأته. من المُفترض أن ترى النّص وحروفه الأولى بخطّ عريض، كهذه الصّورة: قد يختلف ما تراه في متصفحك قليلًا عن الصّورة من حيث الخط أو الألوان أو المسافات بين الحروف، وهذا غير مهمّ الآن. المستند الآن لا يستخدم CSS، وهذا ما سنفعله في الخطوة التالية. لماذا نستخدم CSS؟ استخدم CSS لتعريف تنسيق مستنداتك، بما في ذلك التصميم والتخطيط والاختلافات في العرض بين الأجهزة المختلفة وقياسات الشاشات المختلفة. بإمكانك إضافة CSS في رأس (<head>) مستند (نُسمّي ذلك ورقة أنماط مُضمّنة) أو إرفاق ملف منفصل يحوي تعريف التنسيقات (ورقة أنماط خارجيّة)، ثمّ ربطها مع المستند برابط يُضاف إلى رأسه. لللأسلوب الأخير عدّة محاسن، ففصل التنسيقات عن الرُماز: يقلّل من التكرار يُسهل الصّيانة يسمح بإجراء تغيير على نطاق الموقع كاملًا من مكان واحد مثال يمكنك مثلًا حفظ معلومات التنسيقات في ملفات مشتركة بين كلّ الصّفحات، فعندما يُربَط مستند بورقة أنماط تُحدّد لون العناوين الثانويّة (h2)، بإمكانك استخدام هذا التنسيق في كل صفحات الموقع بالإشارة إلى هذه الورقة. عندما يعرض مستخدم صفحة ويب، يقوم المتصفح بجلب معلومات التنسيق في الوقت نفسه. عندما يطبع مستخدم صفحة ويب، بإمكانك توفير معلومات تنسيق مختلفة تُسهّل من قراءة الصّفحة المطبوعة. كيف تعمل HTML وCSS معًا؟ بشكل عامّ، استخدم HTML لوصف محتوى الموقع، وليس تنسيقه. بإمكانك استخدام CSS لتحديد تنسيق المستند، وليس محتواه. أحيانًا نضطر لإجراء بعض الاستثناءات لهذه القاعدة، كما سنرى لاحقًا. تفاصيل أكثر توفّر لُغة رُماز مثل HTML بعض الوسائل لوصف تنسيق المستند بشكل منفصل عن CSS، فيمكنك مثلًا في HTML استخدام الوسم <b> لجعل النّص عريضًا، ويمكنك تحديد لون خلفيّة المستند في وسم متن المستند <body>. عندما تستخدم CSS، يُفضّل عادةً تجنّب استخدام هذه الميزات في لغة الرُماز بحيث يبقى وصف تنسيق المستند في موضع واحد فقط. لنعمل: إنشاء ورقة أنماط أنشئ ملفًّا نصيًّا آخر بجوار الملف السابق doc1.html. احفظ الملف باسم style1.css. سيحوي هذا الملف وصف التنسيقات. في ملفّ CSS، انسخ والصق السّطر التالي ثمّ احفظ الملف: strong {color: red;} ربط المستند بورقة الأنماط لربط المستند بورقة الأنماط، عدّل ملفّ HTML، أضف السّطر التالي في موقعه: <link rel="stylesheet" href="style1.css"> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Sample document</title> <link rel="stylesheet" href="style1.css"> </head> <body> <p> <strong>C</strong>ascading <strong>S</strong>tyle <strong>S</strong>heets </p> </body> </html> احفظ الملف وحدّث الصّفحة في متصفّحك. تجعل ورقة الأنماط الحروف الأولى بلون أحمر، كهذا: ما التالي؟ لدينا الآن مستند بسيط مرتبط بورقة أنماط خارجيّة، وسنتعلّم كيف يجمعهما المتصفح معًا لعرض الصّفحة في الجزء التالي: كيف تعمل CSS؟ ترجمة (بتصرّف) للجزأين الأوّلين What is CSS و Why use CSS من سلسلة Getting started with CSS على شبكة مطوّري Mozilla.
  8. لكل شيء تجربة مُستخدم. مهمّتنا ليست خلق تجربة المُستخدم، بل تحسينها. ولكن ما معنى "تحسين" تجربة المُستخدم؟ هذا هو الدرس الأول من سلسلة مدخل إلى تجربة المستخدم: مدخل إلى تجربة المستخدم User Experience (هذا الدرس) فهم ودراسة المستخدمين في مجال تجربة المستخدم دراسة الشريحة المستهدفة في مجال تجربة المستخدم كيفية التصميم للأجهزة المختلفة هندسة المعلومات في تجربة المستخدم تعرف على أنماط التصميم في مجال تجربة المستخدم أشياء لا يمكن اعتبارها رسوما تخطيطية (Wireframes) في مجال تجربة المستخدم تعرف على الرسوم التخطيطية (Wireframes) في مجال تجربة المستخدم مفهوم الثقل المرئي (Visual Weight) والألوان في مجال تجربة المستخدم التكرار ومخالفة الأنماط في مجال تجربة المستخدم المحاذاة والقرب في مجال تجربة المستخدم تعرف على أساليب مسح الواجهة والتراتب المرئي في مجال تجربة المستخدم أساليب الإطلاع في مجال تجربة المستخدم: التصفح، البحث والاكتشاف تصميم هيكل صفحة الويب والعناصر الأساسية في مجال تجربة المستخدم الأزرار، النماذج والدعوات إلى الإجراء في مجال تجربة المستخدم استخدام علم النفس في مجال تجربة المستخدم لتكييف المستخدم وإقناعه كيف تغير الخبرة من تجربة المستخدم؟ تصميم تجربة المستخدم من خلال بيانات وإحصائيات المستخدمين تعرف على أنواع المخططات الإحصائية في مجال تجربة المستخدم اختبارات أ/ب (A/B Test) في مجال تجربة المستخدم يشيع الاعتقاد أن تجربة المُستخدم الجيدة هي تحقيق سعادة المستخدمين؛ وهذا غير دقيق! لو كانت السعادة غايتنا لاكتفينا بصور القطط و عبارات المديح العشوائية وعدنا إلى بيوتنا! للأسف لن يكون مديرك في العمل راضيًا (مع أن الفكرة ليست سيّئة!) هدف مصمّمي تجربة المُستخدم هو الوصول إلى كفاءة المستخدم. تجربة المستخدم ليست سوى قمّة جبل الجليد: يعتقد كثير من الناس أن كلمة UX تعني تجربة المستخدم، ولكنها بالأحرى تعني عملية تصميم تجربة المستخدم. تجربة كل مستخدم على حدة ليست سوى رأيه الشّخصي عن موقعك أو تطبيقك. صحيح أن رأي المستخدم مهم (أحيانًا) ولكن على عاتق مصممي التجربة مسؤوليات أكبر من ذلك. تصميم تجربة المُستخدم: يشمل تصميم تجربة المُستخدم (UXD اختصارًا) إجراءات مشابهة جدًّا لأصول البحث العلمي، فنحن نبدأ بفهم طبيعة المستخدمين، ثم التفكير بتلبية حاجاتهم (وحاجات المشروع)، ثم نبني هذه الحلول ونقيس أداءها على أرض الواقع. تابع معنا لتتعلم الكثير عن تجربة المُستخدم، أو تابع صور القطط إن لم تكن مهتمًّا! ركنا تجربة المستخدم الأساسيان ينبغي عليك عندما تبدأ مشروع تجربة مُستخدم جديدًأ وقبل أن تصمّم أيّ شيء، أن تفهم أهدافك؛ هدفين اثنين على وجه الدقّة. كل شيء تفعله قائم على هذين الهدفين ولا شيء أهمّ منهما لنجاح عملك كمصمّم تجربة المُستخدم: أهداف المستخدمين، وأهداف المشروع. أهداف المستخدمين يريد المستخدم شيئًا ما منك، فهو إنسان، وللإنسان دومًا حاجات. سواء كانت هذه الحاجات هادفة أو لا. أهداف المشروع لكل مؤسسة هدف من وراء الموقع أو التطبيق الذي تبنيه، عادة يكون الهدف المال، ويمكن أن يكون الدعاية للشركة، أو جذب المستخدمين للمجتمع… إلخ. تحديد نوع هذا الهدف أمر مهمّ. فلو كان الهدف عرض إعلانات أكثر، فإنّ سياسة تجربة المُستخدم ستكون مختلفة كلّ الاختلاف عمّا إذا كان الهدف هو بيع المنتجات أو الترويج للمشروع في الإعلام الاجتماعيّ. تُسمّى هذه الأمور "القياسات" (metrics) أو "مؤشرات الأداء الأساسيّة" (KPIs) كما يحلو لرجال الأعمال تسميتها. التنسيق بين الهدفين حسن التنسيق بين الهدفين السابقين هو الامتحان الحقيقي لمصمم تجربة المُستخدم، والمقصود هو كيف تجعل غاية المشروع تتحقّق عندما يحصل المستخدم على ما يريد (وليس العكس!). يجني YouTube أرباحه من الإعلانات، ويريد مستخدموه مشاهدة مقاطع فيديو جيدة، ولذلك فإن وضع الإعلانات في المقطع نفسه (أو في الصفحة نفسها) أمر منطقيّ. ولكن الأمر الأهمّ هو أن تسهيل البحث عن مقاطع الفيديو وإيجاد المقاطع المشابهة سيؤدي إلى زيادة ما يشاهده المستخدم، وهذا بدوره يزيد في أرباح YouTube. لو لم يكن الهدفان مُنسّقين، لاستطاع المستخدمون تلبية حاجتهم دون إفادة المشروع (مستخدمون كثر ولكن بلا نجاح) أو أن الأمر على العكس، أي أن المستخدمين لا يستطيعون تلبية حاجتهم (لا مستخدمين ولا نجاح). لو فرض YouTube إعلانًا مدّته دقيقة على كل نصف دقيقة تشاهدها، لانتهى به الأمر سريعًا نهاية عسيرة، ولكنّ إعلانًا مدّته بضع ثوانٍ هو ثمن قليل تدقعه مقابل مشاهدة دب الباندا وهو يعطس… صحيح؟ المكونات الخمسة لتجربة المستخدم في عملية تصميم تجربة المُستخدم، على المصمم أن يحفظ في ذهنه خمسة أمور طيلة العملية. المكوّنات الخمسة لتجربة المُستخدم: الجانب النفسي، وقابليّة الاستخدام، والتصميم، والجمل الترويجية، والتحليل. بإمكاننا أن نفرد في الحديث سلسلة طويلة لكلّ من هذه الجوانب، ولكنّنا سنبُسِّط الأمور بعض الشيء، فهذه السلسلة موجزة، وليس الغرض منها التعمّق في التفاصيل. أولا: الجانب النفسي عقل المستخدم معقّد، وأنت تعرف ذلك. يتعامل مصمّم تجربة المُستخدم مع ذهنيّة غير موضوعيّة تتحكّم بها المشاعر كثيرًا؛ ولهذه الذّهنيّة تأثير سلبيّ أو إيجابيّ على نتائجك، وعلاوةً على ذلك ينبغي على المصمم تجاهل جانبه النفسيّ الخاص أحيانًا، وهذا أمر عسير. اسأل نفسك: ما الذي يدفع المستخدم ليزور خدمتي في الأساس؟ ما شعوره عندما يفعل ذلك؟ كم من الجهد يبذله ليصل إلى ما يريد؟ ما العادات الّتي تنشأ مع تكرار ذلك مرارًا؟ ما الذي يتوقّعه عندما ينقر على هذا؟ هل تفترض أنّه يعلم شيء وهو لم يتعلّمه بعد؟ هل يريد أن يكرّر هذا الأمر؟ كم مرّة؟ هل تفكّر بحاجات المستخدم ورغباته، أم بحاجاتك ورغباتك؟ كيف تكافئ التّصرّف السّليم؟ ثانيا: قابلية الاستخدام صحيح أن الجانب النفسيّ للمستخدم أمر متعلّق ببواطنه، ولكن قابليّة الاستخدام على العكس من ذلك، وباستطاعتك ملاحظة حيرة المستخدم. أحيانًا تكون صعوبة تنفيذ شيء ما أمرًا ممتعًا (كما في الألعاب)، ولكن الغالب لكل ما سوى الألعاب أن تكون سهولة الإنجاز هي ما نريده. اسأل نفسك: هل يستطيع المستخدم إنجاز العمل المطلوب بأقل قدر من الإدخال؟ هل باستطاعتنا تجنيب المستخدم الوقوع في الخطأ؟ (الجواب: نعم!) هل الأمر واضح ومباشر، أم أنّه غامض؟ هل الأمر سهل إيجاده (وهذا أمر جيّد)، أم صعب تفويته (أفضل)، أم متوقّع دون تفكير (الأفضل)؟ هل يتلاءم تصميمك مع افتراضات المستخدم أم يعاكسها؟ هل وفّرت كل ما ينبغي على المستخدم معرفته؟ هل يمكن إنجاز الأمر نفسه بالجودة نفسها ولكن بطريقة مألوفة أكثر؟ هل تبني قراراتك على منطقك أنت؟ أم على بديهة المستخدم؟ كيف تتأكد؟ إن لم يقرأ المستخدم النصوص المكتوبة بخطّ صغير، هل يبقى الأمر مفهومًا؟ هل يمكن إنجازه؟ ثالثا: التصميم تعريفك لكلمة "التصميم" كمصمم تجربة المُستخدم مختلف بعض الشيء عن المفهوم الفنّي الذي يعرفه المصمّمون. لا يهمّ إن كانت الكلمة تعجبك أم لا. التصميم في تجربة المُستخدم يعني كيف تسير الأمور، وهو شيء يمكن إثباته؛ ولا علاقة له بالأسلوب. اسأل نفسك: هل يعتقد المستخدم أن المنتج جميل؟ هل يثقون فيه فورًا؟ هل يوصل المنتج الهدف والوظيفة دون كلمات؟ هل يمثّل العلامة التجارية؟ هل تنسجم مكوّناته معًا؟ هل يقود التصميم عيني المستخدم إلى المواضع الصحيحة؟ كيف تتأكّد؟ هل تساعد الألوان والأشكال والخطوط المستخدم في إيجاد ما يريده وتزيد من قابلية مُستخدم التفاصيل؟ هل تبدو العناصر الّتي يمكن النقر عليه مختلفة عن تلك الّتي لا يمكن النقر عليها؟ رابعا: الإنشاء/النصوص Copywriting هناك فرق هائل بين الإنشاء الخاص بالعلامة التجارية والإنشاء الخاص بقابليّة الاستخدام. فالأولى تعزّز صورة الشركة، والثانية هدفها إنجاز الأمور بأسرع وأبسط ما يمكن. اسأل نفسك: هل تبدو النّصوص واثقة وتُعلِم المستخدم بما عليه فعله؟ هل تحثّ المستخدم على إتمام هدفه؟ هل هذا ما تريده؟ هل أكبر النصوص هي أهمّها؟ إن كان الجواب لا، فلماذا؟ هل تُعلّم المستخدم أم تفترض أنّه يعلم؟ هل هي واضحة ومباشرة وبسيطة وفعّالة؟ خامسا: التحليل التحليل هو نقطة ضعف معظم المصمّمين في رأيي، ولكن يمكن إصلاح هذا الخلل. التحليل هو الفارق الرئيسي بين تجربة المُستخدم وأنواع التصميم الأخرى، وفهمه يُعلي من قيمتك. وإتقانه يعني حرفيًّا دخلًا أعلى. فاسأل نفسك إذًا: هل تستخدم البيانات لإثبات صحّة تصميم، أو الوصول إلى التصميم الصّحيح؟ هل تبحث عن آراء غير موضوعيّة أم حقائق موضوعيّة؟ هل جمعت المعلومات الّتي تعطيك الإجابات المطلوبة؟ هل تعرف لم يفعل المستخدمون ما يفعلونه؟ أمّ أنك تفسّر سلوكهم فقط؟ هل تبحث عن أرقام مجرّدة؟ أم تهدف إلى إدخال تحسينات بناء عليها؟ كيف ستقيس شيئًا ما؟ هل تقيس الجوانب المطلوبة فعلًا؟ هل تبحث عن النتائج السيّئة أيضًا؟ لم لا؟ كيف تطبّق هذا التحليل لتحسين المنتج؟ ترجمة وبتصرّف لكل من المقالات التالية للكاتب Joel Marsh: What is UX User Goals & Business Goals The 5 Main Ingredients of UX حقوق الصورة البارزة: Designed by Freepik. اقرأ أيضًا المقال التالي: فهم ودراسة المستخدمين في مجال تجربة المستخدم النسخة العربية الكاملة من كتاب مدخل إلى تجربة المستخدم (User Experience - UX) 1.0.0 تاريخ موجز عن تجربة المستخدم ثلاثون مصطلحًا في تجربة الاستخدام لا غنى عنها لمصممي الويب
  9. يدهشني كم تُذكر كلمة "الحظ" عندما يعلم الناس أنني بدأت SmartBear. "أنت محظوظ لأنك تدير عملك الخاص، فأنا أكره مديري!""أنت محظوظ لأن عملك بخير رغم الأزمة الاقتصادية.""أنت محظوظ لأنك بعت شركتك." إن كنت تحسب أن جهدك الذي بذلته على مشروعك، والساعات الطوال التي قضيتها في بناءه، والأفكار التي خرجت بها، والطريقة التي تعاملت بها مع الزبائن؛ إن كنت تحسب أن هذا كله سيُبهرهم، فأنت مخطئ! النجاح كله "حظ". تصرف الناس بهذا الأسلوب لا يحطّ من قدرك فحسب، بل يُهينك كذلك، فهو يعني ضمنيًّا أنّ نجاحك ليس بسبب جهدك، وإنما هو ضربة حظ.قراراتك لم تكن مهمّة، وأفكارك لم تكن مميّزة؛ كل ما في الأمر أنك محظوظ. يستطيع أي شخص عمل ما عملته. من السّهل كذلك أن تكون ردّة فعلك حاقدةً ومشابهة لذلك التصرّف: استقالتي من وظيفتي وعملي 60 ساعة في الأسبوع دون أجر لسنوات، حتى لقيت جزاء ما بذلته من جهد… كل هذا حظ إذًا؟ابتكاري منتجًا فريدًا من نوعه أحبه الناس، وبناؤه من الصفر… حظ؟عندما كوّنت علاقات حميدة مع الزبائن وأصغيت لما يريدون… حظ؟وعندما سنحت لي الفرص ببيع شركتي بسعر مُرضٍ ضمن صفقة وضعت في جيوب موظّفيَّ من المال ما لم تكن أي وظيفة لتضعه؟ هل هذا حظ؟هذه الردود ليست ظالمة، وهي تبيّن أنّ الأمر ليس حظًّا فقط، ولكن لأكون صادقًا معك، فالحظ كان له دور! بلى، علاقاتي بالزبائن كانت حميدة، لكن وجود الزبائن بحد ذاته ينطوي على شيء من الحظ! بلى، هم تعرفوا على منتجي من إعلانات Google، لكن أليس من الحظ اختياري لإعلانات Google في بداياتها وهي رخيصة ولمّا تمتلئ بالإعلانات السخيفة؟ نعم رسائل التسويق الّتي اخترتها كانت فعّالة ومُوجَزة، لكن ألم يكن من الحظ أن كان لديّ من يُعلمني كيف أفعل ذلك؟ يمكنني في الواقع اختيار أي قرار في تاريخ Smart Bear وستلاحظ أن القصة السابقة تُعاد بالنمط ذاته. الخلاصة؟ الحظ والاختيار لا ينفصلان. ما أعنيه بالتحديد: الحظ السعيد والحظ السيئ يُحيطان بك باستمرار، أما كيف تستخدم حظك، فهذا ليس حظًّا! ألاحظ نمط قرار/حظ/قرار/حظ هذا في كل قصص النجاح. ولكن ماذا عن قصص الفشل؟ حملات إعلانية كثيرة فشلت في شركتي. الإعلانات لم تُفلح في بعض المجلات (دون ذكر أسماء، لأن الصحافة المطبوعة تواجه ما يكفيها من المشاكل!). أنفقت في بعض الحالات آلاف الدولارات -رقم يعني نسبة كبيرة من العوائد في ذلك الوقت- على إعلانات لم تجلب زبونًا واحدًا! بعض الإعلانات فشلت في مجلة ونجحت في آخرى. هذا حظ سيئ فقط. خيار إلغاء بعض الإعلانات دون غيرها لم يكن حظًّا. الحقيقة، كان قرارنا قياس كفاءة كل إعلان بمفرده في المطبوعات. لو لم نفعل هذا لم نكن لنستطيع تمييز النجاح من الفشل، وعندها كان مصيرنا سيعتمد على الحظ فقط. النجاح النهائي في العمل لا يعني أنك "كنت محظوظًا"، بل يعني أنّك استخدمتالحظ، مستغلًّا النوع الجيّد منه، ومتجنّبًا النوع السيّئ بعد تمييزه. لن تسمعَ كلمة "محظوظ" كثيرًا عندما أتحدّث إلى رياديّين آخرين، فهم مهتمّون بالقصص والنصائح وطريقة العمل، وكيف نُفكّر، لا كيف يُقلّدوننا. السؤال "من أين جئت بالفكرة؟" خاطئ، والصواب "كيف علمت أن الوقت مناسب لهذه الفكرة؟"، أو بالتحديد: "كيف يعلم المرء أن الإعلان المطبوع يؤتي ثماره؟" أفضل رهان على النجاح هو أن تُعامل كل قراراتك على أساس أنها تجارب؛ فالثقة بالنفس والتجربة أمران لا يتعارضان. جرّب أيّ شيء، وقِس كلّ شيء، استمر في ما ينجح، وإن كان معنى هذا تغيير كل شيء! ربما يكون الحظ حليفك بعد كل هذا! ترجمة -وبتصرف- للمقال How much of success is luck لصاحبه: Jason Cohen. حقوق الصورة البارزة: Designed by Freepik.
  10. الكثير من المدوّنين ذوي الأعمال الصغيرة يقولون إن عليك الإصغاء لعميلك والبناء على أساس هذا، المشكلة أن بعض الناس يبالغون في ذلك. كانت لي تجربة مع شركة من هذا النمط مؤخّرًا. بعد انتهائهم من عرض منتجهم، شرعت بإنهاء اللقاء ببضعة أسئلة اعتياديّة: أنا: إذًا ما التالي في خطّتكم؟ CTO: سنصغي لما تريدون. لاحظ كيف يتفادى السؤال، تمامًا كرجال السياسة. ما قاله ليس إلا تعبيرًا أقل صراحة عن الجملة "المستقبل هو أي شيء تعتقد أنّه المستقبل". ربّما يحاول أن يبيّن مدى استجابته للطلبات المستقبليّة، ولكن كان عليه أن يتجنّب هذه الإجابة. عجزت أسئلتي اللاحقة عن تبيّن وجود خطّة مستقبليّة. لعلّ قلة العملاء تجعل طريقهم غير واضح. الإجابة التالية تؤكّد نظريّتي السابقة: أنا: ألديك أسئلة لنا؟ CTO: نعم. ما أكبر مشكلة تواجهك في العمل وتريد أن يحلّها أحدهم؟ هل تتصيّد الأفكار؟ هل تطلب منّي أن أحدّد منتجك القادم بدلًا منك؟ الأمر ليس "إصغاء للعملاء"، بل هو أشبه بسفينة بلا قبطان. وجود أهداف واضحة والثقة بالنفس أمران لا يتعارضان مع النّمو وفق طلبات العملاء. عليك أن تصغي إصغاءً فعّالًا: عندما يصلك اقتراحٌ ما، انتبه إليه، اكتبه على الورق، أعد صياغته بكلماتك وكرّره لتطمئن لفهمه بدقّة.تحرّى طلبات إضافة الميّزات حتى تكتشف المشكلة الجذريّة الكامنة وراءها. هذا يتطلّب محادثة مباشرة مع عميلك، فاستخدم الهاتف أو اللقاء المباشر، لا البريد. كثيرًا ما تجد عشرات الطرق لحلّ المشكلة ولكنّ لديك عملاء آخرين عليك أخذهم بعين الاعتبار، بالإضافة إلى بنية منتجك الحاليّة.اطلب من العميل ترتيب اقتراحاته وفق أهمّيتها. فقد تتلخّص الاقتراحات العشرون في اقتراحين أساسيّين لا مفرّ منهما. لا تطلب منهم إسناد مستوى أهمّية لكل اقتراح، بل ترتيبها فقط، لأنك إن فعلت فستجد 7 اقتراحات في المستوى الأول!إن كنت عاجزًا (أو غير راغب) في تنفيذ اقتراح معيّن، فاعترف بذلك. وضّح للعميل سبب قرارك حتّى يتفهّم ذلك، ولا يعتبره تعاليًا.بادر بطلب تعليقات العملاء على المنتج. لا يرسل كثير من المستخدمين رسالة بريد إلى قسم الدعم فيها طلب ميزة ما؛ لأنّهم تعوّدوا على تجاهل كثير من الشركات لها. لاحظ أنّك تُشرك العميل في كلّ الخطوات وفي الوقت نفسه تصقل هذه الاقتراحات. إشراك العميل يجعله يُدرك أنك تصغي له وتدرس اقتراحه. صقل تلك الاقتراحات يعني أنّ لديك أشياء ملموسة تبني عليها. وحتّى الاعتراف بعجزك عن تنفيذ اقتراح هو أمر حسن، لأنه يعني أن قبولك لاقتراح ما سيؤدي إلى تنفيذه. بهذا تبين نزاهتك وواقعيتك. يعلم الجميع أن تنفيذ أفكارهم كلها أمرٌ غير معقول؛ ولهذا يقدّرون رفضك بصدق. قليلة هي الشركات التي تصغي لعملائها، ولكنها محبوبة. أصغِ، ولا تتصيّد الأفكار! ترجمة -وبتصرف- للمقال Five ways to listen to customers instead of goin' fisghin لصاحبه Jason Cohen. حقوق الصورة البارزة: Designed by Freepik.
  11. هل زرت فيسبوك اليوم؟ ماذا عن تويتر؟ Instagram؟ Snapchat؟ إن كان الجواب نعم، فالغالب أنك زرتها من باب العادة. السؤال إذًا كيف تبني منتجات تخلق هذه العادة؟ لحسن حظك، هناك كتاب Hooked: How to Build Habit-Forming Products لكاتبه Nir Eyal يحاول شرح هذا! أنصح جميع أصحاب المشاريع ومدراء المنتجات وكل من يرغب ببقاء مستخدميه بقراءته. قد يكون استبقاء المستخدمين (retention) أكبر التحديات التي تواجه أي مشروع، وهو ليس بالأمر السهل حتى بالنسبة لأكثر الشبكات الاجتماعيّة نجاحًا. ألقِ نظرة على المخطّط التالي الذي يبين الفرق بين عدد المستخدمين الكلي وعدد المستخدمين النشيطين (بحسب Golbal Web Index). يعرض كتاب Nir نموذج "الصّنارة" (Hook Model) المُستوحى من نموذج BJ Fogg السلوكيّ الذي ينصّ على ضرورة التحام 3 مكوّنات في لحظة واحدة حتى ينشأ السلوك المرغوب: الحافر والقدرة والمُثير. فيما يلي صورة توضّح هذا النّموذج: عندما تستخدم فيسبوك فإنك على الأرجح تتبع نموذج الصّنّارة وهذا ما يخلق "الإدمان" على فيسبوك: المُثير (الداخلي) = الملل، الفضول، الوحدة، الضجر المُثير (الخارجي) = رؤية إشعار من فيسبوك على الهاتف الفعل = النقر على تطبيق فيسبوك لبدء التصفح ببساطة المُكافأة المُتغيّرة = مشاهدة محتوى جديد ملفت للنظر التفاعل = إبداء الإعجاب، التعليق، تحديث الحالة يحتاج نموذج الصّنّارة لكي يعمل مثيرات قويّة، وجهدًا بالحدّ الأدنى للقيام بالفعل، وتنوّعًا في المحتوى، والقدرة على المشاركة في المنتج. تمعّن في منتجك واسأل نفسك هذه الأسئلة الخمسة: 1. هل يؤدي المثير الداخلي للمستخدم إلى قيامهم بالفعل مرارا؟ نشرت إحدى الزميلات هذه الصورة الغريبة على Instagram خلال احتفالات العام الصينيّ الجديد: سألتها عن سبب التقاط هذه الصورة. الحقيقة أنني سألتها خمس مرّات عن ذلك (بهدف الوصول إلى السبب الأساسي). تبيّن أنّها أرادت مشاركة شيء يجعل الآخرين يتبسّمون، وهذا بدوره يُحسّن صورتها ويرفع من مقامها مقارنةً بالآخرين. هذا ما يُسمّى العملة الاجتماعية وهذا مُثير داخليّ شائع للمشاركة. يمكن لمستخدمي Instagram التفكّر في هذا المُثير عندما يمدّون أيديهم إلى جيوبهم لالتقاط الصورة. 2. هل يظهر المثير الخارجي في الوقت المناسب لمستخدميك؟ ما هو مثيرك الخارجيّ؟ فإن كان المثير الداخلي هو الملل مثلًا، فإن المثير الحارجي هو تلك الدائرة الحمراء فوق رمز تطبيق فيسبوك في هاتفك. ربّما تكون أخرجت هاتفك لتبحث في تويتر، ولكن المثير الخارجي الذي يعرضه فيسبوك أدى بك إلى فتحه بدلًا من تويتر. لا تقتصر المُثيرات الخارجيّة على المُنبّهات والإشعارات ورسائل البريد، فلطالما استخدم كوكا كولا آلات الدفع لتحثّ المُشترين على إرواء عطشهم، والذي هو مُثير داخليّ. آلة الدفع هي المثير الخارجيّ. ما هو مثيرك الخارجيّ إذن؟ 3. هل تصميم منتجك بسيط بما يجعل الفعل سهلا؟ التطبيقات التي تخلق العادة سهلة البدء ولا تتطلب جهدًا كبيرًا. ألقِ نظرة على تويتر: افتح التطبيق، مرّر بإصبعك. هذا كل ما في الأمر! غايةٌ في السهولة! ماذا عن منتجك أنت؟ كيف تقلّل الجهد المطلوب لأداء الفعل؟ العادات تلقائيّة، لا تتطلب التفكير. هذا هو هدفك. 4. هل ترضي المكافأة مستخدميك وتتركهم في الوقت نفسه يطمعون في المزيد؟ أستخدم Flipboard لقراءة الأخبار المهمّة. التطبيقات القائمة على المحتوى تخلق العادات لأنها تقدّم تنوّعًا. يلبّي Flipboard حاجتي للمحتويات الإخباريّة ولكنّه يتركني راغبًا في محتوى جديد يكفي لعودتي. هل في منتجك محتوىً متنوّع؟ ذهبت أيام المحتوى الجامد على الويب بلا رجعة، فما بالك بالتطبيقات؟ امنح مستخدميك سببًا للعودة. 5. هل يتفاعل مستخدموك مع منتجك بحيث يضعون في منتجك شيئا قيما بالنسبة لهم؟ لا يتقن أحد هذا كما يتقنه Evernote. باعتباره حافظة لكل شيء يهمّك تجده على الويب، يجعلك Evernote تبني "مستودعًا" من المحتوى. هذا يؤدّي إلى أمرين اثنين: يُحسّن المنتج الذي يُستخدّم، ويجعل المُستخدم يعود ليجد المحتوى الّذي حفظته. الخاتمة العادات سلاح قويّ جدًّا للمنتجات والخدمات التّي تعتمد في نجاحها على التفاعل العالي. ألقينا نظرةً على بعض الأمثلة، ولكنّني أحثّك على فحص المنتجات الّتي تعتاد استخدامها واكتشاف مُثيراتها وأفعالها، وما تقدّمه من مكافئات وكيف تتفاعل معها. ترجمة -وبتصرف- للمقال Understanding Habit: How to Build a Product That Gets Used Daily لصاحبه Jason Allan. حقوق الصورة البارزة: Designed by Freepik.
  12. سأل هذا السؤال نحو 20 سائلًا على موقع Answers OnStartups بصيغ مختلفة: سأقول لك الحقيقة: أنت تسأل السؤال الخاطئ. أجدر بك أن تسأل: ما الذي تفعله الآن وأنت على يقين أن شركة كبيرة سوف تنسخ فكرتك؟ أو ربما من الأجدر أن نسأل: ما الذي ستفعله عندما تنسخ شركة ناشئة، غير منظّمة ولكنّها ذكية، فكرتك وتتلقى 10 ملايين دولار كتمويل، ثمّ ترد ثلاث مرّات في عناوين TechCrunch؟ آسف، السؤال الحقيقي هو: ما الذي ستفعله عندما يظهر أربع منافسين، جميعهم يقدمون منتجًا مفتوح المصدر ومجانيًّا بالكامل؟ نسيت، السؤال الفعلي هو: ما الذي ستفعله عندما يهرب موظفك رقم #2 بمصدر برنامجك وخطتك المستقبلية وبيانات التسويق وقائمة الزبائن إلى بوليفيا، ويبدأ ببيع منتجك لكل العالم بمعشار سعرك؟ إليك الخبر البهيج: لهذه الأسئلة بالفعل إجابات جيّدة! الخبر السيئ: لا أحد ممّن أحادثه يعلم الإجابات الجيّدة، بل هم يظنّون ذلك فحسب. وهذا خطير، لأنّه يعني أنّهم لا يعملون على تصحيح الوضع، وهذا بدوره يعني أنّه عندما يقع أحد السيناريوهات السابقة، فإن الأوان قد فات. الخطوة الأولى هي الاعتراف بالمشكلةناقشت في مقال سابق أبرز الأفكار الخاطئة عن أفضليّة المنافسة... المُلخّص: كل ما يمكن نسخه سوف يُنسَخ، بما في ذلك المزايا والتسويق والتسعير. كلّ ما تقرؤه على المدوّنات الشهيرة يقرؤه الجميع. كونك تعشق عملك، أو تعمل بجد لا يُعطيك أفضليّة. الأفضليّة الوحيدة في التّنافس هي ما لا يمكن نسخه ولا شراؤه. مثل ماذا؟ معلومات من الداخليُقال أن السبيل الوحيد للرّبح في Wall Street هو المعلومات السّريّة. هذه العبارة صحيحة للأسف، مع أن الأمر غير قانوني (وأحيانًا يؤدي إلى السّجن). ولكن الخبراء سيؤكدون لك أنّه الأمر الشائع. لحسن الحظ فإن المعرفة الدقيقة لمجال العمل ونقاط الضعف ضمنه أمر قانونيّ ويُعطي أفضلية المنافسة للشركات الناشئة. هاك مثالًا من أرض الواقع عن هذه الميزة: أدريانا طبيبة نفسيّة بخبرة 10 سنوات؛ وهي على دراية ببواطن المهنة وظواهرها. في فترة راكدة من حياتها المهنية تواتيها فرصة سانحة لتغيير مسارها، لينتهي بها الحال وهي تقود فريق تطوير منتج. (الواضح أن البصيرة والقدرة على تقديم النُصح أكثر أهمّيّة من الخبرة في تنقيح برامج ‎C++‎ بالنسبة للإدارة المشاريع الكبيرة). تقع أدريانا على اكتشاف مفاجئ: برامج إدارة المرضى التقليدية لعيادات الطب النّفسي سيئة للغاية؛ فهي تعرف تلك البرامج، وتعرف نقاط ضعفها، ولديها الآن الرؤية والقدرة على تصميم برنامجها الخاص، مستفيدةً من أحدث التقنيات الدارجة (تطبيق ويب مثلاً يجنّب الطبيب عناء تثبيت البرنامج ومشاكله المحتملة) ومن التفاسير الجديدة لقوانين حماية بيانات المرضى (التي تسمح لتطبيقات الويب بحفظ سجلات المرضى). ‏أدريانا الآن في موقع مميّز: خبرة في المهنة، اهتمام مطابق لاهتمام المستخدم، وفوق ذلك كله القدرة على قيادة فريق المنتج. بعد كل هذا لا يهمّ إن رأي شخص آخر هذا المنتج وحاول تقليده، لأنّه من الصّعب أن تجد شخصًا بهذا الاطّلاع. وحتّى لو قلّده، ستكون أدريانا تجهّز لإطلاق الإصدار الثّاني من منتجها. عقلية أحادية لا تقبل التنازل ومهووسة بشيء واحدمن التعليقات الشهيرة على المنشور السابق ما مُفاده أنّ "ميزة فريدة من نوعها" قد تعطي أفضليّة في التّنافس في بعض الحالات. من الأمثلة على ذلك: تضحّي Apple بكل شيء بحجّة التصميم. فأسعارهم غير مبررّة (حتّى أنّهم يخفّضون سعرها بعد 12 شهرًا للنصف دون خسارة!)، ومنتجاتهم كثيرة العثرات (كم مشكلة صادفتك في iOS؟)، وكل تجاربي مع الدعم الفني كانت مُرعبة. ولكن الحقيقة أن مظهر منتجهم وملمسه جميلان! (ملاحظة: أنا أكتب هذا المنشور على iPad Air وهناك iPhone في جيبي، لا أرغب برسائل من متعصّبي Apple رجاءً!) خوارزمية Google كانت أفضل من المنافسين حين ظهور الشّركة، ولهذا حازوا على انتباه الجميع، ولهذا استطاعوا إيجاد طريقة للرّبح. بالطّبع لا بأس بـBing و‎Yahoo!‎ الآن، ولكن أفضليّة Google كانت أسبقيتهم. لعلّك لم تسمع مطلقًا بـ Photodex، وهي شركة صغيرة عملت فيها في Austin في التسعينيات. صنعنا برنامجًا للصور يعرض معاينات مُصغّرة للصور بحيث لا تضطر لفتح كلّ ملف لمشاهدة الصّورة فيه (هذا كان في التسعينيات، حيث لم يكن هذا شيئًا بديهيًّا في أنظمة التشغيل). ميّزتنا كانت السرعة. لم نكن الأفضل ولا الأكثر استقرارًا، ولم ندعم كلّ صيغ الصور، ولم يكن لدينا كل الميّزات. كنّا الأسرع فقط. بالنسبة لكثير من المستخدمين، السرعة أوّل اعتبار. تجني Photodex اليوم عشرات الملايين من الدولارات كل عام، والسرعة تبقى في قمّة أولوليّاتهم، ولا يتنازلون عنها. على أنّ تفرّد المنتج (مثل برنامج بسيط لعرض فروقات مصادر البرنامج) لا يكفي، لأنّه يمكن بسهولة نسخ هذه الميزة الفريدة، والواقع أنّ معظم ما ابتكرناه في Smart Bear في مجال مراجعة النّصوص البرمجية قد نسخه المنافسون بصورة تجارية أو مفتوحة المصدر. ما تحتاجه هو التزام ثابت "بالشيء الوحيد" الذي (أ) يصعب نسخه (ب) ولا تقبل بالتخلّي عنه مهما كانت الأسباب. أنفقت Google مئات الملايين من الدولارات على خوارزميّة البحث، وهو أكبر اهتماماتهم إلى اليوم. وحتى بعد عقد من ظهور الشركة، فإنّها ترفض أن يهزمها منافس أو خارق من ذوي القبّعات السّوداء black-hat hackers، مهما كلّف الأمر. بإمكان 37signals بناء منتج بسيط (يكاد يكون سخيفًا لفرط بساطته) واكتساب 3 ملايين مستخدم لأنّهم يرفضون بكل حزم التضحية بفلسفة البساطة والشفافية واحتفاظهم بشركتهم الخاصّة؛ وهذا أمر يحترمه ملايين الناس ويدعمونه. بإمكان المنافسين طبعًا بناء تطبيق ويب بالبساطة ذاتها (كما يحب Joel Spolsky أن يقول: "منتجهم ليس سوى بضعة حقول نصّيّة!")، لكنّ المنافسين يعجزون عند تقليد عقلية الهوس الذي يركّز على "الشيء الواحد"، وعندها يكون المنتج بلا ميّزات. لتبقى دون المنافسة، ويعجز المنافسون عن تقليدك، فعلى "الشّيء الواحد" في منتجك أن يكون صعب التّحقيق، وليس فقط محوريًّا في وجودك. فخوارزمية Google، هي والبرامج والحواسيب التي تتطلّبها، والتي تبحث آلاف المليارات من الصّفحات في 0.2 ثانية، صعبة التقليد. وقد تطلّب الأمر مئات (بل ربّما ألوف) المهندسين الأذكياء في Microsoft و‎Yahoo!‎ لتصلا إلى وضع مشابه. منصّة 37singals للتّعبير — بمدوّنتها ذات المئة ألف متابع وكتاب حقق أفضل المبيعات — تكاد تكون مستحيلة التقليد حتى لو جهّز لذك جيش من الكُتّاب المُطّلعين. أن تكون "صعب التقليد" ميزة حقيقيّة، لا سيّما إن خصصتها بجلّ طاقتك. الأسماء الثقيلةيطلب Chris Brogan على يوم واحد من الاستشارة 22 ألف دولار (في مجال التسويق الاجتماعي) مع أن كل ما تحتاجه من معلومات موجود على الويب مجّانًا. يجني Joel Spolsky ملايين الدولار من تعقّب عثرات البرامج (bug tracking) — وهو مجال يخوضه مئات المنافسين ومساحة الابتكار فيه محدودة. شركتي Smart Bear تبيع أغلى الأدوات من نوعها. كيف بنينا هذه الأسماء الثقيلة؟ وكيف تحصل أنت على هذه الميزة الطاغية؟ أنا مثال حيّ على شخص لم يكن له وزن ولكنّه استطاع بناء اسم له مع الوقت إلى درجة أصبحت فيها شركتي (Smart Bear) في موقع القيادة من حيث الأرباح والأفكار في مجال مراجعة الأقران للبرامج (peer code review). الحقيقة أنّني لم أكن خبيرًا في مراجعة النصوص البرمجيّة قبل بناء هذه الأداة، ولم أكن خبيرًا في العمليات العامّة لتطوير البرامج حتى! لم ألقِ محاضرات، ولم أدوّن، ولم يكن لدي عمود في مجلة Dr. Dobbs، وأكثر ما يثير الدهشة: لم أكن أعلم أن "مراجعة النصوص البرمجيّة" هي ما سيجعل شركتي ناجحة! لسوء الحظ، كل هذا الحديث الممل عن "الأسماء الثقيلة" يستغرق سنوات من الجهد الدؤوب، وحتى مع هذا، قد يكون النجاح معتمدًا على الحظ بمقدار مُساوٍ. هل الأمر يستحق كل هذا العناء؟ نعم، والسبب بالضبط أنّه يستغرق سنوات من الجهد وقليلًا من الحظ. لا يمكن شراء اسم. لا يمكنك جمع أموال من شركات التمويل المُخاطر (VC) وامتلاك "اسم له وزن" في سنة. لا تستطيع شركة كبيرة أن تصبح من قادة الفكر في مجالها على حين غرّة. حتى لو توفّرت مجموعة من العباقرة، فالأمر لا يرتبط بكفاءتك في البرمجة. ولكن كيف تتحوّل الأسماء الثّقيلة إلى أرباح؟ إليك مثالًا صغيرًا: أشارك بالحديث عن مراجعة الأقران للبرامج في المؤتمرات. يدفع منافسيّ آلاف الدولارات على أقسام التسويق في المؤتمرات والمعارض، ثم آلافًا فوقها للتسويق وترجّي الحاضرين لزيارة القسم، ثم تفريغ جمل التسويق للمارّين غير المهتمّين الذي يتابعون مسيرهم ليسمعوا كلامًا مشابهًا كثيرًا فوق كل الضوضاء من الأقسام الأخرى. أما أنا فمعروف بخبرتي في هذا المجال، حتى إنني أستطيع الحديث لساعة بطولها لمئة من الحاضرين الجالسين بعيدًا عن الضوضاء، والمهتمّين فعلًا في ما أقوله. وبعد هذا الحديث يأتي 5-20 منهم راغبين في الحديث وجهًا لوجه. بعضهم ينطلقون مباشرًا إلى قسم العرض، ولبعضهم أقدم عرضًا خاصًّا للمنتج على الأرائك في الصالة. ليس غريبًا أن أكسب 10-50 ألف دولار في المبيعات خلال الشهور الثلاثة القادمة من أولئك الذين حضروا حديثي. هذا مثال واحد. أضف إلى ذلك: ما أثر مدوّنة يقرؤها عشرات الألوف؟ ما أثر كتابي على المبيعات وقد أصبح مرجعًا يُشار إليه بالبنان في مجال مراجعة البرامج؟ لا شكّ أن بناء اسم له وزن على السّاحة هو أمر مُكلفة في الوقت والجهد، ولكنّه أيضًا طاغية وتمنحك أفضلية في المنافسة لا يستطيع أحد المساس بها. (ملاحظة: أرجو أن الاسم الذي بنيته لنفسي تدريجيًّا من هذه المدوّنة ستفيدني في مغامرتي التالية. هذا ليس سبب كتابتي في هذه المدوّنة، ولكنّه بالتأكيد يمنحني ميزة عندما يحين الوقت!) فريق الأحلاميمتلئ عالم الشركات الناشئة التقنية بفرق مشهورة مثل: Bill Gates وPaul Allen، ‏Steve Jobs وSteve Wozniak، ‏Sergey Brin وLarry Page، ‏Jason Fried وDavid Heinemeier Hansson. في كل هذه الحالات، المؤسّسان خارقا الذكاء، بارعان في الُمجاملات، ويعملان معًا بكفاءة، ويمثّلان معًا قوّة فريدة وكبيرة. بالطبع، يسهل أن تدرك هذا بأثر رجعيّ، وهذا أسوأ المعلّمين، ولكن المبدأ ينطبق على كل شركة ناشئة، خصوصًا عندما تكون الأهداف أقل طموحًا من بناء Google التالية! خذ على سبيل المثال نجاح ITWatchDogs، الشركة التي ساعدت في انطلاقها ثمّ بيعها (قبل Smart Bear). الخلطة السرية لفريق الأحلام كانت واضحة منذ البداية: مهارات متنوّعة. خبير في المبيعات والشركات الناشئة (جيري)، مطوّر برمجيّات محترف (أنا)، مطوّر عتاد محترف (مايكا).رؤية مشتركة. اتفقنا على رؤيتنا للمنتج والهدف النهائي كان بيع الشركة.معرفة من الداخل. كان لجيري مشروع ناجح في نفس المكان، وكان لدي خبرة عميقة بلغات وأدوات البرمجيات المُضمّنة في الأجهزة (embedded software)، وأمّا مايكل فله باع طويل في بناء الدارات والمُعالجات الرخيصة.بالطّبع، لا يضمن فريق الأحلام النجاح وحده، ولكنّه يقلّل المُخاطرة بشكل ملحوظ، كما أنّه يُصعّب مهمّة المنافسين. هذا يثبت صحته بشكل خاص عندما يكون أحد أعضاء الفريق عاملًا ناجحًا في مجاله، كأن يكون صاحب مدوّنة ناجحة أو شركة ناشئة ناجحة أو على معرفة بعدد هائل من رجال الأعمال. ولأنّ هذا النوع من المزايا التنافسية لا يمكن شراؤه أو نسخه بتكراريّة، فإنّ وجود هذا الشخص في الفريق هو خطوة على طريق النجاح. ملاحظة: هذه هي الميزة التنافسية الأساسية في مشروعي الجديد الذي أعمل عليه وسأعلن عنه قريبًا، لذا سترى مثالًا جديدًا عن هذه النظرية (وأفضل من سابقه!)، وسنشهد أنا وأنتم خلال الشهور القادمة إن كان هذا سيعطينا أفضلية ساحقة أو لا (سأشارك التفاصيل بالتأكيد!). إطراء المشاهير (النوع الجيد منه)شركة Hiten Shah الثالثة هي KISSMetrics. بنظرة سطحيّة، تبدو شركةً إحصاءات لبيانات التسويق لا يميّزها عن منافسيها شيء. وفي هذا السوق مئات المنافسين بأحجام وأسعار وصور مختلفة. ولكن لدى Hiten شيء ليس لدى أحدٍ من منافسيه: إنّه المستثمرون والمُرشدون الذي هم من مشاهير المجال الذي يستهدفه بالضّبط. أشخاص من مثل Dave McClure وSean Ellis وEric Ries، الذين لا يكتفون بالمساعدة عبر المكالمات الجماعيّة، بل يروّجون بنشاط لـKISSMetrics على مدوّناتهم وحسابات تويتر وفي أرض الواقع. كم إعلانًا يحتاج المنافسون للوصول إلى مستوى الإطراءات الّذي يناله Hiten؟ حتى لو أراد منافس إطراء المشاهير، فلن يجد هؤلاء، لأنّهم مشغولون بمنتجه، وعدد المشاهير في أي مجال محدود بثلّة من أصحاب السّلطة المُحترمين. لدى الكثير من المنافسين ميّزات أغنى ممّا تقدّمه KISSMetrics، وباستطاعتي تصوّر عبارات التسويق المرافقة: يعترض العميل على قلّة الميّزات: "كنت أود لو توفّرت كلّ هذه الميّزات"، فيجيب Hiten: "لن تتوفّر لك، لأن Dave وSean وEric يقولون إنّها مجرّد أمور ثانويّة تشغلك دون أن تضيف شيئًا. ميّزاتنا هي الميّزات الضرورية، وهذا تثبته الشركات العشرون الّتي تبيّن زيادة عوائدها." بناءً على نصائح هؤلاء فقط، سيكسب Hiten مئات بل آلاف العملاء. لا يمكنك شراء هذا بملايين الدولارات، لأنّ الأمر لا يعتمد على مشاهدة العملاء إعلانات KISSMetrics، بل على ثقتهم في Hiten بسبب ارتباطه بهؤلاء المشاهير الّي تثق بهم أصلًا. العملاء الحاليونأو كما يقول Frank Rizzo: أنصت لما أقول يا أبله! كل من بعت منتجًا له (وحتى من جرّبه ثم هجره) لديه أفضل الدراسات عن وضع السوق، وهو الشيء الوحيد الذي لن يكون لدى منافسك الجديد على الإطلاق. هذا نوع من الخداع، لأن الجميع يدّعون أنّهم "يُصغون لعملائهم"، وهو شعار مبتذل اليوم كما كان الشعار "عملنا هو شغفنا!"، ولكنه يحمل شيئًا من الحقيقة من حيث أن فهمك لعملائك واستمرارك بالعمل والابتكار بنشاط سيضعك في الطليعة أمام منافسيك على مستوى العالم أجمع. تكتسب الشركة مع نجاحها عزمًا ينعكس على مسارها فيجعله أكثر وضوحًا وتحدّدًا، ويجعل الشركة تعتمد فسلفة واحدة. وهو كما العزم في الفيزياء، يجعل التغيير أقل قدرة على التأثير. الأمر منطقي؛ فعلى سبيل المثال لدينا 35 ألف مستخدم في Smart Bear، وهذا يعني أن إحداث تغيّر جذري في واجهة الاستخدام أو مسار العمل الاعتيادي يعني اضطرار المستخدمين للتعوّد من جديد على المنتج، حتى لو كان هذا يؤدي إلى نتيجة أفضل. حتى تلك الشّركات "الرائعة والرّشيقة" مثل 37signals تقع في الفخّ. فقد كانت الشركة واضحة وواثقة في فسلفة "البساطة"، حتى أصبحت عاجزة عن دخول أسواق تطلب مزايا أوفر. فمثلًا، لم يكن استخدام Highrise مُمكنًا إلا في مؤسسة مبيعات تقليديّة فيها عدد محدود من رجال المبيعات، لأن قادة 37signals يؤمنون أن تقارير التوقعات والمناطق الجغرافيّة وإدارة حملات التسويق أمور معقّدة غير ضروريّة، والحقّ أن Highrise نفسه لم يكن ضروريّا. بالطّبع العالم يتغيّر باستمرار، وعملاؤك يتغيّر معه. هذا يفسح المجال لمنافسك التالي، ولكن إن كنت متحصّنا فبإمكانك استغلال مكانتك الحاليّة والأسرار الداخليّة للعمل، وأرباحك معًا طالما رغبت في التّغيير أيضًا. أموالك أكثر، وسمعتك أفضل، ولديك مستخدمون راضون، وموظّفون جاهزون لبناء أشياء جديدة، وخبرة أكبر في ما يفعله العملاء وما يحتاجونه، وهذا يعني أنّك مطلع أفضل اطّلاع. أي منافس جديد سيتمنّى الحصول على واحدة فقط من مزاياك مهما كلّف الثمن. فإن لم تستخدم أنت كل هذه المزايا، فما أسخف منطقك! هذا هو ردّ Zoho إذ تشرح لما لا يُقلقها دخول Microsoft في المنافسة ضدّها: وقعت 37singals في فخ فلسفتها التي فرضتها على نفسها، أما أنت فلست مضطرًا للوقوع في الفخّ ذاته. انطلق الآن!قد يكون التقليد أكثر أشكال المديح إخلاصًا كما يُقال، ولكنّه أمرٌ سيئ للشركات الناشئة. بالطّبع ما يزال بإمكانك المنافسة في السوق، ولكنّك تريد شيئًا ما لا يمكن نسخه، لا يمكن لأحد أن يهزمك فيه، عندها تعتمد عليه تمامًا دون رجعة. لا تيأس إن لم تكن لك الأفضليّة بعد. لم تكن لي الأفضليّة عندما بدأت Smart Bear! ولكنني عملت على ذلك حتى حقّقته. ترجمة وبتصرّف للمقال: Real Unfair Advantages لصاحبه: Jason Cohen. حقوق الصورة البارزة: Designed by Freepik.
  13. تعلمنا في الجزء السابق أن HTTP بروتوكول على مستوى التطبيقات. حان الوقت لنفهم كيفية استخدام هذا البروتوكول للتواصل بين العميل والخادوم. جلب الأشياء من الويب تذكر أن عميل HTTP (وهو المتصفح عادةً) هو الطرف الذي يبدأ بإرسال الطلب إلى الخادوم. يسمح بروتوكول HTTP للعميل بالتعبير عن نيّته من خلال بضعة مُكوّنات: الرابط (URI)، وأفعال HTTP، وترويساته. تسمية الأشياء تمثّل الروابط (URIs) حجر الأساس في الويب، لأنّها تحل مشكلة مهمّة على مستوى الإنترنت بكاملها، وهي مشكلة منح الأشياء مُعرّفًا تنفرد به على الشّبكة. افترض أنّك سألت شخصًا أن يجلب لك شيئًا ويُحضره إليك، يمكن عدّ الطرق الممكنة للطلب على الأصابع. فعادةً ما تُعرّف الكلمات الّتي تستخدمها الشيء الّذي تريده. بإمكانك أن تطلب من صديقك قائلًا: "اجلب لي الكتاب." قد يجيبك صديقك: "حاضر. ولكن أي كتاب؟". فتقول أنت: "الكتاب في الغرفة الأخرى." فيذهب صديقك إلى الغرفة ويسأل: "أي كتاب قلت؟" فتقول أنت وقد شعرت بالضّيق: "الأخضر!" ليقول صديقك: "تمهّل لحظة... هناك كتابان أخضران في الغرفة!" وعندها تنهض لتحضر الكتاب بنفسك. كان الأمر ليكون أكثر بساطة لو أننا وحدنا طريقة نُحدّد فيها الأشياء بطريقة مميّزة للوصول إليها لاحقًا. إحدى الوسائل الممكنة هي الاعتماد على الذاكرة. كيف نعطي الأوامر لشخص ما ليحضر إلينا الغرض المطلوب؟ لنُنشئ نظامًا لذلك: قص ورقات صغيرة أو استخدم ورق الملاحظات اللّصّاق. ضع هذه الأوراق على الأغراض في الغرفة المجاورة أو على الطّاولة (كالكتب مثلًا). اكتب مُعرّفًا مُميِّزًا على كل ورقة. كرّر العملية في غرفة مجاورة أو على طاولة مجاورة. تذكّر أن هذا النّظام لا يقتصر على أغراضك، بل يمتد ليسمح بنوعٍ من التّواصل بين الأغراض جميعها. طبّقت هذا النّظام على أغراضي، فكتبت على كل قطعة ورق واحدًا من المُعرّفات التالية: myRoom.org/table/book/001 myRoom.org/shelf/book/002 otherRoom.org/cup/001 otherRoom.org/flower/001 otherRoom.org/book/001 أعتقد أنك الآن فهمت ما أقصد. لدينا الآن نظام من اللصاقات الّتي تستخدم لتحديد كل شيء في المكان بدقة. في الويب، نحن نتعامل مع فضاء معلومات واللصاقات المُستخدمة ليست سوى الروابط (URIs). الوصول إلى الأشياء المُسمَّاة شرحنا كيف يُبنى طلب HTTP في المقال السابق من خلال الطرفية. استخدمنا بناءً بسيطًا مع فعل HTTP المُسمّى GET مع ترويسة واحدة: Host. GET / HTTP/1.1 Host: www.opera.com القائمة الكاملة لأفعال HTTP الحالية هي: OPTIONS، GET، HEAD، POST، PUT، DELETE، TRACE، CONNECT. لكل فعل دورٌ مختلف عن الأفعال الأخرى وسنشرح ذلك في المقالات التالية. أكثر الأفعال استخدامًا هو GET، ففي كلّ مرة نُدخل عنوان HTTP في شريط العناوين في المتصفح، فإنّنا نرسل طلب GET إلى خادوم. في عالم الويب يُرسل العميل معلومات أكثر (المزيد من ترويسات HTTP) إلى الخادوم للتفاهم على المطلوب. يستغل الخادوم هذه المعلومات ليُعدّل الجواب بما يناسب الترويسات. تتوفّر أداة عمليّة جدًا في Opera Dragonfly لإنشاء طلبات HTTP مُخصّصة وفحص إجابة الخادوم. يمكنك إيجادها في قسم Network في تبويب "Make Request". هنالك ثلاث مناطق في تبويب Make Request: الرابط (URL): مُعرّف المُحتوى (أو عنوان الويب) متن الطّلب (Request body): ما سيُرسله العميل إلى الخادوم (يُرسل الزّر "Send request" الطّلب إلى الخادوم عبر الشّبكة) الجواب (Response): جواب الخادوم بعد إرسال الطّلب تخصيص طلب HTTP انسخ http://www.opera.com/ إلى قسم URL. انسخ والصق طلب HTTP التّالي إلى قسم "Request body" في تبويب Network. أضفنا الترويسة Accept-Language إلى طلب HTTP السابق. اضغط الزر "Send request". GET / HTTP/1.1 Host: www.opera.com Accept-Language: en سيُجيب خادوم Opera بجواب HTTP مؤلّف من بضع ترويسات يليها محتوى المستند. قد تحتاج إلى تمرير النافذة، لأن الجواب قد يكون طويلًا. لاحظ أن المُستند باللغة الإنكليزيّة، ليس فقط من حيث اللغة الّتي كتب بها، بل أيضًا يُنصّ على ذلك صراحةً في خاصّة lang على ال عنصر html: <!DOCTYPE html> <html lang="en"> لنُجرّب الفرنسيّة: GET / HTTP/1.1 Host: www.opera.com Accept-Language: fr سُيجيب الخادم هذه المرة بالفرنسيّة، ويتبيّن ذلك في نص المستند وفي مصدره: <!DOCTYPE html> <html lang="fr"> لاحظ أنّنا استخدمنا الرابط نفسه بالضّبط (http://www.opera.com/) ولكنّنا تلقّينا إجابتين مُختلفتين لا لشيء إلا لأننا غيّرنا الترويسة Accept-Language. لاحظ أيضًا أنّ الخادوم أجاب بترويسات كثيرة تُعطينا معلوماتٍ عن حالة المحتوى ونوعه... إلخ. يسمح هذا للعميل بتعديل أسلوب معالجة المستند. يمكنك تجربة لغات مُختلفة مثل اليابانية (ja) والألمانية (de)... إلخ. ما الذي يحدث إن طلبنا لغة غير موجودة على الخادوم؟ جرّب مثلًا الصّينيّة (zh): GET / HTTP/1.1 Host: www.opera.com Accept-Language: zh ستتلقى النسخة الإنكليزية من الموقع. هل هذا مُحيّر؟ الحقيقة أنّ هذا الأمر يعتمد على تصميم الموقع ذاته. فقد يُصمم جواب HTTP بأسلوب آخر، كأن يُجيب الخادوم "لا، ما من نسخة صينية من الموقع لدينا"، أو "ليست لدينا نسخة صينية، ولكن لدينا اللغات التالية: ...). ولكن فريق تجربة المستخدمين في Opera قرر إرسال النسخة الإنكليزية من الموقع عند طلب لغة غير مُتوفّرة. المسألة مسألة اختيار، فلا قاعدة تُفرض على المواقع بهذا الشأن. ولهذا السّبب أقوم عادةً بتعليم مُصمّمي تجربة الاستخدام ومُطوّري الواجهات بعض أساسيّات HTTP، فهو بروتوكول يتوسّط التفاعل بين العميل والخادوم، وعليه فإنّ فهمه يُساعد في تصميم تفاعلات ذات معنى عند بناء المواقع، وذلك للبشر والآلات معًا (كالبرامج الّتي تستخدم الواجهات البرمجيّة (APIs) وما شابهها... إلخ). تذكّر URI: نظام لتعريف المعلومات على الشبكة. أفعال HTTP: يتضمّن البروتوكول حاليًا الأفعال التّالية: OPTIONS، GET، HEAD، POST، PUT، DELETE، TRACE، CONNECT. وقد تعرّفنا في هذه المقالة على أكثرها استخدامًا وهو GET. ترويسات HTTP:** الترويسات هي بيانات إضافية يُرسلها العميل لإعطاء معلومات أكثر عن البيانات المُتبادلة بين العميل والخادوم. تُفيد بعض هذه الترويسات الخادوم بحيث يختار الأسلوب الأفضل للإجابة. ترجمة (بشيء من التّصرّف) لمقال HTTP: Let’s GET It On! لصاحبه Karl Dubosy.
  14. كان ختام المقالة السّابقة قولُنا أن بروتوكول HTTP يدير التّفاعل بين عميل وخادوم، وقد شرحنا فكرة ترويسات HTTP. سيكون لدينا الكثير مما يُقال عن هذه الترويسات في أجزاء تالية من هذه السّلسلة، فهذه الترويسات تؤثّر في التّفاعل بين الطّرفين وفي أداء الموقع. أمّا اليوم، فسنطّلع على جانب لا يقلّ أهمّيّة عن التّرويسات، وهو رموز إجابات HTTP. نزهة في الشوارع خرجت ذات صباح قاصدًا مقهى لأقرأ كتابًا، لكنّني وجدت المقهى مُغلقًا حينها، وقد كُتب على لوحة على الباب أنّ احتفالًا يُقام خلال هذا الأسبوع، ولذلك فإنّ المقهى سينتقل مؤقتًا ليُقدّم القهوة في شاحنة الطّعام (التي سمّوها "307") قرب النهر. ذهبت إلى ذلك المكان واستمتعت بشرب القهوة. قرّرت بعدئذٍ التجوّل في مكتبتي المفضّلة في المدينة، فوجدتها مُغلقةً كذلك، إلّا أنّني رأيت لوحة على الباب تقول أن المكتبة ستتوسع ولذلك انتقلت بشكل دائم إلى مبنى جديد في 301 شارع برنرز-لي. لم يُزعجني ذلك، فالمكان قريب. ذهبت إلى هناك فاستقبلني الموظّفون بالتّرحاب: "200 سلامة!". حسنًا، أنا أبالغ قليلًا، لكنّك فهمتني! في طريقي إلى البيت، وجدت متجرًا مهجورًا غطّى الغبار أبوابه في 410 شارع برنرز-لي، وقد أُلصقت ورقةٌ على الباب تقول أنّ صاحب المحلّ تقدّم بطلب إشهار الإفلاس واضطّر إلى إغلاق المتجر، إلى الأبد. وكأنّ العجائب لم تنتهِ اليوم، إذ رأيت في نهاية 500 شارع برنرز-لي مبنى من 4 طوابق وقد انهار بالكامل. ما الذي حدث هنا؟! لم يكن يومي سيئًا بالمجمل، لذا قرّرت أن أكمل يومي بكتابة مقال عن رموز HTTP الّتي تُرسلها الخواديم إلى العملاء الّذين يرسلون الطّلبات. صياغة جواب HTTP وسطر الحالةتطرّقنا في المقال السّابق إلى السّطر الأول من صيغة الطّلبات الّتي يُرسلها العميل (بما في ذلك أفعال HTTP). وسنركّز الآن على السّطر الأول من رسالة الجواب الّتي تصل من الخادوم، ومعاني الرّموز المختلفة الّتي تظهر في هذا السّطر. لاحظ التّشابه بين نوعي الرّسائل (الطّلبات والإجابات). فكما ينصّ توثيق الإصدارة 1.1 من HTTP: إما إن تكون رسالة HTTP طلبًا من العميل إلى الخادوم أو جوابًا من الخادوم للعمل. من حيث الصّياغة، لا يختلف نوعا الرّسائل إلى في السّطر الأوّل، والذي إمّا أن يكون سطر طلب (للطلبات) أو سطر حالة (للإجابات)، وفي خوارزميّة تحديد طول متن الرّسالة (القسم 3.3). يُدعى السّطر الأول في الجواب إذًا سطر الحالة. يبدأ السّطر بإصدارة بروتوكول HTTP ثمّ مسافة ثم رمز من ثلاثة أرقام، ثم مسافة ثمّ جملة تشرح الرّمز، كهذا المثال: HTTP/1.1 200 OKالجملة القصيرة الأخيرة غير إلزامية وعلى العملاء تجاهلها، ولا ينبغي أن يستخدمها برنامج بغرض تفسير الجواب. لنطّلع الآن على بعض أكثر رموز الحالة شيوعًا وما يعنيه كلّ رمز منها. رموز الحالة في HTTP200، كلّ شيء على ما يرام! في كلّ مرّة يريد شخصٌ ما زيارة الصّفحة الرئيسيّة لموقع Opera، يُرسل العميل طلبًا إلى http://www.opera.com/ برسالة مثل هذه: GET / HTTP/1.1 Host: www.opera.com Accept-Language: fr User-Agent: BrowseAndDream/1.0يُحلّل الخادوم الرّسالة الّتي وصلته من العميل ويُرسل جوابًا بناء على ما فهمه من الرابط والترويسات. وكما ذكرنا في المقالتين السّابقتين، يكون الهدف الأهمّ هو إدارة التّواصل بين الطّرفين بما يحقّق أقصى فائدة لكليهما. إن فهم الخادوم الرّسالة، فإنّه يرسل رسالة تبدأ بـ200 OK، أي أنّ كلّ شيء على ما يُرام. تحوي الرسالة بضع ترويسات إجابة ثمّ محتوى الصّفحة، والّذي قد يختلف بناءً على ترويسات الطّلب، فلا إجابة مُطلقة. فكما في كل تفاوض، يجري حوارٌ بين الطّرفين للوصول إلى أفضل تسوية. فيما يلي مثالٌ عن إجابة على الطّلب السّابق: HTTP/1.1 200 OK Date: Fri, 24 Aug 2012, 13:56:44 GMT307، انتقلتُ مؤقّتًا إلى مكان آخر يمكن للخادوم أن يجيب العميل برسالة تبيّن أنّ المحتو قد انتقل مؤقتًا إلى مكان آخر. ويفيد هذا عندما تريد إعادة توجيه العميل إلى صفحة مُعيّنة لفترة قصيرة. افترض مثلًا موقعًا يُعطي توقّعات الطقس لتاييبي، وقد شبّ إعصار هائل مؤخّرًا فيها. سيكون من المفيد إعلام المُستخدمين بوقوع هذا الإعصار حتى هدوئه. قد يكون الطّلب مثل هذا: GET /taiwan/weather/today HTTP/1.1 Host: meteo.example.orgقد يرغب الخادوم بإجابة العميل قائلًا: "سأنقلك إلى صفحة أخرى تُعطيك معلومات مفصّلة عن الأزمة الحاليّة في تاييبي". قد تبدو الإجابة مثل هذه: HTTP/1.1 307 Temporary Redirect Date: Fri, 24 Aug 2012, 13:56:44 GMT Location: http://meteo.example.org/taiwan/weather/crisisيتبع المتصفح عادةً الوجهة الجديدة المذكورة في سطر Location. يمكن أن يطلب الخادوم إعادة التّوجّه إلى نطاق آخر على الويب. وحالما تنتهي الأزمة، يمكن للخادوم إزالة إعادة التّوجيه. ينبغي ألّا يتذكّر العميل إعادة التّوجيه للأبد. فهذا مُهمّ في حالة الإشارات المرجعيّة وسجل التصفّح. من الممكن تصميم برنامج يُدير عمليّات إعادة التّوجيه هذه بطريقة مُفيدة. لا يرى المُستخدم إعادة التّوجيه في معظم المتصفّحات، ولكن من الممكن إرسال متن مع جواب إعادة التّوجيه يعرض على المستخدم رسالة تحوي رابطًا للمكان الجديد يُسمح للمُستخدم بنقره. 301، تغيّر العنوان بشكل دائم عند إدارة المعلومات على موقع ويب، قد نحتاج إلى إعلام العميل (ومستخدميه) أن الصّفحة المطلوبة قد انتقلت بشكل دائم. ففي الشّركات، يُعاد تنظيم الأقسام أحيانًا بعد الاتّحاد مع شركة أخرى أو عند تغيّر الأولويّات. لنفترض مثلًا أن وحدة الآلات الكهربائيّة في شركة تقنية قد ضُمَّت إلى قسم الإلكترونيّات. وعندها يمكن إعادة توجيه عميل يطلب: GET /section/electromech/about HTTP/1.1 Host: inc.example.comإلى: HTTP/1.1 301 Moved Permanently Date: Fri, 24 Aug 2012, 13:56:44 GMT Location: http://inc.example.com/section/electronic/aboutالفرق بين الرمز 307 الّذي شرحناه في الفقرة السّابقة، والرّمز 301، أنّ التّغيّر في العنوان دائم في حالة الرّمز الثّاني، وهي رسالة واضحة من الخادوم للعميل تطلب منه أن يُغيّر الإشارات المرجعيّة المحفوظة لديه إلى العناوين الجديدة. يمكن للمتصفّح تنفيذ ذلك من تلقاء نفسه أو بعد استشارة المستخدم. لإعادة توجيه الروابط القديمة فائدتان مُباشرتان. الأولى هي كسب ثقة المستخدمين بموقعك، بأن تُبدي اهتمامك بالمُحافظة على المعلومات الّتي تستضيفها. والثّانية هي استقرار الموارد، فالمواقع الّتي تُعرف بمحافظتها على الرّوابط تكون أكثر احتمالًا لأن تُشير إليها مواقع أخرى على المدى البعيد، ممّا يزيد ترتيب الموقع في مُحرّكات البحث. 410، وداعًا يا صديقي العزيز! تحتاج بعض المواقع أحيانًا إلى إبلاغ العميل باختفاء المعلومات الّتي كانت موجودة على رابط مُعيّن للأبد. وقد يكون لهذا مُبرّراته. نحن نعلم أن الروابط الجيّدة لا تتعطّل؛ ولكنّ الرّمز 410 Gone هو الوسيلة الوحيدة المناسبة لتعطيلها. لنكن أكثر دقّة: هذا الرّمز هة طريقة لإخبار المستخدمين أن المحتوى الّذي كان موجودًا من قبل على هذا الرابط قد حُذِفَ عمدًا. وهذا معناه أن الخادوم يطلب من العُملاء الّذي يقصدون هذا الرّابط ألّا يتذكّروه. ففي متصفّح يستخدم الإشارات المرجعيّة وسجّلات التّصفّح، يُعتبر هذا الرّمز إبلاغًا للمتصفّح بسلامة حذف هذا الرّابط. افترض شبكة اجتماعيّة يُطلب فيها الوصول إلى صفحة مُستخدم: GET /people/jeanpaulsartres HTTP/1.1 Host: socialnetwork.example.comقرّر المستخدم أن يُغادر شبكتك الاجتماعيّة ويُغلق حسابه، قد تُريد أن تُبلغ غيره من المستخدمين الّذين يطلبون صفحته من سجلّ المتصفّح أو إشاراته المرجعيّة: HTTP/1.1 410 Gone500، يا للمصيبة! قد يتعذّر على الخادوم إجابة الطّلب لسبب مجهول. لا يتدخّل HTTP على الإطلاق في تفاصيل عمل الموقع، مثل طريقة تخزين قواعد البيانات على الخادوم، أو كيف يجلب الخادوم البيانات ويُعالجها. ربّما توقّف الطّلب عند برنامج مُعيّن على الخادوم ولم يصل الجواب، وعندها يُبلغ الخادوم العميل ومستخدمه عن وقوع خطأ ما غير معروف بجواب مثل هذا: HTTP/1.1 500 Internal Server Errorاستخدام سطور الحالة في الإجابات الّتي تُرسلها الخواديمعند تصميم نظام لإدارة المحتوى، يكون من الضّروري فصل الطّبقات بصورة موارد وروابط إلى هذه الموارد، فهذا مُفيد عند إجابة طلبات العملاء بالمعلومات الصّحيحة، وتقديم المحتوى للبرامج أو للبشر هو شيء جوهريّ في صفة الخواديم. ولأنّ المعلومات تتغيّر وتتطوّر،فإنّ تصميم الخواديم بما يراعي هذه النّقطة يُعطيها مرونة أكبر. لا تهدف هذه السّلسلة إلى شرح تفاصيل تطبيق إجابات الخواديم من النّاحية البرمجيّة، ولكنّنا سنستعرض مثالين يُفيدان كنقطتي انطلاق، على الرّغم من أنّهما قد لا يُفيدان في حالة المواقع الضّخمة الّتي تضمّ آلاف الرّوابط. إعادة التّوجيه في Apacheفي حال أردت إعادة توجيه http://inc.example.com/section/electromech/about إلى http://inc.example.com/section/electronic/about، يمكن إضافة ملف .htaccess في جذر الموقع يحوي التّعليمات التّالية: RewriteEngine On RewriteBase / RewriteRule ^/section/electromech/about /section/electronic/about [L,R=301]مُلاحظة: هنالك طرق أخرى لتنفيذ هذا الغرض، كاستخدام httpd.conf أو قواعد البيانات أو من خلال النّصوص البرمجيّة... إلخ. واختيار الطّريقة المناسبة يعتمد على تصميم النّظام. إعادة التّوجيه في nginxخادوم Nginx هو الآخر شائع الاستخدام، وخصوصًا على شبكات توفير المحتوى (CDNs). يمكن إعادة كتابة المثال السّابق لاستخدامه مع nginx: server { listen 80; server_name inc.example.com; rewrite ^/section/electromech/about http://inc.example.com/section/electronic/about permanent; }تصنيف رموز HTTPتعرّفنا على بضع رموز HTTP فيما سبق، ولكنّها أكثر من ذلك، وبعض هذه الرّموز ذائع الصّيت مثل 404 Not Found، وبعضها مغمور لا يُرى كثيرًا. وفي كلا الحالتين يمكن الاستعانة بالرّقم الأوّل للرّمز لأخذ فكرة عن معناه، كون هذا الرقم يُشير إلى العائلة الّتي ينتمي إليها الرّمز: 1xx (بيان): وصل الطّلب، وتجري مُعالجته.2xx (نجاح): وصل الطّلب وفُهم وقُبل.3xx (إعادة توجيه): يُطلب إجراء تالٍ لإكمال الطّلب.4xx (خطأ من جهة العميل): صياغة الطّلب خاطئة أو يتعذّر تحقيقه.5xx (خطأ من جهة الخادوم): فشل الخادوم في تحقيق طلب يبدو سليمًا.الخلاصةإلى هنا نكون قد وصلنا إلى نهاية دراستنا لرموز حالة HTTP. أحثّك على الاطّلاع على كلّ رمز والتّعرّف على فائدته. لبعض هذه الرموز تأثيرات خاصّة على التّخزين المؤقّت وعلى متن رسالة HTTP؛ سنُلقي نظرةً على التّخزين المؤقت لاحقًا. تذكّرتُرسل الخواديم رموز حالة HTTP لتزويد العميل بمعلومات سريعة عن الجواب.تؤثّر رموز HTTP في التّخزين المؤقّت ومُعالجة الرّوابط من جهة العميل.تُصنّف رموز HTTP ضمن عدّة مجموعات.ترجمة (بشيء من التّصرّف) لمقال HTTP: Response Codes لصاحبه Karl Dubosy.
  15. OAuth 2 هو آليّة للتّرخيص تسمح للتطبيقات بطلب وصول محدود إلى حسابات المستخدمين في خدمات HTTP، مثل Facebook وGitHub وDigitalOcean. يعمل OAuth 2 بتوكيل الخدمة المستضيفة لحساب المستخدم باستيثاق هذا الحساب، ثمّ السّماح للتطبيقات الخارجيّة بالوصول إلى حساب المستخدم هذا. يوفّر OAuth 2 مسارًا لترخيص تطبيقات الويب وتطبيقات سطح المكتب والأجهزة المحمولة. هذا الدّرس موجّه لمطوّري التّطبيقات، وهو يُلقي الضّوء على أدوار OAuth 2 وأنواع الرُّخَص المتاحة، وكذلك يستعرض مجالات استخدامه وسير عمليّة التّرخيص. لنبدأ بالتّعرّف على أدوار OAuth. أدوار OAuth يُحدِّد OAuth أربعة أدوار: مالك المحتوى العميل خادوم المحتوى خادوم التّرخيص سنُفصّل كلًّا من هذه الأدوار في الفقرات التّالية. مالك المحتوى: المستخدم مالك المحتوى هو المستخدم الذي يُرخِّص لتطبيقٍ الوصول إلى حسابه. وصول التّطبيق إلى حساب المستخدم محدودٌ "بنطاق" (scope) الترخيص الممنوح (مثلاً: صلاحيّة القراءة والكتابة). خادوم المحتوى/التّرخيص: الواجهة البرمجيّة (API) يستضيف خادوم المحتوى حسابات المستخدمين المحميّة، ويتحرّى خادوم التّرخيص هويّة المستخدم ثمّ يمنح التّطبيق رمز وصول (access token). من وجهة نظر مطوّر التّطبيقات، فإنّ الواجهة البرمجيّة للخدمة تؤدّي كلا الدّورين، دور خادوم المحتوى ودور خادوم التّرخيص. سنُشير إلى هذين الدّورين مجتمعين على أنّهما دور الخدمة أو الواجهة البرمجيّة. العميل: التّطبيق العميل هو التّطبيق الّذي يريد الوصول إلى حساب المستخدم، وقبل أن يستطيع ذلك، يجب أن يحصل على "ترخيص" المستخدم، وعلى هذا التّرخيص أن يُصادَق من الواجهة البرمجيّة. سير البروتوكول نظريًّا بعد أن تعرّفنا على أدوار OAuth، دعونا نلقِ نظرةً على المخطّط التالي، والذي يبيّن كيف تتفاعل هذه الأدوار فيما بينها: وفيما يلي شرح أكثرُ تفصيلًا للخطوات المُبيّنة في المُخطّط: يطلب التطبيق رخصةً للوصول إلى الخدمة من المستخدم إن رخّص المستخدم الطّلب، فإنّ التطبيق يحصل على إذن بالتّرخيص يطلب بعدها التطبيق رمز وصول (access token) من خادوم التّرخيص (الواجهة البرمجيّة) مُقدّمًا ما يُثبت هوّيته مع إذن التّرخيص الّذي حصل عليه. إن كانت هويّة التّطبيق موثّقة وإذن التّرخيص سليمًا، أصدر خادوم التّرخيص (الواجهة البرمجيّة) رمز وصول (access token) يمنحه للتّطبيق، لتكتمل حينئذٍ عمليّة الترخيص. يطلب التّطبيق من خادوم المحتوى (الواجهة البرمجيّة) المحتوى المطلوب، مُقدّمًا رمز الوصول الّذي حصل عليه. إن كان رمز الوصول سليمًا، قدّم خادوم المحتوى (الواجهة البرمجيّة) المحتوى المقصود للتطبيق قد يختلف مسار العمليّة الفعليّ بحسب نوع الرّخصة المُستخدمة، ولكن هذه هي الفكرة العامّة. سنستعرض أنواع الرُّخَص المُختلفة في فقرة لاحقة. تسجيل التّطبيق قبل استخدام OAuth في تطبيقاتك، عليك تسجيل التّطبيق في الخدمة المعنيّة. يجري التسجيل عادةً من خلال نموذج في قسم المُطوّرين أو الواجهة البرمجيّة في موقع الخدمة على الويب، حيث ينبغي عليك تقديم البيانات التالية (وربّما معلومات أخرى عن تطبيقك): اسم التّطبيق موقع التّطبيق رابط إعادة الّتوجيه (Redirect URL) أو الاستدعاء الرّاجع (Callback URL) تُعيد الخدمة توجيه المستخدم إلى رابط إعادة التّوجيه الّذي توفّره بعد ترخيصه لتطبيق (أو رفضه)، وعليه فإنّ هذا الرابط هو المسؤول عن التّعامل مع رموز الترخيص أو رموز الوصول (access tokens). مُعرّف العميل وكلمة سرّ العميل ستمنحك الخدمة بعد تسجيل تطبيقك "وثائق اعتماد العميل" المؤلّفة من معرّف العميل وكلمة سرّ العميل. معرّف العميل هو سلسلة من الحروف مكشوفة للعموم تستخدمها الواجهة البرمجيّة للخدمة لتحديد هويّة التّطبيق، ولبناء روابط التّرخيص المُقدّمة للمستخدمين. أمّا كلمة سر العميل فتُستخدم للاستيثاق من هويّة التّطبيق بالنّسبة للواجهة البرمجيّة للخدمة عندما يطلب التّطبيق الوصول إلى حساب المُستخدم، ويجب أن تبقى سرّيّة بين التّطبيق والواجهة البرمجيّة. إذن التّرخيص في فقرة "سير البروتوكول نظريًّا"، تبيّن الخطوات الأربع الأولى كيفيّة الحصول على إذن بالتّرخيص ورمز للوصول. يعتمد نوع الإذن على طريقة طلب التّطبيق للتّرخيص، وأنواع الأذون الّتي تدعمها الواجهة البرمجيّة. يعرّف OAuth 2 أربعة أنواع من أذون التّرخيص، يمكن الاستفادة من كلّ منها في حالات مُختلفة: رمز التّرخيص (Authorization Code): تستخدمه التّطبيقات الّتي تعمل على الخواديم ضمنيّ (Implicit): تستخدمه تطبيقات الويب وتطبيقات الأجهزة المحمولة (أي التّطبيقات الّتي تعمل على جهاز المُستخدم) كلمة مرور مالك المحتوى: تستخدمها التّطبيقات الموثوقة، كتلك الّتي تتبع للخدمة ذاتها كلمة مرور العميل: تستخدم في حالة الوصول للواجهة البرمجيّة للتّطبيقات سنشرح أنواع الأذون وحالات استخدامها بالتّفصيل في الفقرات التّالية. الإذن من نوع "رمز الترخيص": هذا النّوع من الأذون هو الأكثر استخدامًا لأنّه مُصمّم للتطبيقات الّتي تعمل على الخواديم، حيث لا يُكشف النّص المصدريّ للتّطبيق للعموم، وحيث يمكن الاحتفاظ بسرّيّة كلمة سرّ العميل بصورة تامّة. ويعتمد سير التّرخيص هنا على إعادة التّوجيه، ممّا يعني أنّه على التّطبيق أن يكون قادرًا على التّفاعل مع وكيل المستخدم (كمتصفّح الويب الّذي يستخدمه) وعلى استقبال رموز التّرخيص الّتي توفّرها الواجهة البرمجيّة والّتي تمرّ من خلال وكيل المستخدم. سنشرح الآن سير عمليّة التّرخيص في هذا النّوع من الأذون: سير التّرخيص بالرّمز الخطوة 1: رابط رمز الترخيص يُعطى المُستخدم في البداية رابطًا لرمز التّرخيص يُشبه هذا: https://cloud.digitalocean.com/v1/oauth/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=CALLBACK_URL&scope=read فيما يلي شرحٌ لمكوّنات الرابط: https://cloud.digitalocean.com/v1/oauth/authorize: نقطة الوصول إلى قسم التّرخيص في الواجهة البرمجيّة client_id=client_id: مُعرّف العميل للّتطبيق (كيفيّة تحديد الواجهة البرمجيّة لهويّة التّطبيق) redirect_uri=CALLBACK_URL: المكان الّذي تعيد الخدمة توجيه وكيل المستخدم إليه بعد منح رمز الترخيص response_type=code: يُبيّن أنّ تطبيقك يطلب إذنًا بالحصول على رمز ترخيص scope=read: يُعيّن مستوى الوصول الّذي يطلبه المُستخدم الخطوة 2: يُرخّص المستخدم التّطبيق عندما ينقر المُستخدم الرّابط، يجب عليه أوّلًا تسجيل الدّخول إلى الخدمة، وذلك للتّحقق من هويّة المُستخدم (ما لم يكن قد سجّل دخوله من قبل). ثم تعرض عليه الخدمة ترخيص أو رفض وصول التّطبيق إلى حسابه. فيما يلي مثال عن صفحة ترخيص التّطبيق: هذه الصّورة مُلتقطة من صفحة ترخيص DigitalOcean، ونرى فيها التّطبيق "Thedropletbook App" يطلب إذنًا بقراءة حساب المُستخدم "manicas@digitalocean.com". الخطوة 3: يتلقّى التطبيق رمز التّرخيص إذا نقر المُستخدم "Authorize Application"، فإنّ الخدمة تُعيد تحويل وكيل المستخدم إلى رابط إعادة التّوجيه الّذي حدّده التّطبيق أثناء تسجيل المُطوِّر له، وتُرفق الخدمة مع الرّابط رمز التّرخيص. مثال على الرّابط (مُفترضين أنّ التّطبيق هو "dropletbook.com"): https://dropletbook.com/callback?code=AUTHORIZATION_CODE الخطوة 4: يطلب التّطبيق رمز الوصول (Access Token) يطلب التّطبيق من الخدمة رمز وصول، مُمرّرًا لها رمز التّرخيص مع تفاصيله، بما في ذلك كلمة سرّ العميل، والّتي تُرسل جميعها إلى رابط الحصول على رمز الوصول الخاصّ بالخدمة. فيما بلي مثال على طلب POST يُرسل إلى رابط رمز الوصول في DigitalOcean: https://cloud.digitalocean.com/v1/oauth/token?client_id=CLIENT_ID&client_secret=CLIENT_SECRET&grant_type=authorization_code&code=AUTHORIZATION_CODE&redirect_uri=CALLBACK_URL الخطوة 5: يتلقّى التّطبيق رمز الوصول إن كان التّرخيص سليمًا، فإنّ الواجهة البرمجيّة تردّ على الطّلب بجواب يحوي رمز الوصول (مع رمز إعادة تجديد الرّخصة، غير إلزاميّ) إلى التّطبيق. يبدو الجواب مثل هذا: {"access_token":"ACCESS_TOKEN","token_type":"bearer","expires_in":2592000,"refresh_token":"REFRESH_TOKEN","scope":"read","uid":100101,"info":{"name":"Mark E. Mark","email":"mark@thefunkybunch.com"}} أصبح التّطبيق الآن مُرخّصًا! وبإمكانه استخدام الّرمز للوصول إلى حساب المُستخدم عن طريق الواجهة البرمجيّة للخدمة، محدودًا بنطاق الوصول، إلى أن تنتهي مدّة الرّمز أو يُسحب التّرخيص. في حال أُصدر رمز إعادة تجديد الرّخصة (refresh token)، فبإمكان التّطبيق استخدامه للحصول على رمز وصول جديد في حال انتهى مدّة السّابق. الإذن الضّمنيّ يُستخدم نوع الأذون الضّمنيّ في تطبيقات الويب (التي تعمل في المتصفح) وتطبيقات الأجهزة المحمولة، حيث يصعب ضمان سرّية كلمة سرّ العميل. يقوم هذا النّوع من الأذون على مبدأ إعادة التّوجيه أيضًا، إلّا أنّ رمز الوصول يُعطى لوكيل المُستخدم ليقوم بدفعه إلى التّطبيق، وبهذا قد يُكشف للمُستخدم وللتّطبيقات على جهازه. لا يتضمّن سير التّرخيص في هذا النّوع هوّيّة التّطبيق، بل يعتمد على رابط إعادة التّوجيه (الّذي سُجّل في الخدمة) للوصول إلى هذا الهدف. لا يدعم هذا النّوع من الأذون رموز إعادة تجديد التّرخيص. يسير التّرخيص في هذا النّوع كما يلي: يُطلب من المُستخدم ترخيص التّطبيق، ثمّ يُمرّر خادوم التّرخيص رمز الوصول إلى وكيل المُستخدم، الّذي ينقله بدوره إلى التّطبيق. إن كُنت مُهتمًّا بالتّفاصيل، فتابع القراءة. سير التّرخيص الضّمنيّ الخطوة 1: رابط التّرخيص الضّمني يُعرض على المُستخدم رابط التّرخيص، الّذي يطلب رمزًا من الواجهة البرمجيّة، يبدو هذا الرّابط مُشابهًا لرابط رمز التّرخيص، باستثناء أنّه يطلب رمز token بدلًا من code (لاحظ نوع الجواب المطلوب "token"): https://oauth.example.com/authorize?response_type=token&client_id=CLIENT_ID&redirect_uri=CALLBACK_URL&scope=read ملاحظة: لا تدعم DigitalOcean حاليًا التّرخيص الضّمني، لذا ذكرنا رابطًا وهميًّا "oauth.example.com". الخطوة 2: يرخّص المُستخدم التّطبيق عندما ينقر المُستخدم الرّابط، يجب عليه أوّلًا تسجيل الدّخول إلى الخدمة، وذلك للتّحقق من هويّة المُستخدم (ما لم يكن قد سجّل دخوله من قبل). ثم تعرض عليه الخدمة ترخيص أو رفض وصول التّطبيق إلى حسابه. فيما يلي مثال عن صفحة ترخيص التّطبيق: نرى في الصّورة التّطبيق "Thedropletbook App" يطلب إذنًا بقراءة حساب المُستخدم "manicas@digitalocean.com". الخطوة 3: يتلقّى وكيل المُستخدم رمز الوصول مع رابط إعادة التّوجيه إذا نقر المُستخدم "Authorize Application"، فإنّ الخدمة تُعيد تحويل وكيل المستخدم إلى رابط إعادة التّوجيه الّذي حدّده التّطبيق أثناء تسجيل المُطوِّر له، وتُرفق الخدمة مع الرّابط رمز الوصول. مثال على الرّابط: https://dropletbook.com/callback#token=ACCESS_TOKEN الخطوة 4: يتبع وكيل المُستخدم مسار إعادة التّوجيه يتبع وكيل المُستخدم رابط إعادة التّوجيه مع احتفاظه برمز الوصول. الخطوة 5: يُرسل التّطبيق نصًّا برمجيًّا لاستخراج رمز الوصول يُعيد التّطبيق صفحة ويب تحوي نصًّا برمجيًّا بإمكانه استخراج رمز الوصول من رابط إعادة التّوجيه الكامل الّذي احتفظ به وكيل المُستخدم. الخطوة 6: يُمرّر رمز الوصول إلى التّطبيق يُنفّذ وكيل المستخدم النّصّ البرمجيّ ويُمرّر رمز الوصول المُستخرَج إلى التّطبيق. أصبح التّطبيق الآن مُرخّصًا! وبإمكانه استخدام الّرمز للوصول إلى حساب المُستخدم عن طريق الواجهة البرمجيّة للخدمة، محدودًا بنطاق الوصول، إلى أن تنتهي مدّة الرّمز أو يُسحب التّرخيص. ملاحظة: لا تدعم DigitalOcean حاليًا التّرخيص الضّمني، لذا ذكرنا رابطًا وهميًّا "oauth.example.com". الإذن بالوصول إلى كلمة مرور مالك المُحتوى في هذا النّوع من التّرخيص، يزوّد المستخدم التّطبيق مباشرةً باسم حسابه وكلمة مروره، ليستخدمها للحصول على رمز الوصول من الخدمة. يجب استخدام هذا النّوع من الأذون في الخوادم عندما لا تكون الأنواع الأخرى مُناسبة فقط. ويجب استخدامه فقط في حال كان التّطبيق موضع ثقة المُستخدم، كأن يكون تابعًا للخدمة ذاتها، أو أن يكون نظام التّشغيل على حاسوب المُستخدم هو ما يطلب الوصول. سير التّرخيص بالحصول على كلمة مرور المُستخدم بعد أن يُعطي المستخدم كلمة مروره للتّطبيق، يطلب التّطبيق رمز الوصول من خادوم التّرخيص. يُشبه طلب POST ما يلي: https://oauth.example.com/token?grant_type=password&username=USERNAME&password=PASSWORD&client_id=CLIENT_ID إن كان اسم المُستخدم وكلمة المرور صحيحين، يُعيد خادوم التّرخيص رمز وصول للتّطبيق ويُصبح التّطبيق مُرخّصًا! ملاحظة: لا تدعم DigitalOcean حاليًا التّرخيص بالحصول على كلمة المرور، لذا ذكرنا رابطًا وهميًّا "oauth.example.com". الإذن بالوصول إلى كلمة مرور العميل في هذا النّوع من التّرخيص، يوفّر التّطبيق طريقة للوصول إلى حسابه الخاصّ على الخدمة. من الأمثلة الّتي يكون فيها استخدام هذا النّوع مُفيدًا أن يرغب التّطبيق بتحديث وصفه أو رابط إعادة التّوجيه المُسجّلين في الخدمة، أو أن يصل إلى بيانات أخرى حول حسابه على الخدمة عن طريق الواجهة البرمجيّة. سير التّرخيص بالحصول على كلمة مرور العميل يطلب التّطبيق رمز الوصول مُرسلًا مُعرَّفه وكلمة مروره إلى خادوم التّرخيص، فيما يلي مثال عن طلب POST: https://oauth.example.com/token?grant_type=client_credentials&client_id=CLIENT_ID&client_secret=CLIENT_SECRET إن كان مُعرّف التّطبيق وكلمة مروره صحيحين، يُعيد خادوم التّرخيص رمز وصول للتّطبيق ويُصبح التّطبيق مُرخّصًا باستخدام حسابه الخاصّ! ملاحظة: لا تدعم DigitalOcean حاليًا التّرخيص بالحصول على كلمة مرور العميل، لذا ذكرنا رابطًا وهميًّا "oauth.example.com". مثال على استخدام رمز الوصول بعد أن يحصل التّطبيق على رمز الوصول، إمكانه استخدام هذا الّرمز للوصول إلى حساب المُستخدم عن طريق الواجهة البرمجيّة للخدمة، محدودًا بنطاق الوصول، إلى أن تنتهي مدّة الرّمز أو يُسحب التّرخيص. فيما يلي مثال عن طلب يُرسل للواجهة البرمجيّة للخدمة باستخدام curl، لاحظ أنّه يتضمّن رمز الوصول: curl -X POST -H "Authorization: Bearer ACCESS_TOKEN""https://api.digitalocean.com/v2/$OBJECT" على فرض أنّ رمز الوصول سليم، فإنّ الواجهة البرمجيّة تُعالج الطّلب حسب ما صُمِّمت؛ وإلّا أعادت الواجهة خطأ "invalid_request"، كما يحدث عند انتهاء مدّة التّرخيص أو استخدام رمز خاطئ. سير الحصول على رمز إعادة تجديد الرُخصة يؤدّي استخدام رمز وصول بعد انتهاء مدّة صلاحيّته إلى "خطأ رمز غير سليم (Invalid Token Error)". في هذه النّقطة، يمكن استخدام رمز إعادة تجديد الرّخصة في حال أُصدَر مع رمز الوصول للحصول على رمز وصول جديد من خادوم التّرخيص. فيما يلي مثال على طلب POST للحصول على رمز وصول جديد مُستخدمين رمز إعادة تجديد: https://cloud.digitalocean.com/v1/oauth/token?grant_type=refresh_token&client_id=CLIENT_ID&client_secret=CLIENT_SECRET&refresh_token=REFRESH_TOKEN الخاتمة إلى هناك نكون قد وصلنا إلى ختام دليل OAuth. من المُفترض أن لديك الآن فكرة جيّدة عن البروتوكول وكيف يعمل، ومتى يمكن استخدام كلّ نوع من الأذون. إذا أردت تعلّم المزيد عن OAuth 2، اطّلع على هذه المصادر القيّمة (بالإنكليزيّة): How To Use OAuth Authentication with DigitalOcean as a User or Developer How To Use the DigitalOcean API v2 The OAuth 2.0 Authorization Framwork ترجمة (بشيء من التّصرّف) لمقال Introduction to OAuth 2 لصاحبه Mitchell Anicas.
  16. إذا التقى شخصان في قرى الهيمالايا، حيِّا أحدهما الآخر قائلًا: "هل جسدك معافى؟" وأما في اليابان فقد ينحنيان أحيانًا، وفي عُمان يطبع كلّ منهما قبلة على أنف الآخر بعد التصافح، في كمبوديا وتايلاند، يضمّ كلّ منهما يديه وكأنّه يدعو. كل هذه الوسائل هي "بروتوكولات" للتواصل، أي سلسلة بسيطة من الرموز ذات المعنى والّتي تمهّد لتبادل حديث مُفيد. في عالم الويب، لدينا بروتوكول فعّال جدًّا على مستوى التّطبيقات يُمهّد الحواسيب حول العالم لتبادل الأحاديث النّافعة، واسمه Hypertext Transfer Protocol، أو HTTP اختصارًا؛ وهو بروتوكول يُصنّف ضمن طبقة التّطبيقات فوق TCP/IP، وهو أيضًا بروتوكول للتواصل. كثيرًا ما يغيب شرح HTTP في دروس التصميم والتطوير للويب، وهذا أمرٌ مُخزٍ: ففهمه يُعينك في تحسين تفاعل المستخدم وتحقيق أداء أفضل للموقع وإنشساء أدوات فعّالة لإدارة المعلومات على الويب. هذا المقال هو الجزء الأول من سلسلة تهدف إلى تعليم أساسيّات HTTP، وكيف يمكن استخدامه بفعّاليّة أكبر. سنطّلع في هذا الدّرس على محلّ HTTP من الإنترنت. ما معنى بروتوكول تواصل؟ قبل الدّخول في التفاصيل، لنتخيّل موقفًا بسيطًا يحدث فيه تواصل بين طرفين، ولكي يحدث هذا التواصل، فإن على الطّرفين (برنامجين كانا أم جهازين أم شخصين... إلخ.) أن يتّفقا على: الصياغة (تنسيق البيانات) الدلالات (معلومات التحكم والتعامل مع الأخطاء) التوقيت (تطابق السرعة والتتالي) عندما يلتقى اثنان، فإنّهما يتفاهمان من خلال بروتوكل تواصل: ففي اليابان مثلًا، يؤدي أحدهما حركة جسدية، كأن يحني ظهره. وهذه هي الصياغة المعتمدة في التواصل. وفي عادات اليابان، تدل حركة الانحناء هذه (وحركات أخرى مشابهة) على التّحيّة. وبحركة انحناء أحد الشخصين للآخر تنطلق سلسلة من الأحداث بينهما مرتبة بتوقيت معيّن. يتركّب بروتوكل التواصل عبر الشبكات من المكوّنات ذاتها. فأمّا الصّياغة فهي سلسلة من الحروف كالكلمات المفتاحيّة المُستخدمة في كتابة البروتوكول، وأمّا الدلالات فهي المعاني المُرتبطة بكلّ من هذه الكلمات، وأمّا التوقيت فهو ترتيب تبادل هذه الكلمات بين الطّرفين. ما محلّ HTTP من الإنترنت؟ يقوم HTTP نفسه فوق بروتوكولات أخرى. فعند الاتصال بموقع ويب مثل www.example.org، يستخدم وكيل المستخدم (user agent) مجموعة بروتوكولات TCP/IP، والتي صُمّمت في عام 1970 مؤلّفة من 4 طبقات: طبقة الوصلة (Link)، والتي تصف الوصول إلى الوسيط المادّي (كاستخدام بطاقة الشبكة مثلًا) طبقة الإنترنت، والتي تصف كيفيّة تغليف البيانات وتوجيهها (IP أو Internet Protocol) طبقة النقل (Transport)، والتي تصف كيفية نقل البيانات من نقطة الانطلاق إلى الوجهة (TCP وUDP) طبقة التطبيقات (Application)، والتي تصف معنى وصياغة الرسائل المنقولة (HTTP) فـ HTTP إذًا هو بروتوكول على مستوى التطبيقات يقوم على الطبقات السابقة، لا تنسَ هذه الفكرة. يُساعد فصل هذا النّموذج في طبقات على تطوير أجزاءه بصورة منفصلة دون الحاجة لإعادة تصميمها جميعًا. فمثلًا، يمكن تطوير TCP، باعتباره بروتوكولًا في طبقة النّقل، دون الحاجة لتعديل HTTP كونه برتوكولًا في طبقة التّطبيقات. لكن الواقع العمليّ يجعل التفاصيل أكثر تعقيدًا عند الحاجة للوصول إلى تواصل ذي أداء عالٍ. سنركّز في الأجزاء الأولى من هذه السّلسلة على فصل الطّبقات كما هو مُعرَّف في نموذج TCP/IP. صُمِّم HTTP بغرض تبادل المعلومات بين برنامجين من خلال رسائل تُسمّى رسائل HTTP، وتؤثّر طريقة تشكيل هذه الرسائل في العميل (client) والخادوم (server) والأطراف الوسيطة (كالخواديم الوكيلة proxies). لنتواصل مع خادوم! يُعتبر المنفذ رقم 80 المنفذ المبدئيّ للاتّصال بخواديم الويب، ويمكن التأكّد من ذلك بتجربة نُجريها من الطّرفيّة. افتح الطّرفية (أو سطر الأوامر) وجرّب الاتصال بـ www.opera.com على المنفذ 80 مُستخدمًا الأمر التالي: telnet www.opera.com 80 من المُفترض أن يكون الناتج: Trying 195.189.143.147... Connected to front.opera.com. Escape character is '^]'. Connection closed by foreign host. كما نرى فإن الطرفيّة تحاول الاتصال بالخادوم ذي عنوان IP‏ 195.189.143.147. إن لم نفعل شيئًا آخر سيغلق الخادوم الاتصال بنفسه. من الممكن بالطّبع استخدام منفذ آخر بل وحتّى بروتوكول تواصل آخر، ولكن هذه هي الإعدادات الشّائعة. لنتحدّث بلغة HTTP! لنحاول ثانية التواصل مع الخادوم. أدخل الرسالة التالية في الطرفية (أو سطر الأوامر): telnet www.opera.com 80 ما إن يُؤسّس الاتصال، اكتب رسالة HTTP التالية بسرعة (قبل أن يُغلق الخادوم الاتصال بنفسه)، ثم اضغط Enter مرّتين: GET / HTTP/1.1 Host: www.opera.com تُحدّد هذه الرسالة: GET: أي أننا نريد "الحصول على" تمثيل البيانات. /: أي أنّ المعلومات التي نريدها مخزنة في جذر الموقع. HTTP/1.1: أي أننا نتحدث ببروتوكول HTTP ذي الإصدارة 1.1. Host:: أي أننا نريد الوصول إلى الموقع المُحدّد. www.opera.com: اسم الموقع هو www.opera.com. على الخادوم الآن أن يُجيب طلبنا. من المفترض أن تمتلئ نافذة الطرفية بمحتوى مشابه لما يلي: HTTP/1.1 200 OK Date: Wed, 23 Nov 2011 19:41:37 GMT Server: Apache Content-Type: text/html; charset=utf-8 Set-Cookie: language=none; path=/; domain=www.opera.com; expires=Thu, 25-Aug-2011 19:41:38 GMT Set-Cookie: language=en; path=/; domain=.opera.com; expires=Sat, 20-Nov-2021 19:41:38 GMT Vary: Accept-Encoding Transfer-Encoding: chunked <!DOCTYPE html> <html lang="en"> ... يقول الخادوم هنا: "أنا أتحدث HTTP الإصدارة 1.1. نجحَ طلبك، لذا أجبت بالرمز 200." الكلمة OK ليست إلزامية والهدف منها شرح معنى الرمز للبشر - وهي تُشير في حالتنا إلى أن الأمور تسير على ما يرام وأن رسالتنا قُبلت. يلي ذلك سلسلة من "ترويسات HTTP" التي تُرسل لتصف الرسالة، وكيف يجب أن تُفهم. أخيرًا نجد محتويات الصفحة المُستضافة على جذر الموقع، والّتي تبدأ بـ <!DOCTYPE html>.
  17. ‏AJAX‏AJAX هي اختصار للعبارة "asynchronous JavaScript and XML"، وهي وسيلة لجلب البيانات من الخادوم دون الحاجة لإعادة تحميل الصّفحة، وهي تقوم على استخدام كائن مُتاح في المتصفّح اسمه XMLHttpRequest (أو XHR اختصارًا) لإرسال الطّلب إلى الخادوم ثمّ التّعامل مع البيانات الّتي يُجيب بها الخادوم. تُوفّر jQuery الوظيفة ‎$.ajax‎ (ووظائف أخرى مرافقة مُختصرة) لتسهيل العمل مع طلبات XHR في جميع المتصفّحات. ‏‎$.ajax‎بإمكاننا استخدام الوظيفة ‎$.ajax()‎ المُرفقة مع jQuery بعدّة أساليب: إحداها أن نُمرّر إليها كائنًا يحوي الإعدادات فقط، أو أن نُمرّر الرّابط مع أو بدون كائن الإعدادات. لنُلقِ نظرة على الأسلوب الأول: // أنشئ دالّة الاستدعاء الرّاجع الّتي ستُنفّذ عندما ينجح طلب AJAX var updatePage = function( resp ) { $( '#target').html( resp.people[0].name ); }; // وعندما يفشل var printError = function( req, status, err ) { console.log( 'something went wrong', status, err ); }; // أنشئ كائن الإعدادات الذي يصف الطّلب var ajaxOptions = { url: '/data/people.json', dataType: 'json', success: updatePage, error: printError }; // أرسل الطّلب $.ajax(ajaxOptions);بإمكانك طبعًا أن تُمرّر كائنًا حرفيًّا مباشرةً إلى الوظيفة‎$.ajax() ‎ وأن تستخدم دالّة مجهولة محلّ success وerror، هذا الأسلوب كتابته أسهل، وصيانته في المستقبل أسهل: $.ajax({ url: '/data/people.json', dataType: 'json', success: function( resp ) { $( '#target').html( resp.people[0].name ); }, error: function( req, status, err ) { console.log( 'something went wrong', status, err ); } });كما قلنا، بإمكانك استخدام الوظيفة‎$.ajax() ‎ بأسلوب ثانٍ، وذلك بتمرير الرّابط أوّلًا ثمّ كائن الإعدادات ثانيًا (ليس إلزاميًّا). يُفيدك هذا في حال رغبت في استخدام الإعدادات المبدئيّة للوظيفة أو في حال رغبت في استخدام كائن الإعدادات نفسه لأكثر من رابط: $.ajax( '/data/people.json', { type: 'GET', dataType: 'json', success: function( resp ) { console.log( resp.people ); }, error: function( req, status, err ) { console.log( 'something went wrong', status, err ); } });في المثال السّابق، لا تشترط الوظيفة سوى الرّابط، ولكنّ إضافة كائن الإعدادات تسمح لنا بإخبار jQuery بنوع البيانات الّتي نُرسلها، وأي فعل HTTP نستخدمه (POST، GET، إلخ...‏‏)، وما نوع البيانات الّتي نتوقّع استقبالها من الخادوم، وما الّذي يجب فعله إن نجح الطّلب أو فشل... اطّلع على وثائق الوظيفة‎$.ajax() ‎ لقراءة كامل الخيارات الّتي يمكن إضافتها إلى كائن الإعدادات. ‏A في AJAX تعني "لامتزامن"تجري طلبات AJAX بصورة لا متزامنة، وهذا يعني أنّ الوظيفة‎$.ajax ‎ تنتهي قبل انتهاء الطّلب، وقبل أن تُستدعى دّالة success، أيّ أنّ جملة return تُنفّذ قبل أن يصل جواب الطّلب. فالدّالة getSomeData في المثال التّالي ستُعيد قيمة data قبل أن تُعرّف، مما يؤدّي إلى وقوع خطأ: تحذير: نصّ برمجيّ غير سليم var getSomeData = function() { var data; $.ajax({ url: '/data/people.json', dataType: 'json', success: function(resp) { data = resp.people; } }); return data; } $( '#target' ).html( getSomeData().people[0].name );‏X في AJAX تعني JSON!وضع المصطلح AJAX عام 2005 ليصف طريقة لجلب البيانات من الخادوم دون الحاجة لإعادة تحميل كامل الصّفحة. في ذلك الوقت، كانت الصّيغة الأكثر شيوعًا للبيانات الّتي تُرسلها الخوادم هي XML، أمّا اليوم، فإنّ JSON هي الصّيغة الّتي تعتمدها أكثر التّطبيقات الحديثة. صيغة JSON في أساسها هي سلسلة نصّيّة (string) تُمثّل البيانات، وتبدو مُشابهة كثيرًا لكائن JavaScript عاديّ، ولكنّها لا تستطيع تمثيل كلّ أنواع البيانات الّتي يستطيع كائن JavaScript تمثيلها. فمثلًا: لا يمكن لـJSON تمثيل كائنات التّاريخ (Date) ولا الدّوال (functions). فيما يلي مثال عن نصّ JSON، لاحظ كيف تُحاط كلّ أسماء الخصائص بعلامتي اقتباس مُضاعفتين: { "people" : [ { "name" : "Ben", "url" : "http://benalman.com/", "bio" : "I create groovy websites, useful jQuery plugins, and play a mean funk bass. I'm also Director of Pluginization at @bocoup." }, { "name" : "Rebecca", "url" : "http://rmurphey.com", "bio" : "Senior JS dev at Bocoup" }, { "name" : "Jory", "url" : "http://joryburson.com", "bio" : "super-enthusiastic about open web education @bocoup. lover of media, art, and fake mustaches." } ] }تذكّر أنّ JSON هو تمثيل نصّيّ لكائن، ما يعني أنّه يجب تفسير السّلسلة النّصيّة لتحويلها إلى كائن JavaScript عاديّ قبل التّعامل معها. عندما تعمل مع جواب ورد من الخادوم بصيغة JSON، فإنّ jQuery تتولّى هذه المهمّة عنك. ولكن من المهمّ التمييز بين الكائنات الفعليّة، وطريقة تمثيلها في JSON. ملاحظة: إن أردت إنشاء سلسلة JSON نصّيّة من كائن JavaScript أو تفسير سلسلة JSON نصّيّة لتحويلها إلى كائن JavaScript دون الاستعانة بـjQuery، فإنّ المُتصفّحات الحديثة تُقدّم الوظيفتين ‎JSON.stringify()‎ و‎JSON.parse()‎، ويمكن إضافة هذه الخصائص إلى المُتصفّحات القديمة باستخدام المكتبة json2.js. توفّر jQuery أيضًا وظيفة ‎jQuery.parseJSON()‎، الّتي توافق الوظيفة ‎JSON.parse()‎ في المتصفّحات، إلّا أنّها لا توفّر وظيفة تُقابل ‎JSON.stringify()‎. وظائف مُختصرةإن كان كلّ ما نريده إرسال طلب بسيط، دون الاهتمام بالتّعامل مع الأخطاء الّتي قد تقع، فإنّ jQuery تُوفّر وظائف مُختصرة تسمح لنا بفعل ذلك. تستقبل كل وظيفة مُختصرة رابطًا وكائن إعدادات غير إلزاميّ، ودالّة تُستدعى عند نجاح الطّلب فقط: $.get( '/data/people.html', function( html ){ $( '#target' ).html( html ); }); $.post( '/data/save', { name: 'Rebecca' }, function( resp ) { console.log( resp ); });إرسال البيانات والعمل مع النّماذجبإمكاننا إرسال بيانات مع طلبنا بتعيين قيمة للخاصة data في كائن الإعدادات، أو تمرير كائن كمُعامل ثانٍ للوظائف المُختصرة. ستُضاف هذه البيانات إلى الرّابط في طلبات GET بصورة "جملة استعلام" (query string)، أمّا في طلبات POST فإنّها ستُرسل كبيانات نموذج. توفّر jQuery وظيفة مُفيدة ‎.serialize()‎ الّتي تستقبل مُدخلات نموذج وتُحوّلها إلى صيغة "جملة استعلام" (مثل field1name=field1value&field2name=field2value...): $( 'form' ).submit(function( event ) { event.preventDefault(); var form = $( this ); $.ajax({ type: 'POST', url: '/data/save', data: form.serialize(), dataType: 'json', success: function( resp ) { console.log( resp ); } }); });‏jqXHRتُعيد‎$.ajax() ‎ والوظائف المُختصرة المرافقة لها، كائن jqXHR (اختصارًا لـjQuery XML HTTP Request) والّذي يتضمّن وظائف مُفيدةً كثيرة. بإمكاننا إرسال طلب باستخدام ‎$.ajax()‎ ثمّ حفظ كائن jqXHR في مُتغيّر: var req = $.ajax({ url: '/data/people.json', dataType: 'json' });بإمكاننا استخدام هذا العنصر لربط الاستدعاءات الرّاجعة بالطّلب، حتّى بعد أن يكتمل الطّلب. بإمكاننا مثلًا استخدام الوظيفة ‎.then()‎ (ثُمَّ) لإرفاق استدعاءي نجاح الطّلب وفشله، إذ تقبل ‎.then()‎ دالّة أو اثنتين، تستدعى الأولى عند نجاح الطّلب، والثّانية إن فشل: var success = function( resp ) { $( '#target' ).append( '<p>people: ' + resp.people.length + '</p>' ); console.log( resp.people ); }; var err = function( req, status, err ) { $( '#target' ).append( '<p>something went wrong</p>' ); }; req.then( success, err ); req.then(function() { $( '#target' ).append( '<p>it worked</p>' ); });بإمكاننا استدعاء ‎.then()‎ على كائن الطّلب قدر ما نشاء، وستُنفّذ الاستدعاءات الرّاجعة بالتّرتيب ذاته الّتي أرفقت وفقه. إن لم نُرد إرفاق استدعاءي النّجاح والفشل معًا، فبإمكاننا استخدام الوظيفتين ‎.done()‎ و‎.fail()‎ على كائن الطّلب: req.done( success ); req.fail( err );لو أردنا إرفاق استدعاء راجعٍ يُنفَّذ دومًا، بغض النّظر عن نجاح الطّلب أو فشله، فيمكننا استخدام الوظيفة ‎.always()‎ على كائن الطّلب: req.always(function() { $( '#target' ) .append( '<p>one way or another, it is done now</p>' ); });‏JSONPيستغرب كثيرٌ من المبتدئين في JavaScript فشل طلبات XHR الّتي يرسلونها إلى نطاق آخر على الإنترنت، فمثلاً: يحاول بعض المُطوّرين جلب بيانات من واجهة برمجيّة من طرف ثالث (third-party API)، ليفاجؤوا بفشل الطّلب باستمرار. السّبب وراء ذلك أنّ المُتصفّحات لا تسمح بإرسال طلبات XHR إلى نطاقات إنترنت أخرى لأسباب أمنيّة، ولكنّ بعض الواجهات البرمجيّة تُعيد البيانات بصيغة JSONP (اختصارًا لـJSON with Padding)، الّتي تسمح للمُطوّرين بجلب البيانات متجاوزين حظر المُتصفّح. الحقيقة أن JSONP ليس طلب AJAX فعليًّا، فهو لا يستخدم طلب XHR الّذي يوفّره المُتصفّح، بل يعمل بإدراج وسم <script> في صفحة الويب، الّذي يحوي بدوره البيانات المطلوبة، مُحاطة بدالّة تُعيد هذه البيانات عند استدعائها. ليس هذه التّفاصيل مهمّة الآن، لأنّ jQuery تسمح لك بطلب JSONP كما لو كان XHR باستخدام الوظيفة ‎$.ajax()‎ بتعيين نوع البيانات dataType إلى 'jsonp' في كائن الإعدادات. $.ajax({ url: '/data/search.jsonp', data: { q: 'a' }, dataType: 'jsonp', success: function( resp ) { $( '#target' ).html( 'Results: ' + resp.results.length ); } });ملاحظة: عادةً ما توفّر الواجهات البرمجيّة خيارًا لتعيين اسم الدّالّة الّتي تُحيط بالبيانات والّتي ستُستدعى في عنوان الرّابط. عادةً ما يكون هذا اسم مُعامل الرّابط callback، وهذا ما تتوقّعه jQuery مبدئيًّا، إلّا أن بإمكانك تغييره بتعيين قيمة للخاصة jsonp في كائن الإعدادات الّذي تُمرّره لـ ‎$.ajax()‎. بإمكانك أيضًا استخدام الوظيفة المُختصرة ‎$.getJSON()‎ لإرسال طلب JSONP، حيث تستطيع jQuery تمييزه من خلال وجود ‎callback=?‎ أو ما يشبهها في الرّابط: $.getJSON( '/data/search.jsonp?q=a&callback=?', function( resp ) { $( '#target' ).html( 'Results: ' + resp.results.length ); } );مشاركة الموارد عبر الأصول (cross-origin resource sharing أو CORS اختصارًا) هي خيارٌ آخر للسّماح بالطّلبات العابرة للأصول. ولكنّها غير مدعومة في المتصفّحات القديمة، كما أنّها تحتاج تهيئة خاصّة على الخادوم وتعديل ترويسات الطّلبات في XHR لتعمل. الكائنات المُؤجّلة (Deferreds)ليست كائنات jqXHR الّتي تعرّفنا عليها إلا "نكهة" خاصّة ممّا يُعرف "بالكائنات المؤجّلة". تسمح jQuery لك بإنشاء كائنات مؤجّلة بنفسك، والّتي يمكن الاستفادة منها في تسهيل التّعامل مع الأوامر اللامتزامنة، فهي توفّر طريقة للاستجابة لعمليّة تجري بصورة غير متزامنة بعد نجاحها أو فشلها، وتجنّبك الحاجة لكتابة استدعاءات راجعة مُتداخلة فيما بينها. ‏‎$.Deferred‎بإمكانك إنشاء كائنٍ مؤجّل باستخدام ‏‎$.Deferred()‎. في المثال التّالي نُنفّذ دالة داخل setTimeout، ثمّ "نفي" (resolve) بوعدنا بإعادة القيمة الّتي تُرجعها الدّالة هذه. نُعيد الوعد (promise)، وهو كائن يمكن ربط الاستدعاءات الرّاجعة به، ولكنّه لا يؤثّر في نتيجة الكائن المؤجّل بحدّ ذاته. بإمكاننا "الإخلاف" (reject) بالوعد إذا وقع خطأ ما أثناء عمل الدّالّة: function doSomethingLater( fn, time ) { var dfd = $.Deferred(); setTimeout(function() { dfd.resolve( fn() ); }, time || 0); return dfd.promise(); } var promise = doSomethingLater(function() { console.log( 'This function will be called in 100ms' ); }, 100);‏‎.then()‎ و‎.done()‎ و‎.fail()‎ و‎.always()‎يمكننا ربط دوالّ تتولّى حالات الخطأ والنّجاح بالوعود، تمامًا كما في كائنات jqXHR: function doSomethingLater( fn, time ) { var dfd = $.Deferred(); setTimeout(function() { dfd.resolve( fn() ); }, time || 0); return dfd.promise(); } var success = function( resp ) { $( '#target' ).html( 'it worked' ); }; var err = function( req, status, err ) { $( '#target' ).html( 'it failed' ); }; var dfd = doSomethingLater(function() { /* ... */ }, 100); dfd.then( success, err );‏‎.pipe()‎بإمكاننا استخدام الوظيفة ‏‎.pipe()‎ للوعود للاستجابة إلى القيمة الّتي تُوفى وذلك بتعديلها ثمّ إعادة كائن مؤجّل جديد. تعمل الوظيفة ‏‎.then()‎ بدءًا من الإصدارة 1.8 من jQuery كما تعمل الوظيفة ‏‎.pipe()‎. function doSomethingLater( fn, time ) { var dfd = $.Deferred(); setTimeout(function() { dfd.resolve( fn() ); }, time || 0); return dfd.promise(); } var dfd = doSomethingLater(function() { return 1; }, 100); dfd .pipe(function(resp) { return resp + ' ' + resp; }) .done(function(upperCaseResp) { $( '#target' ).html( upperCaseResp ); });التّعامل مع العمليّات الّتي قد تكون لامتزامنةأحيانًا تكون لدينا وظيفة قد تعمل بصورة متزامنة أو لا متزامنة وفق ظروف مُعيّنة، فمثلًا: دالّة تقوم بعمليّة لا متزامنة أوّل مرّة تُستدعى فيها، ثمّ تُخزّن القيمة الّتي أنتجتها العمليّة لتُعيدها مباشرةً عند استدعاءها مُستقبلًا. في هذه الحالة يمكننا الاستفادة من ‎$.when()‎ للاستجابة لكلا الحالتين: function maybeAsync( num ) { var dfd = $.Deferred(); // أعِد وعدًا مؤجّلًا عندما num === 1 if ( num === 1 ) { setTimeout(function() { dfd.resolve( num ); }, 100); return dfd.promise(); } // أنهِ مباشرة فيما سوى ذلك، مُعيدًا num return num; } // هذا سيُجرى بصورة غير متزامنة ويعِد بإعادة 1 $.when( maybeAsync( 1 ) ).then(function( resp ) { $( '#target' ).append( '<p>' + resp + '</p>' ); }); // هذا سُيعيد 0 مُباشرةً $.when( maybeAsync( 0 ) ).then(function( resp ) { $( '#target' ).append( '<p>' + resp + '</p>' ); });بإمكانك أيضًا تمرير أكثر من معامل إلى ‎$.when()‎، الأمر الّذي يسمح لك بدمج عمليّات متزامنة ولا متزامنة معًا ثمّ الحصول على نتائج تنفيذها كلّها كُمعاملات للاستدعاء الرّاجع: function maybeAsync( num ) { var dfd = $.Deferred(); // أعد وعدًا مؤجّلًا عندما num === 1 if ( num === 1 ) { setTimeout(function() { dfd.resolve( num ); }, 100); return dfd.promise(); } // أنهِ مباشرةً فيما سوى ذلك، مُعيدًا num return num; } $.when( maybeAsync( 0 ), maybeAsync( 1 ) ) .then(function( resp1, resp2 ) { var target = $( '#target' ); target.append( '<p>' + resp1 + '</p>' ); target.append( '<p>' + resp2 + '</p>' ); });عندما يكون إحدى مُعاملات ‎$.when()‎ كائن jqXHR، فإنّنا نحصل على مصفوفة من المُعاملات تُمرّر إلى استدعائنا الرّاجع: function maybeAsync( num ) { var dfd = $.Deferred(); // أعد وعدًا مؤجّلًا عندما num === 1 if ( num === 1 ) { setTimeout(function() { dfd.resolve( num ); }, 100); return dfd.promise(); } // أنهِ مباشرةً فيما سوى ذلك، مُعيدًا num return num; } $.when( maybeAsync( 0 ), $.get( '/data/people.json' ) ) .then(function( resp1, resp2 ) { console.log( "Both operations are done", resp1, resp2 ); });مصادر إضافيةتوثيق AJAXكائن jqXHRالكائنات المؤجّلة في jQueryترجمة (بشيء من التصرف) للجزء السادس من سلسلة  jQuery Fundamentals لمؤلّفتها Rebecca Murphey.
  18. code { font-size: 1rem !important; }تجعل jQuery إضافة التأثيرات الحركيّة على الصّفحة أمرًا سهلًا للغاية، ويمكن لهذه التأثيرات أن تعتمد الإعدادات المبدئيّة أو إعدادات يُعيّنها المُطوّر. بإمكانك أيضًا إنشاء حركاتٍ مُخصّصة من خصائص CSS عشوائيّة. اطّلع على وثائق التأثيرات لتفاصيل أكثر عن تأثيرات jQuery. ملاحظة مهمّة عن الحركات: يكون إنجاز الحركات باستخدام CSS بدل JavaScript أكثر كفاءةً في المُتصفّحات الحديثة، وخصوصًا في الأجهزة المحمولة. تفاصيل إنجاز هذه الحركات خارجةٌ عن نطاق السّلسلة، ولكن إن كنت تستهدف المُتصفّحات والأجهزة المحمولة الّتي تدعم حركات CSS، فقد ترغب بتعيين الإعداد jQuery.fx.off إلى القيمة true على الأجهزة ذات المواصفات الضّعيفة؛ فهذا من شأنه إبطال الحركات والوصول بالعنصر المطلوب تحريكه إلى حالته النّهائية مباشرةً دون تطبيق الحركة. التأثيرات المُرفقة مع jQueryتُرفَق الحركات المُستخدم بكثرة مع jQuery كوظائف يمكنك استدعاؤها على أي كائن jQuery: ‏‎.show()‎: أظهر العناصر المُحدّدة.‏‎.hide()‎: أخفِ العناصر المُحدّدة.‏‎.fadeIn()‎: حرّك ظلاليّة العناصر (opacity) المُحدّدة إلى 100%.‏‎.fadeOut()‎: حرّك ظلاليّة العناصر المُحدّدة إلى 0%.‏‎.slideDown()‎: أظهر العناصر المُحدّدة بحركة سحب شاقوليّة.‏‎.slideUp()‎: أخفِ العناصر المُحدّدة بحركة سحب شاقوليّة.‏‎.slideToggle()‎: أخفِ العناصر المُحدّدة أو أظهرها بحركة سحبٍ شاقوليّة، اعتمادًا على كون العناصر المُحدّدة مخفيّة أو ظاهرة.يسهل تطبيق إحدى هذه التأثيرات على التّحديد بعد إنشائه: $( '.hidden' ).show();جرّب المثال في ساحة التّجربة (تأكد من ضغط الزرّ Run with JS في هذ المثال وكلّ الأمثلة التّالية) بإمكانك أيضًا تحديدُ مدّة للتأثيرات السّابقة، وهناك طريقتان لتحديدها، الأولى: تعيين الوقت بالميللي ثانيّة: $( '.hidden' ).show( 300 );جرّب المثال في ساحة التّجربة والثّانية استخدام إحدى السُرعات المُعرّفة مُسبقًا: $( '.hidden' ).show( 'slow' );جرّب المثال في ساحة التّجربة عُرِّفت هذه السُرعات في الكائن jQuert.fx.speeds؛ ممّا يعني أنّ بإمكانك تعديله لتغيير القيم المبدئيّة، أو إضافة سُرعات جديدة إليه: // أعد تعيين سرعةٍ مُعرّفة jQuery.fx.speeds.fast = 50; // عرّف سرعة جديدة jQuery.fx.speeds.turtle = 3000; // بما أنّنا غيّر قيمة السّرعة `fast`، فإنّ هذه الحركة ستستغرق 50 ميللي ثانية $( '.hidden' ).hide( 'fast' ); // بإمكاننا استخدام السّرعات الّتي عرفناها بأنفسنا تمامًا كتلك المُعرّفة مسبقًا $( '.other-hidden' ).show( 'turtle' );كثيرًا ما يرغب المُطوّر بفعل شيءٍ ما بعد انتهاء الحركة مباشرةً، فإن حاول فعله قبل انتهاء الحركة، فقد يسبّب تشوّه الحركة وتقطّعها، أو قد يحذف سهوًا عناصر تتحرّك في لحظة حركتها. بإمكانك تمرير استدعاء راجع (callback) إلى وظائف الحركة إن رغبت بتنفيذ أمرٍ ما بعد انتهاء التأثير، وتُشير this داخل هذا الاستدعاء إلى عنصر DOM الخام الّذي طُبقّت عليه الحركة، ومثلها ومثل دوالّ تولّي الأحداث، يمكن إحاطة this بالوظيفة ‎$()‎ لاستخدامها ككائن jQuery: $( 'p.old' ).fadeOut( 300, function() { $( this ).remove(); });جرّب المثال في ساحة التّجربة إن لم يحوِ التّحديد أيّة عناصر، فلن تُستدعى الدّالة. إن احتجت إلى استدعاء الدّالة بصرف النّظر عن وجود العناصر أو غيابها في التّحديد، بإمكانك إنشاء دالّة تتعامل مع الحالتين: var oldElements = $( 'p.old' ); var thingToAnimate = $( '#nonexistent' ); // هذه الدّالة ستكون الاستدعاء الرّاجع للوظيفة `show` في حال وجود عناصر نريد إظهارها، فإن لم توجد أيّة عناصر، فإنّنا نستدعيها مباشرةً بأنفسنا. var removeOldElements = function() { oldElements.remove(); }; if ( thingToAnimate.length ) { // ستُستدعى وظيفتنا بعد انتهاء الحركة thingToAnimate.show( 'slow', removeOldElements ); } else { removeOldElements(); }جرّب المثال في ساحة التّجربة تأثيرات مُخصّصة باستخدام ‎.animate()‎إن لم تُلبِّ الحركات المُرفقة مع jQuery حاجتك، فبإمكانك استخدام الوظيفة ‎.animate()‎ لإنشاء حركات مخصّصة قائمة على خصائص CSS مُتعدّدة (إحدى الاستثناءات: الخاصّ' color الّتي لا يمكن تحريكها، ولكن تتوفّر إضافة تسمح بذلك). تقبل الوظيفة ‎.animate()‎ ثلاثة مُعاملات على الأكثر: كائن يُحدّد الخصائص الّتي يُراد تحريكهامدّة الحركة، مُقدّرة بالميللي ثانيةدالّة تُستدعى عند انتهاء الحركةيمكن أن تُعيّن قيمة الحركة بكتابة القيمة النّهائيّة المُراد التّحريك إليها، أو كتابة المقدار الّذي يجب تحريكه (الفرق بين موضعي الحركة): $( '.funtimes' ).animate({ left: '+=50', // زد بمقدار 50 opacity: 0.25, fontSize: '12px' }, 300, function() { // تنفّذ عند انتهاء الحركة } );جرّب المثال في ساحة التّجربة ملاحظة: إن أردت تحريك خاصّة CSS يحوي اسمها على الإشارة "-"، فعليك تحويل الاسم إلى صيغة camelCase أوّلًا إن لم تشأ إحاطة اسم الخاصّة بعلامات اقتباس، فمثلًا الخاصّة font-size تُصبح fontSize. إدارة الحركاتتُوفّر jQuery وظيفتين مُهمّتين لإدارة الحركات: ‏‎.stop()‎: تُوقف الحركات الجارية على العناصر المُحدّدة.‏‎.delay()‎: تُؤخِّر بدء الحركة القادمة بالمقدار الذي يُمرّر إليها (بالميللي ثانية).تُوفّر jQuery أيضًا وظائف لإدارة تعاقب الحركات وتنظيمها في "طوابير"، وإنشاء طوابير مُخصّة، وإضافة دوالّ مُخصّصة إلى هذه الطّوابير. مناقشة هذه الوظائف موضوع أكبر من هذه السّلسلة، ولكن قد ترغب بالاطّلاع عليها في وثائق jQuery. ترجمة (بشيء من التصرف) للجزء الخامس من سلسلة  jQuery Fundamentals لمؤلّفتها Rebecca Murphey.
  19. تُسهّل jQuery الاستجابة لتفاعل المُستخدم مع صفحات الويب. معنى هذا أنّ بإمكانك تنفيذ أمرٍ ما عندما ينقر المُستخدم على جزءٍ مُعيّن من الصّفحة، أو عندما يُحرّك مؤشّر الفأرة فوق عنصر في نموذج مثلاً. في المثال التّالي، لدينا أمر يُنفَّذ عندما ينقر المُستخدم فوق أيّ عنصر قائمة في الصّفحة: $( 'li' ).click(function( event ) { console.log( 'clicked', $( this ).text() ); });جرّب المثال في ساحة التّجربة  (تأكد من ضغط الزرّ Run with JS في هذ المثال وكلّ الأمثلة التّالية) يُحدّد النّصّ السّابق كلّ عناصر القائمة ويُسند إليها دالّة تتولّى حدث النّقر على كلّ عنصر، باستخدام وظيفة ‎.click()‎ في jQuery. توفّر jQuery وظائف مُختصرةً عديدةً لربط الأحداث، وكلّ من هذه الوظائف يوافق حدث DOM أصليًّا: td {direction:ltr; }اسم الحدث الأصليّ                    الوظيفة المُختصرة‏click‎.click()‏keydown.keydown()‏keypress.keypress()‏keyup.keyup()‏mouseover.mouseover()‏mouseout.mouseout()‏mouseenter.mouseenter()‏mouseleave.mouseleave()‏scroll.scroll()‏focus.focus()‏blur.blur()‏resize.resize()تستخدم هذه الوظائف المُختصرة الوظيفة ‎.on()‎ ما وراء الكواليس، وهي وظيفة يمكنك استخدامها بنفسك لمرونةٍ أكبر. وعند استخدامك لها، فإنّك تُمرّر اسم الحدث الأصليّ كمُعامل أوّل للوظيفة، ثمّ دالّة تتولى الحدث كمعامل ثانٍ: $( 'li' ).on( 'click', function( event ) { console.log( 'clicked', $( this ).text() ); });جرّب المثال في ساحة التّجربة ما إن "تربط" مُتولّي الحدث بعنصرٍ من العناصر، فبإمكانك إثارة هذا الحدث بـjQuery أيضًا: $( 'li' ).trigger( 'click' );وإن كان للحدث الّذي تُريد إثارته وظيفةٌ مختصرة (كما ورد في الجدول السّابق)، فبإمكانك إثارة الحدث باستدعاء الوظيفة المختصرة ذاتها: $( 'li' ).click();ملاحظة: عندما تُثير حدثًا باستخدام ‎.trigger()‎، فإنّك تستدعي مُتولّيات الأحداث الّتي أُنشئت في JavaScript فقط ولا تستدعي السّلوك الافتراضيّ للحدث. فمثلًا إن أثرت حدث النّقر على رابط (عنصر <a>) فلن ينتقل المتصفّح إلى الرّابط المُسند إليه في صفة href (مع أنّ بإمكانك كتابة أوامر تُنفّذ هذه الغاية). بعد أن تربط حدثًا بُعنصر، بإمكانك فكّ هذا الارتباط باستخدام الوظيفة ‎.off()‎ الّتي تُزيل أية مُتولّيات ارتبطت بهذا الحدث: $( 'li' ).off( 'click' );حصر الأحداث ضمن فضاء أسماءمن المزايا الّتي تُتيحها ‎.on()‎ إمكانيّة حصر الأحداث ضمن "فضاء أسماء". قد تتساءل عن الحاجة لذلك. افترض مثلًا أنّك تريد ربط بعض الأحداث بعنصر ما، ثمّ إزالة بعض المُتولّيات، يمكنك أن تفعل ما فعلناه في الفقرة الماضية: تحذير: أسلوب برمجيّ غير مُفضّل $( 'li' ).on( 'click', function() { console.log( 'a list item was clicked' ); }); $( 'li' ).on( 'click', function() { registerClick(); doSomethingElse(); }); $( 'li' ).off( 'click' );إلّا أنّ هذا الأسلوب سيُزيل كلّ مُتولّيات النقر على كلّ عناصر القوائم، وليس هذا ما نريد. إذا ربطت متولّيًا للأحداث محصورًا ضمن فضاء أسماء، فبإمكانك استهدافه بدقّة: $( 'li' ).on( 'click.logging', function() { console.log( 'a list item was clicked' ); }); $( 'li' ).on( 'click.analytics', function() { registerClick(); doSomethingElse(); }); $( 'li' ).off( 'click.logging' );هذا الأسلوب لا يؤثّر على الأحداث المرتبطة بالنّقر والمُتعلّقة بأغراض إحصاءات الاستخدام في الصّفحة، بينما يزيل أحداث النّقر المُتعلّقة بالسّجلّات. بإمكاننا استخدام ميزة حصر الأحداث لإثارة أحداثٍ مُحدّدة: $( 'li' ).trigger( 'click.logging' );ربط أحداث مُتعدّدة في وقت واحدميزة أخرى تُوفّرها ‎.on()‎، وهي إمكانيّة ربط أحداث مُتعدّدة في وقتٍ واحد. افترض مثلًا أنّك تريد تنفيذ أمرٍ مُعيّن عندما يُمرّر المستخدم الصّفحة أو يغيّر قياس النّافذة. فهذه الوظيفة تُتيح لك تمرير الحدثين معًا مفصولين بمسافة في سلسلة نصيّة، يتبعهما الدّالّة الّتي تريد أن تتولّى الحدثين: $( 'input[type="text"]' ).on('focus blur', function() { console.log( 'The user focused or blurred the input' ); }); $( window ).on( 'resize.foo scroll.bar', function() { console.log( 'The window has been resized or scrolled!' ); });جرّب المثال في ساحة التّجربة تمرير دوالّ مُسمّاة كُمتولّيات الأحداثفي كلّ أمثلتنا السّابقة، كنّا نُمرّر دوالّ مجهولة كمُتولّيات للأحداث، ولكن يمكننا إنشاء دالّة قبل إمرارها وحفظها في مُتغيّر ثمّ تمرير هذا المُتغيّر كمُتولّي الحدث. هذا يُفيد في حال أردت استخدام الدّالة نفسها لتتولّى أحداثًا مُختلفة أو أحداثًا من عناصر مُختلفة: var handleClick = function() { console.log( 'something was clicked' ); }; $( 'li' ).on( 'click', handleClick ); $( 'h1' ).on( 'click', handleClick );كائن الحدثفي كلّ مرّة يُثار فيها حدثٌ ما، تستقبل الدّالّة المُتولّية للحدث مُعاملًا واحدًا، وهو كائن الحدث الّذي يتبع معايير مُتّفقًا عليها بين كلّ المُتصفّحات. ولهذا الكائن خصائص مُفيدة كثيرة، منها: $( document ).on( 'click', function( event ) { console.log( event.type ); // نوع الحدث، مثل: "click" console.log( event.which ); // الزرّ أو المفتاح الّذي ضغط console.log( event.target ); // العنصر الّذي انطلق منه الحدث console.log( event.pageX ); // موقع مؤشّر الفأرة على المحور X console.log( event.pageY ); // موقع مؤشّر الفأرة على المحور Y });جرّب المثال في ساحة التّجربة داخل مُتولّي الأحداثعندما تُحدِّد الدّالة المُتولّية لحدث ما، فإنّه يُتاح لهذه الدّالّة وصول إلى عنصر DOM الخام الّذي أطلق الحدث كسياق الدّالّة this، فإن أردت استخدام jQuery للتّعامل مع الحدث، فأحطه بـ‎$()‎: $( 'input' ).on( 'keydown', function( event ) { // this: العنصر الخام الّذي أطلق الحدث // event: كائن الحدث // غير لون الخلفية إلى أحمر إذا ضغط زر مسح الحرف، أو إلى أخضر فيما سوى ذلك $( this ).css( 'background', event.which === 8 ? 'red' : 'green' ); });جرّب المثال في ساحة التّجربة منع السّلوك المبدئيّقد ترغب أحيانًا في منع السّلوك المبدئيّ لحدثٍ ما، كأن ترغب في تولّي النّقر فوق رابط باستخدام AJAX، بدلًا من بدء إعادة تحميل كاملةٍ للصّفحة (وهو السّلوك المبدئيّ). يصل العديد من المُطوّرين إلى هذه الغاية بإعادة false من مُتولّي الحدث، ولكنّ لهذا تأثيرًا جانبيًّا آخر: فهو يمنع تفشّي الحدث (propagation) أيضًا (سنشرحه بعد قليل)، ممّا قد يعطي نتائج غير مرغوبة. الطّريقة السّليمة لمنع السّلوك المبدئيّ لحدث تكون باستدعاء ‎.preventDefault()‎ على كائن الحدث: $( 'input' ).on( 'keydown', function( event ) { // this: العنصر الخام الّذي أطلق الحدث // event: كائن الحدث // غير لون الخلفية إلى أحمر إذا ضغط زر مسح الحرف، أو إلى أخضر فيما سوى ذلك $( this ).css( 'background', event.which === 8 ? 'red' : 'green' ); });جرّب المثال في ساحة التّجربة صعود الأحداث (Event bubbling)تمعّن النّص البرمجيّ التّالي: $( 'a' ).on( 'click', function( event ) { // امنع الحدث المبدئيّ. event.preventDefault(); // سجّل ما حدث. console.log( 'I was just clicked!' ); });جرّب المثال في ساحة التّجربة يربط هذا النّص مُتولّيًا للنقر على كلّ العناصر في الصّفحة (وهو أمر يجب ألّا تفعله نهائيًّا في المواقع الحقيقيّة)، بالإضافة إلى عنصري النّافذة والمُستند. ولكن ما الّذي يحدث عندما تنقر على عنصر <a> مُدرج داخل عناصر أخرى؟ الحقيقة أنّ الحدث سيُثار على العنصر <a> وعلى كلّ العناصر الّتي تُحيط به صعودًا حتّى الوصول إلى العنصرين document وwindow. يُسمّى هذا السّلوك "صعود الأحداث"، فالحدث يُثار على العنصر الّذي نقر عليه المُستخدم، ثمّ ينتقل صاعدًا إلى كلّ العناصر الّتي تحويه وصولًا إلى أعلى DOM، إلّا إن استدعيت الوظيفة ‎.stopPropagation()‎ على كائن الحدث. بإمكانك فهم ذلك بسهولة أكبر في هذا المثال: <a href="#foo"><span>I am a Link!</span></a> <a href="#bar"><b><i>I am another Link!</i></b></a>$( 'a' ).on( 'click', function( event ) { event.preventDefault(); console.log( $( this ).attr( 'href' ) ); });عندما تنقر على "I am a Link!‎"، فإنك لا تنقر فعليًّا على العنصر <a>، بل على العنصر <span> داخله، ولكن الحدث "يصعد" نحو العنصر <a> ويُثير حدث النّقر المُرتبط به. تفويض الأحداث (Event delegation)يسمح لنا مفهوم "صعود الأحداث" بإنجاز فكرة "تفويض الأحداث"، أي ربط الأحداث بعناصر في مستوى أعلى، ثمّ اكتشاف أيّ عنصر فرعيّ ضمنها أثار الحدث. بإمكاننا مثلًا ربط حدث بقائمة غير مُرتّبة، ثمّ تحديد أيّ العناصر أثار الحدث: $( '#my-unordered-list' ).on( 'click', function( event ) { console.log( event.target ); // يُسجّل العنصر الّذي أثار الحدث });جرّب المثال في ساحة التّجربة بالطّبع ستتعقّد الأمور إذا احتوت عناصر القائمة على عناصر فرعيّة ضمنها، ولهذا تُقدّم jQuery وظيفةً مُساعدة تسمح لنا بتحديد أي العناصر نهتمّ بها، مع الاحتفاظ بالحدث مُرتبطًا بالعنصر ذي المُستوى الأعلى: $( '#my-unordered-list' ).on( 'click', 'li', function( event ) { console.log( this ); // يُسجّل عنصر القائمة الّذي أثار الحدث });جرّب المثال في ساحة التّجربة لتفويض الأحداث فائدتان اثنتان: أولاهما أنّه يسمح بربط عددٍ أقلّ من مُتولّيات الأحداث مقارنة بالعدد الّذي نحتاجه لو قرّرنا ربط الأحداث بالعناصر المُنفردة، وهذا يُحسّن الأداء في الصّفحة بصورة كبيرة. وثاني الفائدتين أنّه يسمح لنا بربط الأحداث بالآباء (كالقائمة غير المرتّبة في مثالنا)، مع اطمئنانا إلى أنّ الأحدث ستُثار حتّى وإن تغيّرت مُحتويات العنصر الأب. هذا النّصّ مثلًا، يُضيف عنصر قائمةٍ جديدًا بعد تفويض الحدث إلى العنصر الأب، والنّقر فوق هذا العنصر سيُثير الحدث كما ينبغي، دون الحاجة لربط أيّة أحداث جديدة: $( '#my-unordered-list' ).on( 'click', 'li', function( event ) { console.log( this ); // يُسجّل عنصر القائمة الّذي نُقر عليه }); $( '<li>a new list item!</li>' ).appendTo( '#my-unordered-list' );جرّب المثال في ساحة التّجربة خاتمةتعلّمنا في هذا الجزء الوسائل المُختلفة للإنصات إلى تفاعل المُستخدم مع صفحتنا، بما في ذلك كيفيّة الاستفادة من التفويض لتحسين كفاءة ربط الأحداث بالعناصر. سنتعرّف الجزء القادم كيف نُحرّك العناصر باستخدام وظائف التأثيرات الحركيّة في jQuery. ترجمة (بشيء من التصرف) للجزء الرابع من سلسلة  jQuery Fundamentals لمؤلّفتها Rebecca Murphey.
  20. توفّر jQuery أدوات قويّة لإيجاد العنصر أو العناصر التي تريدها في الصّفحة، ثمّ العمل بهذه العناصر للوصول إلى النّتيجة المرغوبة. تسهّل jQuery بأدواتها هذه عمليّات كانت لتكون أكثر تعقيدًا لو أردنا تنفيذها من خلال وظائف DOM الأصليّة. سنطّلع في هذا الجزء على بعض (لا كلّ) وظائف الانتقال عبر الصّفحة وتّعديل العناصر في jQuery. وقبل أن نبدأ، علينا فهم بعض المصطلحات الضّروريّة. لنفترض أنّ لدينا نصّ HTML التّالي: <ul> <li> <span> <i>Foo</i> </span> </li> <li>Bar</li> </ul> نقول عن عنصر القائمة الأوّل (<li>) أنّه ابن (child) القائمة غير المرتّبة (<ul>). نقول عن القائمة غير المرتّبة (<ul>) أنّها والد (parent) عنصري القائمة الاثنين. نقول عن العنصر <span> أنّه خَلَفُ (descendant) القائمة غير المرتّبة. نقول عن القائمة غير المرتّبة أنّها سَلَفٌ (ancestor) لكلّ ما داخلها. نقول عن عنصري القائمة أنّهما شقيقان (siblings). الانتقال عبر الصّفحة (Traversal) تسمح jQuery لنا بالانتقال عبر عناصر HTML الّتي تكوّن صفحتنا. إذ نُنشئ أوّلًا تحديدًا مبدئيًّا ثمّ ننتقل عبر DOM انطلاقًا منه. وخلال مسيرنا عبر DOM، فإنّنا نُغيّر من تحديدنا الأوّل فنضيف إليه أو نحذف منه بعض العناصر، أو نستبدل به تحديدًا آخر بالكامل في بعض الأحيان. تصفية التّحديدات بإمكانك تصفية تحديد موجودٍ بحيث يتضمّن فقط العناصر الّتي تطابق معاييرَ مُحدّدة. بإمكانك مثلًا إجراء التّصفية بإحدى الطّرق التّالية: var listItems = $( 'li' ); // صفِّ التّحديد ليحوي فقط العناصر ذات الصّنف 'special' var special = listItems.filter( '.special' ); // صفّ التّحديد ليحوي فقط العناصر من غير الصّنف 'special' var notSpecial = listItems.not( '.special' ); // صفّ التّحديد ليحوي فقط العناصر الّتي تتضمّن span var hasSpans = listItems.has( 'span' ); من المهمّ أن تعرف أن الوظيفة ‎.not()‎ ليست عكس ‎.is()‎، لأنّ ‎.is()‎ تُعيد قيمة منطقيّة (true أو false)، بينما تُعيد ‎.not()‎ كائن jQuery جديدًا. إيجاد العناصر انطلاقًا من تحديد يمكن الاستفادة من تحديد أوّليّ كأساس لإنشاء تحديدات إضافيّة؛ فإذا كان لديك تحديدٌ يحوي عنصر قائمة مُفردًا مثلًا، وأردت التّعامل مع "أشقّائه" أو مع القائمة غير المُرتّبة الّتي تحويه، فبإمكانك إنشاء تحديد جديد انطلاقًا من التّحديد الموجود بسهولة: // اختر أوّل عنصر قائمة في الصّفحة var listItem = $( 'li' ).first(); // أيضًا: .last() // اختر أشقّاء عنصر القائمة var siblings = listItem.siblings(); // اختر الشّقيق التّالي لعنصر القائمة var nextSibling = listItem.next(); // أيضًا: .prev() // اختر والد عنصر القائمة var list = listItem.parent(); // اختر عناصر القائمة الّتي تنحدر مباشرةً من القائمة var listItems = list.children(); // اختر كلّ عناصر القائمة ضمن القائمة، بما في ذلك العناصر الفرعيّة var allListItems = list.find( 'li' ); // اختر كل أسلاف عنصر القائمة ذوي الصّنف "module" var modules = listItem.parents( '.module' ); // اختر أقرب سلفٍ لعنصر القائمة له الصّنف "module" var module = listItem.closest( '.module' ); بإمكانك كذلك الإضافة على التّحديد الحاليّ باستخدام الوظيفة ‎.add()‎، الّتي تقبل مُحدِّدًا أو مصفوفة عناصر أو نص HTML أو كائن jQuery. var list = $( '#my-unordered-list' ); // افعل شيئًا ما بالقائمة ثم ... var listAndListItems = list.add( '#my-unordered-list li' ); العودة إلى التّحديد الأصليّ تحتفظ jQuery بإشارة إلى تحديد الأصليّ عندما تستخدمه للانتقال إلى تحديدات أخرى انطلاقًا منه، في حال أردت العودة إلى التّحديد الأصليّ. افترض مثلًا أنّك حدّدت قائمة غير مرتّبه، ثمّ أردت التّعديل على عناصر القائمة، ثمّ العودة مجدّدًا للعمل على القائمة غير المرتّبة، عندها بإمكانك استخدام الوظيفة ‎.end()‎ للرّجوع إلى التّحديد الأصليّ: $( '#my-unordered-list' ) .find('li') // نحن الآن نعمل على عناصر القائمة .addClass('special') .end() // عدنا الآن للعمل على القائمة ذاتها .addClass('super-special'); تُسهِّل الوظيفة ‎.end()‎ إجراء تعديلات كثيرة في جملة واحدة، إلّا أنّ هذا الأسلوب لا يُلقي بالًا لوضوح النّصّ البرمجيّ، فهو أشبه بأن تحكي قصّة دون أن تلتقط أنفاسك. لهذا السّبب لا أنصحك بالإكثار من استعماله، فهو يؤدّي في معظم الحالات إلى جعل قراءة النّصّ البرمجيّ وصيانته وتنقيحه أكثر صعوبة. فيما يلي حلّ أفضل للمشكلة ذاتها: var list = $( '#my-unordered-list' ); var listItems = list.find('li'); listItems.addClass( 'special' ); list.addClass( 'super-special' ); توفّر jQuery أيضًا الوظيفة ‎.addBack()‎ إن أردت إضافة تحديدك الأصليّ إلى التّحديد الحاليّ. مثال: $( 'li.special' ) .siblings() // نحن نعمل الآن على أشقّاء التّحديد السّابقة .removeClass( 'important' ) .addBack() // الآن نعمل على عناصر القائمة الأصليّة وأشقائها **معًا** .addClass( 'urgent' ); هل اختلط عليك الأمر؟ الوظيفة ‎.addBack()‎ تشبه الوظيفة ‎.end()‎ في عيوبها، فكلاهما (وإن كان لهما استخدامها) يزيدان تعقيد النّصّ البرمجيّة. الحلّ الأفضل هو استخدام الوظيفة ‎.add()‎ لدمج التّحديدين الأصليين معًا: var specialListItems = $( 'li.special' ); var otherListItems = specialListItems.siblings(); otherListItems.removeClass( 'important' ); specialListItems.add( otherListItems ).addClass( 'urgent' ); هناك وظائف عديدة لم نتطرّق إليها هنا، يمكنك الاطّلاع عليها في وثائق الانتقال عبر الصّفحة. التّعامل مع العناصر (Manipulation) تسمح وظائف التّعامل مع العناصر في jQuery بتغيير DOM الصّفحة بصياغة أكثر بساطة من تلك الّتي توفّرها وظائف DOM الخام. تُعيد وظائف التّعامل مع العناصر في jQuery كائن jQuery الّتي استدعيت للعمل عليه، وهذا يعني إمكانيّة ربطها في سلسلة أو دمجها مع وظائف jQuery أخرى كالّتي ناقشناها في الفقرات السّابقة. تعديل العناصر كثيرةٌ هي طرق تعديل العناصر في jQuery. سنطّلع فيما يلي على طرق إنجاز المهام الأكثر شيوعًا. إضافة أو حذف الأصناف (classes) يمكن الاستفادة من أصناف الكائنات في HTML بأن نستهدفها في CSS بغرض تنسيقها، كما يُستفاد منها في إنشاء تحديدات jQuery. فمثلًا يمكن لعنصر في الصّفحة أن يقع تحت الصّنف hidden، والّذي يُستخدم في CSS لجعل خاصّة display موافقة للقيمة none للعناصر من هذا الصّنف، ثمّ يمكن حذف هذا الصّنف أو إضافته لتغيير حالة ظهور العناصر الموافقة في jQuery: $( 'li' ).addClass( 'hidden' ); $( 'li' ).eq( 1 ).removeClass( 'hidden' ); جرّب المثال في ساحة التّجربة (تأكد من ضغط زر Run with JS في هذا المثال وكلّ الأمثلة التالية) إن تطلّبت حالتك إضافة صنفٍ أو حذفه مرارًا، فبإمكانك استخدام الوظيفة ‎.toggleClass()‎ الّتي تُبدّل حالة الصّنف على العنصر، فتضيفه إن لم يكن موجودًا أو تحذفه إن وُجد: $( 'li' ).eq( 1 ).toggleClass( 'hidden' ); جرّب المثال في ساحة التّجربة تغيير المظهر ملاحظة: يُفضّل دومًا استخدام الأصناف واستهدافها بقواعد CSS لتغيير طريقة عرض العناصر، والاقتصار على استخدام jQuery في إضافة هذه الأصناف أو حذفها كما ورد للتوّ. في هذه الفقرة سنتعرّف كيف نُغيّر مظاهر العناصر مُباشرةً في jQuery، ولكننا نُفضِّل دومًا الأسلوب الأوّل إن كان يُحقِّق النّتائج ذاتها. عندما تعجز عن تحقيق هدفك بإضافة الأصناف أو حذفها، فإنّ jQuery تقدّم الوظيفة .css() الّتي تسمح بتعيين مظهر العناصر مباشرةً، ولعلّ هذا يكون ضروريًّا عادةً عندما تحتاج إلى إسناد قيم عدديّة لا يمكن حسابها إلّا أثناء عمل التّطبيق، كمعلومات توضّع العناصر في الصّفحة. لا يُفضَّل استخدام الوظيفة ‎.css()‎ لإجراء تنسيقات بسيطة مثل ‎display: none‎، بل يُفضَّل في معظم الحالات إنجاز الغاية ذاتها باستخدام الأصناف وCSS. افترض مثلًا أنّنا نريد تعيين مظهر العنصر بالاعتماد على عرض والده، وربّما يصعب أو يستحيل معرفة عرض الوالد مُسبقًا عند اعتماد تخطيط مرنٍ للصّفحة. في هذه الحالة قد نلجأ إلى الوظيفة ‎.css()‎ لتنسيق العنصر: var list = $( '#my-unordered-list' ); var width = Math.floor( list.width() * 0.1 ); list.find('li').each(function( index, elem ) { var padding = width * index; $( elem ).css( 'padding-right', padding + 'px' ); }); جرّب المثال في ساحة التّجربة إن احتجت إلى تعيين أكثر من خاصّة في وقت واحدٍ، مرّر كائنًا إلى الوظيفة ‎.css()‎ بدلًا من اسم الخاصّة وقيمتها. لاحظ أنّ عليك إحاطة أيّة خاصّة تحوي الرّمز "-" بعلامتي اقتباس: $( 'li' ).eq( 1 ).css({ 'font-size': '20px', 'padding-right': '20px' }); جرّب المثال في ساحة التّجربة تغيير قيم النّماذج (forms) تقدّم jQuery الوظيفة ‎.val()‎ لتعديل قيمة العناصر في النّماج مثل input وselect. بإمكانك تمرير سلسلة نصّيّة لتعيين محتوى حقول input النّصّيّة: $( 'input[type="text"]' ).val( 'new value' ); جرّب المثال في ساحة التّجربة بالنّسبة للعناصر من نوع select، بإمكانك تعيين الخيار المُختار باستخدام ‎.val()‎ أيضًا: $( 'select' ).val( '2' ); جرّب المثال في ساحة التّجربة أمّا لحقول input من نوع checkbox، فعليك تعيين الخاصّة checked على العنصر بالوظيفة ‎.prop()‎. $( 'input[type="checkbox"]' ).prop( 'checked', 'checked' ); جرّب المثال في ساحة التّجربة ملاحظة: أُضيفت الوظيفة ‎.prop()‎ في الإصدارة 1.6 من jQuery؛ وقبل ذلك كانت تُستخدم الوظيفة ‎.attr()‎ للغرض ذاته، وهي ما تزال تعمل في الإصدارات الحديثة من jQuery، ولكنّها في حالة checked تكتفي باستدعاء ‎.prop()‎. إن كنت تستخدم إصدارةً أحدث من 1.6، فأنصحك باستخدام ‎.prop()‎ دومًا لتعيين الخاصّة checked وخصائص عناصر DOM الأخرى. اطّلع على الوثائق لتفاصيل أكثر. تغيير الصّفات (attributes) الأخرى بإمكانك استخدام وظيفة ‎.attr()‎ لتغيير صفات العناصر، فيمكنك مثلًا تغيير عنوان رابط (الخاصّة title لعنصر <a>): $( 'a' ).attr( 'href', 'new title'); عند تعيين قيمة لصفة، بإمكانك تمرير دالّة في موضع المُعامل الثّاني للوظيفة، ومثلها مثل كلّ دوالّ الكتابة السّابقة، تتلقّى هذه الدّالّة مُعاملين اثنين: دليل العنصر الّذي تعمل عليه، والقيمة الحاليّة للصّفة. يجب أن تُعيد هذه الدّالة القيمة الجديدة للصّفة: $( 'a' ).attr( 'href', function(index, value) { return value + '?special=true'; }); جرّب المثال في ساحة التّجربة بإمكانك حذف الصّفات أيضًا، وذلك باستخدام ‎.removeAttr()‎. الحصول على معلومات من العناصر ناقشنا في الجزء السّابق (أساسيّات jQuery) فكرة وظائف القراءة والكتابة. كلّ الوظائف الّتي يمكن استخدامها لتغيير العناصر، يمكن أيضًا استخدامها لقراءة معلومات من تلك العناصر. فيمكن مثلًا استخدام الوظيفة ‎.val()‎ الّتي وصفناها أعلاه كوظيفة قراءة وكتابة معًا: var input = $( 'input[type="text"]' ); input.val( 'new value' ); input.val(); // returns 'new value' وكذلك الأمر بالنّسبة للوظيفة ‎.css()‎، إذ يمكن استخدامها لقراءة قيمة خصائص CSS مُفردة بإمرار اسم الخاصّة فقط دون قيمة: var listItemColor = $( 'li' ).css( 'color' ); عندما تُستخدم وظائف التّعامل مع العناصر للقراءة، فإنّها تعمل فقط مع العنصر الأول في التّحديد، باستثناء الوظيفة ‎.text()‎ الّتي تقرأ المحتوى النّصيّ لكلّ العناصر المُحدّدة إن لم يُمرّر معامل إليها. إضافة العناصر إلى الصّفحة سواءٌ حدّدت عنصرًا أو أنشأت واحدًا جديدًا، فبإمكانك إضافة هذا العنصر إلى الصّفحة يمكن فعل ذلك بطريقتين: باستدعاء وظيفة تتبع للعنصر (أو العناصر) المطلوب إضافته، أو باستدعاء وظيفة تتبع لعنصر مرتبط بذلك الّذي تريد إضافته. افترض مثلًا أنّك تريد نقل عنصر في قائمة من رأسها إلى ذيلها، هناك عدّة طرق لفعل ذلك. بإمكانك مثلًا إضافة العنصر إلى القائمة باستدعاء الوظيفة ‎.appendTo()‎ على عنصر القائمة ذاته: var listItem = $( '#my-unordered-list li' ).first(); listItem.appendTo( '#my-unordered-list' ); جرّب المثال في ساحة التّجربة وبإمكانك أيضًا إضافة العنصر باستدعاء ‎.append()‎ على القائمة: var listItem = $( '#my-unordered-list li' ).first(); $( '#my-unordered-list' ).append( listItem ); أو إضافته باستدعاء ‎.insertAfter()‎ على العنصر المُراد نقله مُمرّرًا العنصر الأخير في القائمة إلى الوظيفة: var listItems = $( '#my-unordered-list li' ); listItems.first().insertAfter( listItems.last() ); جرّب المثال في ساحة التّجربة أو إضافته باستدعاء ‎.after()‎ على العنصر الأخير في القائمة مُمرًّرا العنصر الأولى في القائمة إلى الوظيفة: var listItems = $( '#my-unordered-list li' ); listItems.last().after( listItems.first() ); جرّب المثال في ساحة التّجربة هناك طرق آخرى كثير لإضافة العناصر، فبإمكانك إضافتها حول عناصر أخرى أو داخلها أو خارجها بحسب حاجتك. تعتمد أكثر الطّرق كفاءة في إضافة عنصر إلى الصّفحة على العناصر الّتي تتوفّر بين يديك بالفعل. فقد ترغب في إضافة العنصر إلى القائمة غير المُرتّبة في المثال السّابق إن كنت قد حدّدت القائمة غير المرتّبة من قبل لغرض آخر؛ أو إن كنت قد حدّدت عناصر القائجة جميعها، فقد يكون إضافة العنصر الأول بعد العنصر الأخير أمرًا أسهل. عندما تختار الطّريقة المناسبة لإضافة العنصر، فلا تكتفِ بالنّظر في سهولة الطّريقة، بل فكّر في إمكانيّة صيانتها لاحقًا. تجنّب الطّرق الّتي تعتمد على افتراض بنية مُحدِّدة بدقَّة لصفحتك، فقد تقرّر تغيير هذه البنية فيما بعد. نسخ العناصر بإمكانك إنشاء نسخة من عنصر أو مجموعة عناصر باستخدام الوظيفة ‎.clone()‎ في jQuery، وستُنشئ النُّسخة في الذّاكرة دون أن تُدرج في الصّفحة، فعليك فعل ذلك بنفسك إن أردته. بإمكانك تعديل العناصر المنسوخة قبل إضافتها: var clones = $( 'li' ).clone(); clones.html(function( index, oldHtml ) { return oldHtml + '!!!'; }); $( '#my-unordered-list' ).append( clones ); جرّب المثال في ساحة التّجربة ملاحظة*: لن تمنعك jQuery من نسخ عنصر ذي مُعرِّف (ID)، ولكن عليك التأكّد من حذف المُعرِّف أو تغييره في العنصر المنسوخ بتعديل الصّفة id قبل إدراجه في المستند، إذ لا ينبغي أ يوجد عنصران بمُعرِّف واحدٍ في الصّفحة. حذف العناصر هناك ثلاث طرق لحذف العناصر من الصّفحة: ‎.remove()‎‏ و‎.detach()‎‏ و‎.replaceWith()‎‏، ولكلّ منها غرضٌ مُختلف. يجب استخدام ‎.remove()‎‏ عند الحاجة لحذف العناصر بصورة دائمة، فهي ستحذف مع العنصر كلّ مُتوليّات الأحداث المُرتبطة به (event handlers). تُعيد الوظيفة ‎.remove()‎‏ إشارةً إلى العناصر المُحذوفة، ولكن عند إضافة هذه العناصر مرّة ثانيةً، فلن تكون أيّة أحداث مُرتبطةً بها. $( '#my-unordered-list li' ).click(function() { alert( $( this ).text() ); }); var removedListItem = $( '#my-unordered-list li' ).first().remove(); removedListItem.appendTo( '#my-unordered-list' ); removedListItem.trigger( 'click' ); // لا رسالة تنبيه! جرّب المثال في ساحة التّجربة تُفيد الوظيفة ‎.detach()‎‏ في حذف العناصر مؤقّتًا من الصّفحة، فمثلًا إن رغب في إجراء تعديلات كبيرة على بنية الصّفحة باستخدام jQuery، فقد يكون حذف العناصر مؤقّتًا من الصّفحة ثمّ إضافتها ثانيةً أفضل أداءً بمراحل. ستحتفظ العناصر المحذوفة بهذه الوظيفة بمُتولّيات الأحداث المُرتبطة بها، ويمكن بعد ذلك إضافتها إلى الصّفحة مُجدّدًا باستخدام ‎.appendTo()‎‏ أو غيرها من وظائف الإضافة. $( '#my-unordered-list li' ).click(function() { alert( $( this ).text() ); }); var detachedListItem = $( '#my-unordered-list li' ).first().detach(); // افعل شيئًا ما مُعقّدًا بعنصر القائمة detachedListItem.appendTo( '#my-unordered-list' ); detachedListItem.trigger( 'click' ); // alert! أخيرًا لدينا الوظيفة ‎.replaceWith()‎‏ الّتي تُحلِّ عنصرًا أو نصّ HTML محلّ عنصرٍ أو عناصر أخرى. تُعاد العناصر الّتي أُزيلت من الوظيفة، ولكنّ كلّ متولّيات الأحداث المرتبطة بها تُحذف، تمامًا كالوظيفة ‎.remove()‎‏. $( '#my-unordered-list li' ).click(function() { alert( $( this ).text() ); }); var replacedListItem = $( '#my-unordered-list li' ).first() .replaceWith( '<li>new!</li>' ); replacedListItem.appendTo( '#my-unordered-list' ); replacedListItem.trigger( 'click' ); // لا رسالة تنبيه! جرّب المثال في ساحة التّجربة خاتمة تعلّمنا في هذا الجزء الطّرق المختلفة للانتقال بين العناصر في الصّفحة، وكيفيّة نقلها وتغييرها وإضافة عناصر جديدة. سنتعلّم في الجزء القادم كيف نُنصِت لتفاعل المستخدم مع صفحتنا. ترجمة (بشيء من التصرف) للجزء الثالث من سلسلة jQuery Fundamentals لمؤلّفتها Rebecca Murphey.
  21. تُسهِّل مكتبة jQuery التّعامل مع محتويات صفحة HTML بعد أن يعرضها المتصفّح، وتوفّر أدوات تُساعدك في متابعة تفاعل المستخدم مع الصّفحة، وتحريك العناصر فيها، والتّواصل مع الخواديم دون إعادة تحميل الصّفحة. سنشرح هذه الأدوات بعد قليل. لنبدأ أوّلًا بالاطّلاع على أساسيّات jQuery، وكيف يمكن استخدام وظيفتها الأساسيّة: الوصول إلى عناصر مُحدَّدة في الصفحة وفعل شيءٍ ما بها. ملاحظة: يفترض هذا الدّليل أنّك على علم بأساسيّات HTML ومُحدِّدات CSS. إن لم تكن تألف كيف يمكن استخدام مُحدّدات CSS للوصول إلى عناصر مُحدّدة في الصّفحة، فعليك أوّلًا تعلّم ذلك قبل الشروع في متابعة هذا الدّليل. ما هذا الرمز: $؟توفّر مكتبة jQuery الدّالّة jQuery، التي تتيح لك تحديد العناصر بمُحدّدات CSS. var listItems = jQuery( 'li' );إن قرأت من قبل برامج تستخدم jQuery، فلا بدّ أنّك اعتدت على هذا: var listItems = $( 'li' );كما ناقشنا في الجزء السّابق (أساسيّات JavaScript)، فكلّ الأسماء تكاد تكون سّليمة في JavaScript ما لم تبدأ برقم أو تحوي إشارة "-". ولذا فالاسم $ في المثال الأخير ليس إلّا اسمًا مُختصرًا للدّالّة jQuery، ولو اطّلعت على مصدر jQuery، لقرأت هذا قرب نهايته: // Expose jQuery to the global object window.jQuery = window.$ = jQuery;عندما تستدعي الدّالّة ‎$()‎ وتمرّر لها مُحدّدًا، فإنّك تحصل على كائن jQuery جديدٍ. الدّوالّ في JavaScript هي الأخرى كائنات، وهذا يعني أنّ للدّالّة $ (وjQuery بالطّبع) خصائص ووظائف أيضًا. مثلاً: يمكنك استخدام الخاصّة ‎$.support‎ لمعرفة ما الميزات الّتي يدعمها المتصفّح الحاليّ، كما يمكنك استخدام الوظيفة ‎$.ajax‎ لإرسال طلب AJAX. ملاحظة: من الآن فصاعدًا سنستخدم $ بدلًا من jQuery في هذه السّلسلة سعيًا للاختصار. لاحظ أنّه إن احتوت صفحتك أكثر من مكتبة واحدة، فقد يُستخدم الاسم $ من مكتبة أخرى، ممّا يمنع عمل jQuery، فإن واجهتك مشكلة كهذه، جرّب استخدام jQuery.noConflict قبل تحميل المكتبات الأخرى. ‏‎$(document).ready()‎قبل استخدام jQuery لفعل أيّ شيء في الصّفحة، علينا التأكّد من كون الصّفحة قد بلغت حالةً تسمح بتعديل محتوياتها. يمكن تنفيذ ذلك في jQuery بإحاطة برنامجنا ضمن دالّة ثمّ إمرار هذه الدّالة إلى ‎$(document).ready()‎. كما ترى في المثال التّالي، يمكن للدّالّة الّتي نمرّرها أن تكون مجهولة (بلا اسم): $(document).ready(function() { console.log( 'ready!' ); });هذا سيؤدّي إلى استدعاء الدّالّة الّتي مرّرناها إلى ‎.ready()‎ بعد أن يصبح المُستند (الصفحة) جاهزًا. ما الذي يحدث هنا؟ استخدمنا ‎$(document)‎ لإنشاء كائن jQuery من document في الصّفحة، ثمّ استدعينا الدّالّة ‎.ready()‎ على هذا الكائن، مُمرِّرين إليها الدّالّة الّتي نريد تنفيذها. بما أنّك ستجد نفسك تُعيد كتابة هذا النّصّ مرارًا، فإنّ jQuery تقدّم لك طريقةً مُختصرةً لإنجازه، إذ تقوم الدّالّة ‎$()‎ بمهمّة مُختلفة عند إمرار دالّة إليها بدلًا من مُحدِّد CSS، وعندها تتصرّف وكأنّها اسم بديلٌ للوظيفة ‎$(document).ready()‎: $(function() { console.log( 'ready!' ); });ملاحظة: من الآن فصاعدًا، سنفترض أنّ النّصوص الّتي ترد في هذه السّلسلة مُحاطة بالعبارة ‎$(document).ready(function() { ... });‎، وسنترك هذه العبارة بغرض الإيجاز. الوصول إلى العناصرأبسط ما يمكن إنجازه بـjQuery تحديد بعض العناصر ثمّ فعل شيء ما بها. إن كنت تفهم مُحدّدات CSS، فستجد أنّ الوصول إلى بعض العناصر سهل ومباشر: ليس عليك إلا إمرار المُحدِّد المناسب إلى ‎$()‎. $( '#header' ); // حدّد العنصر الّذي مُعرِّفه 'header' $( 'li' ); // حدّد كل عناصر القوائم في الصّفحة $( 'ul li' ); // حدّد كل عناصر القوائم الموجودة في قوائم غير مُرتّبة $( '.person' ); // حدّد كل العناصر ذات الصّنف 'person'من المهمّ أن تفهم أنّ أيّ تحديد تُجري لن يتضمّن إلّا العناصر الموافقة للمُحدّد والتي كانت موجودة في اللّحظة الّتي أجريت فيها التّحديد، بمعنى أنّك إذا كتبت ‎var anchors = $( 'a' );‎ ثمّ أضفت عنصر <a> إلى الصّفحة لاحقًا، فلن تحوي anchors العنصر الجديد. طرق أخرى لإنشاء كائن jQueryبالإضافة إلى إمرار مُحدّد بسيط إلى الدّالة ‎$()‎، يمكن إنشاء كائنات jQuery بطرق أخرى: // أنشئ كائن jQuery من عنصر DOM $( document.body.children[0] ); // أنشئ كائن jQuery من قائمة بعناصر DOM $( [ window, document ] ); // أجرِ التّحديد بسياق عنصر DOM var firstBodyChild = document.body.children[0]; $( 'li', firstBodyChild ); // أجرِ التّحديد ضمن تحديد سابق var paragraph = $( 'p' ); $( 'a', paragraph );هل يحتوي التّحديد الّذي أجريته على أيّة عناصر؟ترغب أحيانًا في تنفيذ بعض الأوامر عندما يُطابق تحديدك عنصرًا أو أكثر فقط، ولكنّ الدّالّة ‎$()‎ تُعيدُ دومًا كائن jQuery، وهذا الكائن دائمًا صائب (truthy)، ولذا عليك فحص محتوى التّحديد لمعرفة إن كان يحوي أيّة عناصر. تحذير: نصّ برمجيّ غير سليم if ( $( '#nonexistent' ) ) { // خطأ! الأوامر هنا ستُنفَّذ دومًا! } if ( $( '#nonexistent' ).length > 0 ) { // صحيح! لن تُنفّذ الأوامر هنا إلّا إن احتوت الصّفحة على كائن مُعرّفه 'nonexistent' }بإمكاننا اختصار هذا الفحص أكثر بالاعتماد على كون 0 قيمة خاطئة (falsy): if ( $( '#nonexistent' ).length ) { // لن تُنفّذ الأوامر هنا إلّا إن وجد عنصر مُطابق }الوصول إلى عناصر مُفردة من تحديدإن كنت تحتاج التّعامل مع عنصر DOM خام من تحديد، فعليك الوصول إلى هذا العنصر من كائن jQuery. لنفترض مثلًا أنّك تريد الوصول إلى الخاصّة value لكائن <input> مباشرةً، عليك إذن التّعامل مع عنصر DOM الخام: var listItems = $( 'li' ); var rawListItem = listItems[0]; // أو listItems.get( 0 ) var html = rawListItem.innerHTML;لاحظ أنّه ليس بإمكانك استدعاء وظائف jQuery على عناصر DOM الخام، فلن يعمل المثال التّالي: تحذير: نصّ برمجيّ غير سليم var listItems = $( 'li' ); var rawListItem = listItems[0]; var html = rawListItem.html(); // Object #<HTMLInputElement> has no method 'html'إن أردت العمل مع عنصر مُفرد في تحديد وأردت استخدام وظائف jQuery معه، فعليك إنشاء كائن jQuery جديد انطلاقًا منه باستخدام الدّالّة ‎.eq()‎، وإمرار دليل العنصر الّذي تريده: var listItems = $( 'li' ); var secondListItem = listItems.eq( 1 ); secondListItem.remove();جرب المثال في ساحة التّجربة (تأكد من ضغط زر Run with JS في هذا المثال وكل الأمثلة التالية) إنشاء كائنات جديدةللدّالّة $ دورٌ ثالث أخير: إنشاء عناصر جديدة. إن مرّرت قصاصة HTML إلى $()‎، فستُنشئ كائنًا جديدًا في الذّاكرة، بمعنى أنّ الكائن يُنشأ ولكن لا يُضاف إلى الصّفحة إلى أن تفعل ذلك بنفسك. $( '<p>' ); // يُنشئ عنصر <p> بلا محتوى $( '<p>Hello!</p>' ); // يُنشى عنصر <p> فيه نصّ $( '<p class="greet">Hello!</p>' ); // يُنشى عنصر <p> فيه نصّ وله صنفبإمكانك أيضًا إنشا عنصر جديد بإمرار كائنٍ يحوي معلومات تصف كيفيّة إنشاء العنصر: $( '<p>', { html: 'Hello!', 'class': 'greet' });لاحظ أنّه يجب أن نُحيط class بعلامتي اقتباس، لأنّ class كلمة محجوزة في JavaScript، وعدم إحاطتها بالعلامتين سيسبّب وقوع أخطاء في بعض المتصفّحات. راجع وثائق jQuery‏ لتفاصيل الخصائص المختلفة الّتي يمكنك تضمينها في هذا الكائن. سنتعرّف كيف نُضيف العناصر في الصّفحة في الجزء القادم من السّلسلة، الّذي يشرح الانتقال عبر الصّفحة وتعديل محتوياتها. التّعامل مع التحديداتبعد إنشائك كائن jQuery يحوي تحديدًا، فإنّك غالبًا ما تريد فعل شيء ما به، وقبل ذلك عليك أن تتعرّف على أسلوب jQuery في التّعامل مع الكائنات. فحص تحديدبإمكاننا معرفة إن كان تحديد ما يُطابق معايير مُعيّنة باستخدام الوظيفة ‎.is()‎. أكثر الطّرق شيوعًا لاستخدام هذه الوظيفة تزويدها بمُحدِّد كمُعاملٍ مفرد لها، وعندها تُعيد true أو false حسب مُطابقة التّحديد للمُحدِّد: $( 'li' ).eq( 0 ).is( '.special' ); // false $( 'li' ).eq( 1 ).is( '.special' ); // trueبإمكانك تمرير كائن jQuery أيضًا إلى الوظيفة ‎.is()‎، أو حتّى كائن DOM خام، أو حتّى دالّة لإجراء اختبار أكثر تعقيدًا إن لزم. راجع الوثائق لمزيد من التّفاصيل. وظائف القراءة والكتابة والسّرد الضّمنيّبعد عمل التّحديد، تتوفّر وظائف عديدة يمكنك استدعاؤها. تقع هذه الوظائف عمومًا في إحدى مجموعتين: وظائف القراءة (getters) ووظائف الكتابة (setters). فالأولى تعطينا معلومات عن التّحديد، والثّانية تُغيّر التّحديد بشكل من الأشكال. وفي معظم الحالات يقتصر عمل وظائف القراءة على العنصر الأول في التّحديد (‎.text()‎ إحدى استثناءات هذه القاعدة)؛ أمّا وظائف الكتابة فتشمل بعملها كلّ العناصر في التّحديد، مستخدمةً ما يُعرف بالسّرد الضّمنيّ (implicit iteration). معنى السّرد الضّمنيّ أنّ jQuery ستمرّ تلقائيًّا على كلّ العناصر في التّحديد عندما تستدعي وظيفة كتابة على هذا التّحديد، أيّ أنّه ليس عليك استدعاء وظيفة على كلّ عنصر في التّحديد بمفرده عندما تريد فعل شيء على كل العناصر في تحديد واحدٍ، بل اكتفِ باستدعاء هذه الوظيفة على التّحديد نفسه، وستفهم jQuery أنّ عليها تنفيذه على كلّ العناصر في التّحديد. لنفترض أنّنا نريد تغيير نصّ HTML في كل عناصر القوائم في الصّفحة، ولفعل ذلك علينا استخدام الوظيفة ‎.html()‎ الّتي تقوم بتغيير نصّ HTML في كلّ عناصر القوائم المُحدّدة. $( 'li' ).html( 'New HTML' );جرب المثال في ساحة التّجربة بإمكانك أيضًا إمرار دالّة إلى وظائف الكتابة في jQuery، وستُستخدم القيمة المُعادة منها باعتبارها القيمة الجديدة، وتستقبل هذه الدّالة مُعاملين اثنين: دليل العنصر (index) في التّحديد، والقيمة القديمة للشّيء الذي تحاول تغييره، وهذا مُفيد في حال احتجت معلومات عن حالة العنصر الحاليّة لتعيين حالته الجديدة بشكل صحيح. $( 'li' ).html(function( index, oldHtml ) { return oldHtml + '!!!' });جرب المثال في ساحة التّجربة السّرد الصّريح (Explicit Iteration)في بعض الأحيان، لن تلبّي وظائف jQuery الأصليّة المهمّة الّتي تريد إنجازها بدقّة، وسيكون عليك حينها المرور على العناصر في التّحديد بشكل صريح، وهذا ما تتيحه الوظيفة ‎.each()‎. في المثال التّالي نستخدمها لإضافة وسم <b> في بداية عنصر القائمة، يحوي دليل العنصر: $( 'li' ).each(function( index, elem ) { // this: عنصر DOM الخام الحالي // index: دليل العنصر الحالي في التّحديد // elem: عنصر DOM الخام الحالي (مثل this) $( elem ).prepend( '<b>' + index + ': </b>' ); });جرب المثال ساحة التّجربة ملاحظة: ستلاحظ أنّ عنصر DOM الخام مُتاح ضمن الدّالّة الّتي نُمرّرها إلى ‎.each()‎ بطريقتين: الأولى عبر this والثّانية عبر elem. وكما ناقشنا في الجزء السّابق (أساسيّات JavaScript)، فإنّ this كلمة خاصّة في JavaScript تُشير إلى الكائن الّذي يُمثّل سياق الدّالّة الحاليّ. وفي jQuery فإنّ this تُشير في معظم الحالات إلى عنصر DOM الخام الّذي تعمل عليه الدّالّة الحاليّة. لذا فإنّها تُشير في حالة ‎.each()‎ إلى العنصر الحاليّ في مجموعة العناصر الّتي نسردها. السَّلسَلة (Chaining)من أكثر الأمور فائدةً في jQuery إمكانيّة "سَلسَلة" الوظائف معًا. هذا يعني أنّ بإمكاننا استدعاء سلسِلة من الوظائف على تحديدٍ ما دون الحاجة لإعادة التّحديد أو حفظه في متغيّر. بإمكاننا حتّى إنشاء تحديدات جديدة بناء على التّحديد السّابق، دون كسر السّلسلة: $( 'li' ) .click(function() { $( this ).addClass( 'clicked' ); }) .find( 'span' ) .attr( 'title', 'Hover over me' );الأمر ممكن لأنّ كل دالّة كتابة (setter) في jQuery تُعيد التّحديد الذي اُستدعيت لتعمل عليه. وهذا أمر عظيم الفائدة، حتّى أنّ مكتبات كثيرة اعتمدته تأثّرًا بـjQuery. ولكن يجب الحذر عند استخدامه. فالسّلاسل الطّويلة تجعل النّصّ البرمجيّة صعب القراءة والتّعديل والتنقيح لا قاعدة واضحة تفرض طولًا مناسبًا للسّلسلة، ولكن حتّى السلاسل القصيرة قد تحتاج إلى إعادة الصّياغة تسهيلًا لقراءتها. var listItems = $( 'li' ); var spans = listItems.find( 'span' ); listItems .click(function() { $( this ).addClass( 'clicked' ); }); spans.attr( 'title', 'Hover over me' );خاتمةلدينا الآن معلومات ممتازة عن تفاصيل عمل jQuery؛ وسنستعرض في الجزء القادم كيف يمكننا تطبيق هذه المعلومات لإنجاز أشياء حقيقيّة بها! مصادر إضافيةوثائق الواجهة البرمجيّة لـjQueryوثائق المُحدّداتترجمة (بشيء من التصرف) للجزء الثاني من سلسلة  jQuery Fundamentals لمؤلّفتها Rebecca Murphey.
  22. بُنيت jQuery على لغة جافاسكريبت، وهي لغةٌ غنيّة وقويّة في حدّ ذاتها. يُغطّي هذا الدّرس أساسيّات لغة جافا سكريبت، وبعض الأخطاء الشّائعة الّتي يرتكبها المبتدئون بها. يُفيد هذا الدّرس القادمين الجدد إلى عالم البرمجة، ويفيد أيضًا المبرمجين بلغات أخرى الّذين لم يسبق لهم الاطّلاع على الجوانب المميّزة لـJavaScript. فيما يلي برنامج JavaScript بسيط يُضيف رسالةً إلى صفحة ويب: // أنشئ دالّة لإلقاء التّحية على شخص // وأسندها إلى المُتغيّر `greet` var greet = function( person, message ) { var greeting = 'Hello, ' + person + '!'; log( greeting + ' ' + message ); }; // استخدم الدالّة لتحيّة Jory، بإمرار اسمها ورسالة التّحيّة greet( 'Jory', 'Welcome to JavaScript' ); // استخدم الدالّة لتحيّة Rebecca، بإمرار اسمها ورسالة مختلفة greet( 'Rebecca', 'Thanks for joining us' );مُلاحظة: في المثال السابق، استخدمنا الدّالّة log. وهي دالّة مُساعِدة متوفّرة في الأمثلة في هذه السّلسلة فقط، وليست متوفّرة تلقائيًّا في JavaScript، يمكن استخدام log في محرّر النّصوص البرمجيّة في هذه السّلسلة، ولكن ستحتاج إلى استخدام console.log محلّها في النّصوص البرمجيّة خارج السّلسلة، وعندها ستُطبع نتائج النّصّ إلى طرفيّة المتصفّح الّذي تستعمله. // create a function that will greet a person, // and assign the function to the `greet` variable var greet = function( person, message ) { var greeting = 'Hello, ' + person + '!'; log( greeting + ' ' + message ); }; // use the function to greet Jory, passing in her // name and the message we want to use greet( 'Jory', 'Welcome to JavaScript' ); // use the function to greet Rebecca, passing in her // name and a different message greet( 'Rebecca', 'Thanks for joining us' );النّتيجة النّتيجة مطالعةلم نخض في أعماق لغة JavaScript بعدُ. شبكة مُطوّري موزيلّا (MDN) مصدر ممتاز (بالإنكليزيّة) لتعلّم JavaScript بتفاصيلها، وخصوصًا دليل JavaScript على الشّبكة. أكثر المواضيع أهمّيّة لك الآن: نظرة عامّة على JavaScriptالقيم والمتغيّرات والمكوّنات الحرفيّةالدّوالّعبارات الدّوالّ المُستدعاة فورًاالمصفوفاتمصادر إضافيةشبكة مُطوّري موزيلّا: JavaScriptChrome Developer Tools OverviewFixing these jQuery: A Guide to DebuggingChrome Developer Tools Cheat SheetChrome Dev Tools: 12 Tricks to Develop Quicker (فيديو)ترجمة (بشيء من التصرف) للجزء الأول من سلسلة  jQuery Fundamentals لمؤلّفتها Rebecca Murphey.
  23. عندما بدأت العمل على إنشاء تطبيق UXPin المُتخصّص في إنشاء النّماذج الأوّليّة، كنّا نؤمن بأسلوب "الحدث العظيم" للكشف عن المُنتجات، حيث تقضي شهورًا مع فريق التّطوير تُنشئ مُنتجًا كثير الميّزات، ثمّ تكشفه للعالم. ولكنّنا اكتشفنا خلال وقتٍ قصير أنّ هذه الاستراتيجيّة غير مضمونة دومًا، بل هي تُصيب وتُخطئ، فإن أصابت تحوّل مستخدمو الإصدار التّجريبيّ إلى مُشتركين دافعين في المنتج سريعًا؛ ولكن إن أخطأت ضاعت ساعاتٌ طويلة من العمل وأموال كثيرة، وشيء من صحّتنا معها. تعلّمنا بعد ذلك أنّ علينا إصدار المُنتجات بطريقة أكثر ذكاءً، لا أكثر ضخامة! وهنا يأتي دور MVP، أو المنتج الفعّال القاعديّ (Minimum Viable Product)، الّذي يتيح للشّركات تحرّي صدق توقّعاتهم عن زبائنهم بأدنى حدّ من الجهد. مؤخّرًا، بعد أن تعلّمنا من أخطائنا السّابقة، انتقلنا إلى إصدار ميّزات أصغر بصورة أكثر تواترًا، وهذه الميّزات الصّغيرة تُمثّل منتجاتنا الفعّالة القاعديّة. فإذا أردنا مثلًا طرح كتاب إلكترونيٍّ كامل، فإنّنا ننشر في البداية صفحة تشرح الكتاب لنقيس الاهتمام العامّ به. ولو أردنا طرح ميّزات التّكامل مع مجموعة من البرامج الأخرى (كما فعلنا منذ فترة مع برنامجي Photoshop وSketch)، فإنّنا ندمج بضع ميّزات مع البرنامج الأبسط أوّلًا (في حالتنا هو Sketch) ثمّ نطرح نُسخة بيتا تجريبيّة قبل أن نتابع نحو الدّمج الشّامل. سنُبيّن في هذه المقالة بعض المفاهيم الخاطئة المُتعلّقة بـMVPs، وبعض الوسائل المُختلفة الّتي يمكن لمُصمّمي تجربة الاستخدام استعراضها بهدف تحقيق التوازن بين "القاعديّة" و"الفعّاليّة" و"جودة المنتج". منتج فعّال قاعديّ، لا تنس "المنتج الفعّال"يُشير المُصطلح "المنتج الفعّال القاعديّ"، الّذي وضعه مؤسّس SyncDev ‏Frank Robinson وعمّمه Eric Ries مؤسّس IMVU، يُشير إلى إصدار شيءٍ ما بُسرعة، سواء أكان مُنتجًا مادّيًّا أو مجرّد صفحة ترويجيّة، وغاية ذلك تبيّن صدق الافتراض الّذي قام عليه المُنتج. ولكن الحقيقة أنّه من السّهل أن نقع في فخ "القاعديّة" في MVP دون أن نتأكد من أنّ لدينا "مُنتجًا فعّالًا". يعود هذا الخطأ إلى الرّغبة في استغلال أكثر ما يمكن استغلاله من أقلّ مقدار من الموارد. ليس MVP أقل مجموعة من الميّزات الضّروريّة للحصول على مُنتج يعمل كما يُشاع، ولا هو المُنتج النّهائيّ ذاته، بل هو عمليّة. MVP هو أصغر تجربة تُثبت صحّة أو خطأ الافتراضات الّتي تقوم عليها فكرة تجاريّة. صحيحٌ أنّ التّطوير السّريع هو جوهرُ هذه العمليّة، ولكنّه صحيحٌ إلى الحدّ الّذي يُتيح لنا الوصول إلى أهداف البحث والتّعلّم فحسب. مصدر الصّورة: عمليّة MVP في Spotify تتبع عمليّة MVP خطواتٍ أربع: جِد مُشكلة يجب حلّها.حدّد أصغر حلّ ممكن (MVP).نفّذ الحلّ وجرّبه في نطاق محدود (لتُثبت قيمته الفريدة).أشرك زبائنك الّذين يهمّم تجربة الحلّ في أقرب فرصة، وأثِر حماسهم (يُعرف هؤلاء بطلائع المُبشرين "earlyvangelists").ولكن متى يكون MVP خيارًا مُناسبًا؟ تتباين الآراء حول جدوى MVP وكيفيّة تحسينه. ولكي نتبيّن الآراء المدروسة عن تلك الخاوية، علينا أن نُنصت لأولئك الّذي يخوضون التّجربة ذاتهم. MVP في صفحة ترويجيّةعندما بدأ مؤسسBuffer،‏ Joel Gascoigne، كان لديه فكرةٌ عن منتج، لكنّه لم يرغب أن يبني شيئًا لن يستخدمه أحد، لذا قرّر بناء صفحة ترويجيّة بسيطة ليعلم إن كان مُستخدمو Twitter يواجهون فعلًا مُشكلةً في جدولة التّغريدات وتدبيرها. مصدر الصّورة: Buffer كان منتجّ Buffer الفعّال القاعديّ صفحةً تشرح ما هو Buffer وكيف سيعمل، ودعت الزّوّار إلى التّسجيل وعرضت عليهم زرًّا ينقلهم إلى صفحة "الخطط والأسعار" إن أبدوا اهتمامًا، فإذا نقروا هذا الزّر عُرضت عليهم رسالة قصيرة تبيّن أنّ Buffer غير جاهز بعدُ وتنصحهم بالتّسجيل لتلقّي الأخبار المُتعلّقة بالمُنتج. استخدم Joel عناوين البريد الإلكتروني الّتي حصل عليها من المُستخدمين المُسجّلين ليُراسلهم بصفتهم مُستخدمين مُحتمَلين للمُنتج القادم، وقد أفاده ذلك أيّما فائدة في معرفة توقّعاتهم ورغباتهم. وكما في مثال السّيّارة الّذي عرضناها منذ قليل، طوّر Joel منتجه الفعّال ليتحرّى صدق فرضيّته الجديدة: هل سيدفع النّاس لمنتج كهذا؟ أضاف Joel جدول الأسعار بين صفحة التّرويج ونموذج التّسجيل، وتأكّد أن فرضيّته صحيحة، وأنّ المُستخدمين مُستعدِّون للدفع مقابل هذه الخدمة. استطاع Joel بصفحته التّرويجيّة وحدها التّحقّق من فرضيّتين دون بناء المُنتج الحقيقيّ، وبأقلّ تكلفة. يجدر بنا أن نذكر أن منتج Buffer الفعّال الأوّل اكتفى بتحرّي وجود طلب على الفكرة، ثمّ انتقل Joel وفريقه بعد ذلك إلى بناء الحلّ القاعديّ الفعّال ما إن تأكّد من جدوى تلك الفكرة. منتج إلكترونيّ فعّال على الورقمنتج UXPin الحالّي هو تطبيق ويب لإنشاء النّماذج الأوّليّة للواجهات، ولكنّهم بدؤوا بمنتجٍ قاعديّ على الورق، ورق دفترٍ تُرسم عليه النّماذج الأوّليّة. كان Marcin Treder يعلّم أن عمليّة تصميم تجربة الاستخدام قد تؤول إلى الفوضى والانفصال، وأنّ تحسين هذه العمليّة ليس أمرًا يسيرًا، لذا انصبّ اهتمامه على تبسيط مراحل النّمذجة الأوّليّة. مصدر الصّورة: UXPin يقول Marcin: "صنع المُنتجات على الورق أقل تكلفة طبعًا، ولكنّ لم يخطر ببالنا أن يكون الإصدار القادم من منتجنا تقنيًّا، فلم نكن سوى بضع مُصمّمين نحاول أن نُساعد زملائنا في تطوير مهاراتهم". بيعت أوّل دفعة من الدّفاتر الّتي صمّمها Marcin وزُملائه في 48 ساعة بعد إطلاقها عام 2011، مُؤكّدةً صحّة النّظريّة الّتي قامت عليها الفكرة، وهي حاجة المصمّمين إلى أداة أفضل لصنع نماذج أوّليّة للواجهات. سرعان ما لاحظ Marcin أنّ الأدوات الإلكترونيّة الحاليّة قاصرة، فقد افتقر Balsamiq إلى الدّقّة، وكان Axure ثقيلًا وغير مريح. بالطّبع لم تكن الدّفاتر مُنتجًا إلكترونيًّا، لذا قرّر أن يُراجع نظريّته، وبنى MVP جديدًا: نسخةً بدائيّة من أداة نمذجة إلكترونيّة. وبدل أن يسعى Marcin إلى الكمال في منتجه، فقد قرّر أن يبني UXPin على المصدر البرمجيّ لبرنامج آخر، وأضاف إليه إمكانيّة رسم النّماذج، مُقدِّمًا قيمةً مشابهة لما قدّمته الدفاتر في بيئةٍ مُختلفة. لم يكلّف إنتاج هذا المُنتج الكثير، كونه قام على مصدر برنامج آخر، ولكنّه قدّم بميّزاته حلًّا قاعديًّا فعّالًا. يقول Marcin: "لا ننظر إلى MVP على أنّه أسرع منتج أو أفضل مُنتج، بل هو المُنتج الذي يتطلّب أقلّ جهد في التّطوير ويعطي أعظم قيمةٍ في الوقت ذاته". يعترف Marcin أنّه منتجه الورقيّ الأوّل لم يُقدم أعظم قيمة، ولكنّه تطلّب أقل جهدٍ أعانه على أن يكتشف سرٍّا ثمينًا، وهو أنّ الزبائن يريدون الدّفتر، ولكنّهم يريدون أيضًا مُنتجًا إلكترونيًّا أفضل. بهذا المعنى، يمكن أن نقول أنّ منتجه القاعديّ الأوّل قد نجح. منتج فعّال قاعديّ، بلا منتج!بدأ UXPin بمُنتج ملموس كما رأينا، ولكن Dropbox بدأ بلا منتج على الإطلاق! فبالنّظر إلى التّعقيدات التّقنيّة الكبيرة الّتي تُرافق خدمة تخزين الملفّات سحابيًّا، فإنّ مُجرّد نموذج أوّليّ للخدمة يتطّلب تجاوز العديد من هذه التّعقيدات ليصل إلى حالةٍ يُعتمد عليها. وبدل أن يقضي فريق التّطوير شهورًا (إن لم تكن سنواتٍ) في بناء مُنتجٍ قد لا يستخدمه النّاس، قرّروا شرح تجربة المُستخدم في فيديو لا يتجاوز طوله 4 دقائق، فكان هذا مُنتجهم الفعّال القاعديّ، الّذي قدّم مؤسّس Dropbox، ‏Drew Houston وهو يشرح تجربة الاستخدام البسيطة: تظهر علامة "صح" خضراء على الملفّات الّتي تُحفظ في Dropbox، حفظ الملفات يتمّ بسحب الملفّات وإسقاطها في مُجلّد، لا تعقيدات تقنيّة، التّجربة تشرح نفسها! مصدر الصّورة: Dropbox هذا المُنتج الفعّال، البسيط من النّاحية التّقنيّة، أثبت جدواه، فقد انتقل عدد المُسجّلين في الخدمة بين ليلةٍ وضحاها من 5 آلاف إلى 75 ألفًا، وعُرف Dropbox في أوساط شبكة Digg باسم "قاتل Google Drive". صحيحٌ أنّ مُنتج Dropbox هذا أثبت فرضيّة Drew، لكنّه أيضًا علّمهم أن تجربة الاستخدام الممتازة الّتي يجب عليهم تقديمها هي الأمر الحاسم في نجاح المُنتج. مع أنّ إتاحة تجربة المنتج الحقيقيّ للمُستخدمين كانت وسيلةً أفضل لتجربة Dropbox، إلّا أن مقطع الفيديو كان كافيًا لفريق التّطوير. وقد استطاع Dropbox أن يقتحم سوقًا مُشبعًا بالفعل، وأن ينجح رغم ذلك، والسّبب أنّ فريق التّطوير أوفى بوعده بتقديم تجربة استخدام انسيابيّة. ينطبق في هذه الحالة المثل: "لا تُصدّق ما لم ترَ"، فقد بقي مقطع الفيديو على الصّفحة الرّئيسيّة لـDropbox من 2008 وحتى 2013! عمليّة 10x MVPبنى Ash Maurya (مؤلّف كتاب Running Lean—Helping Entrepreneurs Succeed) مُنتجًا لشركته النّاشئة Cloudfire وسوّقه مُتّبعًا عمليّة 10x، وهي عمليّة أنشأها باتّباع خطواتٍ ثلاث: جد مجموعة من 10 مستخدمين وأجرِ معهم مقابلاتٍ للوصول إلى أسباب مُشكلاتهم.ابنِ مُنتجًا فعّالًا قاعديًّا يقدّم حلًّا سريعًا وقدّمه إلى الدّفعة الأولى من الزّبائن.استخدم آراء الزّبائن لإنشاء صفحة ترويجيّة فيها رسائل حقيقيّة، وذلك بهدف جمع عناوين البريد الإلكتروني للزبائن المئة القادمين.توفّر الدّفعة التّالية من الزّبائن مزيدًا من المُرشحّين لإجراء المقابلات، ممّا يُساعد على تحسين التّسويق على الموقع، الّذي يجلب بدوره مزيدًا من الزّبائن، فهذه العمليّة تُساعد إذن في مضاعفة قاعدة المُستخدمين بمقدار 10 في كلّ خطوة. مصدر الصّورة: إطلاق منتج وفق عمليّة 10x الفكرة الأساسيّة في عمليّة 10x هي أنّ المُنتج الفعّال القاعديّ يُعامل معاملة المُنتج الحقيقيّ، لا على أنّه مُنتج تجريبيّ. هذا يعني أنّ تسعير المُنتج يُناقش في المرحلة 1 (كما في أسلوب Buffer)، والذي يساعد في حلّ مشكلة الأرباح منذ البداية. من المهمّ أن نتذكّر أنّ مُنتجنا ينافِس مُنتجاتٍ أخرى مجّانيّة، ولهذا يكون الإصغاء إلى الزّبائن ضروريًّا لفهم الأسباب الّتي تجعل الزّبائن يدفعون لمنتجنا. عمليّة إطلاق المُنتج وفق 10x ليست إلّا تنويعًا على عمليّة MVP التّقليديّة، ومع أنّها تتطّلب جهدًا أكبر ممّا يبذل على المُنتجات التّقليديّة، إلّا أنّها تسمح للفريق بمتابعة تحسين المُنتج وتوسيعها بعد انطلاقه، فمن خلال المُقابلات واستطلاعات الرأي واختبارات قابليّة الاستخدام، تهدف عمليّة 10x إلى بناء مُنتج قاعديٍّ أفضل مع الوقت، وهي الفكرة ذاتها الّتي تقوم عليها MVP، والّتي يمكن تلخيصها في تقليص المخاطرة مع ضمان الفعّاليّة القصوى. تُساعد المقابلات مع الزّبائن في تحديد من يواجهون مُكشلةً، وما الحلّ الأصغر لها، ممّا يُقلّص مخاطرة المُنتج، كما أنّ المقابلات تُساعد في قياس ردود فعل الزّبائن تجاه التّسعير والمنافسين، وتوفر فرصة لقياس اهتمام الزّبائن ووضع السّوق من خلال عدد زيارات الصّفحة التّرويجيّة وعدد المشتركين في القائمة البريديّة. التجربة الكاملة بدل الميّزات الكاملةكل الوسائل السّابقة، من الصّفحات التّرويجيّة، ومقاطع الفيديو، والمُنتجات غير الإلكترونيّة، وعمليّة 10x، كلّها وسائل مختلفة للوصول إلى الغاية ذاتها. عمليّة MVP ضروريّة لكلّ مصمّم تجربة استخدام، سواء في شركة ناشئة أو في مؤسّسة تصميم، أو لمن يعمل في بيته، والتّعلم من المستخدمين، وزيادة الفعاليّة، وتقليص المخاطر كلّها أهداف مُجزية. ربّما تكون فرضيّة "قطعة الكعك" لـBrandon Schauer أفضل طريقة للتّفكير في MVP، وهي الفرضيّة الّتي تؤكّد على ضرورة وجود تجربة مُتكاملة في كلّ مرحلة من مراحل المُنتج. فقطعة الكعك الصّغيرة تُمثِّل مُنتجًا قاعديًّا أكثر فعالية من كأس من الطّحين. تأكّد إذن أن مُنتجك القاعديّ يوصل للزّبائن قيمة المُنتج الحقيقيّ كاملةً. فإذا كان مُنتجك القاعديّ صفحةً ترويجيّة، فاحرص على أنّ تكون النّصوص واضحةً ومفهومة، وأن تكون الدّعوة صريحة، والواجهة سهلة الاستخدام؛ وإن كان مُنتجك القاعديّ نموذجًا أوّليًّا يعمل بالفعل، فيجب أن يعمل بشكل ممتاز. بناء مُنتجٍ صغير أمر يستحق الإعجاب، أمّا بناء مُنتجٍ لا يعمل كما ينبغي، فأمر غير مقبول. ترجمة (بتصرّف) لمقال Putting the “VP” into MVP لصاحبه Jerry Cao
  24. الغالب أنّ البرامج المفتوحة المصادر تتطلّب شيئًا من التّهيئة المُملّة عند أوّل استعمال، وقد يبدو للوهلة الأولى أنّ تلك التّهيئة تؤخّرك عن غايتك، التي هي حلّ المشكلة باستخدام الأداة المعنيّة، إلّا أنّ عمليّة التّهيئة فرصةٌ حسنةٌ للتّعوّد على استخدام واحدةٍ من أهمّ أدوات المهنة: سطر الأوامر. يصيب سطر الأوامر بطبيعته الرّهبة في قلوب كثير من النّاس، إذ يرونه (في عُرف المجتمع) تقنيّة غامضةً يمارسها سحرةُ الحاسوب و"المُخترقون". الحقيقة أنّه سطر الأوامر ليس بهذه الرّوعة، فما هو إلا مجموعة من الأدوات شديدة البساطة أنشأها موظّفو Bell (الّتي تُعرف الآن بالاسم AT&T) لإنجاز مهامّ معظمها بسيط في السّبعينيّات من القرن الماضي، وليس فيها من التّطوّر التّقنيّ أكثر ممّا في فرن المايكروويف في مطبخك! ولكنّ سطر الأوامر مفيد جدًّا، وفائدته تُشبه استخدام الأدوات الكهربائيّة في العمارة بدلًا من استخدام الأيدي. يمكننا تعلّم أدقّ تفاصيله بالاستعانة ببعض المفاهيم والاستعارات اللّفظية. ولعلّ واحدًا من أهمّ تلك المفاهيم هو مفهوم المسار (Path). تعتمد أدوات التّطوير للمتصفّح ومُعالجات CSS ومكتبات JavaScript وأدوات أخرى كثيرة على وجود Ruby أو Node.js مثبّتتين على جهازك. من هذه الأدوات Bower. يجمع بين هذه الأدوات كلّها أنّها ستُضطرّك إلى التّعامل مع المسار عاجلًا أو آجلًا، والسّبب أنّ يجب على المسار أن يعلم بوجود الأدوات الّتي تثبّتها في بيئة التّطوير على جهازك حتّى يستطيع سطر الأوامر أن يعمل بصورة صحيحةٍ. قد تبدو الحاجة لفهم آليّة عمل المسار عودةً إلى الوراء، ولكنّك كلّما استخدمت أدوات سطر الأوامر أكثر، كان احتمال نشوء المُشكلات عن المسار أكبر. ولئلّا تُضيّع ساعات من يومك (أو ربّما لئلّا تبدأ برمي أغراضٍ ثقيلة على شاشة حاسوبك!)، تعّلم أساسيّات استخدام المسار. مُتغيّر صغير ومتواضع ‏‎$PATH‎، كما تُفصح إشارة الدولار في أوّله والحروف الكبيرة الّتي تتلوها، مُتغيّر في بيئة Unix. القيمة المحفوظة في هذا المتغيّر هي قائمةً بمسارات إلى أدلّة (مُجلّدات)، مفصولةٍ بنقطتين (:)، كهذه القائمة: /root/directory/binary:/root/other_directory/other_binary إن كنت من أولئك المهووسين بتسمية المُتغيّرات، فقد تتساءل لما لم يُسمَّ المُتغيّر ‎$PATHS‎ (بصيغة الجمع)، كونه يحوي عدّة مسارات. اسمحوا لي بأن أخمّن السبب تخمينًا، فلربّما يُشير الاسم المُفرد إلى "مسار التّحميل المُكوّن من عدّة مسارات منفردة"، يبدو تخمينًا معقولًا! لو قادك فضولك إلى تحرّي أنواعٍ أخرى من متغيّرات البيئة في نظامك، فاكتب env في سطر الأوامر ثمّ اضغط Enter وسترى قائمة بكلّ متغيّرات البيئة الموجودة حاليًّا. بما أنّ ‏‎$PATH‎ مُتغيّر، يمكننا تغييره كما نشاء، وفي أيّ وقت. يمكنك مثلًا كتابة هذا الأمر في الصّدفة (shell) لديك: export PATH=banana ما نتيجة هذا الأمر؟ جرّب تشغيله في نافذة جديدة داخل الطّرفيّة لديك أو في أي تطبيق صدفة تستخدمه، كبرنامج Terminal في OS X. ثمّ جرّب بعد ذلك تنفيذ أي أمر Unix مثل ls (الّذي يطبع قائمة بمحتويات دليل/مُجلّد). ستكون النّتيجة: ‎-bash: ls: command not found ‎ مع أنّ الأمر كان يعمل بلا مشكلات من قبل! هذه الحيلة الشقيّة علّمتنا أنّ محتويات ‏‎$PATH‎ ضروريّة جدًّا، وإلّا اختلط الحابل بالنّابل! ولكن لماذا؟ لأنّ المسار يُحدّد ما يمكن تنفيذه في الصّدفة، فإن لم تعثر الصّدفة على شيءٍ يطابق اسمه ما كتبته، فلن تستطيع تنفيذه، والفكرة نفسها مُطبّقة في عدّة لغات برمجة وأطر عمل للويب مثل Rails. كدت أنسى! لتصحيح هذه المشكلة، أغلق برنامج الطّرفية وأعد فتحه، فالعطل مؤقّتٌ في حالتنا، ولكن إيّاك أن تحفظ القيمة الخاطئة في ملفّ ‎~/.bash_profile‎، فعندها سيكون الوضع سيئًا للغاية! حكاية عن البرامج الثّنائيّة تُدعى بعض البرامج القابلة للتنفيذ في Unix "ملفّات ثنائيّة (binaries)". لأكون صادقًا معك، هذا الاسم غير مناسب على الإطلاق، لأنّه يهتمّ بصيغة البرامج بدلًا من وظيفتها. عندما تكتب برنامج Unix يؤدّي مهمّة مُحدَّدة، فإنّك قد تحتاج إلى تجميعه (compile) قبل أن تتمكّن من تنفيذه، عمليّة التجميع هذه تُنتج ملفًّا ثنائيًّا (binary). تعتمد هذه الملفّات الصّيغة الثنائيّة لتمثيل التّعليمات بصورة يسهل تنفيذها على الحاسوب بدلًا من الملفّات النّصيّة البسيطة (كملفّات مصدر البرنامج source code). في Unix مُجلّدات عديدة تُحفظ فيها هذه الملفّات الثّنائيّة، وبإمكانك معرفة المُجلّد الذي يُستخدم بصورة مبدئيّة لتحميل الملفّات الثّنائيّة في الملف ‎/etc/paths‎: # الأمر cat يطبع محتويات ملفّ $ cat /etc/paths /usr/bin /bin /usr/sbin /sbin /usr/local/bin في كلّ سطر من الملف دليل واحدٌ. وقد رُتّبت المسارات بأسلوب مدروس، فإذا وجد ملفّ ثنائيّ في مسارٍ منها، حُمِّل، وإن وجد ملفّ آخر بالاسم نفسه في مسار آخر، لم يُنظر إليه. أي إنّ المسارات المُرتّبة في أعلى القائمة ذات أولويّة أكبر من تلك الّتي تقع أدناها. هذا هو سبب المشكلات الّتي تواجهها عندما تُحاول تثبيت برنامجٍ جديد موجودٍ مسبقًا على نظامك، كما في حالة تثبيت إصدارة جديدةٍ من Git على OS X بدل تلك المُرفقة مع النّظام. هذا أمرٌ سيّئ لأنّ Git 2.0 ممتاز! عندما أستعمل cd (الّذي يُغيّر الدّليل الحاليّ) للانتقال إلى ‎/usr/bin‎ (وهو مُجلّد يشيع تخزين البرامج فيه) ثمّ أستعمل ls، فإنّنى أرى أكثر من ألف نتيجة! وهذا أمرٌ غير مُفيد، ولذا أستعمل grep ضمن الأمر ls | grep git لأُصفِّيَ النّتائج بحيث تقتصر على البرامج تحوي الكلمة "git": $ ls | grep git git git-cvsserver git-receive-pack git-shell git-upload-archive git-upload-pack كما ترى، هناك نسخة من Git في ‎/usr/bin‎، وعند تثبيت نسخة نظيفة من OS X فستكون هذه النّسخة هي المُعتمدة، كما يُبيّن تنفيذ الأمر which git. $ which git /usr/local/bin/git مهلًا! لماذا تُخالف النّتيجة لديّ ما توقّعته؟ لنفحص الأمر بدقّة بإرفاق which بالخيار ‎-a‎: $ which -a git /usr/bin/git /usr/local/bin/git هذا يؤكّد وجود نسختين من Git مثبّتتين على نظامي، ولكنّ الأولى هي المُعتمدة عندما أُنفّذ أوامر git في سطر الأوامر. تغيير المسارات قرّرت تثبيت إصدارٍ مُختلف من Git على نظامي مُستخدمًا مُدير حزم اسمه Homebrew لأنّني أحبّ أن أُسيطر على الأدوات الّتي أستخدمها كلّ يوم وأحدّثها وقتما أشاء. كان بإمكاني تحديث نسخة Git المُرفقَة مع النّظام، لكنّني لا أعلم إن كان هذا سيُعطِب البرامج الّتي تعتمد على وجودها. نعلم إذن أنّ البحث عن البرامج يتبع ترتيب القائمة في الملفّ ‎/etc/paths‎، فلم لا نُغيّر هذا التّرتيب؟ أرى أن المُجلّد ‎/usr/local/bin‎ الّذي يحوي إصدار Git الّذي ثبّته Homebrew يقع في نهاية الملفّ ‎/etc/paths‎، وهذا يعني أن برنامج git في ‎/usr/bin‎ يطغى على الإصدار الّذي أريده. بالطّبع لا شيء يمنعك من تغيير التّرتيب في الملفّ ‎/etc/paths‎ بحيث تضع ‎/usr/local/bin‎ في أعلى القائمة لتُعتمد نسخة Git من Homebrew، وستصادف هذه النّصيحة كثيرًا على Stack Overflow، لكن صدّقني... هذا أمرٌ بالغ السوء، لا تفعله! لم يُقصد من هذه الملفّات أن يُعدّلها مستخدمٌ منفردٌ لأنّه تؤثّر على كلّ النظام (حتّى وإن كنت الوحيد الّذي تستعمل الحاسوب)، وقد يسبب هذا التّغيير مشكلات لن تُدرك أسبابها في المستقبل، فقد تعتمد إحدى الأدوات في OS X على التّرتيب الأصليّ للملفّ ‎/etc/paths‎. بدلًا من ذلك، عدّل المُتغيّر ‏‎$PATH‎ في بيئتك أنت، في نسختك أنت من الملفّ ‎.bash_profile‎، الموجودة في ‎/Users/yourusername/.bash_profile‎. ما عليك إلّا أن تجعل البحث يُجرى في ‎/usr/local/bin‎ أوّلًا في الملفّ ‎.bash_profile‎: # في الملفّ /Users/olivierlacan/.bash_profile export PATH=/usr/local/bin:$PATH وهذا يؤدّي إلى تصدير مُتغيّر البيئة ‎$PATH‎ الجديد وذلك بطباعة المُتغيّر الحاليّ وإسباقه بـ ‎/usr/local/bin‎ على يسار كلّ المسارات الأخرى. بعد حفظ الملفّ ‎~/.bash_profile‎، وإعادة تشغيل الصّدفة، يُفترض أن ترى هذه النّتيجة عند استدعاء echo على المُتغيّر: $ echo $PATH /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin لاحظ أنّ ‎/usr/local/bin‎ مذكور مرّتين في المتغيّر ‎$PATH‎ ولا بأس في هذا. ولأنّه ذُكر أوّلًا، فكلّ البرامج الّتي تُحمّل منه في المّرة الأولى ستُتجاهَل عند الوصول إليه ثانيةً. كنت أتمنّى وجود طريقة آمنة وبسيطة لتغيير ترتيب المسارات، ولكنّ الحلول الّتي أعرفها شديدة التّعقيد. بإمكانك دومًا تجاوز القيمة المبدئيّة لـ‎$PATH‎ بالكامل، ولكن فقط عندما تُدرك تمامًا ما الّذي تفعله وأيّ المسارات يجب تضمينها. على مفترق المسارات بعد أن عدّلت المسار ‎$PATH‎ كما يحلو لك، بإمكانك التأكّد من أنّ البرنامج المرغوب يُستدعى عندما تُنفّذ الأمر git: $ which git /usr/local/bin/git $ git --version git version 2.0.0 /usr/bin/git --version git version 1.8.5.2 (Apple Git-48) عظيم! الإصدارة 2.0.0 من Git هي الّذي يتلقّى أوامر git (وهي الإصدارة الّتي ثبّتها Homebrew)، بينما تغيب الإصدارة 1.8.5.2 المُرفقة مع النّظام. إن قرّرت ألّا تستخدم الإصدارة 2.0.0 بعد الآن، ليس عليك سوى إزالتها وستعود الإصدارة الأقدم للعمل بصورة انسيابيّة. احمِ مسارك تُضيف أدوات تطوير (وتصميم) كثيرة أوامر برمجيّة إلى الملفّ ‎.bash_profile‎ بعد تثبيتها، والغالب أنّها لا تخبرك بذلك، فإن وجدت مسارات غريبة مُدرجةً في الملفّ، فقد يُفسّر ذلك بطء تحميل جلسة جديدة كلّ مرّة (الأمر الّذي يحدث عند فتح صدفة جديدة أو لسان جديد في الطّرفيّة): مُتغيّر ‎$PATH‎ طويل، يتطلّب وقتًا طويلًا لتحميله. إليكم مساري اليوم: /Users/olivierlacan/.rbenv/shims:/Users/olivierlacan/.rbenv/bin:/usr/local/bin:/usr/local/heroku/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/opt/X11/bin:/usr/local/MacGPG2/bin تصعب قراءته قليلًا، لذا أُفضّل تجزئته على سطور باستخدام الأمر tr (الّذي يستبدل حروفًا مُحدّدة): $ echo $PATH | tr ':' '\n' /Users/olivierlacan/.rbenv/shims /Users/olivierlacan/.rbenv/bin /usr/local/bin /usr/local/heroku/bin /usr/bin /bin /usr/sbin /sbin /usr/local/bin /opt/X11/bin /usr/local/MacGPG2/bin أشياءٌ كثيرة تحدث هنا، ولكن فهمها أسهل وهي موزّعة على سطور. جرّب تنفيذ الأمر ذاته، فإنّ لم تفهم سبب وجود إحدى السّطور، فاحرص على إيجاده، فقد تتعلّم شيئًا مُفيدًا. قد لا تبدو فائدة تعلّم ‎$PATH‎ وكيفيّة عمله عظيمةً. ولكن لأنك مُطوِّر ويب، فستحتاج إلى التّعامل مع سطر الأوامر، وقد تتعطّل إحدى أدواتك يومًا، والآن وقد تعلّمت مسارك وكيف يبدو وهو نظيف، والأسلوب الصّحيح لتعديله، وكيف تتأكّد من أنّه يعلم بوجود برامجك، فإنّ عودتك إلى "مسارك" المهنيّ ستستغرق دقائق بدلًا من ساعات! ترجمة (بتصرّف) لمقال The $PATH to Enlightement لصاحبه Oliver Lacan
  25. لعلّ من أقوى ميّزات Sass الدّوالّ (mixins) ﻷنّها تسمح بتحديد نمط مُتكرِّر وعزله في صورة قطعة برمجيّة يمكن إعادة استخدامها مرارًا. مثال ذلك: عزل كلّ الخصائص المسؤولة عن تنسيق زرّ في صفحة الويب ثمّ —بدل الحاجة لتذكّر كل هذه الخصائص— جمعها في قطعة منفصلة يمكن استدعاؤها في مُحدِّد آخر (selector). هذا يحفظ أنماط الزرّ في موضع واحد ممّا يجعل تعديلها وتحديثها أسهل. المشكلة في هذا أنّ كثيرًا من دوّال Sass هذه تُكتب بأسلوب يجعل الخصائص مُكرّرة، وهذا قد يؤدي إلى حدوث أخطاء في عدة مواضع عند تحويلها إلى CSS بالإضافة إلى زيادة حجم الملفّ النّهائي دون طائل. صحيح أنّ استخدام Sass هو خطوةٌ على الطّريق الصّحيح لإنتاج CSS أخفّ حجمًا وأكثر اختصارًا ممّا قد يكتبه مبرمج متوسّط المستوى، لكنّك إن كنت مهووسًا بتحسين الأداء مثلي، فلن ترضى بوجود أدنى مقدار من التّكرار الّذي لا مُبرِّر له. سأدلكّ في هذه المقالة على كيفيّة تحسين مستوى CSS النّاتج عن دوالّ Sass الّتي تكتبها. مقدّمة سريعة عن Sassيمكن وصف Sass بأنّها مرحلة وسيطة بين ورقة الأنماط الّتي يكتبها مُصمّم الموقع وناتج CSS النّهائي الّذي يطلبه المتصفّح، وتفيد Sass في هذه المرحلة بإضافة العديد من الميّزات الّتي تُسهلّ كتابة CSS وصيانتها. واحدة من فوائد Sass أنّها تُساعِد الُمصمّمين في تجنّب التّكرارات في CSS، ممّا يجعل صيانتها أكثر سهولة. اصطلح على مبدأ "لا تُكرِّر نفسك" (Don't Repeat Yourself أو اختصارًا DRY) في كتاب "المُبرمج البراغماتيّ"، والذي نصّ على التّالي: لا تُساعِدنا CSS كثيرًا في تطبيق هذا المبدأ، لأنّها تُضطرّنا إلى تكرار كتابة الكثير من الخصّائص (كما في مثال الأزرار الذي ضربناه). فلو احتجت إلى إنشاء أنماط لثلاثة أنواع من الأزرار، فستضطر إلى كتابة هذه الأنماط مرّة لكلّ نوع من الأزرار، أو تجزئة الأنماط على أكثر من مُحدّد. وهما أمران أحلاهما مُرٌّ. فالأوّل يعني نسخ الخصائص ولصقها (وزيادة حجم الملفّ بالنتيجة) وتكرار مواضع الأخطاء، وغياب مصدر واحدٍ للحقيقة، وبنية هشّة في حصيلة الأمر؛ وأمّا الثاني فيعني غياب تمثيل مُفرد ومُوحَّد لكلّ نوع من الأزرار في النّظام، لأنّه يُجبرك على تجزئة الأنماط على عدّة مُحدِّدات، وهو ما يجعل البنية هشّة أيضًا، لأنّ تكوينها غير واضح. لو أردنا تحرّي المثاليّة، فعلينا إيجاد طريقة لتعريف الأنماط الأساسيّة في موضع واحد دون تكرار، ثمّ تعريف مُحدِّد مُفرد يمكن استخدامه لتطبيق كلّ الأنماط. لماذا قد أرغب باتّباع مبدأ "لا تُكرِّر نفسك"؟قد تسأل نفسك: لم كلّ هذا التّعقيد؟ الجواب باختصار: لأنّ اتّباع مبدأ DRY يُحسّن من أداء الموقع. فعندما تؤسّس هيكل الموقع، سيكون الأداء عاملًا مُهمًّا، بدءًا من صيغ الصّور الّتي نختارها ومرورًا بطريقة كتابة مُحدِّدات CSS. وهذا العاملّ يُصبح أكثر أهمّية عندما نتحدّث عن الأجهزة المحمولة، ففي هذه الأجهزة قد يفرض مُجرّد طلب HTTP بسيط تحدّيات كبيرةً في الأداء، وخطأ الاعتقاد الشّائع بعدم أهمّيّة حجم ملفّ CSS في أداء الموقع يظهر أكثر وضوحًا في الأجهزة المحمولة الّتي لا تسمح بأكثر من 100 ميغابايت من الحجم الكلّي للتّخزين المؤقّت لكلّ المواقع، وعندها يجب استغلال أكبر مقدار ممكن من مساحة التّخزين المؤقّت بأفضل صورة. الهدف من ذلك إذن هو إنشاء مُحدِّدات يمكن صيانتها في Sass وفي HTML، ويمكن تمثيلها في CSS بأقصر أسلوبٍ ممكن لتقليل حجم التّخزين المؤقّت. الدّوال (mixins) والتّوسعة (extends): حلّان غير كاملينتٌقدّم دوالّ Sass حلًّا لإحدى هاتين المُشكلتين، فهي تسمح بإنشاء موضع واحد يمكن فيه تعريف الأنماط الأساسيّة والرّجوع إليها. يمكن لهذه الدّوال أن تقبل مُعاملات (arguments) كذلك، مما يسمح باختلافات طفيفة بين استدعاء وآخر للدّالة وإنشاء أشكال مختلفة لنفس النّمط. لكن الأمر لا يخلو من مُشكلات: فقد تُسبّب هذه الدّوال تكرارًا غير مُبرّر للخصائص لأنّها ستكتب الخصائص الّتي تحويها في كلّ موضع استدعيت فيه. فالدّوالّ إذن تحل مشكلة التُمثيل المُفردّ في مبدأ DRY، لكنّها تترك خصائص متكرّرة في النّاتج النّهائيّ من CSS. ولهذا تُقدّم Sass مفهومًا آخر يُساعِد في تطبق مبدأ DRY: التّوسعة (extends)، والّتي يمكن استخدامها بكتابة الكلمة ‎@extend، والّتي تسمح لمُصمّم CSS أن يقول: "أريد للُمحدّد A أن يظهر كما يظهر المُحدّد B"، وعندها تقوم Sass بإنشاء نمط جديد يجمع بين A وB (مفصول بينهما بفاصلة)، ثمّ كتابة الخواصّ غير المشتركة بالأسلوب العاديّ. لا يمكن تمرير مُعاملات للتّوسعة (خلافًا للدّوالّ)، فهي حلّ من نوع "الكلّ أو لا شيء". .couch { padding: 2em; height: 37in; width: 88in; z-index: 40; } .couch-leather { @extend .couch; background: saddlebrown; } .couch-fabric { @extend .couch; background: linen; } .couch, .couch-leather, .couch-fabric { padding: 2em; height: 37in; width: 88in; z-index: 40; } .couch-leather { background: saddlebrown; } .couch-fabric { background: linen; } تحلّ التّوسعة مشكلة تكرار الخصائص والمُحدِّد المُفرّد في CSS النّاتج، لكنّها لا تُخلّص مُصمّمي المواقع من الحاجة لصيانة مجموعتين منفصلتين من الأنماط في ملفّات Sass، ولا من الحاجة لتذكّر أيّ الخصائص ينطبق على أيّ نوع—فالأمر لا يختلف كثيرًا فيما لو كتبنا محدّدين منفصلين في الأساس. كما نرى، فكلا الحّلين (الدّوالّ والتّوسعة) غير كاملين، ولكن عند جمعهما معًا ثمّ اعتماد بنيّة ذكيّة في تصميم مشروعنا، مع الاستفادة من بعض ميّزات Sass سنحصل على دالّة نهائيّة تخضع لمبدأ DRY تمامًا، وتجمع نصفي الحلّ في صيغة موحّدة سواءٌ في ملفّات Sass الأصليّة أو في ناتج CSS. المكوّنات الأساسيّة لمبدأ "لا تُكرِّر نفسك"تضع المُكوّنات الأربعة التّالية حجر الأساس لبناء دوالّ موافقة لمبدأ DRY في Sass: المُحدّدات بالنّيابة (placeholder selectors)، والجداول (maps)، والكلمة ‎@at-root، والدّالة unique-id()‎. المُحدّدات بالنّيابةوهي نوعٌ خاصّ من المُحدّدات يُستخدم مع الكلمة ‎@extend في Sass. تُكتب هذه المُحدّدات كما تُكتب الأصناف التّقليديّة (classes)، ولكنّها تبدأ بالرّمز % بدل النّقطة .، وتتصرّف عند توسعتها بصورة عاديّة، ولكنّها لا تُضاف إلى النّاتج النّهائيّ إلّا عند توسعتها. وكما في التّوسعة العاديّة، تُضاف خواصّ المحدّدات في الموضع الّذي عُرّف فيه المُحدّد. %foo { color: red; } .bar { @extend %foo; background: blue; } .baz { @extend %foo; background: yellow; } .bar, .baz { color: red; } .bar { background: blue; } .baz { background: yellow; } الجداول (maps)وهي نوعٌ من أنواع البيانات في Sass ‎3.3 (مثلها مثل الأرقام والسلاسل النّصيّة والقوائم) تتصرّف بطريقة مُشابهة للكائنات في JavaScript. تتألّف الجداول من أزواج مفتاح/قيمة (key/value)، حيث يمكن للمفتاح والقيمة أن يكونا أي نوع من أنواع البيانات في Sass (بما في ذلك الجداول نفسها). المفاتيح فريدة دومًا ويمكن الوصول إليها باسمها، ممّا يجعلها مثاليّة لتخزين البيانات المُميّزة واسترجاعها. $properties: ( background: red, color: blue, font-size: 1em, font-family: (Helvetica, arial, sans-serif) ); .foo { color: map-get($properties, color); }الكلمة ‎@at-rootقُدّمت هذه الكلمة في Sass ‎3.3، وهي تقوم بنقل الخواصّ المُعرّفة ضمنها إلى جذر ورقة الأنماط (أعلى مستوىً فيها)، بغض النّظر عن الموضع الّذي استخدمت فيه. الدّالّة unique-id()‎قدّمت في Sass ‎3.3 كذلك، وهي تُعيد مُعرّف CSS يُضمن كونُه فريدًا في كلّ عمليّة تحويل من Sass إلى CSS. كتابة دالّة بسيطةيتطلّب تحويل نمط مُتكرِّر إلى دالّة النّظرَ في الأنماط الأساسيّة المُكوّنة لها ثم تحديد ما المشترك بينها وما الذي يختلف بحسب مُدخَلات المستخدم. سنستخدم زرًّا بسيطًا كمثال في حالتنا: .button { background-color: #b4d455; border: 1px solid mix(black, #b4d455, 25%); border-radius: 5px; padding: .25em .5em; &:hover { cursor: pointer; background-color: mix(black, #b4d455, 15%); border-color: mix(black, #b4d455, 40%); } }لتحويل هذا إلى دالّة، اختر الخصائص الّتي يتحكّم بها المستخدم (الديناميكيّة)، والخصائص الثّابتة. يُتحكّم بالخصائص الدّيناميكيّة عبر مُعامِلات تُمرّر إلى الدّالة، أمّا الخصائص الثّابتة فتكتب بالأسلةب العاديّ. في حالة الزّرّ الذي نُصمّمه، لا نريد سوى أن يتغيّر اللّون، ولهذا نستدعي الدّالّة بمُعامل اللّون، لُينتَج CSS كما نتوقّع: @mixin button($color) { background-color: $color; border: 1px solid mix(black, $color, 25%); border-radius: 5px; padding: .25em .5em; &:hover { cursor: pointer; background-color: mix(black, $color, 15%); border-color: mix(black, $color, 40%); } } .button { @include button(#b4d455); }لا بأس بهذا الحلّ، ولكنّه سيؤدّي إلى تكرار خصائص كثيرة، افترض مثلًا أنّنا نريد إنشاء زرّ آخر بلون مختلف، عندها ستبدو شيفرة Sass كما يلي (دون تعريف الدّالّة): .button-badass { @include button(#b4d455); } .button-coffee { @include button(#c0ffee); }وستبدو شيفرة CSS كما يلي: .button-badass { background-color: #b4d455; border: 1px solid #879f3f; border-radius: 5px; padding: .25em .5em; } .button-badass:hover { cursor: pointer; background-color: #99b448; border-color: #6c7f33; } .button-coffee { background-color: #c0ffee; border: 1px solid #90bfb2; border-radius: 5px; padding: .25em .5em; } .button-coffee:hover { cursor: pointer; background-color: #a3d8ca; border-color: #73998e; }هناك الكثير من الخصائص المُكرَّرة هنا، لا نريد هذا! لذا سنلجأ إلى استخدام المُحدِّدات بالنّيابة. إزالة التّكرار من دالّةإزالة التّكرار من الدّالة يقتضي تجزئتها إلى أجزاء ثابتة وأخرى ديناميكيّة. الأجزاء الدّيناميكيّة هي ما سيستدعيه المستخدم، وأمّا الثّابتة فتحوي الأجزاء الّتي ستكون مُكرّرة فيما لو لم نجمعها في دالّة. @mixin button($color) { @include button-static; background-color: $color; border-color: mix(black, $color, 25%); &:hover { background-color: mix(black, $color, 15%); border-color: mix(black, $color, 40%); } } @mixin button-static { border: 1px solid; border-radius: 5px; padding: .25em .5em; &:hover { cursor: pointer; } }الآن وقد فصلنا دالّتنا على جزأين، نريد أن نُوسِّع العناصر في button-static لتجنّب التّكرار. يمكن إنجاز ذلك باستخدام مُحدّد بالنّيابة بدل الدّالة، ولكن هذا يعني نقل المُحدّدات في ورقة الأنماط، لذا سنقوم بإنشاء مُحدّد بالنّيابة في الموضع ذاته ديناميكيًّا، بحيث يُنشأ حالما يُحتاج إليه ويبقى بترتيبه الأصليّ كما نتوقّع. لفعل ذلك، سنقوم أوّلًا بإنشاء مُتغيّر في النّطاق العامّ لحفظ أسماء المُحدّدات الدّيناميكيّة: $Placeholder-Selectors: ();ثمّ نتحرّى وجود مفتاح يُطابق محدّدنا في button-static، وسندعو هذا المفتاح "button" في الوقت الحالي. باستخدام الدّالّة map-get، سنحصل على قيمة المفتاح المطلوب أو القيمة null إن لم يُوجد، وفي الحالة الأخية سنُعيّن قيمته إلى مُتغيّر فريد (unique ID) باستخدام map-merge. سنستخدم الوسم ‎!global كوننا نريد كتابة قيمة متغيّر عُرِّف في النّطاق العامّ: $Placeholder-Selectors: (); // ... @mixin button-static { $button-selector: map-get($Placeholder-Selectors, 'button'); @if $button-selector == null { $button-selector: unique-id(); $Placeholder-Selectors: map-merge($Placeholder-Selectors, ('button': $button-selector)) !global; } border: 1px solid; border-radius: 5px; padding: .25em .5em; &:hover { cursor: pointer; } }بعد معرفة وجود مُعرّف لمُحدّدنا، نُريد إنشاء هذا المُحدّد، ونفعل هذا باستخدام الكلمة ‎@at-root وصياغة تعويض القيم في النّصوص (‎#{}‎) لإنشاء مُحدّد بالنّيابة في جذر ورقة الأنماط اسمه هو المُعرِّف الفريد الّذي حصلنا عليه. محتويات هذا المُحدِّد ستكون استدعاء للدّالة الثّابتة (لاحظ الاستدعاءات المتداخلة! يا للرّوعة!). ثمّ نُوسّع هذا المُحدّد بالنّيابة ذاته، ممّا يُفعّله ويؤديّ لكتابة خواصّه إلى CSS. باستخدام مُحدّد بالنّيابة في هذه الحالة بدل توسعة مُحدّد تقليديّ كصنف (class)، فإنّ محتويات المُحدّد ستُضاف إلى CSS النّهائي فقط إن وُسِّع المُحدِّد، ممّا يسمح بتقليص حجم الملفّ. وباستخدام التّوسعة بدل كتابة الخصائص مباشرةً، نكون قد تجنّبنا تكرار الخصائص في الوقت ذاته. في النّهاية سيمنع هذا هشاشة النّاتج النّهائي من CSS، لأنّه في كلّ مرّة تُستدعى هذه الدّالة، فستكون الخصائص المُشتركة ضمنها مُشاركة بالفعل ضمن ناتج CSS، وليس فقط مرتبطة مع بعضها في مرحلة المُعالجة المسبقة لـCSS فحسب. $Placeholder-Selectors: (); // ... @mixin button-static { $button-selector: map-get($Placeholder-Selectors, 'button'); @if $button-selector == null { $button-selector: unique-id(); $Placeholder-Selectors: map-merge($Placeholder-Selectors, ('button': $button-selector)) !global; @at-root %#{$button-selector} { @include button-static; } } @extend %#{$button-selector}; border: 1px solid; border-radius: 5px; padding: .25em .5em; &:hover { cursor: pointer; } }لكن مهلًا، فلم ننتهِ بعدُ. ستبقى هناك محتويات مُكرّرة في النّاتج النّهائي، وهذا شيء لا نريده (حيث سنخصل على مُحدّد يوسّع نفسه، وهذا لا نريده أيضًا). لتجنّب هذا، سُنضيف مُعاملًا إلى button-static يُحدّد إن كان يجب المُضيّ بعمليّة التّوسعة أم لا. سُنضيفه هذا إلى دالّتنا الدّيناميكيّة أيضًا، ونمرّره إلى دالّتنا الثّابتة، وفي النّهاية ستكون لدينا الدّوالّ التّالية: $Placeholder-Selectors: (); @mixin button($color, $extend: true) { @include button-static($extend); background-color: $color; border-color: mix(black, $color, 25%); &:hover { background-color: mix(black, $color, 15%); border-color: mix(black, $color, 40%); } } @mixin button-static($extend: true) { $button-selector: map-get($Placeholder-Selectors, 'button'); @if $extend == true { @if $button-selector == null { $button-selector: unique-id(); $Placeholder-Selectors: map-merge($Placeholder-Selectors, ('button': $button-selector)) !global; @at-root %#{$button-selector} { @include button-static(false); } } @extend %#{$button-selector}; } @else { border: 1px solid; border-radius: 5px; padding: .25em .5em; &:hover { cursor: pointer; } } }بعد كلّ هذا العناء، أنشأنا لأنفسنا طريقة لتسهيل صيانة أنماط Sass وتوفير مُحدِّد مُفرد في HTML، وإبقاء حجم CSS في أدنى الحدود. فهمها بلغ عدد استدعاءات دالّة button في شيفرتنا، لن تُكرَّر الخصائص الثّابتة فيها. عند استدعاء دالّتنا لأوّل مرّة، سُتنشأ الأنماط الّتي فيها ضمن CSS في الموضع الّذي استدعيت فيه، ممّا يُحافظ على تراكب الأنماط بالتّرتيب المُتوقّع، ويقلّل هشاشة البنية. وبما أنّنا نسمح بعدّة استدعاءات للدّالّة نفسها، فبإمكاننا إنشاء "نكهات" مُختلفة بسهولة وصيانتها في كلا Sass وHTML. في هذه المرحلة سيكون لدينا مصدر Sass وناتج CSS كما يلي: .button-badass { @include button(#b4d455); } .button-coffee { @include button(#c0ffee); } .button-decaff { @include button(#decaff); } .button-badass { background-color: #b4d455; border-color: #879f3f; } .button-badass, .button-coffee, .button-decaff { border: 1px solid; border-radius: 5px; padding: .25em .5em; } .button-badass:hover, .button-coffee:hover, .button-decaff:hover { cursor: pointer; } .button-badass:hover { background-color: #99b448; border-color: #6c7f33; } .button-coffee { background-color: #c0ffee; border-color: #90bfb2; } .button-coffee:hover { background-color: #a3d8ca; border-color: #73998e; } .button-decaff { background-color: #decaff; border-color: #a697bf; } .button-decaff:hover { background-color: #bcabd8; border-color: #857999; } لاحظ كيف يُفصل بين الخصائص الثّابتة بفواصل في موضع تعريفها، ممّا يجعل تتبّع الأخطاء أسهل، ويحافظ على ترتيب النّصّ المصدريّ، ويقلّل حجم ملف CSS، وبحيث لا تُنشأ مُحدّدات جديدة إلّا للخصائص الّتي تتغيّر. أليس هذا رائعًا؟ المتابعةالحقيقة أنّ كتابة النّمط نفسه لكلّ دالّة لا يتوافق مع مبدأ DRY على الإطلاق؛ بل على العكس تمامًا فهو WET (اختصارًا لـWrite Everything Twice، هل لاحظت ظرافة المبرمجين؟!). لا نريد هذا، بل يجب أن نُفكّر في كتابة دالّة لتوليد المُحدّدات بالنّيابة، يمكننا استدعاءها بدل ذلك. أو في حال استخدام إضافة Toolkit لـSass (إما من خلال Bower أو كإضافة لـCompass)، فيمكن في هذه الحالة استخدام الدّالة dynamic-extend لإيجاد وإنشاء وتوسعة مُحدّد ديناميكيّ بالنّيابة. كلّ ما عليك فعله إرسال سلسلة نصيّة للبحث عنها (مثل "button"): @import "toolkit"; @mixin button($color, $extend: true) { @include button-static($extend); background-color: $color; border-color: mix(black, $color, 25%); &:hover { background-color: mix(black, $color, 15%); border-color: mix(black, $color, 40%); } } @mixin button-static($extend: true) { $button-selector: map-get($Placeholder-Selectors, 'button'); @if $extend == true { @include dynamic-extend('button') { @include button-static(false); } } @else { border: 1px solid; border-radius: 5px; padding: .25em .5em; &:hover { cursor: pointer; } } }وهكذا يمكن القضاء على أي تكرار في دالّتنا، مما يقلّل حجم ملفّات Sass الأصلية وملفّات CSS النّهائيّة معًا، ممّا يدفعنا خطوة على طريق إعادة استخدام مكوّناتنا في مشاريع أخرى. ترجمة (بشيء من التصرف) للمقال ‎DRY-ing out Your Sass Mixins‎ لصاحبه Sam Richard.
×
×
  • أضف...