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

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

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

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

  • Days Won

    13

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

  1. إذا التقى شخصان في قرى الهيمالايا، حيِّا أحدهما الآخر قائلًا: "هل جسدك معافى؟" وأما في اليابان فقد ينحنيان أحيانًا، وفي عُمان يطبع كلّ منهما قبلة على أنف الآخر بعد التصافح، في كمبوديا وتايلاند، يضمّ كلّ منهما يديه وكأنّه يدعو. كل هذه الوسائل هي "بروتوكولات" للتواصل، أي سلسلة بسيطة من الرموز ذات المعنى والّتي تمهّد لتبادل حديث مُفيد. في عالم الويب، لدينا بروتوكول فعّال جدًّا على مستوى التّطبيقات يُمهّد الحواسيب حول العالم لتبادل الأحاديث النّافعة، واسمه 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>.
  2. استضافة المشروع على الويب تختلف في كثير من نواحي عن البيئة المحلّيّة أثناء التّطوير، علينا أخذ عدّة أمور في عين الاعتبار قبل أن يصبح مشروعنا جاهزًا للنّشر (deployment). الأمان والحمايةعندما طوّرنا المشروع، لم نلقِ بالًا لمواضيع الحماية والأمان (سوى تجزئة كلمة المرور)، لأنّ الطّلبات في بيئة التّطوير تصدر وتعود للجهاز نفسه دون خوف من مرورها على جهات أخرى، أمّا على الويب فإنّ الطّلبات ستمرّ عبر عشرات ومئات بل ربّما آلاف الأجهزة قبل أن تصل للطّرف الآخر. ولذا علينا تنفيذ ما يلي كحدّ أدنى لحماية خادومنا والمستخدمين في وقت واحد: استخدام HTTPS: عند إنشاء المستخدم لحساب جديد أو تسجيله دخوله، يجب أن تنتقل كلمة المرور مُعمَّاة (encrypted) من المتصفّح للخادوم لكي لا تستطيع أطراف أخرى، مثل مزوّد خدمة الإنترنت أو مُخترقي الشّبكة، الاطّلاع عليها أثناء انتقالها، وهو ما يمكن تحقيقه باستخدام بروتوكول HTTPS، كيفيّة اعتماد HTTPS على الخادوم أمر خارج عن نطاق هذه السّلسلة. صلاحية الجلسات: ذكرنا أنّه من المُفضّل فرض حدّ على صلاحيّة الجلسة يتمّ بعدها إعادة طلب تسجيل الدّخول من المستخدم. إذا استطاع مخترق ما الوصول إلى جهاز المستخدم أو سرقة الكعكات من متصفّحه، فسيصبح بإمكانه انتحال شخصيّة هذا المستخدم والتّعليق باسمه أو إضافة التّدوينات إن كان الكاتب يملك هذا الإذن. التحقق من البريد الإلكتروني: لم نطلب من المستخدم عنوان بريده الإلكتروني عند إنشاء نموذج التّسجيل في الموقع لغرض تبسيط الدرس، ولكن من الأفضل طلبه مع التّحقّق من صحته بإرسال رسالة تحوي رابط التّحقّق وذلك لحماية المدوّنة من الهجمات الآلية الّتي تهدف إلى خلق ضغط على الخادوم أو إغراقه بالتّعليقات غير المرغوبة. الأداءالتخزين المؤقت (caching): الحواسيب اليوم سريعة للغاية، لا سيما الخواديم، معالجات بقدرات هائلة وأحجام كبيرة من الذاكرة وسعة التّخزين، لكن المشكلة الوحيدة الّتي لا تزال تؤثّر على أدائها، على الرّغم من كلّ التّطوّرات في السّنوات الأخيرة، ما تزال أقراص التّخزين، نعم هناك أنواع جديدة من الأقراص (SSD) تقدّم سرعات أكبر، لكنّها ما تزال مُكلفة وغير شائعة على الخوادم، في كلّ استعلام لقاعدة البيانات سيذهب خادوم MySQL إلى القرص ليجلب النّتائج، وهذا يعني أنّ كلّ صفحة يزورها كلّ مستخدم ستتطلّب على الأقل مرورًا واحدًا على القرص، وإذا كان القرص ميكانيكيًّا، وهي الأقراص الأكثر شيوعًا في الحواسيب إلى اليوم، فإنّ هذا سيستغرق وقتًا ملحوظًا. من الأفضل تخزين النّتائج للاستعلامات الشّائعة والّتي لا تتغيّر كثيرًا (كنصّ التّدوينة، ومعلومات المستخدمين) في الذّاكرة (RAM) لأنّها كبيرة الحجم عادةً، وأسرع بكثير من الأقراص الصّلبة. يمكننا تخزين هذه النّتائج بأسلوب بدائي بشكل كائن JavaScript ضمن متغيّر يبقى ضمن الذّاكرة، لكنّ هذا الحلّ ليس عمليًّا، لذلك وُجدت العديد من الحلول الّتي تعتمد بنية خادم/عميل مثل Redis‏ وMemcached‏، ببساطة يمكن تخزين نتائج الاستعلامات في قاعدة بيانات Memcached الّتي تعمل في الذّاكرة، والخادوم يتولّى إدارة مساحة الذّاكرة وتوزيعها على عدّة حواسيب إن تطلّب الأمر، كما في المواقع الضّخمة. العديد من المواقع الكبيرة تستخدم Memcached، منها Google وTwitter وWikipedia.من الوسائل الأخرى لتسريع عمل الموقع أن نطلّب من المتصفّح تخزين الملفّات الّتي لا تتغيّر كثيرًا لفترة أطول قبل أن يجلبها من جديد، يمكن القيام بهذا من خلال ترويسات في جواب HTTP تُدعى بترويسات إدارة الذّاكرة المؤقتة (Cache Control Headers)، يمكن أن نطلب من المتصفّح على سبيل المثال أن يُخزّن ملفّ style.css لمدّة 10 أيّام على جهاز المتصفّح بحيث لا يضطّر لجلبه مجدّدًا لكلّ صفحة يزورها المستخدم. في Express يمكن استخدام خيار maxAge للوظيفة static()‎ لتعيين المُدّة القصوى لتخزين الملفّات الثّابتة مُقدّرة بالميلي ثانية: app.use(express.static(__dirname + '/public', { maxAge: 60 * 60 * 24 * 1000 }));وأمّا لطلباتنا الأخرى، فيكمننا تعيين قيم ترويسات الجواب بالطّريقة التّالية: response.set("Cache-Control", "private, max-age=3600"); أو: response.set({ "Cache-Control": "private, max-age=3600, must-revalidate", "Expires": "Tue, 13 Jan 2015 21:49:10 GMT" }); إضافة الفهارس لقاعدة البيانات: قاعدة البيانات لدينا ما تزال صغيرة ولا تحتوي الكثير، لكنّنها ستتضخّم مع زيادة التّعليقات والتّدوينات والمستخدمين بلا شكّ، وعندها سيصبح من الضّروريّ إضافة الفهارس على الأعمدة الأكثر استعلامًا لتسريع جلب النّتائج. يمكن تشبيه الفهارس في قواعد البيانات بالفهارس في الكتب؛ إذا أردت الوصول إلى قسم معيّن في كتاب ضخم، فإنّك ستفضّل الاطّلاع على الفهرس، لأنّ ذلك أسرع من المرور على كلّ أقسام الكتاب، على الرّغم من أنّ الفهرس قد يتطلّب بضع صفحات إضافية من الكتاب، الّتي يمكن تشبيهها بمساحة تخزين إضافيّة على القرص. تقليص الملفات: قد يصبح حجم وعدد ملفّات المشروع الّتي ستصل للمتصفح مثل ملفّات CSS وJavaScript كبيرًا مع تطوّر المشروع، على الرّغم أنّنا لم نستخدم أيّة مكتبات JavaScript ضخمة أو خطوط ويب كبيرة الحجم، إلّا أنّنا سنفعل ذلك في المشاريع الواقعيّة غالبًا، عندها ستصبح الحاجّة لتقليص حجم هذه الملفّات وجمعها أمرًا له ما يُبرّره، المتصفّحات لا تهتمّ إن كانت ملفّات CSS الّتي تصلها خمسة أو واحدًا، ما يهمّها هو المحافظة على ترتيب سطور الشّيفرة، كما أنّها لا تهتم بالفواصل والمسافات في ملفّات JavaScript، ولا حتّى بأسماء المتغيّرات طالما استخدمت الأسماء نفسها، وهذا هو مبدأ التّقليص (minification) والجمع (concatenation)، لنأخذ نصّ JavaScript كهذا: var myVeryLongVariableName = "something"; function doSomething() { return myVeryLongVariableName; }بعد تقليص هذا الملفّ: var a="something";function doSomething(){return a;}بالطّبع سيصبح فارق الحجم بين الملفّين كبيرًا مع تضخّم الشّيفرة وزيادة تعقيدها. كلا الشّيفرتين تؤدّيان المهمّة ذاتها على المتصفّح، ولكن الأخيرة ستقلل من حجم طلب HTTP وهو ما ينعكس على سرعة تحميل الموقع وبالتّالي على قبول المستخدم للموقع وتجربته. تتوفّر في Node.js الكثير مع الوحدات الّتي تنفّذ مهمّات الجمع والتّقليص لمختلف لغات الويب، ولعلّ من أشهرها UglifyJS‏ لنصوص JavaScript، وclean-css‏ لملفّات CSS. أمّا كيفيّة استخدامها وأتمتتها فهو موضوع خارج عن نطاق هذه السّلسة. استخدام وسائل ضغط مثل gzip: وهي آليّة لضغط الصّفحات لتقليص حجمها ثمّ تعيين ترويسة Encoding في جواب HTTP لتُشير للمتصفّح بأنّ المحتوى المنقول مضغوط، وسيقوم المتصفّح الّذي يدعم هذه الآليّة بفكّ ضغطه ثمّ التعامل معه كأيّ جواب آخر، يتعرّف الخادوم على المتصفّحات الّتي تدعم gzip عبر ترويسة Accept-Encoding، أمّا المتصفّحات الأخرى فتُرسل لها الإجابة دون ضغط. معظم المتصفّحات الحديثة تدعم gzip، وتتوفّر لـExpress الوحدة compression‏ للقيام بالمهمّة تلقائيًّا. ملفات المشروعللحصول على نسخة من ملفّات المشروع لتجربتها محلّيًّا والتّطوير عليها، يمكن عمل فرع عن المشروع أو استنساخه من صفحته على GitHub‏.
  3. المستخدمون، محور عالم تجربة المستخدم، وهمّنا الأوّل والأخير. أحد القوانين المقدّسة في تجربة المستخدم: "لا تلم المستخدم مهما يكن"، على الرغم من أنّ لومه قد يكون مُغريًا أحيانًا. فهرس سلسلة مدخل إلى تجربة المستخدم: مدخل إلى تجربة المستخدم 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.
  4. لكل شيء تجربة مُستخدم. مهمّتنا ليست خلق تجربة المُستخدم، بل تحسينها. ولكن ما معنى "تحسين" تجربة المُستخدم؟ هذا هو الدرس الأول من سلسلة مدخل إلى تجربة المستخدم: مدخل إلى تجربة المستخدم 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.
  5. هل زرت فيسبوك اليوم؟ ماذا عن تويتر؟ 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.
  6. في الدّرس السّابق قمنا بثتبيت Node.js وخادوم MySQL وبقيّة متطلّبات المشروع، حان الوقت لنبدأ العمل الحقيقيّ! إنشاء صفحة المدوّنة الرئيسيّةأهمّ ما تعرضه الصّفحة الرئيسيّة لكلّ مدوّنة عادةً آخر التّدوينات بتاريخ كتابتها من الأحدث للأقدم، وسنركّز الآن على تطبيق هذا الجزء على أنّ نتوسّع في إضافة الميّزات في وقتٍ لاحق. أنشئ الملفّ index.js الّذي يُمثّل نقطة انطلاق مشروعنا، ولنبدأ باستيراد Express ضمنه: var express = require("express");لنُنشئ الآن تطبيق Express جديد، وهو يمثّل الخادوم الذي يُدير مدوّنتنا بالكامل، يتمّ إنشاء تطبيق Express ببساطة باستدعاء دالّة express الّتي أنشأناها لتوّنا: var express = require("express"); var app = express();تكون الصّفحة الرئيسيّة للمدوّنة على الرّابط الجذر للموقع عادةً، وهو ما نُعبّر عنه بـ/، سنطلب من تطبيقنا الاستجابة للطّلبات التي تصل إلى هذا الرّابط بعرض صفحة HTML تحوي آخر 10 تدوينات مرتّبة وفق تاريخ كتابتها من الأحدث إلى الأقدم: var express = require("express"); var app = express(); app.get("/", function(request, response) { // أرسل HTML });لندع إرسال الصّفحة جانبًا ولنفهم أسلوب استخدام Express، لكلّ تطبيق Express وظائف أربعة تُستخدم في استقبال وتوجيه الطّلبات، وهي get وpost وput وdel، وهذه الوظائف توافق أفعال HTTP الشّائعة. ولكن ما هي أفعال HTTP؟ كيف يعمل HTTP؟في كلّ مرّة تزور صفحة على الويب فإنّ متصفّحك يرسل للخادوم الذي يستضيف الموقع طلبًا بالحصول (GET) على المحتوى في الرابط الذي كتبته، يكون طلب HTTP هذا مشابهًا للمثال التّالي (المُبسَّط عمدًا): GET www.myblog.com/hello-world HTTP/1.1 Accept: text/html Accept-Language: ar-sy,ar; User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:34.0) Gecko/20100101 Firefox/34.0تُسمّى الحقول Accept وAccept-Language... بترويسات الطّلب (Request Headings)، ولكلّ ترويسة معنىً بالنّسبة للخادوم الذي يستقبل الطّلب، فمثلاً يقوم المتصفّح في الحقل User-Agent بالتّعريف عن نفسه، وهو ما يسمح للخادوم بإرسال جواب مخصّص لكلّ متصفّح مثلاً (إن شاء)، وفي الحقل Accept-Language يُرسِل المتصفّح اللّغات الّتي يرغب المستخدم برؤية الجواب بها، فيقوم الخادوم بإرسالة الصّفحة بالعربيّة (سوريا) ar-sy في حالتنا إن توفّرت لديه، أو بالعربية ar كخيار ثانٍ... وهكذا. يردّ الخادوم على الطّلب بجواب HTTP (‏HTTP Response) الذي يُشبه مثالنا هذا: HTTP/1.1 200 OK Content-Type: text/xml; charset=utf-8 Content-Length: 3918 <!DOCTYPE html> <html lang="ar"> <head> <title>مُدوّنتي - مرحبًا بالعالم!</title> </head> <body> مرحبًا بكم في مدوّنتي المتواضعة! </body> </html>السّطر الأوّل في الجواب يُسمّى سطر الحالة، ويتضمّن حالة الطّلب (حيث الرّقم 200 يعني أن الخادوم تلقّى الطّلب وردّ عليه بما هو متوقّع)، بقيّة السّطور هي ترويسات الجواب (Response Headings) الّتي تعني كلّ واحدة منها شيئًا ما لمستقبل الجواب (المتصفّح). يلي الترويسات متن الجواب (Response Body) الذي يحوي في حالتنا صفحة HTML الّتي سيقوم المتصفّح بعرضها على المستخدم. فعل GET الذي استخدمناه ليس وحيدًا، فهناك أفعال أخرى مثل POST الذي يُستخدم في المتصفّح لإرسال الحقول التي يُعبّئها المستخدم (كتعبئة حقل تسجيل الدّخول)، والفعل DELETE الذّي يستخدم ليطلب من الخادوم حذف محتوى ما (مثل حذف تدوينة من قبل المستخدم). الجدير بالذّكر أن الخادوم حرّ التّصرّف بالطّلبات التي يتلقّاها، والطّريقة التي شرحناها بهذه الأفعال مبنيّة على التّقاليد الشّائعة لاستخدامها، فلا شيء في الحقيقة يمنع الخادوم من حذف تدوينة عندما يتلقّى طلب GET بدلاً من DELETE وإنّما هو عُرف متّفق عليه. لنعد الآن إلى مثالنا السّابق، تقبل الوظيفة get مُعاملين أولهما الرّابط المطلوب التّعامل معه، والأخرى دالّة تقرأ الطّلب وتعدّل جوابه قبل إرسال الجواب للمُتصفّح، يمكن إرسال متن الجواب للمتصفّح من خلال الوظيفة send()‎ للكائن response: var express = require("express"); var app = express(); app.get("/", function(request, response) { var html = "<!DOCTYPE html><html lang='ar'>" + "<head><title>مُدوّنتي!</title></head>" + "<body>" + posts.map(function(post) { return "<li>" + post.title + "</li>"; }).join("") + "</body></html>" response.send(html); });في الحالة الافتراضية سيكون جواب هذا الطّلب بالرّمز ‎200 OK مع متن يطابق محتوى المُتغيّر html. سنتعرّف فيما بعد على كيفيّة تغيير رموز الحالة بحيث نُرسل الرّمز الشّهير ‎404 Not Found عندما لا نجد تدوينة على الرّابط المطلوب. سيتوقّف البرنامج في هذه الحالة مُعطيًا خطأ بسبب كون posts غير معرّف، كلّ ما علينا الآن هو جلب التّدوينات من خلال قاعدة البيانات وتخزينها ضمن المُتغيّر posts، نحتاج إذًا لتنفيذ استعلام MySQL لجلب أحدث التدوينات، ولهذا سنقوم باستيراد وحدة mysql التي قمنا بتثبيتها وتأمين الاتصال بقاعدة البيانات: var express = require("express"); var mysql = require("mysql"); var connection = mysql.createConnection({ host: "localhost", user: "root", password: "", database: "myblog" }); connection.connect(); var app = express(); app.get("/", function(request, response) { connection.query( "SELECT * from `posts` ORDER BY date DESC LIMIT 10;", function(err, posts) { if (err) throw err; var html = "<!DOCTYPE html><html lang='ar'>" + "<head><title>مُدوّنتي!</title></head>" + "<body>" + posts.map(function(post) { return "<li>" + post.title + "</li>"; }).join("") + "</body></html>"; response.send(html); }); }); ملاحظة: لا تنسَ تغيير اسم المستخدم وكلمة المرور ليتوافقا مع ما اخترته أثناء تثبيت MySQL. تمتلك وحدة mysql وظيفة createConnection()‎ الّتي تُعيد لنا نسخة من اتّصال بقاعدة البيانات الّتي حدّدناها، والذي يمكن بدؤه باستدعاء الوظيفة connect()‎ ثم تّنفيذ الاستعلامات query()‎ الّتي تتمّ بأسلوب غير متزامن (asynchronous) لتُعيد لنا الصّفوف النّاتجة عن الاستعلام ضمن المعامل الثّاني للدّالة (function(err, posts) { ... }‎) الّتي تُستدعى بعد انتهاء الاستعلام. بهذه السّطور القليلة التي يمكن فهمها بالقليل من الجهد تمكنّنا من إنشاء مدوّنة بسيطة، وهنا يبرز جمال Node.js الذي يسمح للمبتدئين بتطبيق أفكار قد تبدو بعيدة المنال وجعلها واقعًا ملموسًا! الآن حان وقت تجربة المشروع، نحتاج لإخبار Express بالإنصات إلى الطّلبات الّتي ترد على منفذ معيّن على جهازنا (localhost): var express = require("express"); var mysql = require("mysql"); var connection = mysql.createConnection({ host: "localhost", user: "root", password: "", database: "myblog" }); connection.connect(); var app = express(); app.get("/", function(request, response) { connection.query( "SELECT * from `posts` ORDER BY date DESC LIMIT 10;", function(err, posts) { if (err) throw err; var html = "<!DOCTYPE html><html lang='ar'>" + "<head><title>مُدوّنتي!</title></head>" + "<body>" + posts.map(function(post) { return "<li>" + post.title + "</li>"; }).join("") + "</body></html>"; response.send(html); }); }); app.listen(3000);لبدء البرنامج، افتح الطّرفيّة وانتقل إلى مجلّد المشروع، ثم نفّذ الأمر التّالي: node index.js ستتوقّف المؤشّر في الطّرفيّة عن الاستجابة بسبب انشغال هذه الطّرفيّة بتنفيذ البرنامج، اذهب إلى المتصفّح وانتقل إلى الرابط http://localhost:3000/‎ وشاهد النّتيجة: قد تبدو الصّفحة غاية في البساطة وخالية من أي عُنصر جماليّ، لكنّ ما يهمّنا الآن هو أنّنا قمنا بإنشاء خادوم يتواصل مع قاعدة بيانات ويعرض النّتائج على المستخدم... كلّ هذا في 16 سطرًا من JavaScript! لإنهاء البرنامج عُد إلى الطّرفيّة ذاتها واضغط Ctrl+C. تعرّف على لغة القوالب Jadeبعد أن تأكدنا من تنفيذ المكوّن الرئيسيّ لمشروعنا، سنعمل على تحسين شيفرتنا لجعلها أكثر بساطة وقابلة للتّطوير بسهولة فيما بعد. إذا ألقينا نظرةً على آخر ما كتبناه، سرعان ما نكتشف التّعقيد الذي ستصل إليه شيفرتنا إن أردنا إضافة المزيد من المزايا ضمن HTML، لأنّ هذا يعني إضافة المزيد من النّصّ إلى المتغيّر html بحيث يصبح طويلاً جدًّا وصعب القراءة؛ لا بدّ أن توجد طريقة أفضل من هذه! تتوفّر في كلّ اللّغات طريقة لتوليد صفحات HTML ديناميكيّة على الخادوم، بمعنى أنّه يمكن تغيير بعض محتوياتها وإدخال محتوى مُتغيّر فيها قبل إرسالها إلى المستخدم، هل تساءلت يومًا كيف يعرض فيس بوك لكلّ مستخدم صفحةً خاصّة به؟ بحيث يكون هيكلها متماثلاً لكلّ المستخدمين ولكن محتواها من الأخبار مختلف من مستخدم لآخر، الجواب هو باستخدام القوالب؛ لن نقوم بإنشاء فيس بوك جديد الآن، لكنّنا سنستفيد من ميزات القوالب الدّيناميكيّة لتوليد HTML بدلًا من كتابتها يدويًّا ضمن شيفرتنا! في عالم Node.js ستجد الكثير من لغات القولبة، لكنّ الامتداد الطّبيعيّ لاستخدام Express يكون باعتماد Jade كلغة قولبة كونها بدأت من المُطوّر ذاته، لنُعد كتابة HTML الصّفحة الرّئيسيّة لمدوّنتنا باستخدام Jade: doctype html html(lang="ar") head title "مُدوّنتي!" body for post in posts li #{ post.title }قارن بين نصّ HTML ونصّ Jade الأخير، أوّل ما نلاحظه في Jade هو بساطة صياغتها، فهي تلغي الوسوم النّهائيّة (مثل </head> و</body>) وتستعيض عن ذلك بكونها حسّاسة للمحاذاة، فكون الوسم title مُزاحًا إلى يمين head يعني أنّه محتوىً ضمنه، وكذلك الأمر بالنّسبة لـbody، نلاحظ كذلك دعم Jade للحلقات والمُتغيّرات، وهي من أبرز مزايا لغات القوالب، لأنها تسمح بتوليد عناصر متكرّرة دون الحاجة لكتابتها يدويًّا. سنحتاج أوّلًا لتثبيت Jade وحفظه في متطلّبات المشروع: npm install jade --save احفظ شيفرة Jade السابقة في ملفّ home.jade ضمن مجلّد جديد سمّه views داخل مُجلّد المشروع، ثمّ عُد للملفّ index.js، ولنقم باستخدام Jade عوضًا عن الأسلوب السابق: var express = require("express"); var mysql = require("mysql"); var connection = mysql.createConnection({ host: "localhost", user: "root", password: "", database: "myblog" }); connection.connect(); var app = express(); app.set("view engine", "jade"); app.get("/", function(request, response) { connection.query( "SELECT * from `posts` ORDER BY date DESC LIMIT 10;", function(err, posts) { response.render("home", { posts: posts }); }); }); app.listen(3000);ضبطنا الإعداد view engine في Express إلى القيمة "jade"، يستخدم Express هذا الإعداد عندما يُطلب منه عرض ملفّ ما باستخدام الوظيفة render التّابعة لكائن الجواب response، بحيث يبحث عن مُفسّر لغة القوالب (jade في حالتنا) ويطلب منه تحويل الملفّ "home" إلى HTML، مُمرّرًا له الكائن الذي يحوي المتغيّرات الّتي يحتاجها ({ posts: posts }). يبحث Express عن ملفّات العرض في المجلّد views بشكل افتراضيّ، وهو ما قمنا بإنشاءه للتّوّ.قم بتشغيل البرنامج مرّة أخرى باستخدام الأمر node index.js ثمّ زر الرّابط http://localhost:3000/‎. لم يتغيّر شيء ظاهر، لكنّنا انتقلنا إلى استخدام لغة قوالب وراء الكواليس، وسنستفيد من هذا بكتابة شيفرة أبسط وأكثر تنظيمًا. لنقم الآن بتعديل القالب home.jade ليبدو بشكل أجمل: doctype html html(lang="ar", dir="rtl") head title "مُدوّنتي!" body style :css body { font-family: Arial, sans-serif; } h1 مُدوّنتي hr for post in posts h2 #{ post.title } p #{ post.body } small بتاريخ #{ post.date }قمنا بتغيير اتّجاه النّص لجعله من اليمين إلى اليسار عبر الخاصة "dir"، ثمّ أدخلنا بعض التنسيق من خلال الوسم "<style>" في HTML، تسمح Jade بكتابة لغات أخرى ضمن القالب مثل كتابة CSS وCoffeeScript أو Markdown أو Sass عبر الصّياغة :language ليتم تحويلها إلى اللّغة المناسبة للمتصفّح إن تطلّب الأمر، وفي هذه الحالة أدخلنا CSS بسيط (الذي لا يحتاج للتّحويل) بكتابة :css قبل الشّيفرة. سنتعرّف على مزيد من مزايا Jade خلال عملنا. تبدو مدوّنتنا بشكل أجمل الآن، لكنّها بالتأكيد تحتاج المزيد من العمل! يمكننا تحسين عرض صيغة التّاريخ باستخدام مكتبة moment‏ للتّعامل مع التّواريخ والوقت، سنحتاج أولاً إلى تثبيتها وحفظها في متطلّبات المشروع: npm install --save moment سنُدخل التّعديلات اللّازمة على الملفّين index.js وhome.jade: var express = require("express"); var mysql = require("mysql"); var moment = require("moment"); moment.locale("ar"); var formatDate = function(date) { return moment(new Date(date)).fromNow(); } var connection = mysql.createConnection({ host: "localhost", user: "root", password: "", database: "myblog" }); connection.connect(); var app = express(); app.set("view engine", "jade"); app.get("/", function(request, response) { connection.query( "SELECT * from `posts` ORDER BY date DESC LIMIT 10;", function(err, posts) { response.render("home", { posts: posts, formatDate: formatDate }); }); }); app.listen(3000);doctype html html(lang="ar", dir="rtl") head title "مُدوّنتي!" body style :css body { font-family: Arial, sans-serif; } h1 مُدوّنتي hr for post in posts h2 #{ post.title } p #{ post.body } small كُتِبَت #{ formatDate(post.date) }يمكن تمرير الدّوال (functions) إلى Jade كما نُمرّر المتغيّرات، وفي حالتنا قمنا بتعريف دالّة تقوم بتنسيق التّاريخ الذي تتلقاه بصياغة نسبيّة (منذ كذا يومًا، منذ ساعتين...) وذلك بالاستفادة من مكتبة moment التي استوردناها وعيّنّا لغة التّاريخ فيها إلى العربيّة. أجرينا التغييرات اللازمة في Jade مستخدمين الدّالة التي فرضناها وأصبحت متوفّرة ضمن القالب: إنشاء صفحة التدوينةمن المعتاد لصفحات التّدوينات أن تكون بهذه الهيئة: http://myblog.com/posts/hello-world، ويمكن أن نشاهد في مدوّنات أخرى روابط تحوي تاريخ كتابة التّدوينة أو رقمًا خاصًّا بها... إلخ، لكنّنا سندع الأمور بسيطة. لدينا حاليًّا 4 تدوينات، ستكون روابطها: ‎/posts/hello-world‎/posts/quotes-1‎/posts/quotes-2‎/posts/quotes-3الثّابت بين هذه الرّوابط هو اعتمادها على الحقل slug الّذي أدخلناه في كلّ سطر في جدول التّدوينات. من غير المنطقيّ أن نُسجّل رابطًا لكلّ تدوينة على حدة في Express، وسيصبح هذا مستحيلاً مع إنشاء تدوينات جديدة. يوفّر Express آليّة للإجابة على الطّلبات الواردة على الروابط التي تطابق نمطًا معيّنًا، وهو في حالتنا /posts/‏ متبوعًا بحقل متغيّر slug، أو ‎/posts/:slug بصياغة Express، سنضيف الشيفرة التالية إلى برنامجنا (قبل آخر سطر): app.get("/posts/:slug", function(request, response) { var slug = request.params.slug; connection.query("SELECT * from `posts` WHERE slug = ?", [ slug ], function(err, rows) { var post = rows[]; response.render("post", { post: post, formatDate: formatDate }); }); }) نطلب من Express الاستجابة لأي رابط يطابق النمط "‎/posts/:slug" بالبحث عن التدوينة التي تملك القيمة slug ضمن العمود الموافق في جدول التّدوينات، نلاحظ أنّ Express يوفّر لنا هذه القيمة المتغيّرة من خلال الكائن params التابع لكائن الطّلب request (كائن الطّلب يحوي كذلك ترويسات الطّلب الّتي تحدّثنا عنها في الجزء السّابق). من المهمّ أنّ نحمي قاعدة بياناتنا من العبث وذلك بتجنب هجمات حقن SQL‏، ولهذا توفّر وحدة mysql دالّة query()‎ ذاتها لكن مع 3 معاملات بدل اثنين فقط، حيث يكون الثاني مصفوفة تحوي القيم الّتي نريد التأكّد من سلامتها (escape) قبل إحلالها محلّ إشارات الاستفهام في استعلاماتنا. هذا أسلوب شائع جدًا في استعلامات SQL، وهو أقلّ ما يمكننا فعله لحماية قاعدة البيانات. لم نقم بعد بإنشاء قالب صفحة التّدوينة، لننشئ ملفًا جديدًا اسمه post.jade ضمن مجلّد views: doctype html html(lang="ar", dir="rtl") head title مُدوّنتي! body style :css body { font-family: Arial, sans-serif; } h1 مُدوّنتي hr h2 #{ post.title } p #{ post.body } small كُتِبَت #{ formatDate(post.date) }لنبدأ برنامجنا، ونذهب إلى الصّفحة http://localhost:3000/posts/hello-world: لدينا الآن بعض المشكلات، ماذا يحدث لو أدخلنا رابطًا لتدوينة غير موجودة؟ جرّب مثلاً http://localhost:3000/posts/another-post: وقع خطأ في تفسير Jade سببه أن المتغيّر post الّذي وصله هو في الحقيقة غير معرّف undefined، لأنّه ما من تدوينة في قاعدة البيانات يطابق حقل slug فيها القيمة another-post، وعندما أجرينا الاستعلام أُعيدت لنا مصفوفة فارغة rows، وفي JavaScript فإنّ محاولة الوصول إلى خاصّة غير موجودة ("0") في عنصر مُعرّف (المصفوفة rows في حالتنا) تُرجع undefined. ما الذي كان علينا فعله لتجنب هذا الخطأ؟ أولاً يجب التأكّد قبل كلّ شيء أنّ الخطأ الذي يقع في مرحلة الاستعلام يتم التّعامل معه (handled) قبل الانتقال لما بعده، انتبه إلى أنّ الاستعلام الذي يتم بنجاح ويعيد مصفوفة فارغة لا يعتبر خطأ، لذا يجب التّعامل مع هذه الحالة أيضًا؛ مبدئيًا سنكتفي بإيقاف تنفيذ الدّالة: app.get("/posts/:slug", function(request, response) { var slug = request.params.slug; connection.query("SELECT * from `posts` WHERE slug = ?", [ slug ], function(err, rows) { if (err || rows.length == ) return; var post = rows[]; response.render("post", { post: post, formatDate: formatDate }); }); }) جرّب الآن إعادة تشغيل البرنامج وزيارة الصّفحة ذاتها... سيستمرّ المتصفّح بمحاولة تحميلها لوقت طويل قبل أن يفشل بسبب انتهاء مهلة الطّلب. لماذا يحدث هذا؟ علينا أن نفهم واحدًا من أهمّ المفاهيم في Express، وهو الكيفيّة التّي تسير بها عمليّة توجيه الرّوابط (routing)، في شيفرتنا الأخيرة سيتوقف Express عند return دون أن يعرف ما ينبغي فعله في الخطوة التّالية، وهذا يجعل البرنامج عالقًا في الفراغ، نحتاج لطريقة نخبر بها Express أن يتابع التّنفيذ ويفعل شيئًا ما عندما تنتهي إحدى وظائف التّعامل مع الرّوابط، ولهذا يعطينا Express دالّة next التي تتوفّر كمعامل ثالث للدالة التّي تتلقّى الرابط: app.get("/posts/:slug", function(request, response, next) { var slug = request.params.slug; connection.query("SELECT * from `posts` WHERE slug = ?", [ slug ], function(err, rows) { if (err || rows.length == ) return next(); var post = rows[]; response.render("post", { post: post, formatDate: formatDate }); }); }) أعد تشغيل البرنامج وزر الصّفحة مجدًّدا: هذا أفضل! لكن ما هي الدّالة التّالية التي استدعاها Express ليعرف أنّ صفحة على هذا الرّابط غير موجودة؟ الإجابة هي أنّ Express يحوي بشكل افتراضي دوالّ داخليّة يستدعيها عندما لا نزوّده بالدّالة التّالية، لكنّنا نستطيع فعل ذلك بسهولة: app.get("/posts/:slug", function(request, response, next) { var slug = request.params.slug; connection.query("SELECT * from `posts` WHERE slug = ?", [ slug ], function(err, rows) { if (err || rows.length == ) return next(); var post = rows[]; response.render("post", { post: post, formatDate: formatDate }); return; }); }) app.get("/posts/:slug", function(request, response) { response.send("التدوينة غير موجودة"); }) سجّلنا أكثر من دالّة تتعامل مع الرّابط ذاته، سينفّذها Express جميعًا بالتّرتيب ذاته، يمكن لكلّ دالّة أن تستدعي الدّالة التّالية أو أن توقف سلسلة الاستدعاءات بإرسال الطّلب للمتصفّح وإيقاف التّنفيذ. (إرسال الطّلب لا يعني بالضّرورة أنّ الدّوال التّالية لن تنفّذ، بل يجب إيقاف التّنفيذ صراحةً إن لم نرغب بهذا السّلوك). أعد تشغيل البرنامج ثم زُر الصّفحة ذاتها: حدث ما نتوقّعه بالضّبط، على سبيل التّأكد من كوننا لم نعبث بالوظيفة الرئيسيّة، جرّب زيارة تدوينة موجودة مثل http://localhost:3000/posts/quotes-1. كاختبار لك، قم بتعديل صفحة "التّدوينة غير موجودة" مستخدمًا قالبًا خاصًّا ولتجعله جميلاً! تصرّف براحتك! سأقوم بإدخال تعديل بسيط على الدّالة الثّانية، لجعلها ترسل الرّمز 404 (غير موجود) للمتصفّح بدل القيمة الافتراضيّة (200): app.get("/posts/:slug", function(request, response) { response.status(404); response.send("التدوينة غير موجودة"); })لن يغيّر هذا شيئًا في الظّاهر، لكنّه العرف المتّفق عليه، يمكن لبعض المتصفّحات أن تتعامل مع خطأ كهذا بعرض صفحة نتائج البحث على Google مثلاً (مع أنّه لا يوجد متصفّح يفعل ذلك)، لكنّها طريقة HTTP في التّفاهم بين الخادوم والمتصفّح. عظيم! لدينا الآن صفحة رئيسيّة منسّقة وصفحات مفردة للتّدوينات، في الدّرس القادم سنقوم بإنشاء نظام للمستخدمين تمهيدًا لإتاحة التّعليقات وكتابة تدوينات جديدة.
  7. سأل هذا السؤال نحو 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.
  8. يمكن القول إن TypeScript تشمل ES6 وتزيد عليها الأنواع، في النّهاية تبقى TypeScript لغة غير معياريّة على الرّغم من كونها مفتوحة المصدر وتطويرها مُتعلّق بمصالح الشّركة المُطوّرة لها (Microsoft)، أمّا ES6 فهي اللغة المعيارية الّتي نضمن أنّها ستكون متاحة دومًا في المستقبل.
  9. التفكيك Destructuringذكرنا في الجزء السابق أن اهتمامًا كبيرًا أُوليَ لتسهيل كتابة الشفرة وقراءتها في ECMAScript 6، والإسناد بالتفكيك (Destructuring assignment) لا يخرج عن هذا السياق، وهو ليس بالمفهوم الجديد في عالم البرمجة، فهو معروف في Python وفي Ruby. بعيدًا عن تعقيدات المصطلحات، إليك هذا المثال: var [a, b, c] = [1, 2, 3]; a == 1 // true b == 2 // true c == 3 // trueما الذي يحدث هنا؟ بكل بساطة تسمح ECMAScript 6 بصياغة جديدة للتعريف عن المتغيرات أو إسناد قيم جديدة إليها جُملةً واحدة من خلال جمعها ضمن قوسي مصفوفة (Array) وسيقوم مُفسّر اللغة بإسناد قيمة مقابلة لكل متغيّر من المصفوفة الواقعة على يمين مُعامل الإسناد (=). الأمر لا يقتصر على إسناد المصفوفات، بل يمكن أيضًا إسناد خصائص العناصر: let person = { firstName: "John", lastName: "Smith", Age: 42, Country: "UK" }; let { firstName, lastName } = person; console.log(`Hello ${ firstName } ${ lastName }!`); // Hello John Smith!في هذا المثال لدينا متغيّرات تتبع للنطاق العامّ firstName وlastName، وقد أسندنا لها قيمًا من خصائص الكائن person، حيث يبحث مفسّر اللّغة عن خصائص في الكائن person يماثل اسمها اسم المتغيّر المفروض ويُسندها إلى المُتغيّرات. يمكن توضيح المقصود بصورة أفضل إذا أعدنا كتابة الشفرة لتتوافق مع الإصدار الحالي من JavaScript: var person = { firstName: "John", lastName: "Smith", Age: 42, Country: "UK" }; var firstName = person.firstName; var lastName = person.lastName; console.log("Hello " + firstName + " " + lastName + "!"); // Hello John Smith! يشيع استخدام التفكيك في CoffeeScript (وهي لغة أقر Brendan Eich مُخترع JavaScript بأنّ الإصدار الأخير من JavaScript استوحى الكثير منها)، وخصوصًا عندما تُنظّم البرامج في وحدات كما في Node.js ويكون اهتمامًا مُقتصرًا على استيراد جزء مُحدّد من الوحدة المعنيّة: { EventEmitter } = require 'events' { EditorView } = require 'atom' { compile } = require 'coffee-script' compile('# coffeescript code here');عند تحويل هذا النص إلى JavaScript الحالية، سنحصل على: var EventEmitter = require('events').EventEmitter; var EditorView = require('atom').EditorView; var compile = require('events').compile; compile('# coffeescript code here');من الاستخدامات المفيدة للإسناد بالتفكيك التبديل بين قيمتي متغيّرين بصورة سهلة، سنقتبس المثال من توثيق CoffeeScript ونُعيد كتابته بـJavaScript: var theBait = 1000; var theSwitch = 0; [theBait, theSwitch] = [theSwitch, theBait];قبل ES6 كنا لنحتاج لكتابة مُتغيّر مؤقّت نخزن فيه قيمة إحدى المتغيّرين للاحتفاظ بها قبل التبديل بين القيمتين، وهو ما يفعله محوّل CoffeeScript بالفعل ليعطينا شفرة JavaScript متوافقة مع الإصدار الحالي (مع أنه يقوم بتخزين كلا القيمتين في مصفوفة، إلا أنّ الفكرة تبقى ذاتها): var theBait, theSwitch, _ref; theBait = 1000; theSwitch = 0; _ref = [theSwitch, theBait], theBait = _ref[0], theSwitch = _ref[1];المُكرِّرات (Iterators) وحلقة for... ofما من لغة برمجة تخلو من وسيلة للمرور على عدد من القيم وتكرار تنفيذ عمليّة معيّنة على هذه القيم، من أبسط هذه الوسائل حلقة for التقليديّة الشّهيرة، وفي JavaScript يشيع استخدام حلقة for... in إلى جانبها للمرور على أسماء خصائص العناصر، إذ يمكننا معرفة كل خصائص العنصر document بسطرين فقط: for (var propertyName in document) { console.log(propertyName); } // "body" // "addEventListener" // "getElementById" // ...لاحظ أن حلقة for... in تُعيد أسماء خصائص العنصر (كسلسة نصيّة String)، والأمر لا يستثني المصفوفات، فهي ليست سوى كائنات بأسماء خصائص توافق رقم الفهرس (Index): for (var i in [1, 2, 3]) { console.log(i); } // "0" // "1" // "2" من عيوب حلقة for... in أن لا شيء في تعريف اللغة يُجبر مُفسّر اللّغة على إخراج العناصر بترتيب ثابت بالضّرورة، وهذا يعني أنها تصبح مباشرة غير صالحة للمرور على المصفوفات - التي تستخدم لحفظ عناصر مُرتّبة - بطريقة بديهيّة، ويحلّ محلّها حلقة for التقليديّة عندئذٍ، وأمّا عند استخدامها للمرور على الكائنات، فإنّها لا تُعيد إلّا الخصائص الّتي تُعرّف على أنها قابلة للتعداد (enumerable)، وهو شيء يُحدّد عند تعريف الخاصّة، كما أنّها تُعيد الخصائص القابلة للتعداد التي ورثها الكائن عن "آباءه" ضمن سلسلة الوراثة، وهو تصرّف قد لا يكون مرغوبًا دومًا، وغالبًا سترى المطوّرين يُجرون فحصًا للخاصّة قبل متابعة تنفيذ الشفرة لمعرفة ما إذا كانت تخصّ العنصر ذاته أمّ أنّه ورثها: var obj = { a: 1, b: 2, c: 3 }; // كائن جديد لا يرث سوى النموذج Object for (var prop in obj) { console.log("o." + prop + " = " + obj[prop]); } // "o.a = 1" // "o.b = 2" // "o.c = 3"في هذا المثال (المنقول عن شبكة مطوّري موزيلا) فرضنا كائنًا جديدًا بثلاث خصائص، وعند المرور عليه بحلقة for... in فإنّنا حصلنا على النتيجة المتوقّعة، ولم نحصل على خصائص إضافيّة لأنّ الكائن الذي فرضناه لا يرث أي كائن آخر سوى Object (الذي ترثه كل الكائنات افتراضًا). أما في المثال التالي، فقد احتجنا لإجراء اختبار hasOwnProperty على العنصر الوارث لكي لا تظهر سوى الخاصة color التي يملكها بذاته ولم يرثها: var triangle = {a: 1, b: 2, c: 3}; function ColoredTriangle() { this.color = "red"; } ColoredTriangle.prototype = triangle; var obj = new ColoredTriangle(); for (var prop in obj) { if (obj.hasOwnProperty(prop)) { console.log("o." + prop + " = " + obj[prop]); } } // Output: // "o.color = red"حسنًا، لقد أطلنا الحديث عن حلقة for... in وهي ليست بالجديدة؛ لكنّنا أصبحنا نرى الحاجة لشيء جديد أكثر بساطة ومرونة، فهذا ما تبتغيه ES6 في النهاية، ولهذا نشأت فكرة المُكرّرات؛ التي تسمح لأي عنصر بأن يختار لنفسه الطّريقة التي يتصرّف بها عند المرور به في حلقة، ومع المُكرّرات لا بدّ من نوع جديد من الحلقات لتلبية هذه الحاجة والمحافظة على حلقة for... in للتوافق مع الإصدارات القديمة. من هنا نشأت حلقة for... of الجديدة. for (var num of [1, 2, 3]) { console.log(num); } // 1 // 2 // 3 for (var node of document.querySelectorAll('a')) { console.log(node); } // <a class="title" href="/"> // <a class="contact" href="/contact/">حصلنا في المثالين السابقين على قيمة الخاصّة وليس اسم الخاصّة، لكنّ هذا لا يعني أنّ حلقة for... of تُعيد قيم الخصائص دومًا، بل إنّها تستدعي مُكرّر الكائن (Iterator) وتطلب منه في كلّ دورة للحلقة تزويدها بشيء ما، وتترك للمُكرّر الحُريّة بإعادة أي قيمة يرغب بها، ولكن ولأنّنا نستدعي في مثالنا مصفوفة أولاً، وعنصر من نوع NodeList ثانيًا، وكلا النّوعين يُعيد مُكرُّرهما قيمَ العناصر في المصفوفة، فإنّنا نحصل على تلك النتيجة البديهيّة. بإمكاننا إنشاء أصناف بمُكرّرات خاصّة نُنشئها بأنفسنا، ولنفترض أن لدينا نوعًا لصفّ ضمن مدرسة ابتدائية، ونريد أن نحصل على تفاصيل الطلّاب على هيئة نص مُنسّق عند المرور على الصّفّ في حلقة for... of: function SchoolClass(students) { this.students = students; } SchoolClass.prototype[Symbol.iterator] = function*() { for (let i = 0; i < this.students.length; i++) { let student = this.students[i]; yield `#${i+1} ${student.name} (${student.age} years old)`; } } var ourClass = new SchoolClass([ { name: "Ahmed", age: 10 }, { name: "Alaa", age: 9 }/*, ...*/ ]); for (student of ourClass) { console.log(student); } // "#1 Ahmed (10 years old)" // "#2 Alaa (9 years old)" // "#3 ..." ...استخدمنا الرمز الخاص Symbol.iterator لإسناد دالّة مُولّد (Generator function) التي تُعطينا عند استدعائها نسخة من مُكرّر الصنف المُخصّص الذي أنشأناه. سنتعرف بعد قليل على المُولّدات (Generators) وكذلك على الرموز (Symbols) في وقت لاحق. لاحظ أنّنا استخدمنا حلقة for... of للمرور على محتويات ourClass. تذكّر أنّنا استخدمنا هذه الحلقة في الجزء السابق مع Array Comprehension، كما في المثال: let people = ["Samer", "Ahmed", "Khalid"]; console.log([`Hello ${person}` for (person of people)]); إن كانت الفقرة الأخيرة غامضة بعض الشيء فلا تقلق، سنتوسّع بشرح المولّدات بعد قليل. لكن دعونا نتوقّف قليلاً ولننتقل إلى الجانب الفلسفي لهذه الإضافات في JavaScript، قد تبدو للوهلة الأولى تعقيدات بلا طائل، خصوصًا وأنّ كثيرًا منها لا يهدف سوى للتسهيل، ولا يقدّم شيئًا يستحيل إنجازه بالإصدارات السابقة من اللغة؛ هنا يمكن الرّدّ بأنّ تطوّر اللغة متعلّق بكيفيّة استخدامها والخبرة التي تُكتسب مع مرور السّنين، حيث تظهر للمطوّرين حاجات جديدة وأفكار تطبّق مرارًا لدرجة أنها ترتقي لتصبح ضمن أساسات اللغة. سهولة كتابة الشفرة لم تعد رفاهية، بل هي ضرورة لإنجاز المشاريع الكبيرة لأنّها تتيح اختصار الوقت الذي كان سيضيع في كتابة متكرّرة ومُملّة، كما أنّها تُلبّي ما يتوقّعه المطوّرون من لغة أصبحت تؤخذ على محمل الجدّ وتُستخدم في تطوير تطبيقات ضخمة ومُعقّدة بعد أن كان جُلّ استخدامها تنفيذ بعض المهام البسيطة. المُولِّدات (Generators)المولدات (Generators) ببساطة هي دوال يمكن إيقافها والعودة إليها في وقت لاحق مع الاحتفاظ بسياقها دون تغيير، صياغة دوال المولدات لا تختلف كثيرًا عن صياغة الدوال التقليدية، كل ما عليك هو إضافة إشارة * بعد function واستخدام yield بدل return، المثال التالي سيوضح فكرة المولدات أكثر: function* getName() { let names = ['Muhammad', 'Salem', 'Abdullah']; for (name of names) { yield name; } } let nameGenerator = getName(); nameGenerator.next().value; // 'Muhammad' nameGenerator.next().value; // 'Salem' nameGenerator.next().value; // 'Abdullah' nameGenerator.next().value; // undefined } ما الذي يحدث هنا؟ فرضنا دالّة مولّد (Generator function) (والتي تُميّز بإشارة النجمة *) سمّيناها getName، وفيها صرحنا عن مصفوفة فيها أسماء، وظيفة هذه الدالة أن تعطينا عند استدعائها نسخة من مُكرّر (Iterator) (الذي شرحناه لتوّنا)، يزوّدنا بالأسماء بالترتيب في كل مرة نستدعيه فيها ليعطينا النتيجة التالية (next())، أولاً يجب حفظ نسخة المُكرّر ضمن متغير لكي نسمح له بحفظ حالته، ودون ذلك سيعطينا استدعاء دالّة المولد مباشرةً getName().next() دوماً النتيجة الأولى لأننا عملياً نُنشئ نسخة جديدة عنه في كل مرة نستدعيه، أما استدعاء نسخة عنه وحفظها في متغير مثل myGenerator فيسمح لنا باستدعاء .next() عليها كما هو متوقع. لا ترجع الدالة .next() القيمة التي نرسلها عبر yield فقط، بل ترجع كائناً يحوي القيمة المطلوبة ضمن الخاصة value، وخاصة أخرى done تسمح لنا بمعرفة ما إذا كان المولد قد أعطانا كل شيء. لنُعِدْ ترتيب أفكارنا: المولّدات تسمح بتوقّف تنفيذها مع الاحتفاظ بحالة التنفيذ (يحدث توقّف التنفيذ عند كلّ كلمة yield). فلو أنّنا كتبنا دالّة تقليديّة في المثال أعلاه مع return بدل yield لحصلنا في كلّ مرّة على الاسم الأول (Muhammad). وهذه الميزة في المولّدات يمكن استغلالها لإنشاء حلقات لا نهائية دون إعاقة متابعة البرنامج: function* numberGenerator() { for (let i = 0; true; i++) { yield i; } } let numGen = numberGenerator(); numGen.next(); // { value: 0, done: false } numGen.next(); // { value: 1, done: false } numGen.next(); // { value: 2, done: false } numGen.next(); // { value: 3, done: false } // ...دوالّ المولّدات تُعطي عند استدعاءها مُكرّرات، وهذا يعني إمكانيّة استخدامها في حلقة for... of (احذر من تطبيق مثال كهذا على مولّد غير منتهٍ كما في المثال السابق!): for (let name of getName()) { console.log(name); } // "Muhammad" // "Salem" // "Abdullah"لكلّ مُكرّر وظيفة .next() مهمّتها بدء تنفيذ الدّالّة أو متابعة تنفيذها ثم إيقافها مؤقّتًا عند كلّ كلمة yield. استدعاء next() على المُكرّر يعيد لنا في كلّ مرة كائنًا ذا خاصّتين: الأولى value وهي أيّ شيء نُعيده بكلمة yield، والثّانية done وهي قيمة منطقيّة (Boolean) تشير إلى حالة انتهاء تنفيذ الدّالة.تقبل الوظيفة .next() للمُكرّرات مُعاملاً اختياريًّا تستقبله وتُرسله لدالّة المولّد بعد متابعة التنفيذ، ويمكن استخدامها لإرسال رسائل لدالّة المولّد بحيث نؤثّر في تنفيذه: function* numberGenerator() { for (let i = 0; true; i++) { var reset = yield i; if (reset) i = -1; } } let numGen = numberGenerator(); numGen.next(); // { value: 0, done: false } numGen.next(); // { value: 1, done: false } numGen.next(); // { value: 2, done: false } numGen.next(); // { value: 3, done: false } numGen.next(true); // { value: 0, done: false }في هذا المثال مرّرنا القيمة true إلى الوظيفة .next() على المُكرّر، والذي بدوره يُرسلها لدالّة المولّد كنتيجة yeild i في الدّورة الموافقة للحلقة، لنقومَ بحفظها في متغيّر reset ونُجريَ فحصًا عند متابعة التنفيذ لإعادة تعيين قيمة i، التي ستزداد بمقدار واحد مع بدء الدورة التالية لحلقة for جاعلةً قيمة i مساوية للصّفر. خصائص المولّدات تجعلها مناسبة جدًا لكتابة شيفرة غير متزامنة بصورة أسهل تكاد تبدو فيها وكأنها شيفرة متزامنة خالية من الاستدعاءات الراجعة المتداخلة (Nested callbacks)؛ هذه الفكرة تحتاج إلى تركيز لأنها أساس لعدد من المكتبات مثل co وsuspend التي ظهرت مؤخّرًا وتصاعدت شعبيّتها بسرعة لأنّها تحلّ مشكلة جوهرية في استخدام JavaScript، ألا وهي التعامل مع الدوال غير المتزامنة (asynchronous functions) وذلك بالاعتماد كُليًّا على المُولّدات. لنفترض أنّ لدينا موقعًا لقراءة الكتب يعرض ملفّ المستخدم الشّخصيّ مع عدد الكتب التي قرأها وعنوان آخر كتاب مع تقييم المستخدم له: var list = document.querySelector("#book-list"); getJSON("http://reading-website.com/users/fwz.json", function(err, user) { if (err) return; // افعل شيئًا بما بخصوص الخطأ var num_books = user.books.length; var most_recent_book_id = user.books[num_books - 1]; getJSON("http://reading-website.com/users/fwz/ratings/" + most_recent_book_id + ".json", function(err, user_rating) { getJSON("http://reading-website.com/books/" + most_recent_book_id + ".json", function(err, book) { var fragment = document.createDocumentFragment(); var h2 = document.createElement("h2"); h2.textContent = user.full_name; var h3 = document.createElement("h3"); h3.textContent = "الكتب التي قرأها"; for (let book of books) { let li = document.createElement("li"); li.textContent = book.title + (book.id == most_recent_book_id ? " " + user_rating : ""); fragment.appendChild(li); } list.appendChild(fragment); }); }); })في المثال السابق احتجنا إلى إرسال 3 طلبات AJAX يعتمد أحدها على الآخر، ولأنّنا لا نستطيع إرسال طلب بتقييم المستخدم للكتاب قبل معرفة مُعرّف الكتاب، فلا بدّ من أن يرسل الطلب الخاصّ بتقييم الكتاب ضمن الاستدعاء الرّاجع لطلب معلومات المستخدم، ثمّ يمكن جلب عنوان الكتاب ضمن الاستدعاء الرّاجع للطلب السّابق، وهذا يعني زيادة تعقيد الشفرة مع تداخل الاستدعاءات الرّاجعة لتبدو أشبه بسباغيتي لا تُعرف بدايته من نهايته. تخيّلوا -لغرض التّخيّل- لو أمكننا كتابة هذه الشفرة (وهي غير متزامنة) لتبدو لقارئها وكأنها نص برمجي يسير بترتيب متزامن وبديهيّ... ألن يكون هذا أعظم شيء منذ اختراع JavaScript؟ var list = document.querySelector("#book-list"); try { var user = getJSON("http://reading-website.com/users/fwz.json"); var num_books = user.books.length; var most_recent_book_id = user.books[num_books - 1]; var user_rating = getJSON("http://reading-website.com/users/fwz/ratings/" + most_recent_book_id + ".json"); var book = getJSON("http://reading-website.com/books/" + most_recent_book_id + ".json"); var fragment = document.createDocumentFragment(); var h2 = document.createElement("h2"); h2.innerText = user.full_name; var h3 = document.createElement("h3"); h3.innerText = "الكتب التي قرأها"; for (let book of books) { let li = document.createElement("li"); li.innerText = book.title + (book.id == most_recent_book_id ? " " + user_rating : ""); fragment.appendChild(li); } list.appendChild(fragment); } catch (e) { // افعل شيئًا بما بخصوص الخطأ // Error } نحن نعلم أن الأمور لا يمكن أن تكون بهذه الروعة، وأنّ الشفرة أعلاه لن تعمل... نحن نعلم أن شيفرتنا تحتاج تفاصيل المستخدم للحصول على الكتب، وأننا نحتاج للكتاب لجلب عنوانه وتقييمه، وتنفيذ هذه المهمّات بشكل غير متزامن لا يعني أنّه ليس علينا انتظار المهمّة الأولى قبل إطلاق الثانية - بل يعني فقط أن المتصفح يمكنه تنفيذ رسم العناصر الأخرى وعرض الصفحات وإرسال طلبات أخرى في هذا الوقت. حسنًا، لدي خبر جيّد وآخر سيئ: أمّا الجيّد فهو أنّنا كتابة شيفرة شبيه بهذه أصبحت قريبة المنال مع الدّوالّ غير المتزامنة (Async Functions)، وأمّا الخبر السيّئ فهو أنّ علينا الانتظار إلى الإصدار 7 من ECMAScript لنستطيع كتابتها! (مع العلم أن المتصفّحات لم تنتهِ من تطبيق ES6!). لكن هذا لا يعني أن نقف مكتوفي الأيدي إلى أن تصدر ES7، بل بإمكاننا إيجاد حلّ وسط لهذه المشكلة؛ لماذا نضطّر إلى تعقيد الأمور بالاستدعاءات الرّاجعة المتداخلة؟ ألا يتوفّر في اللّغة بنية برمجيّة تسمح بإيقاف شيفرتنا ريثما يتمّ أمر ما غير متزامن (الانتظار لإكمال طلب AJAX) ثمّ المتابعة بعد انتهاءه؟ يبدو هذا الحديث مألوفًا! نعلم حتى الآن أننا بحاجة لاستخدام مولّد، ولذلك سنحيط شيفرتنا بدالّة مولّد كخطوة أولى: var list = document.querySelector("#book-list"); function* displayUserProfile() { // شيفرتنا هنا } الآن نحتاج لتنفيذ طلب AJAX الأوّل والانتظار إلى انتهاءه قبل الانتقال إلى الطّلب الثّاني، نعلم أنّ yield توقف تنفيذ المولّد: var list = document.querySelector("#book-list"); function* displayUserProfile() { yield getJSON("http://reading-website.com/users/fwz.json"); // ... } عظيم! لكن كيف نُخبر المولّد بأنّ عليه متابعة التنفيذ؟ var list = document.querySelector("#book-list"); function* displayUserProfile() { yield getJSON("http://reading-website.com/users/fwz.json", resume); // ... } سنمرر دالة اسمها resume للدّالة getJSON، وهذه الدالة ستُستدعى عند انتهاء جلب جواب الطّلب الذي أرسلناه، وهي فرصتنا لإخبار المولّد بمتابعة التنفيذ... فكيف سيكون محتواها؟ var list = document.querySelector("#book-list"); var resume = function(err, response) { displayIterator.next(response); } function* displayUserProfile() { var user = yield getJSON("http://reading-website.com/users/fwz.json", resume); var num_books = user.books.length; var most_recent_book_id = user.books[num_books - 1]; var user_rating = yield getJSON("http://reading-website.com/users/fwz/ratings/" + most_recent_book_id + ".json", resume); var book = yield getJSON("http://reading-website.com/books/" + most_recent_book_id + ".json", resume); var fragment = document.createDocumentFragment(); var h2 = document.createElement("h2"); h2.innerText = user.full_name; var h3 = document.createElement("h3"); h3.innerText = "الكتب التي قرأها"; for (let book of books) { let li = document.createElement("li"); li.innerText = book.title + (book.id == most_recent_book_id ? " " + user_rating : ""); fragment.appendChild(li); } list.appendChild(fragment); } var displayIterator = displayUserProfile(); displayIterator.next(); حفظنا نسخة عن المكرّر في متغيّر ثم استدعينا وظيفته next() في دالّة المتابعة، ممرّرين لها جواب الطّلب ليمكننا تخزينه ضمن المتغيّر user. الدّالة resume تستطيع الوصول إلى displayIterator لأنّه يكون معرّفًا قبل استدعاءها حتمًا، ولا ننسَ أن تعريف المتغيّرات في JavaScript يخضع لعملية الرّفع إلى أعلى النّطاق (variable hoisting) ممّا يجعل المتغيّر displayIterator موجودًا (وإن كان بلا قيمة) منذ بداية تنفيذ الشيفرة. للتأكّد من فهم هذه الشيفرة، سنعيد تحليلها خطوة بخطوة: في طلب AJAX الأوّل تستدعى الدالة resume ويمرّر إليها جواب الطّلب (response)، الذي يمرّر بدوره إلى المُكرّر ليُحفظ في المتغيّر user الذي سيُستخدم في الخطوة التّالية للمولّد لإرسال الطّلب الثّاني. تُكرّر العمليّة ذاتها للطلبين الآخرين ثمّ تُعرض النتائج في الصّفحة. الفائدة التي جنيناها من استخدام المولّدات هي التخلّص من تعقيد الاستدعاءات الرّاجعة نهائيًّا وتحويل شيفرة غير متزامنة وجعلها تبدو وكأنّها متزامنة. ذكرنا القليل عن مكتبات مثل co وsuspend، لكنّها باختصار تعمل بطريقة مماثلة جدًا لمثالنا الأخير: var suspend = require('suspend'), resume = suspend.resume; suspend(function*() { var data = yield fs.readFile(__filename, 'utf8', resume()); console.log(data); })();هذه المكتبات خطوة نحو مستقبل JavaScript، الذي بدأ يتشكل مع مشروع الدّوال غير المتزامنة باستخدام الكلمتين المفتاحيتين الجديدتين async وawait اللّتان ستتوفّران في الإصدار السّابع وتستندان في عملهما إلى أرضيّة الوعود (Promises) الّتي تتوفّر اليوم في ES6. سيكون بإمكاننا كتابة هذه الشيفرة بدل الاعتماد على المولّدات: async function displayUserProfile() { var user = await getJSON("http://reading-website.com/users/fwz.json"); var num_books = user.books.length; var most_recent_book_id = user.books[num_books - 1]; var user_rating = await getJSON("http://reading-website.com/users/fwz/ratings/" + most_recent_book_id + ".json"); var book = await getJSON("http://reading-website.com/books/" + most_recent_book_id + ".json"); var fragment = document.createDocumentFragment(); var h2 = document.createElement("h2"); h2.innerText = user.full_name; var h3 = document.createElement("h2"); h2.innerText = "الكتب التي قرأها"; for (let book of books) { let li = document.createElement("li"); li.innerText = book.title + (book.id == most_recent_book_id ? " " + user_rating : ""); fragment.appendChild(li); } list.appendChild(fragment); }في هذا المثال يجب على getJSON أن تُعيد وعدًا Promise ليستطيع مُفسّر اللّغة انتظاره إلى أن يُحقّق (resolve) أو يُرفض (reject)، والقيمة الّتي تُحقّق تُحفظ ضمن المُتغيّر user، وأما عند رفض الوعد يُرمى خطأ (throw) يمكن تلقّيه (catch) كما في الشيفرة غير المتزامنة. مُعامِل البقيّة (Rest parameter) والناشرة (Spread)بعد كلّ هذا الكلام المُعقّد عن الأشياء غير المتزامنة التي نريد جعلها تبدو متزامنة وما إلى ذلك، سنختم الجزء الثّاني بفكرتين بسيطتين أُضيفتا إلى ECMAScript في الإصدار السّادس وتحلّان مشكلتين شائعتين في كثير من اللّغات البرمجيّة: أمّا الأولى فهي الحاجة إلى تنفيذ نصّ برمجيّ ضمن دالة على عدد غير معروف من المُعاملات، فلنفترض أنّ لدينا دالة تجمع عددين: function add(n1, n2) { return n1 + n2; } add(1) // 1 add(1, 2) // 3ونظرًا لكوننا مبرمجين أذكياء فقد قرّرنا جعل الدّالة تقبل أي عددين أو ثلاثة أو أكثر... لنجعلها تقبل عددًا لا نهائيًّا من الأعداد؛ في الإصدار الحالي سنلجأ إلى استخدام الكائن الخاصّ arguments المتوفّر ضمن نطاق كلّ دالّة تلقائيًا: function add() { return [].reduce.call(arguments, function(memo, n) { return memo + n; }); } add(1) // 1 add(1, 2) // 3 add(1, 2, 3) // 6 حسنًا لقد اضطررنا إلى "استعارة" دالة الاختزال من مصفوفة فارغة لتطبيقها على الكائن الخاص arguments الذي يُعتبر "شبيه مصفوفة" ولا يملك ما تمتلكه المصفوفة من دوالّ، لماذا لا يمكننا كتابة هذا فحسب: function add(...numbers) { return numbers.reduce(function(memo, n) { return memo + n; }); } add(1) // 1 add(1, 2) // 3 add(1, 2, 3) // 6 وأمّا الفكرة الثّانية فهي تكاد تكون عكس السّابقة، فإذا كانت الأولى تجمع بقيّة المعاملات في كائن مُفرَد، فإنّ هذه "تَنشر" محتويات المصفوفة إلى عناصرها المكوّنة لها، ماذا لو لم نكن أذكياء وعجزنا عن الإتيان بدالة تجمع عددًا غير منتهٍ من الأرقام: function addThreeNumbers(n1, n2, n3) { return n1 + n2 + n3; } var myNumbers = [1, 2, 3]; addThreeNumbers(...myNumbers); // 6 لاحظ أن صياغة النشر (Spread) تطابق تمامًا صياغة البقيّة (Rest)، والاختلاف في السّياق فقط. لاحظ أيضًا أنّ معامل البقيّة، وكما يوحي اسمه، يمكن استخدامه لتجميع ما تبقى من مُعاملات الدّالة فقط: function addThreeOrMoreNumbers(n1, n2, ...numbers) { return n1 + n2 + numbers.reduce(function(memo, n) { return memo + n; }); } addThreeOrMoreNumbers(1, 2, 3); // 6في الجزء القادم سنتعرف بمشيئة الله على الوحدات (Modules) التي تُعتبر طريقة جديدة لتنظيم الشفرة اُستلهمَت من عالم Node.js وrequire.js، وسُنلقي نظرة على الأصناف (Classes)، المكوّن البرمجيّ الذي وجد طريقه أخيرًا إلى JavaScriptّ! المصادرشبكة مطوّري موزيلّاGoing Async With ES6 GeneratorsReplacing callbacks with ES6 Generators Iterators gonna iterate
  10. Harmony هو الاسم الرمزي لـ ECMAScript 6 وهي اللغة القياسية التي تقوم عليها JavaScript، والإصدار الجديد يأتي بميزات جديدة تتناول العديد من جوانب اللغة بما فيها الصياغة (syntax) وأسلوب البناء وأنواع جديدة من المكونات المدمجة في اللغة. في هذا المقال نتعرف على بعض من المميزات التي ستجعل كتابة شيفرة جافاسكربت أكثر اختصاراً وفعالية. متغيرات نطاقها القطعة البرمجية (Block-scoped Variables)في الإصدار الحالي من JavaScript، تُعامل كل المتغيرات المفروضة ضمن دالة (function) على أنها تابعة لهذه الدالة (Function-scoped) أي يمكن الوصول إليها من أي موقع ضمن هذه الدالة، حتى وإن كانت هذه المتغيرات قد فُرضِت ضمن قطعة برمجية فرعية ضمن هذه الدالة (كحلقة for أو جملة شرطية if)، وهذا يخالف ما تتبناه بعض من أشهر لغات البرمجة، وقد يسبب بعض الارتباك لمن لم يعتد عليه. لنوضح أكثر في هذا المثال: var numbers = [1, 2, 3]; var doubles = []; for (var i = 0; i < numbers.length; i++) { var num = numbers[i]; doubles[i] = function() { console.log(num * 2); } } for (var j = 0; j < doubles.length; j++) { doubles[j](); }عند تنفيذ هذا المثال، سنحصل على الرقم 6 ثلاث مرات، وهو أمر غير متوقع ما لم نكن على معرفة بطبيعة مجالات JavaScript، ولو طبق ما يشبه هذا المثال في لغة أخرى، لحصلنا على النتيجة 2 ثم 4 ثم 6، وهو ما يبدو النتيجة المنطقية لشيفرة كهذه. ما الذي يحدث هنا؟ يتوقع المبرمج أن المتغير num محصور ضمن حلقة for وعليه فإن الدالة التي ندخلها في المصفوفة doubles ستعطي عند استدعائها القيمة التي ورثتها عن مجال حلقة for إلا أن الحقيقة هي أن المتغير num يتبع للمجال العام، لأن حلقة for لا تُنشئ مجالًا فرعيًّا وعليه فإن القيمة العامة num تتغير ضمن حلقة for من 2 إلى 4 إلى 6 وعند استدعاء أي دالة ضمن المصفوفة doubles فإنها ستعيد إلينا القيمة العامة num، وبما أن الاستدعاء يحدث بعد إسناد آخر قيمة للمتغير num، فإن قيمته في أي لحظة بعد انتهاء الحلقة الأولى ستكون آخر قيمة أسندت إليه ضمن هذه الحلقة، وهي القيمة 6. يعطينا الإصدار القادم طريقة لحل هذا الارتباك باستخدام الكلمة المفتاحية let بدلاً عن var، وهي تقوم بخلق مجال ضمن القطعة البرمجية التي تُستخدم فيها، بمعنى آخر: ستكون let هي بديلنا عن var من الآن فصاعدًا، لأنها ببساطة تعطينا النتائج البديهية التي نتوقعها. لنُعِد كتابة المثال السابق باستبدال var num بـlet num: var numbers = [1, 2, 3]; var doubles = []; for (var i = 0; i < numbers.length; i++) { let num = numbers[i]; doubles[i] = function() { console.log(num * 2); } } for (var j = 0; j < doubles.length; j++) { doubles[j](); }عند تطبيق هذا المثال (يمكنك تطبيقه في Firefox وChrome لأن كلا المتصفحين شرعا في دعم let) سنحصل على النتيجة البديهية 2 ثم 4 ثم 6. بالطبع بإمكاننا تحسين الشيفرة باعتماد let عند التصريح عن كل المتغيرات السابقة، وهو الأمر الذي يجب أن تعتاد فعله من اليوم! شيفرة أقصر وأسهل للقراءةلعل أكثر ما أُحبّه في JavaScript مرونتها الفائقة، وبالذات القدرة على إمرار دوال مجهولة (Anonymous Functions) لدوال أخرى، الأمر الذي يسمح لنا بكتابة شيفرة ما كان من الممكن كتابتها بلغات أخرى إلا بضعفي عدد الأسطر وربما أكثر. لاحظ هذا المثال: var people = ['Ahmed', 'Samer', 'Khaled']; var greetings = people.map(function(person) { return 'Hello ' + person + '!'; }); console.log(greetings); // ['Hello Ahmed!', 'Hello Samer!', 'Hello Khaled!'];لو أردنا تنفيذ هذه المهمة في لغة أخرى، فلربما احتجنا إلى حلقة for لنمرّ من خلالها على كل عنصر ضمن المصفوفة ثم إدخال العبارات الجديدة ضمن مصفوفة أخرى، وهذا يعني أن مهمة يمكن كتابتها بسطرين في JavaScript قد تتطلب 5 سطور في لغة أخرى. لو لم تمتلك JavaScript القدرة على إمرار الدالة المجهولة function(person) {...} أعلاه، لفقدت جزءًا كبيرة من مرونتها. لكن الإصدار القادم من JavaScript تذهب أبعد من ذلك، وتختصر علينا كتابة الكثير من النص البرمجي. لُنعد كتابة المثال السابق: let people = ['Ahmed', 'Samer', 'Khaled']; let greetings = people.map(person => 'Hello ' + person + '!'); console.log(greetings); // ['Hello Ahmed!', 'Hello Samer!', 'Hello Khaled!'];في هذا المثال استخدمنا ما اصطلح على تسميته دوال الأسهم (Arrow Functions)، وهي طريقة أكثر اختصارًا لكتابة الدوال المجهولة، لن تحتاج لكتابة return، فهي ستضاف تلقائيًا عند التنفيذ. من الآن فصاعداً اعتمد دوال الأسهم عندما تريد تنفيذ دالة مجهولة بسيطة بسطر واحد. بمناسبة الحديث عن الشيفرة المختصرة... ما رأيكم لو جعلنا الشيفرة أعلاه أكثر اختصارًا؟! let people = ['Ahmed', 'Samer', 'Khaled']; let greetings = ['Hello ' + person + '!' for (person of people)]; console.log(greetings); // ['Hello Ahmed!', 'Hello Samer!', 'Hello Khaled!'];قد تبدو الصياغة غريبة بعض الشيء، لكنها تتيح لنا فهم النص بسهولة أكبر، وتغنينا عن الحاجة لدالة مجهولة (الأمر الذي قد يؤثر على الأداء، وإن كان بأجزاء من الثواني). الصياغة التي استخدمناها أعلاه تُسمى Array Comprehensions، وإن كنت قادرًا على ترجمتها إلى العربية بطريقة واضحة، فلا تبخل بها علينا! لكن... ألا ترون أنه يمكن تحسين هذه الشيفرة قليلاً؟ let people = ['Ahmed', 'Samer', 'Khaled']; let greetings = [`Hello ${ person }!` for (person of people)]; console.log(greetings); // ['Hello Ahmed!', 'Hello Samer!', 'Hello Khaled!']; هنا استبدلنا إشارات الاقتباس (' أو ") بالإشارة ` الأمر الذي أتاح لنا إحاطة المتغير person بقوسين معكوفين مسبوقين بإشارة $، وهذه الصياغة تدعى "السلاسل النصية المقولبة" أو Template Strings، والتي تسمح -بالإضافة إلى القولبة- بالعديد من الأشياء الرائعة، كالعبارات على عدة أسطر: let multilineString = `I am a multiline string`; console.log(multilineString); // I am // a multiline // string للأسف لن تعمل الشفرة السابقة في أي من المتصفحات الحالية، لأن السلاسل النصية المقولبة ما تزال غير معتمدة ضمن أي منها. تحديث: بدأ Firefox Nightly باعتمادها. من المميزات الجديدة كذلك إمكانية اختصار بناء الكائنات ذات الخصائص بالشكل التالي: حاليًا، نقوم بكتابة شيفرة مثل هذه: var createPerson = function(name, age, location) { return { name: name, age: age, location: location, greet: function() { console.log('Hello, I am ' + name + ' from ' + location + '. I am ' + age + '.'); } } }; var fawwaz = createPerson('Fawwaz', 21, 'Syria'); console.log(fawwaz.name); // 'Fawwaz' fawwaz.greet(); // "Hello, I am Fawwaz from Syria. I am 21." في الإصدار القادم، سيكون بالإمكان كتابة الشيفرة كالتالي:let createPerson = function(name, age, location) { return { name, age, location, greet() { console.log('Hello, I am ' + name + ' from ' + location + '. I am ' + age + '.'); } } }; let fawwaz = createPerson('Fawwaz', 21, 'Syria'); console.log(fawwaz.name); // 'Fawwaz' fawwaz.greet(); // "Hello, I am Fawwaz from Syria. I am 21."بما أن اسم المُعامل (parameter) يماثل اسم الخاصة (property)، فإن هذا يتم تفسيره على أن قيمة الخاصة توافق قيمة المعامل، بمعنى: name: name، بالإضافة إلى كتابة greet() {...} بدل greet: function() {...}. كذلك سيكون بإمكاننا تحسين هذا النص أكثر من ذلك باستخدام الأصناف (Classes)، نعم! سيكون لدينا أصناف أخيرًا! (سنستعرضها لاحقاً) الثوابت (Constants)سيداتي وسادتي... رحبوا بالثوابت... نعم إنها أخيرًا متوفرة في JavaScript، إحدى المكونات الأساسية لأي لغة برمجية التي لم تكن متوفرة في JavaScript، أصبحت الآن متوفرة. والآن نأتي للسؤال البديهي: لماذا أحتاج للثوابت؟ أليس بإمكاني التصريح عن متغير دون أن أغير قيمته بعد إعطاءه القيمة الأولية؟ نعم بالطبع بإمكانك ذلك، لكن هذا لا يعني بالضرورة أن المستخدم أو نصاً برمجيًا من طرف ثالث ليس بإمكانه تغيير قيمة هذا المتغير في سياق التنفيذ، وطالما أن المتغير "متغير" بطبيعته، فإننا دومًا بحاجة إلى شيء من أصل اللغة يحمينا من تغييره خطأ. عند التصريح عن ثابت فإننا نعطيه قيمة أولية ثم ستتولى الآلة البرمجية لـJavaScript حماية هذا الثابت من التغيير، وسُيرمى خطأ عند محاولة إسناد قيمة جديدة لهذا الثابت. const myConstant = 'Never change this!'; myConstant = 'Trying to change your constant'; // TypeError: redeclaration of const myConstant console.log(myConstant); // "Never change this!" المُعاملات الافتراضية (Default Parameters)غياب دعم المُعاملات الافتراضية في JavaScript واحد من أكثر الأشياء التي تزعجني، لأنها تجبرني على كتابة شيفرة مثل هذه: function SayHello (user) { if (typeof user == 'undefined') { user = 'User'; } console.log('Hello ' + user); } console.log(SayHello('Fawwaz')); // Hello Fawwaz! console.log(SayHello()); // Hello User!لو كان عندي 3 متغيرات غير إجبارية، فهذا يعني أنني سأحتاج 3 جمل شرطية، الأمر الذي يتطلب الكثير من الكتابة المُملة. بفضل الإصدار القادم من JavaScript، سيكون بالإمكان كتابة شيفرة أبسط بكثير: function SayHello (user='User') { console.log('Hello ' + user); } SayHello('Fawwaz'); // Hello Fawwaz! SayHello(); // Hello User! الوعود (Promises)الوعود هي الحل الذي تأتينا به JavaScript لحل مشكلة هرم الموت (Pyramid of Death) الذي نواجهه عند تنفيذ مهمات غير متزامنة تعتمد إحداها على الأخرى: function getFullPost(url, callback) { var getAuthor = function(post, callback) { $.ajax({ method: 'GET', url: '/author/' + post.author_id }, callback); }; var getRelatedPosts = function(post, callback) { $.ajax({ method: 'GET', url: '/related/' + post.id }, callback); }; $.ajax({ method: 'GET', url: url }, function(post) { getAuthor(post, function(res) { post.author = res.data.author; getRelatedPosts(post, function(res) { post.releated = res.data.releated; callback(post); }); }); }); } هل تلاحظ أن الشيفرة تتجه نحو اليمين؟ لو أردنا تنفيذ هذه المهمات غير المتزامنة واحدة بعد الأخرى وكان عددها 10 مثلًا فستصبح الشيفرة شديدة التعقيد، كما أن هذه الطريقة ليست بديهية، ولا يمكن لك أن تفهم ماذا تفعل هذه الدالة المجهولة (المعامل الثاني في كل دالة) ما لم تألفها. ماذا لو أمكننا كتابة هذه الشيفرة بصورة أفضل؟ function getFullPost(url) { var post = { }; var getPost = function(url) { return $http.get(url); }; var getAuthor = function(post) { return $http.get('/author/' + post.author_id).then(function(res) { post.author = res.data.author; }); }; var getRelatedPosts = function(post) { return $http.get('/related/' + post.id).then(function(res) { post.related = res.data.related; }); }; return getPost().then(getAuthor).then(getRelatedPosts).catch(function(err) { console.log('We got an error:', err); }); } في الجزء القادم سنتعرّف على المكوّنات الجديدة الأكثر إثارة، كالمولّدات التي ستجعلنا نغير من طريقة تعاملنا مع البيانات اللامتزامنة كليًّا!
  11. Version 1.0.0

    14,007 تحميلات

    يضع هذا الكتاب المُوجز القارئ على أعتاب عالم تصميم تجربة المُستخدمين UX، وهو علم له قواعده وأصوله وأدواته، ويهدف إلى تعريف القارئ المُبتدئ بأساس هذا العلم وكيف يُطبّق على المُنتجات الرّقمية من مواقع ويب خدميّة وتطبيقات على الأجهزة الذّكية وصولًا إلى التّصميم الأمثل الّذي يُوفِّق بين هدف المُستخدم أوّلًا وهدف الخدمة التّجاريّ، الأمر الّذي يعني منتجًا ناجحًا.
  12. ما هو AngularJS؟AngularJS هو إطار عمل JavaScript لطرف العميل يتبع بنية Model-View-Controller/Model-View-View-Model، ويعتبر اليوم مُهمًا لبناء تطبيقات ويب وحيدة الصفحة (SAP) أو حتى المواقع العادية. يُعتبر Angular JS قفزة كبيرة نحو مستقبل HTML وما يجلبه الإصدار الخامس منها (مع التطورات على صعيد JavaScript بالطبع!)، ويبعث الحياة من جديد في تعاملنا مع الويب الحديث. هذا المقال جولة شاملة في Angular JS مستخلصة من تجاربي ونصائح وممارسات تعلمتها خلال استخدامي. المُصطلحاتليس عليك أن تبذل جهداً كبيراً لتعلم Angular، وأهم ما يجب معرفته هو معاني المُصطلحات وتبني نمط MVC، وMVC هو اختصار لـModel-View-Controller، أي نموذج-طريقة عرض-مُتحكِّم. فيما يلي بعض المصطلحات والواجهات البرمجية الأساسية التي تزوّدنا بها Angular. MVCربما سمعت بهذا الاختصار من قبل، وهو مستخدم في لغات برمجة عديدة كوسيلة لبناء هيكل التطبيقات أو البرامج. وهاك تلخيص سريع لمعناه: النموذج (Model): بنية البيانات التي تقوم عليها أجزاء التطبيقات، غالبًا ما تُمثل بصيغة JSON. يفضل أن تكون لديك معرفة مسبقة بـJSON قبل تعلم Angular، لأنها ضرورية للتواصل بين الخادوم وطريقة العرض (سنأتي على شرحها في النقطة التالية). على سبيل المثال، لنفترض أنه لدينا مجموعة من المستخدمين، يمكن تمثيل بيانات تعريفهم كما يلي:{ "users" : [{ "name": "أحمد", "id": "82047392" },{ "name": "سامر", "id": "65198013" }] }عادة ما تُجلب هذه المعلومات من خادوم بطلب XMLHttpRequest، ويقابله في jQuery الإجراء $.ajax، وفي Angular الكائن $http. وقد تكون هذه المعلومات مكتوبة ضمن النص البرمجي أثناء تفسير الصفحة (من قاعدة بيانات أو مخزن بيانات). بعد ذلك يكون بإمكانك تعديل هذه المعلومات وإعادة إرسالها. طريقة العرض (View): وهو أمر سهل التفسير، فهي ببساطة المُخرج النهائي أو صفحة HTML التي تعرض البيانات (النموذج) على المستخدم مثلاً. باستخدام إطار عمل MVC، تُجلب البيانات من النموذج وتُعرض المعلومات المناسبة في صفحة HTML.المُتحكِّم (Controller): وله من اسمه نصيب، فهو يتحكم بالأشياء، ولكن أية أشياء؟ البيانات. المُتحكمات هي الطريقة التي تصل من خلالها بين الخادوم وطريقة العرض، فتسمح بتحديث البيانات سريعًا من خلال التواصل مع كلا الخادوم والعميل.إعداد مشروع Angular JS (الأساسيات)يجب أولاً تهيئة الأساسيات التي يقوم عليها مشروعنا. يجب أن نبدأ بتطبيق (ng-app) الذي يُعرِّف التطبيق (وng هي بادئة تعني Angular وتسبق عادة كل مكونات Angular JS)، ثم متحكم (Controller) ليتواصل مع طريقة العرض، ثم ربط DOM ولا ننسى تضمين Angular بالطبع. إليك الأساسيات: نص HTML مع تصريحات ng-*:<div ng-app="myApp"> <div ng-controller="MainCtrl"> <!-- محتويات المتحكم --> </div> </div>وحدة Angular مع متحكم:var myApp = angular.module('myApp', []); myApp.controller('MainCtrl', ['$scope', function ($scope) { // أوامر المتحكم }]);قبل أن نستبق الأمور، نحتاج لإنشاء وحدة Angular (أو Angular module)، التي ستتضمن كل النص البرمجي المُتعلق بالمشروع. هناك أكثر من طريقة للتصريح عن الوحدات، إحداها سَلسَلة كل النص البرمجي معًا (لا أفضل هذه الطريقة): angular.module('myApp', []) .controller('MainCtrl', ['$scope', function ($scope) {...}]) .controller('NavCtrl', ['$scope', function ($scope) {...}]) .controller('UserCtrl', ['$scope', function ($scope) {...}]);ولكن الطريقة التي أفضلها، والتي أثبتت أنها الأفضل لكل مشاريع Angular التي صمّمتها هي تعريف الوحدة العامة بشكل منفصل. الطريقة التي تعتمد على تسلسل التصريحات قد تجعلك تنسى إغلاق بعض الأقواس وتجعل قراءة النص البرمجي وتصحيحه أكثر تعقيدًا. لذا أُفضّل هذا الأسلوب: var myApp = angular.module('myApp', []); myApp.controller('MainCtrl', ['$scope', function ($scope) {...}]); myApp.controller('NavCtrl', ['$scope', function ($scope) {...}]); myApp.controller('UserCtrl', ['$scope', function ($scope) {...}]);بهذه الطريقة أُقسّم النص البرمجي على عدة ملفات، وفي كل ملف أربط مكوّناً من المكونات مع فضاء الأسماء myApp فيصبح تلقائيًا جزءًا من تطبيقي. نعم، الأمر كما فهمت، أفضل أن أنشئ ملفًا مستقلًا لكل متحكم ومُرشِد (directive) ومعمل (factory) وأي شيء آخر (ستشكرني على هذا). فيما بعد يمكنك دمجها معًا وتقليصها لتصبح ملفًا واحدًا (مستخدمًا مُدير مهام مثل Grunt أو Gulp) فتدفعَه إلى DOM. المُتحكّمات (Controllers)أصبحت تعرف الآن مفهوم MVC وتعلمت طريقة إعداد مشروع جديد، فلنطّلع الآن على الكيفية التي يُطبِّق فيها Angular JS العمل بالمتحكّمات. بناء على المثال السابق، بإمكاننا الآن أن نخطو خطوة بسيطة نحو عرض بعض البيانات ضمن طريقة العرض مستخدمين مُتحكّمًا. يستخدم Angular تركيب " handlebars" لقولبة HTML. ببساطة يعني هذا أنه بإمكان المتحكمات أن تعرض البيانات في صفحة HTML بأن تستبدل كل عبارة فيها مكتوبة ضمن الأقواس المزدوجة هكذا: {{ data }} قيمة يُعيّنها المُتحكم. في الحالة المثالية يجب أن لا تحوي صفحة HTML نصًا حقيقيًا أو قيمًا مُدرجة مسبقًا، ويجب أن تترك هذه المهمة لمتحكمات Angular. فيما يلي مثال يُبيّن كيف يمكن عرض نص أو سلسلة حروف String بسيطة ضمن الصفحة: <div ng-app="myApp"> <div ng-controller="MainCtrl"> {{ text }} </div> </div>في ملف JavaScript: var myApp = angular.module('myApp', []); myApp.controller('MainCtrl', ['$scope', function ($scope) { $scope.text = 'مرحباً بمعجبي Angular!'; }]);والناتج النهائي: رابط المثال أهم مفهوم هنا هو مفهوم النطاق ($scope) والذي ستربطه بكل الوظائف ضمن متُحكّم مُعيّن. يُشير $scope إلى العُنصر أو المنطقة الحالية في DOM (فهو لا يُساوي this ضمن النص البرمجي) وبهذا يخلق نطاقًا يُحيط بكل البيانات والوظائف ضمن العناصر (DOM elements)، ويعزلها عن العناصر الأخرى، فيبدو وكأنه ينقل مجالات JavaScript العامة/الخاصة إلى DOM، وهذا شيء رائع. قد يبدو مفهوم النطاق مخيفًا للوهلة الأولى، لكنه طريقك الواصل بين الخادوم (أو حتى البيانات المحلية) من جهة وDOM من الجهة الأخرى. يعطيك هذا المثال فكرة عن الطريقة التي "تُدفع" بها البيانات إلى DOM. لنٌلقِ نظرة على بيانات حقيقية نفترض أننا جلبناها من خادوم لنعرض تفاصيل تسجيل دخول المستخدم، سنكتفي في هذه المرحلة باستخدام بيانات جاهزة، وسنتعلم كيفية جلبها من الخادوم على هيئة JSON لاحقًا. أولاً سنكتب شفرة JavaScript: var myApp = angular.module('myApp', []); myApp.controller('UserCtrl', ['$scope', function ($scope) { // لنجعل معلومات المستخدم ضمن عنصر فرعي $scope.user = {}; $scope.user.details = { "username": "Todd Motto", "id": "89101112" }; }]);ثم ننتقل إلى DOM لعرض هذه البيانات <div ng-app="myApp"> <div ng-controller="UserCtrl"> <p class="username">Welcome, {{ user.details.username }}</p> <p class="id">User ID: {{ user.details.id }}</p> </div> </div>النتيجة من المهمّ أن تتذكر أن المتحكمات تستخدم فقط للبيانات ولإنشاء وظائف تتواصل مع الخادوم وتجلب أو ترسل بيانات JSON. لا تستخدم المتحكمات لمعالجة DOM (كأن تنقل عنصرًا ضمن الصفحة أو تخفيه أو تظهره...)، فمعالجة DOM مهمة المُرشِدات (directives)، وهي ما سنشرحه لاحقًا، المهم أن تتذكر أن موضع jQuery وغيرها من المكتبات التي تتعامل مع DOM ليس ضمن المتحكّمات. تنويه: خلال اطلاعك على وثائق Angular الرسمية، ستلاحظ أن الأمثلة المقدمة تعتمد الأسلوب التالي لإنشاء المتحكمات: var myApp = angular.module('myApp', []); function MainCtrl ($scope) { //... };لا تفعل ذلك. هذا سيجعل كل الوظائف المُصرّحة تابعةً للنطاق العامّ (global scope) ولا يربطها بشكل جيد مع التطبيق. هذا يعني كذلك أن عمليات التقليص للنص البرمجي والتجارب ستكون أكثر صعوبة. لا تلوّث فضاء الأسماء العام، بل اجعل المتحكمات ضمن التطبيق. المُرشِدات (Directives)الُمرشد في أبسط صوره هو نص HTML مٌقولَب، يفضل أن يكون استخدامه متكررًا ضمن التطبيق. توفر المرشدات طريقة سهلة لإدخال أجزاء DOM ضمن التطبيق دون عناء. تعلم استخدام المرشدات ليس أمرًا سهلًا على الإطلاق، وإتقانها يتطلب جهدًا، ولكن الفقرات التالية ستضعك على الطريق الصحيح. إذن، ما فائدة المُرشدات؟ إنها مفيدة في عدة أمور، منها إنشاء عناصر DOM، مثل علامات التبويب (tabs) وقوائم التصفح - ففائدتها تعتمد على ما يفعله تطبيقك في الواجهة. لتسهيل الشرح، سأقول ببساطة: إن كنت استعملت ng-show وng-hide من قبل، فقد استعملت المرشدات (حتى وإن كان هذان لا يُدرجان أية عناصر DOM). على سبيل التمرين، سنُنشئ نوعًا خاصًّا من الأزرار ونُسميه customButton، يُدرج هذا العنصر بعض العناصر الفرعية التي لا نريد كتابتها في كل مرة. تتنوع طرق التصريح عن المرشدات في DOM، وهي مبينة في النص البرمجي التالي: <!-- 1: تصريح عن مُرشد كخاصّة (attribute) --> <a custom-button>انقرني</a> <!-- 2: كعنصر مخصص (custom elements) --> <custom-button>انقرني</custom-button> <!-- 3: كصنف (class) (للتوافق مع النسخ القديمة من IE) --> <a class="custom-button">انقرني</a> <!-- 4: كتعليق (comment) (ليس ملائماً لهذا التمرين) --> <!-- directive: custom-button --> أفضّل استخدام المرشدات كخواصّ (attributes)، أما العناصر المُخصصة (custom elements) فقادمة في النسخ المستقبلية من HTML باسم Web Components، يوفر Angular ما يشبهها، ولكنها قد تنطوي على بعض العيوب والعلل في المتصفحات القديمة. الآن نعرف كيف نصرح عن المرشدات ضمن الصفحة، سننتقل إلى إنشائها ضمن JavaScript. لاحظ أنني سأربطه مع فضاء الأسماء العام myApp؛ في صيغته الأبسط يُكتب المرشد كما يلي: myApp.directive('customButton', function () { return { link: function (scope, element, attrs) { // هنا اكتب التعليمات التي تعالج DOM أو تتعامل مع أحداثه } }; });عرّفنا المرشد باستخدام الدالة .directive()، ومررنا إليها اسم المرشد 'customButton'. عندما تكتب حرفًا كبيرًا بالإنجليزية في اسم المُرشد، فإنه ينبغي استخدام اسم المرشد ضمن DOM بصيغته التي يُفصل بها باستخدام الشرطة (-) بين الحروف الكبيرة (كما في المثال السابق: استخدمنا 'customElement' في JavaScript و"custom-button" في HTML). يُرجع المُرشد كائنًا (Object) له عدد من الخصائص. أهم ما يجب تعلّمه منها: restrict وreplace وtransclude وtemplate وtemplateUrl وأخيرًا link. لنُضف بعضها إلى شفرتنا البرمجية: myApp.directive('customButton', function () { return { restrict: 'A', replace: true, transclude: true, template: '<a href="" class="myawesomebutton" ng-transclude>' + '<i class="icon-ok-sign"></i>' + '</a>', link: function (scope, element, attrs) { // هنا اكتب التعليمات التي تعالج DOM أو تتعامل مع أحداثه } }; });النتيجة تأكد من فحص العنصر (من الأمر Inspect element في المُتصفح) لرؤية العناصر الجديدة التي أُدخلت في الصفحة. اعلم أن الرمز لم يظهر ضمن العنصر الجديد، ببساطة لأنني لم أُضمّن Font Awesome ضمن المشروع، ولكن يمكنك فهم كيف تعمل المرشدات. لنتعرف الآن ما تعنيه كل واحدة من خصائص المرشد السابقة الذكر: الخاصية restrict: تُقيّد هذه الخاصة كيفية استخدام المُرشد، كيف نريد أن نستخدمه؟ إن كنت تبني مشروعًا يتطلب دعم النسخ القديمة من IE، فعليك استخدامه كخاصّة (attribute) أو صنف (class). القيمة 'A' تعني حصر استخدام المرشد بالخواص (attributes) فقط. 'E' تعني Element و'C' صنف و'M' تعليق. القيمة الافتراضية هي 'EA' (أي عنصر وخاصة). الخاصية replace: تعني استبدال HTML العنصر المصرّح عن المُرشد ضمن الصفحة بالقالب (template) الذي يُحدد في الخاصة template (مشروحة أدناه). الخاصةtransclude: تسمح بنسخ المحتوى الأصلي للعنصر المُصرّح عن المُرشد في الصفحة ودمجه ضمن المرشد (عند التنفيذ، ستلاحظ أن العبارة "انقرني" انتقلت إلى المُرشد). الخاصية template: قالب (كذلك المستخدم في المثال) يُدخل إلى الصفحة. يفضّل استخدام القوالب الصغيرة فقط. تُعالج القوالب وتبنى من قبل Angular مما يسمح باستخدام صيغة handlebars ضمنها. الخاصة templateUrl: مشابهة للسابقة، ولكنها تُجلب من ملف أو من وسم <script> بدل كتابتها ضمن تعريف المُرشد. كل ما عليك هو تعيين مسار الملف الذي يحوي القالب. يكون هذا الخيار مناسبًا عندما تريد الاحتفاظ بالقوالب خارج النص البرمجي لملفات JavaScript: myApp.directive('customButton', function () { return { templateUrl: 'templates/customButton.html' // directive stuff... });وضمن الملف، نكتب: <!-- inside customButton.html --> <a href="" class="myawesomebutton" ng-transclude> <i class="icon-ok-sign"></i> </a>ملاحظة: لا يخضع اسم الملف إلى أية قاعدة، وليس من الضروري أن يوافق اسمَ المُرشد. عند استخدام الأسلوب السابق، سيحتفظ المتصفح بنسخة مُخبأة (cached) من ملف HTML، وهو أمر رائع، الخيار البديل هو استخدام قالب ضمن وسم <script> وهنا لا تُخبأ نسخة منه في المتصفح: <script type="text/ng-template" id="customButton.html"> <a href="" class="myawesomebutton" ng-transclude> <i class="icon-ok-sign"></i> </a> </script>هنا أخبرنا Angular بأن وسم <script> هذا هو قالب (ng-template) وأعطيناه المُعرف. سيبحث Angular عن القالب أو عن ملف html، فاستخدم ما تراه مناسبًا. شخصيًا، أفضّل إنشاء ملفات html لسهولة تنظيمها ولتحسين الأداء وإبقاء DOM نظيفًا، فقد يستخدم مشروعك مع الوقت عشرات المُرشدات، وترتيبها في ملفات مستقلة يجعل مراجعتها أسهل. الخدمات (Services)كثيرًا ما تثير الخدمات في Angular ارتباك المطورين، ومن خبرتي وأبحاثي، أعتقد أن الخدمات وُضعت كنمط وأسلوب للتصميم أكثر من اختلافها بالوظيفة التي تؤديها. بعد التنقيب في مصدر Angular، وجدت أنها تُعالج وتبنى باستخدام المُجمّع (compiler) ذاته، وكذلك فهي تقدم العديد من الوظائف المشابهة. أنصح باستخدام الخدمات لبناء الكائنات المُتفرِّدة (singletons)، واستخدام المعامل (Factories) لبناء وظائف أكثر تعقيدًا كالكائنات الحرفيّة (Object Literals). فيما يلي مثال لاستخدام خدمة توجد ناتج الضرب لعددين: myApp.service('Math', function () { this.multiply = function (x, y) { return x * y; }; });يمكنك بعد هذا استخدامها ضمن مُتحكم كما يلي: myApp.controller('MainCtrl', ['$scope', function ($scope) { var a = 12; var b = 24; // الناتج: 288 var result = Math.multiply(a, b); }]);نعم بالطبع إيجاد ناتج الضرب سهل ولا يحتاج خدمة، لكننا نستخدمه لإيصال الفكرة فحسب. عندما ننشئ خدمة (أو معملاً) نحتاج إلى إخبار Angular عن متطلبات هذه الخدمة، وهو ما يسمى "حقن المتطلبات Dependency Injection" - إن لم تُصرّح عن المتطلبات فلن يعمل المتحكم المعتمد على الخدمة، وسيحدث خطأ عند التجميع. ربما لاحظت الجزء function ($scope) ضمن التصريح عن المتحكم أعلاه، وهذا هو ببساطة حقن المتطلبات. ستُلاحظ أيضًا [$scope] قبل الجزء function ($scope)، وهو ما سأشرحه لاحقًا. فيما يلي طريقة استخدام حقن المتطلبات لإخبار Angular أنك تحتاج إلى الخدمة التي أنشأتها للتو: // مرر الخدمة Math myApp.controller('MainCtrl', ['$scope', 'Math', function ($scope, Math) { var a = 12; var b = 24; // يُعطي 288 var result = Math.multiply(a, b); }]);المعامل (Factories)إيضاح فكرة المعامل سهل إذا كنت قد استوعبت فكرة الخدمات، بإمكاننا إنشاء كائن حرفي (Object Literal) ضمن المعمل أو عبر طرق أكثر تعقيدًا: function ($http) { return { get: function(url) { return $http.get(url); }, post: function(url) { return $http.post(url); }, }; }]);هنا أنشأنا مُغلفات (wrappers) مخصصة لخدمة $http في Angular المسؤولة عن طلبات XHR. بعد حقن المتطلبات ضمن المتحكم يمكننا استخدام هذا المعمل بسهولة: myApp.controller('MainCtrl', ['$scope', 'Server', function ($scope, Server) { var jsonGet = 'http://myserver/getURL'; var jsonPost = 'http://myserver/postURL'; Server.get(jsonGet); Server.post(jsonPost); }]);إذا أرت طلب التحديثات من الخادوم، بإمكانك إنشاء دالة Server.poll أو إن كنت تستخدم مقبسًا (ٍSocket)، فربما سترغب في إنشاء دالة Server.socket وهكذا... المعامل تسمح لك بتنظيم نصك البرمجي ضمن وحدات يمكن إدراجها ضمن المتحكمات منعًا لتكرار النص البرمجي فيها والحاجة المتكررة لصيانته. المُرشّحاتتستخدم المرشحات مع مصفوفات (arrays) من البيانات وخارج الحلقات (loops). إن احتجت للمرور على عناصر من مصفوفة بيانات والحصول على بعض منها فقط، فأنت في المكان الصحيح، يمكنك أيضًا استخدام المرشحات لتصفية ما يكتبه المستخدم ضمن حقل إدخال <input> مثلًا. هناك عدة طرق لاستخدام المُرشحات: ضمن متحكم أو دالة مُعرفة. فيما يلي الطريقة الأخيرة: myApp.filter('reverse', function () { return function (input, uppercase) { var out = ''; for (var i = 0; i < input.length; i++) { out = input.charAt(i) + out; } if (uppercase) { out = out.toUpperCase(); } return out; } }); // Controller included to supply data myApp.controller('MainCtrl', ['$scope', function ($scope) { $scope.greeting = 'Todd Motto'; }]);وفي HTML: <div ng-app="myApp"> <div ng-controller="MainCtrl"> <p>No filter: {{ greeting }}</p> <p>Reverse: {{ greeting | reverse }}</p> </div> </div>رابط المثال وهنا نستخدم المُرشح ضمن حلقة ng-repeat: <ul> <li ng-repeat="number in myNumbers |filter:oddNumbers">{{ number }}</li> </ul>مثال عن مُرشح ضمن متحكم: myApp.controller('MainCtrl', ['$scope', function ($scope) { $scope.numbers = [10, 25, 35, 45, 60, 80, 100]; $scope.lowerBound = 42; // Does the Filters $scope.greaterThanNum = function (item) { return item > $scope.lowerBound; }; }]);واستخدامه حلقة ng-repeat: <li ng-repeat="number in numbers | filter:greaterThanNum"> {{ number }} </li>رابط المثال كان هذا القسم الأكبر مما تحتاج لمعرفته عن AngularJS وواجهاتها البرمجية، ومع أن ما تعلمناه كافٍ لبناء تطبيق Angular، ولكننا إلى الآن لم نسكتشف أغوراها بعد. ربط البيانات ثنائي الاتجاهعندما سمعت للمرة الأولى عن ربط البيانات ثنائي الاتجاه لم أفهم ما يعنيه. باختصار يمكن القول إنه حلقة متصلة من البيانات المُزامنة: حدّث النموذج (Model) لتُحدَّث طريقة العرض (View)، أو حدّث طريقة العرض ليُحدَّث النموذج (Model). هذا يعني أن البيانات تبقى محدثة دومًا دون عناء. إن ربطت نموذج ng-model مع حقل إدخال <input> وكتبت فيه، فهذا يُنشئ (أو يُحدِّث) نموذجاً في الوقت ذاته. فيما يلي نقوم بإنشاء حقل <input> ونربطه بنموذج نسميه myModel، يمكنني الآن استخدام صياغة handlebars لعكس هذا النموذج وما يطرأ عليه من تحديثات في طريقة العرض في الوقت ذاته: <div ng-app="myApp"> <div ng-controller="MainCtrl"> <input type="text" ng-model="myModel" placeholder="Start typing..." /> <p>My model data: {{ myModel }}</p> </div> </div> myApp.controller('MainCtrl', ['$scope', function ($scope) { // Capture the model data // and/or initialise it with an existing string $scope.myModel = ''; }]); النتيجة طلبات XHR/Ajax/$http وربط JSONنعرف الآن كيف نرسل بيانات بسيطة ضمن المجال ($scope)، ونعرف ما يكفي عن كيفية عمل النماذج وربط البيانات ثنائي الجانب، والآن حان الوقت لمحاكاة طلبات XHR حقيقية للخادوم. ليس هذا ضروريًا لمواقع الويب العادية، لكنه مناسب جدًا لجلب البيانات في تطبيقات الويب. عندما تطور تطبيقك على جهازك المحلي، فغالبًا ما ستستخدم لغة مثل Java أو ASP.NET أو PHP أو غيرها لتشغيل خادوم محلي. وسواء كنا نتصل بقاعدة بيانات محلية أم بخادوم بعيد كواجهة برمجية، فإننا نتبع نفس الخطوات بالضبط. هنا يأتي دور $http، صديقك المخلص من اليوم فصاعدًا. الدالة $http هي مُغلّف wrapper تقدمه Angular للوصول إلى البيانات من الخادوم، وهو سهل الاستخدام للغاية ولا يحتاج لأية خبرة. فيما يلي مثال عن طلب GET لجلب البيانات من الخادوم. الصياغة مشابهة جدًا لصياغة jQuery، وهذا يُسهل الانتقال من الأخيرة إلى Angular: myApp.controller('MainCtrl', ['$scope', '$http', function ($scope, $http) { $http({ method: 'GET', url: '//localhost:9000/someUrl' }); }]);يُعيد Angular إلينا أمرًا يُصطلح على تسميته الوعد (Promise) ، وهو بديل أسهل استخدامًا من الاستدعاءات الراجعة (callbacks). يمكن تركيب الوعود في سلسلة باستخدام النقطة، ويمكننا ربطها مع مستقبلات النجاح والفشل: myApp.controller('MainCtrl', ['$scope', function ($scope) { $http({ method: 'GET', url: '//localhost:9000/someUrl' }) .success(function (data, status, headers, config) { // successful data retrieval }) .error(function (data, status, headers, config) { // something went wrong :( }); }]);سهلة الاستخدام والقراءة. هنا نربط طريقة العرض والخادوم بربط نموذج أو تحديثه. لنفترض أنه لدينا خادومًا مُعدًّا ولنقم بدفع اسم المستخدم إلى طريقة العرض عن طريق طلب AJAX. علينا --لو كنا حريصين على المثالية-- أن نصمم بيانات JSON التي نريدها أولاً. دعونا الآن نُبسط الأمور، ولندع هذا الأمر ليتولاه من يفهم في أمور النهاية الخلفية (backend)، ولكن لنقل أننا تفترض أن نستقبل بيانات مثل هذه: { "user": { "name": "Todd Motto", "id": "80138731" } }هذا يعني أننا سنحصل على كائن Object من الخادوم (سنسميه data، وسترى أنه يُمرر إلى مستقبلات الوعد الذي أنشأناه). علينا الآن أن نربط هذا الكائن بالخاصة data.user، وضمنها لدينا name وid. يمكن ببساطة الوصول إلى هذه القيم باستخدام data.user.name للحصول على "Todd Motto" مثلاً. فيما يلي الشفرة البرمجية (اطلع على التعليقات المضمنة): myApp.controller('UserCtrl', ['$scope', '$http', function ($scope, $http) { // create a user Object $scope.user = {}; // Initiate a model as an empty string $scope.user.username = ''; // نريد أن نرسل طلباً ونحصل على اسم المستخدم $http({ method: 'GET', url: '//localhost:9000/someUrlForGettingUsername' }) .success(function (data, status, headers, config) { // عند نجاح الطلب، نُسنِد الاسم إلى النموذج الذي أنشأناه $scope.user.username = data.user.name; }) .error(function (data, status, headers, config) { // وقع خطأ ما! :( }); }]);الآن ضمن الصفحة يمكننا ببساطة كتابة: <div ng-controller="UserCtrl"> <p>{{ user.username }}</p> </div>هذا سيعرض اسم المستخدم. لننتقل الآن إلى خطوة أبعد بفهم ربط البيانات التصريحي (Declarative data-binding) حيث تصبح الأمور أكثر إثارة. ربط البيانات التصريحيتقوم فلسفة Angular على إنشاء نصوص HTML ديناميكية قادرة على القيام بوظائف بنفسها لم نكن نتوقع أنها ممكنة ضمن المتصفح. هذه هي المهمة التي تقوم بها Angualar على خير وجه. لنتخيل أننا أرسلنا طلب AJAX لجلب قائمة بعناوين البريد الإلكتروني وسطر الموضوع فيها مع تاريخ إرسالها، ولنفترض أننا نريد عرضها ضمن الصفحة. في هذا المكان بالضبط تذهلنا Angular بقدراتها. لننشئ أولاً متحكماً بالبريد الإلكتروني: yApp.controller('EmailsCtrl', ['$scope', function ($scope) { // نُنشئ كائناً يدعاً `emails` $scope.emails = {}; // لنفترض أننا حصلنا على هذه البيانات من الخادوم // هذه **مصفوفة** من **الكائنات** $scope.emails.messages = [{ "from": "Steve Jobs", "subject": "I think I'm holding my phone wrong :/", "sent": "2013-10-01T08:05:59Z" },{ "from": "Ellie Goulding", "subject": "I've got Starry Eyes, lulz", "sent": "2013-09-21T19:45:00Z" },{ "from": "Michael Stipe", "subject": "Everybody hurts, sometimes.", "sent": "2013-09-12T11:38:30Z" },{ "from": "Jeremy Clarkson", "subject": "Think I've found the best car... In the world", "sent": "2013-09-03T13:15:11Z" }]; }]);علينا الآن دفعها ضمن الصفحة. هنا نستخدم الربط التصريحي لنُعلن عما سيفعله تطبيقنا: إنشاء أول جزء من عناصر HTML الحيوية. سنستخدم مُرشد ng-repeat المبني ضمن Angular، والذي سوف يمر على البيانات ويعرض الناتج دون عناء الاستدعاءات الرجعية أو تغيير الحالة، بهذه السهولة: <ul> <li ng-repeat="message in emails.messages"> <p>From: {{ message.from }}</p> <p>Subject: {{ message.subject }}</p> <p>{{ message.sent | date:'MMM d, y h:mm:ss a' }}</p> </li> </ul>النتيجة قمت أيضًا باستخدام مُرشّح التاريخ لأبين لك كيف يمكن أن تعرض تواريخ UTC. اطلع على المُرشدات التي توفرها Angular لتتعرف على القدرات الكاملة للربط التصريحي. بهذا نكون قد عرفنا كيف نصل البيانات بين الخادوم وطريقة العرض. وظائف المجال (Scope functions)تعتبر وظائف المجال الخطوة التالية في بناء وظائف التطبيق واستكمالاً للربط التصريحي. فيما يلي وظيفة بسيطة تحذف إحدى الرسائل: myApp.controller('MainCtrl', ['$scope', function ($scope) { $scope.deleteEmail = function (index) { $scope.emails.messages.splice(index, 1) }; }]);تنويه: من المهم أن تفكر في حذف البيانات من النموذج، لا تحذف العناصر أو أي شيئ من الصفحة، دع Angular يتولى هذا بربطه ثنائي الجانب للبيانات، فقط فكر بذكاء واكتب شفرة برمجية تستجيب لبياناتك. ربط الوظائف مع طريقة العرض يمر عبر المُرشدات، هذه المرة نستخدم مُرشد ng-click: <a ng-click="deleteEmail($index)">Delete email</a>هذا يختلف تمامًا عن مستقبلات النقر التقليدية في JavaScript، لأسباب عديدة سنشرحها لاحقًا. لاحظ أنني أيضًا أمرر فهرس العنصر $index، إذ يعرف Angualr ما العنصر المُراد حذفه (كم يوفر هذا من العناء؟) النتيجة دوال DOM التصريحيةننتقل الآن لدوال DOM، وهي أيضًا مُرشدات تؤدي وظيفة ضمن الصفحة بدونها كنا سنكتب شفرات برمجية طويلة. أحد الأمثلة المناسبة لإيضاح الفكرة هنا هو إظهار أو إخفاء قسم التنقل ضمن الصفحة باستخدام ng-show وng-click، لنرَ بساطة هذا: <a href="" ng-click="toggle = !toggle">Toggle nav</a> <ul ng-show="toggle"> <li>Link 1</li> <li>Link 2</li> <li>Link 3</li> </ul>هنا ندخل عالم MVVM (اختصار Model-View-View-Model)، لاحظ أننا لم نستخدم متحكمًا، وسنشرح فكرة MVVM بعد قليل. الناتج (جرب الإظهار والإخفاء): النتيجة التعابير (Expressions)من أفضل ما يقدمه Angular، يقدم بديلاً عن الحاجة لكتابة الكثير من JavaScript والنصوص المكررة. هل قمت يومًا ما بكتابة شيء كهذا؟ elem.onclick = function (data) { if (data.length === 0) { otherElem.innerHTML = 'No data'; } else { otherElem.innerHTML = 'My data'; } };ربما يكون هذا استدعاءً راجعًا عن طلب GET، ونحتاج لنغير محتوى في الصفحة بناء على البيانات، يقدم Angular هذا بدون الحاجة لكتابة حرف JavaScript! <p>{{ data.length > 0 && 'My data' || 'No data' }}</p>سيقوم هذا بتحديث الصفحة تلقائيًا دون استدعاءات عند وصول البيانات أو ما شابه. إن لم تتوفر البيانات، سيظهر هذا واضحًا، وإن وجدت فسيظهر كذلك. هناك حالات كثيرة جدًا يُمكن فيها لـAngular أن يتولاها عبر ربطه ثنائي الجانب للبيانات الذي يعمل بشكل رائع. النتيجة طرق العرض الديناميكية والتوجيه (Routing)هي ما تقوم عليه فلسفة تطبيقات الويب (والمواقع) أحادية الصفحة: لديك قسم الترويسة وقسم التذييل وشريط جانبي ثم المحتوى الذي يُحدث تلقائيًا بناءً على الرابط الحالي. يجعل Angular إعداد هذا الأمر في منتهى السهولة. تحقن طرق العرض الديناميكة دوالا مُعينة بناء على الرابط، عبر استخدام $routeProvider. فيما يلي إعداد بسيط: myApp.config(['$routeProvider', function ($routeProvider) { /** * $routeProvider */ $routeProvider .when('/', { templateUrl: 'views/main.html' }) .otherwise({ redirectTo: '/' }); }]);لاحظ أنه عندما (when) يكون الرابط / فقط (الصفحة الرئيسية)، ستعرض الصفحة main.html. من المفيد تسمية طريقة العرض الأساسية main.html وليس index.html لأنه سيكون لدينا مسبقاً الصفحة index.html وهي الصفحة التي تحوي طرق العرض الديناميكية وبقية الأجزاء. يمكن ببساطة إضافة المزيد من طرق العرض: myApp.config(['$routeProvider', function ($routeProvider) { /** * $routeProvider */ $routeProvider .when('/', { templateUrl: 'views/main.html' }) .when('/emails', { templateUrl: 'views/emails.html' }) .otherwise({ redirectTo: '/' }); }]);بإمكاننا الآن تحميل الصفحة emails.html ببساطة لتقوم بعرض قائمة الرسائل الإلكترونية. الخلاصة أننا استطعنا أن نبني تطبيقًا معقدًا للغاية بجهد ضئيل جدًا. توفر الخدمة $routerProvider المزيد من الخيارات، لكن ما تعلمناه عنها كافٍ في البداية. هناك أيضًا أمور أخرى مثل مُعترِضات $http التي تبدأ أحداثًا خلال مسير طلب AJAX، فتتيح لنا عرض مقدار التقدم على سبيل المثال أثناء جلب البيانات. البيانات الثابتة العامةفي تطبيق Gmail للويب، تُكتب بيانات كثيرة بصيغة JSON ضمن الصفحة (انقر باليمين واختر عرض المصدر في صفحة Gmail). إن قمنا بخطوة مشابهة، أي كتابة البيانات ضمن الصفحة، فهذا سيجعل وقت عرضها أقل وسيبدو التطبيق أسرع. عندما أطور تطبيقات مؤسستنا، تُدرج وسوم Java ضمن الصفحة وعندما تُعرض، تُرسل البيانات من الخادوم (لا خبرة لدي في Java لذا سأكتب فيما يلي تصريحات وهمية، يمكنك استخدام أي لغة على الخادوم إن أحببت). النصوص التالية توضح كيف يمكنك كتابة JSON ضمن الصفحة ثم تمريرها إلى المتحكم لربطها مباشرة: <!-- ضمن index.html (في نهاية الصفحة بالطبع) --> <script> window.globalData = {}; globalData.emails = <javaTagHereToGenerateMessages>; </script>سيُنشئ وسم Java الذي اختلقته البينات بينما سيعالج Angular الرسائل فورًا. كل ما عليك هو إعطاؤه البيانات عبر المتحكم: myApp.controller('EmailsCtrl', ['$scope', function ($scope) { $scope.emails = {}; // Assign the initial data! $scope.emails.messages = globalData.emails; }]);تقليص الملفات (Minification)سنتحدث قليلاً عن تقليص حجم الشفرات البرمجية التي كتبناها. ربما تكون قد جربت تقليص شفراتك البرمجية التي كتبتها لـAngular وصادفت خطأ. ليس هناك أمور خاصة يتطلبها تقليص حجم هذه الملفات، باستثناء الحاجة لإدراج أسماء المتطلبات ضمن مصفوفة قبل الدالة المُصرّح عنها، لنوضح أكثر: myApp.controller('MainCtrl', ['$scope', 'Dependency', 'Service', 'Factory', function ($scope, Dependency, Service, Factory) { // code }]);بعد التقليص: myApp.controller('MainCtrl', ['$scope', 'Dependency', 'Service', 'Factory', function (a,b,c,d) { // a = $scope // b = Dependency // c = Service // d = Factory // $scope alias usage a.someFunction = function () {...}; }]);عليك أن تحافظ على ترتيب المتطلبات المحقونة في المصفوفة ['_one', '_two'] وضمن مُعاملات الدالة function(_one, _two)، وإلا فإنك ستسبب لنفسك ولفريق العمل معك مشاكل كثيرة! الاختلافات بين MVC وMVVMسننهي مقالتنا العملاقة الآن بشرح سريع يشمل الفروق بين MVC وMVVM: MVC: التواصل يعتمد على المتحكم (Controller)، لذلك نقول Model-View-ControllerMVVM: يشمل ربط البيانات التصريحي الذي يتواصل، بالمفهوم التقني، مع نفسه؛ أي Model-View-View-Model. يتواصل النموذج مع طريقة العرض، وتتواصل هذه الأخيرة مع النموذج ثانية. يسمح هذا للبيانات بأن تبقى محدّثة على الجانبين دون الحاجة لفعل أي شيء. لا داعي هنا للمتحكم. مثال على ذلك: إنشاء حلقة ng-repeat دون أن نعتمد على بيانات يُرسلها متحكم:<li ng-repeat="number in [1,2,3,4,5,6,7,8,9]"> {{ number }} </li>يبدو هذا مناسبًا للتجارب السريعة، ولكنني أنصح دومًا باستخدام متحكم للحفاظ على تنظيم النص البرمجي. مثال مكونات الويب في HTML5قد تبدو هذه الفكرة مُكررّة، ولكنني سأعيدها هنا لنتحدث عن مكونات الويب. يسمح Angular بإنشاء عناصر (elements) مخصصة مثل: <myCustomElement></myCustomElement> في الحقيقة هذا أشبه ما يكون بمستقبل HTML5 التي تقدم فكرة جديدة تُسمى مكونّات الويب (Web Components)، والتي تتركب من عناصر مخصصة ضمن HTML مترافقة مع نص JavaScript ديناميكي، وهذا أمر مثيرٌ للغاية - والأكثر إثارة أنه أمر ممكن اليوم باستخدام Angular. فريق Angular بعيد النظر - شكرًا لكم! تعليقات المجال (Scope)أعتقد أن تعليقات المجال إضافة جميلة لسياق العمل، فبدل الحاجة لكتابة التعليقات ضمن HTML بالطريقة التالية: <!-- header --> <header> Stuff. </header> <!-- /header -->أصبحنا نتحدث عن طرق العرض والمجالات بدل الصفحة، البيانات ضمن مجال ما لا تُشارك مع مجالات أخرى إلا إن قمت بفعل ذلك عن عمدٍ. لنستغل هذا في تحسين كتابة النص البرمجي بتقسيمه إلى مناطق تسبقها تعليقات: !-- scope: MainCtrl --> <div class="content" ng-controller="MainCtrl"> </div> <!-- /scope: MainCtrl -->تصحيح العلل في Angularتتوفر إضافة جميلة للغاية لمتصفح Chrome ينصح بها فريق Angular، وتسمى Batarang. برمجة سعيدة! ترجمة -وبتصرف- للمقال: Ultimate guide to learning AngularJS in one day لصاحبه Todd Motto (الذي -بطبيعة الحال- يعود عليه ضمير المُتكلم في هذا المقال).
  13. هذا ختام سلسلة تجربة المُستخدم، ولكنّه ليس إلا بداية طريقك في هذا العالم، فإن كنت رافقتنا منذ البداية، فهذا يعني أنّ لديك أدوات كثيرة عليك استخدامها، وقبل أن تبدأ عملك المهنيّ، ينبغي تعلّم شيء واحد أخير: اختبارات أ/ب. فهرس سلسلة مدخل إلى تجربة المستخدم: مدخل إلى تجربة المستخدم User Experience فهم ودراسة المستخدمين في مجال تجربة المستخدم دراسة الشريحة المستهدفة في مجال تجربة المستخدم كيفية التصميم للأجهزة المختلفة هندسة المعلومات في تجربة المستخدم تعرف على أنماط التصميم في مجال تجربة المستخدم أشياء لا يمكن اعتبارها رسوما تخطيطية (Wireframes) في مجال تجربة المستخدم تعرف على الرسوم التخطيطية (Wireframes) في مجال تجربة المستخدم مفهوم الثقل المرئي (Visual Weight) والألوان في مجال تجربة المستخدم التكرار ومخالفة الأنماط في مجال تجربة المستخدم المحاذاة والقرب في مجال تجربة المستخدم تعرف على أساليب مسح الواجهة والتراتب المرئي في مجال تجربة المستخدم أساليب الإطلاع في مجال تجربة المستخدم: التصفح، البحث والاكتشاف تصميم هيكل صفحة الويب والعناصر الأساسية في مجال تجربة المستخدم الأزرار، النماذج والدعوات إلى الإجراء في مجال تجربة المستخدم استخدام علم النفس في مجال تجربة المستخدم لتكييف المستخدم وإقناعه كيف تغير الخبرة من تجربة المستخدم؟ تصميم تجربة المستخدم من خلال بيانات وإحصائيات المستخدمين تعرف على أنواع المخططات الإحصائية في مجال تجربة المستخدم اختبارات أ/ب (A/B Test) في مجال تجربة المستخدم (هذا الدرس) لنفترض مثلًا أنّك تريد تصميم صفحة لبيع الأحذية، وتريد بالطّبع بيع أكبر عدد ممكن، لكن ما الّذي يؤدّي إلى شراء المزيد من الأحذية؟ هل هو فيديو عن الأحذية؟ أم تفاصيل الشّحن الكاملة قبل النّقر على زرّ الشّراء؟ أم شعار الشّركة المُنتجة للحذاء؟ أم ضمان استعادة الأموال؟ كيف ستختار؟ إن كان أول ما تبادر لذهنك هو "أن نسأل المُستخدمين" فهذه الفكرة ليست سيّئة، ولكنّ هذه الخيارات شخصيّة غير موضوعيّة، ولهذا فإنّ لكلٍّ رأيه. إذًا كيف نُحسن الاختيار من بين خيارات شخصيّة؟ الجواب: صمّمها كلّها، ثمّ أطلق هذه الخيارات في الوقت نفسه في صيغة اختبار أ/ب. ما المقصود باختبارات أ/ب ( A/B Test)؟ اختبار أ/ب هو طريقة لسؤال آلاف أو ملايين الزّوّار الحقيقة أيّ الخيارات هو الأفضل، حيث تُصمّم كلّ الخيارات الّتي تريد مقارنتها، وتُصدرها جميعًا. يضمن الاختبار أن لا يرى زائر فريد واحد سوى واحدًا من هذه الخيارات، وبعد أن يرى عددٌ كافٍ من الزّوّار كلّ الخيارات، يمكنك معرفة الخيار الّذي تلقّى نقرات أكثر. يقيس الاختبار أيضًا "مستوى الثّقة" بالأرقام، لتعرف متى توقف الاختبار (احذر من إيقافه قبل أوانه!) يمكنك تطبيق هذا الاختبار على إصدارين أو عشرين، ولكن تذكّر أنّ زيادة عدد الخيارات تتطلّب زيادة عدد الزّوّار، وبالتّالي وقتًا أطول. بعض التفاصيل اختبارات أ/ب مجّانيّة عادةً، ولا تُكلّف سوى وقت تصميم وإنشاء الصّفحات، ولكنّ النتيجة تكون قيّمة للغاية على المدى البعيد. اختبارات أ/ب مختلفة عن تغيير تصميم الصّفحة بالكامل ثمّ مقارنته بالتّصميم القديم، والطّريقة الوحيدة لمقارنة تصميمين هي إطلاقهما معًا لشريحتين متساويتين تقريبًا من الزّوّار. يكون اختبار أ/ب A/B Test أكثر دقّة إذا غيّرت عنصرًا واحدًا في كلّ مرّة، فلو كانت لديك صفحتان متماثلتان تمامًا إلا في لون الرّوابط، فالاختبار دقيق، ولكن إن كان في الصّفحتين قائمتان مختلفتان فلا يمكن معرفة أيّ التّغييرين يصنع الفرق، لون الرّوابط أم القوائم؟ لا فائدة من مقارنة صفحتين مُختلفتين تمامًا كالرئيسيّة وصفحة الدّفع، فهذا لا يُعتبر اختبار أ/ب صحيحًا. انتهينا! إن كنت تابعتنا على مدى السّلسلة كلّها فهنيئًا لك! فقد أصبح تفكيرك في التّصميم أكثر سعةً، وما عليك الآن إلّا أن تعزّز ما تعلّمته بالتّدريب، فتجربة المُستخدم في طلب متزايدٍ في قطاع المُنتجات التّقنيّة. ترجمة بتصرّف للدّرس A/B Tests من سلسلة Daily UX Crash Course لصاحبها Joel Marsh.
  14. ليس عليك أن تكون خبيرًا في الإحصاء لكي تخرج بشيء مُفيد من مُخطّط إحصائيّ، يظهر سلوك البشر على المُخطّطات بأشكال يمكن توقّعها، وهذا سيكون حديثنا اليوم. فهرس سلسلة مدخل إلى تجربة المستخدم: مدخل إلى تجربة المستخدم User Experience فهم ودراسة المستخدمين في مجال تجربة المستخدم دراسة الشريحة المستهدفة في مجال تجربة المستخدم كيفية التصميم للأجهزة المختلفة هندسة المعلومات في تجربة المستخدم تعرف على أنماط التصميم في مجال تجربة المستخدم أشياء لا يمكن اعتبارها رسوما تخطيطية (Wireframes) في مجال تجربة المستخدم تعرف على الرسوم التخطيطية (Wireframes) في مجال تجربة المستخدم مفهوم الثقل المرئي (Visual Weight) والألوان في مجال تجربة المستخدم التكرار ومخالفة الأنماط في مجال تجربة المستخدم المحاذاة والقرب في مجال تجربة المستخدم تعرف على أساليب مسح الواجهة والتراتب المرئي في مجال تجربة المستخدم أساليب الإطلاع في مجال تجربة المستخدم: التصفح، البحث والاكتشاف تصميم هيكل صفحة الويب والعناصر الأساسية في مجال تجربة المستخدم الأزرار، النماذج والدعوات إلى الإجراء في مجال تجربة المستخدم استخدام علم النفس في مجال تجربة المستخدم لتكييف المستخدم وإقناعه كيف تغير الخبرة من تجربة المستخدم؟ تصميم تجربة المستخدم من خلال بيانات وإحصائيات المستخدمين تعرف على أنواع المخططات الإحصائية في مجال تجربة المستخدم (هذا الدرس) اختبارات أ/ب (A/B Test) في مجال تجربة المستخدم هناك نمطان للمخطّطات الإحصائيّة يتكرّران كثيرًا في السّلوك الإنسانيّ، وهما: الزّيارات والسّلوك المُهيكَل. ملاحظة: استخدمت هنا مخطّطات من نوع الأعمدة (bar graph) لأنّها ببساطة أسهل رسمًا، قد يستخدم برنامج الإحصاءات الّذي تستخدمه أنواعًا أخرى، لا تقلق فهي جميعًا ذات مبدأ واحد، ولهذا نحن نتعلّم أشكال المُخطّطات وليس أنواعها. مخططات الحركة (Traffic Graphs) تُظهر هذه المخطّطات عدد النّاس الّذين قاموا بفعل مُحدّد على فترة زمنيّة مُحدّدة، كعدد الزّوّار في اليوم، يُسمّى هذا الحركة أو "traffic". ستكون الحركة دومًا بين صعودٍ وهبوط متقاربين، لأنّ أشياء عشوائيّة تحدث كلّ يوم في العالم، حتّى وإن لم يتغيّر موقعك، ولهذا لا يمكن اعتبار أن سبب تُغيّر بسيط في الحركة هو ميزة جديدة بعينها أو تغيير التّصميم. إليك أشكال المُخطّطات! الميل العام للحركة لو كان هناك تغيّر بطيء مُنتظم، ستُشاهده خلال الوقت. يسهلُ النّظر إلى مُخطّط يُظهر هبوطًا أو صعودًا منتظمًا واستنتاج استمرار ذلك، ما لم يطرأ تغيير ما على موقعك. أحداث فردية عشوائية/طارئة لا يُغيّر النّاس سلوكهم فجأة دون مُحرِّض. هل قمت بحملة إعلانية خلال عطلة الأسبوع؟ أو هل هي مشكلة تقنيّة في إحدى صفحات الموقع؟ عندما تُشاهد "طفرة" في المُخطّط (أو هبوطًا مُفاجئًا) فحاول تحديد سببه، ولا يغرينّك الاعتقاد بأنّ موقعك في تحسّن، بل لا بدّ من سبب لهذه الطّفرة، أحيانًا يكون حسنًا وأحيانًا سيّئًا. الحركة المتوقعة هل ترى نمطًا مُتكرّرًا مرّة بعد مرّة؟ لنقل إن موقعك شائع بين الموظّفين في مكاتبهم، ستشاهد ارتفاعًا في الحركة خلال أيام الدّوام، إمّا إن كان زوّارك من الأطفال الّذين يقضون يومهم في المدارس، فترتفع الحركة في عطلة الأسبوع، هذا أمر طبيعيّ وشائع. ولكن هذا النّمط المُتكرّر سُيعاني من نموّ بطيء، لو كان النّمط مُتكرّرًا ولكن الأرقام في هبوط، فهذا يعني أنّ مُستخدميك يغادرون من الملل، افعل شيئًا ما! مخططات السلوك المهيكل النّوع الآخر المُهم للمُخطّطات يُظهر ما يفعله النّاس، ولا يهتمّ بوقت أو تاريخ الفعل. يمكن التأثير على هذه المخطّطات بشدّة بتغيير هندسة المعلومات الّتي يقوم عليها الموقع. ملاحظة: أتيت بكلمة "السّلوك المُهيكل" من نفسي لهذا الدّرس، ولو قلتها أمام غيرك لبدوت ذكيًّا، ولكن لن يفهم أحد مقصدك! المخطط الأسي (Exponential graph) يُظهر هذا المُخطّط انحيازًا كبيرًا نحو نوع مُعيّن من السّلوكيّات أو القرارات. يبيّن المُخطّط أعلاه أنّ النّاس سينقرون مثلًا أولّ عنصر أكثر من الثّاني، والثّاني أكثر من الثّالث... إلخ. وحيثما وجد ترتيب مرئي (كما في القوائم) أو تسلسل طبيعيّ، سيبدو المُخطّط مثل هذا. من الأمثلة على هذا المُخطّط قائمة "أكثر الصّفحات زيارة"، لأنّه لا يمكن الوصول للصّفحة 2 دون المرور بالصّفحة 1، وكذلك المُخطّط التّفصيليّ لمتوسّط مدّة الجلسة أو عدد الصّفحات في الجلسة، لأن البقاء أكثر من 10 ثوانٍ في الموقع أمر بالغُ الصّعوبة! المخطط الأسي ذو الترتيب غير المتوقع (Exponential graph with unexpected order) هذا المُخطّط مثيرٌ للاهتمام، وهو ينتج عادة عن ترتيب البيانات في مواضعها الصّحيحة ما عدا بعض المُكوّنات، وهو يشير إلى أنّ أوّلويّات المُستخدمين مُختلفة عمّا تعتقده، فهم ينقرون العنصر الثّاني أكثر من الأوّل، هؤلاء المجانين! حاول تغيير تصميمك أو هندسة معلومات بما يطابق ما تُشير إليه هذه البيانات، لا تحاول تغيير المُستخدمين، فهم يُبغضون التّغيير. مخطط أسي مع مستخدمين متقدمين يبدو هذا المُخطّط مشابها للمُخطّط الأسّي التّقليديّ، ولكنّ فيه صعودًا بسيطًا في موضع مُحدّد، وهذا يشير إلى وجود مجموعة من المُستخدمين المُخلصين يُمضون وقتًا طويلًا في الموقع، ويفعلون أشياء أكثر من المُستخدمين العاديّين. اعثر على ما يشجّعهم، وأكثر منه! مخطط أسي مع مشكلة في معدل التحويل (Conversion) يجب أن يكون المُخطّط انسيابيًّا، فلو وُجدت أيّ فروقات كبيرة بين عمودين لأشار ذلك إلى وجود مُشكلات، كأن تكون الصّفحة الرئيسيّة مُعقّدة فلا يصل الزّوار إلى ما بعدها. الجأ إلى اختبارات أ/ب في حالات كهذه إن لم تكن المُشكلة واضحة، وهي موضوع فصلنا القادم! ترجمة بتصرّف للدّرس Graph Shapes من سلسلة Daily UX Crash Course لصاحبها Joel Marsh.
  15. الآن وقد تعلّمنا كيف نُجرِي أبحاث المُستخدمين، ونحدّد أهدافنا، ونضع هندسة المعلومات، ونلفت انتباه المُستخدمين، ونضع الرّسوم التّخطيطيّة، ونفهم عقليّة المُستخدم، حان وقت إطلاق الخدمة! وهذا يعني أنّنا سنحتاج إلى إجراء بعض القياسات. فهرس سلسلة مدخل إلى تجربة المستخدم: مدخل إلى تجربة المستخدم User Experience فهم ودراسة المستخدمين في مجال تجربة المستخدم دراسة الشريحة المستهدفة في مجال تجربة المستخدم كيفية التصميم للأجهزة المختلفة هندسة المعلومات في تجربة المستخدم تعرف على أنماط التصميم في مجال تجربة المستخدم أشياء لا يمكن اعتبارها رسوما تخطيطية (Wireframes) في مجال تجربة المستخدم تعرف على الرسوم التخطيطية (Wireframes) في مجال تجربة المستخدم مفهوم الثقل المرئي (Visual Weight) والألوان في مجال تجربة المستخدم التكرار ومخالفة الأنماط في مجال تجربة المستخدم المحاذاة والقرب في مجال تجربة المستخدم تعرف على أساليب مسح الواجهة والتراتب المرئي في مجال تجربة المستخدم أساليب الإطلاع في مجال تجربة المستخدم: التصفح، البحث والاكتشاف تصميم هيكل صفحة الويب والعناصر الأساسية في مجال تجربة المستخدم الأزرار، النماذج والدعوات إلى الإجراء في مجال تجربة المستخدم استخدام علم النفس في مجال تجربة المستخدم لتكييف المستخدم وإقناعه كيف تغير الخبرة من تجربة المستخدم؟ تصميم تجربة المستخدم من خلال بيانات وإحصائيات المستخدمين (هذا الدرس) تعرف على أنواع المخططات الإحصائية في مجال تجربة المستخدم اختبارات أ/ب (A/B Test) في مجال تجربة المستخدم البيانات موضوعية تحدّثنا في الدّروس الأولى من السّلسلة عن أبحاث المُستخدمين. البيانات مختلفة عن الأبحاث، فهي تقيس سلوك المُستخدم، أي ما يفعله، وكم مرّة، وكم دام هذا الفعل، وهكذا... تُجمع البيانات بواسطة حاسوب، فهي حياديّة لا تؤثّر في المُستخدم، وهامش الخطأ فيها صغير لأنّها ذات مقاييس مُعرَّفة بدقّة؛ ويمكن أن نقيس بها سلوك ملايين النّاس دون عناء، ويمكن أن تخبرنا بمتصفّح المُستخدم وبلده. البيانات لا تكذب، فهي علمٌ. لكنّها أيضًا لا تخبر شيئًا عن السياق، لذا كُن حذرًا. للأسف يقع عاتق تفسير هذه البيانات علينا نحن المُصمّمين، وهنا تقع الأخطاء. البيانات قائمة على الناس سيُغريك قول أنّ البيانات "ليست إلّا أرقامًا مُجرَّدة"، وهذا يعني أنّك ستفسرها كما تشاء. تذكّر أنّ هذه الأرقام تُمثّل أفعال أناسٍ حقيقيّن ذوي حياة مُعقّدة. لا تختزل ملايين النّاس في عدد مُفرد وتتوقّع أن تعتمد عليها في كلّ موقف. قد يغريك أيضًا أن تبحث عن أرقام "تُثبت" رأيك، لا تفعل ذلك! ولا تسمح لأحد أن يطلب منك ذلك! كلما زادت البيانات كانت أفضل إن قست نقرات 5 أشخاص، فلن تكون بياناتك تمثيليّة بما يكفي، أمّا إن قست بيانات 5 ملايين زائر، فلا بدّ أنّها تغطّي شرائح كبيرة من الزّوار. كلّما زادت أهمّيّة القرار الّذي تريد بناءه على أساس البيانات، احتجت إلى بيانات أكبر قبل اتّخاذه. طرق جمع بيانات موضوعية تتوفّر طرق كثير لجمع بيانات موضوعيّة: التحليلات (Analytics) توفّرها Google وغيرها بأسعار رخيصة أو مجّانًا، وتسمح بمتابعة ما يفعله المُستخدمون دون كشف هويّتهم. كلّما نقر المُستخدم زرًّا أو انتقل إلى صفحة جديدة، سيظهر لديك ذلك، ويمكنك تصميم قياسات خاصّة بك، ولا حدود لإمكانيّاتها! اختبارات أ/ب صمّم إصدارين من عنصر ما وأطلقهما معًا، ستعلم أيّ الاثنين أفضل، لأنّك تُجرّبهما على أناس حقيقيّن في فترة مناسبة، سيُخبرك البرنامج متى توقف الاختبار، لأنّه زيادة عدد الخاضعين للاختبار بعد حدّ معين أمرٌ غير مُجدٍ. متابعة العين يُجرى هذا الاختبار في مُختبر خاصّ، ولكن لا يستطيع المُستخدم التّحكّم به، لذا يُعتبر موضوعيًّا، وتُستخدم فيه برامج وأدوات خاصّة لقياس موضع نظر المُستخدمين أثناء استخدامهم لتصميم، لتستفيد من ذلك في معرفة سلامة الأساليب الّتي استخدمتها للفت نظرهم. ClickTale هذا مثال عن استخدام الخرائط الحراريّة لمتابعة النّقرات وتمرير الصّفحات والانتقال بينها، ولكنّ هناك أمثلة أخرى. يسمح برنامج ClickTale بجمع بيانات استخدام الواجهة للمُستخدمين الحقيقيّين دون كشف هوّيتهم، وبطريقة مخفيّة، ويسمح لك بمشاهدة مواضع نقراتهم وتحرّك مؤشّر الفأرة ومدى تمرير الصّفحة، وأيّ الصّفحات شاهدوها، وهو غايةٌ في الفائدة. سجلات البحث لا يُدرك كثيرٌ من النّاس أن حقل البحث في موقعك يمكنه أن يحتفظ بكلّ كلمة تُكتب فيها، فلو كان المُستخدمون يبحثون عن شيءٍ ما، فهذا يعني أنّهم لا يجدونه، وعندها تكون سجلّات البحث قيّمة جدًّا لتحسين هندسة معلوماتك وتخطيط الواجهة. التصميم اعتمادا على البيانات المجموعة إن كنت تُخطّط لتحليل استخدام موقعك، فلن يطول الأمر قبل أن تحتاج إلى فهم ما تعنيه إحصاءات Google، وتُجرى دراسة هذه الإحصاءات بأسلوب مُختلف عن خبراء التّسويق. هناك 7 إحصاءات تحتاج لفهمها قبل فعل أيّ شيء، لا أقصد فهم ما تقيسه فقط، بل ما تعنيه أيضًا. لا يمكن أن نقول عن الأرقام أنّها "جيّدة" أو "سيّئة"، فالأمر نسبيّ، ومليون مستخدم لا تعتبر جيّدة إلّا إن كانت أعلى من الشّهر الماضي، لو كنت فيسبوك وكان زوّارك هذا الشّهر مليونًا، فأنت في ورطة. قبل تحليل أي رقم، فكّر بما يجب أن يفعله موقعك، وافهم ما يجب أن تُشير إليه الأرقام عن سلوك المُستخدمين. الجلسات (الزيارات الكلية) والمستخدمون (الزيارات الفريدة) عبارة الزّيارات الكلّيّة تعني مجموع عدد زيارات الموقع (يا للمفاجأة!) أمّا الزّيارات الفريدة (Unique Visits) فهي مختلفة، فلو زرتُ أنا موقعك 50 مرّة، لاحتُسبتُ مُستخدمًا فريدًا واحدًا (unique visitor)، وللدّقّة، فإنّها من النّاحية التّقنيّة تقيس الأجهزة الفريدة وليس النّاس. معنى كلّ منهما: مقارنة هذين الرّقمين سيُقودك إلى استنتاج بعض الحقائق عن الزّيارات: جودة عالية: الكلّيّة أكبر بكثير من الفريدة. كميّة عالية: الكلّيّة مساوية تقريبًا للفريدة، والفريدة أكبر من الشّهر الماضي. كلاهما: الفريدة أعلى من الشّهر الماضي والكلّيّة أعلى بكثير من الفريدة. ليس أيّ منهما: الفريدة أقلّ من الشّهر الماضي والكُلّيّة مساويةً تقريبًا للفريدة. الزّيارات الفريدة تمثيل أكثر صدقًا للزّيارات، ولكنّني أفضّل أن يزور موقعي 1000 شخص كلّ يوم على أن يزوره 10 آلاف شخص مرّةً في الشّهر، ولكن مع ذلك، إن زار شخصٌ واحد موقعي مليون مرّة، فلن يكون هذا مُفيدًا، وربّما يُعاني هذا الشّخص من مشكلة ما! عدد مرات مشاهدة الصفحات (Pageviews) ما تقيسه: تزداد بمقدار 1 في كلّ مرّة يحمّل فيها أيّ زائر أيّ صفحة. ما تعنيه: يمكنك اعتبارها "مؤشِّرًا عامًّا" على الزّيارات، لأنّها تصف المقدار الكليّ للمحتوى المُشاهدة وتتجاهل معظم العوامل الأخرى. لو كان موقعك يعتمد على الإعلانات في أعلى الصّفحات فهذا رقم مهمّ. لو كان موقعك مُعتمدًا على المحتوى، كالأخبار، فقد تكون زيادة هذا الرّقم أكثر أهمّية. معدل عدد الصفحات في كل جلسة (Pages-per-Visit) ما يقيسه: متوسّط عدد الصّفحات الّتي يُشاهدها كلّ زائر، في كلّ زيارة، يمكن اعتبارها عدد "النقرات" في كلّ زيارة (ولكن هذا غير دقيق من النّاحية التّقنيّة). ما يعنيه: إن كان موقعك مُركّزًا على المّهامّ أو التّفاعلات الاجتماعيّة، فقد يكون هذا الرّقم أهمّ من مرّات مشاهدة الصّفحات (pageviews). خلافًا لذلك، إن كنت مُحرَّك البحث Google، فسترغب في تخفيض هذا الرّقم قدر الإمكان، لأنّ نتائج البحث الأكثر جودةً يجب أن تكون في الصّفحة الأولى. متوسط مدة الجلسة (Time-per-Visit) ما يقيسه: مدّة كلّ زيارة المتوسّط، قد تكون مقارنته مع الصّفحات/الجلسة مهمّة جدًا. ما يعنيه: في عالم مثالي، من المُفترض أن يقرأ الزّائر المقال بكامله في الموقع المُعتمد على المُحتوى، وأن يقرأ مقالات كثيرة، وهذا يعني رقمين كبيرين لكلّ من "متوسّط مدّة الجلسة" و"الصّفحات/الجلسة". لو كان الرّقم "الصّفحات/الجلسة" كبيرًا والآخر ("متوسّط مدّة الجلسة") صغيرًا، فقد يعني هذا أنّ الزّوّار يبحثون عن شيء ما ولا يجدون (وهذا سيّئ) أو أنّهم يُنجزون مهمّاتهم بسرعة شديدة (وهذا جيّد)، فالأمر نسبيّ كما ترى. لو كان "متوسّط مدّة الجلسة" كبيرًا والآخر ("متوسط عدد الصّفحات في كل جلسة") صغيرًا، فقد تكون عناصر التّنقّل في الموقع غير فعّالة (وهذا سيّئ)، أو أنّ المقالات طويلة والمُستخدمون مُهتمّون بقراءتها (وهذا جيّد). لو كان الرّقمان مُنخفضين فهذا مؤشّر سيّئ، إلّا إن كان هدف موقعك هو الدّخول والخروج بسرعة، مثل Google. معدل الارتداد (Bounce Rate) ما يقيسه: الزّوّار الّذين يُشاهدون صفحة واحدة ويُغادرون دون أن ينقروا أيّ شيء. ما يعنيه: بشكل عامّ يُعتبر هذا الرّقم رفضًا من الزّوّار لموقعك، ولكن هناك بعض الاستثناءات. تميل المُدوّنات إلى معدّل ارتداد عالٍ لأنّها مُصمّمة لمُشاهدة صفحة واحدة: إمّا مشاهدة صفحة آخر التّدوينات أو زيارة تدوينة مُعيّنة. يمكن أن يتأثر مُعدّل الارتداد بشدّة بِبُنية موقعك ومصدر زياراتك، فحتّى إن بدا الرّقم بسيطًا، فهو مؤشّر مُعقَّد. النسبة المئوية للزيارات الجديدة (New vs. Return Visitors) ما يقيسه: لو أنّ زائرًا (أو جهازًا) زار موقعك من قبل، فإنّه يُعتبر "عائدًا" (returning)، وإلّا فهو جديد. ما يعنيه: يعرف العائدون موقعك أكثر، ولهذا "يرتدّون" أقل ويشاهدون صفحات أكثر، فإن كانوا يعودون فهذا لأنّه ما تقدّمه يُعجبهم، ولهذا يقضون وقتًا أطول عادةً. أمّا الزّوّار الجدد فهم مؤشّر جيّد، لأنّ ذلك يعني أنّ موقعك يصل إلى أناس أكثر. الفكرة الأساسيّة هي نسبة الجُدد إلى العائدين، فلو لم يكن لديك زوّار عائدون فهذا يعني أنّ موقعك جديد، أو سيّئ، ولو لم يكن لديك إلّا زوّار عائدون فهذا يعني أنّ مُستخدميك مُخلصون، ولكنّ الموقع يحتضر. بشكل عامّ، كلّما كان الموقع أكثر "نُضجًا"، سيكون العائدون أكثر (كنسبة مئويّة)، لأنّ العائدين إلى موقعك بصورة مُتكرّرة أفشل من الزّيارات من خلال مُحرّكات البحث والحملات الإعلانيّة. ترجمة بتصرّف للدّرسين What is Data و Summary Statistics من سلسلة Daily UX Crash Course لصاحبها Joel Marsh.
  16. هذا هو الجزء الثاني من درس علم نفس المُستخدمين، وفيه سنُلقي نظرة على الاختلاف بين المُستخدمين الجُدد والخبراء في تعاملهم مع تصميمك. فهرس سلسلة مدخل إلى تجربة المستخدم: مدخل إلى تجربة المستخدم User Experience فهم ودراسة المستخدمين في مجال تجربة المستخدم دراسة الشريحة المستهدفة في مجال تجربة المستخدم كيفية التصميم للأجهزة المختلفة هندسة المعلومات في تجربة المستخدم تعرف على أنماط التصميم في مجال تجربة المستخدم أشياء لا يمكن اعتبارها رسوما تخطيطية (Wireframes) في مجال تجربة المستخدم تعرف على الرسوم التخطيطية (Wireframes) في مجال تجربة المستخدم مفهوم الثقل المرئي (Visual Weight) والألوان في مجال تجربة المستخدم التكرار ومخالفة الأنماط في مجال تجربة المستخدم المحاذاة والقرب في مجال تجربة المستخدم تعرف على أساليب مسح الواجهة والتراتب المرئي في مجال تجربة المستخدم أساليب الإطلاع في مجال تجربة المستخدم: التصفح، البحث والاكتشاف تصميم هيكل صفحة الويب والعناصر الأساسية في مجال تجربة المستخدم الأزرار، النماذج والدعوات إلى الإجراء في مجال تجربة المستخدم استخدام علم النفس في مجال تجربة المستخدم لتكييف المستخدم وإقناعه كيف تغير الخبرة من تجربة المستخدم؟ (هذا الدرس) تصميم تجربة المستخدم من خلال بيانات وإحصائيات المستخدمين تعرف على أنواع المخططات الإحصائية في مجال تجربة المستخدم اختبارات أ/ب (A/B Test) في مجال تجربة المستخدم الخبراء هم الأقلية يستحيل من النّاحية الإحصائيّة أن يكون المُستخدمون الخبراء هم غالبيّة مُستخدميك، مع أنّه قد يحلو لك هذا التّفكير. معظم المُستخدمين القادمين إلى خدمتك، ما لم تكن ذات طابع تقنيّ، سيكونون عاديين، مشغولين بأعمال أخرى، ليسوا ملمّين بالتّفاصيل التّقنيّة مثلك أنت ومثل زملائك في العمل. الحقيقة المُرَّة: إن أردت ملايين المُستخدمين الراضين، فعليك التّصميم بما يراعي هذا النّمط من المُستخدمين، وليس أولئك المُهووسين العباقرة! الإخفاء والإظهار: مفارقة الاختيار ستمرّ عليك مواقف تضطّر فيها إلى اختيار مدى "نظافة" واجهتك. سيختار المُصمّمون عادةً إخفاء كلّ شيء لأنّ ذلك يبدو أجمل، بينما يريد غيرهم إظهار ميزاتهم المُفضّلة كل الوقت (وهذه مختلفة من شخص لآخر)؛ فماذا تختار؟ ستكون الميّزات الظّاهرة أكثر استعمالًا دومًا من تلك المخفيّة، فنحن نتذكّر وجودها كلّما رأيناها. إلّا أنّ "مفارقة الاختيار" تنصّ على أنّه كلّما كثرت الخيارات، قل احتمال اعتماد أحدها، فإذا أغرقت مستخدميك العاديّين بالخيارات، فسيشعرون بالحيرة ويغادرون الموقع. تأكّد من أن المُبتدئين بإمكانهم إيجاد الميّزات الأساسيّة بسهولة، دون الحاجة إلى نقر أيّ شيء في الحالة المثاليّة، ثمّ حاول أن توفّر وصولًا سهلًا لكامل الميّزات تلبيةً لرغبة المُستخدمين المُتقدّمين، حتّى وإن لم تكن ظاهرة طوال الوقت. نصيحة: هل أنت راضٍ عن إخفائك خيارات مُشاركة الشّبكات الاجتماعيّة وراء زرّ مشاركة واحد؟ للأسف هذه الواجهة ليست بسيطة، فلقد عطّلت خيارات المُشاركة لأنّك أوّلًا أضفت خيارات كثيرة، ثمّ أخفيتها. اعرض خيارات أقلّ، واجعلها ظاهرة طوال الوقت، ستشكرني لاحقًا. التعرف مقابل الذاكرة كم أيقونة يمكنك أن تسردها من ذاكرتك الآن؟ كم أيقونة يمكنك أن تتعرّفها إن أعطيتك قائمة بها؟ إن كنت إنسانًا عاديًّا، فستكون الإجابة الثّانية أكبر بكثير من الأولى. إن صمّمت واجهتك بحيث يحتاج النّاس إلى السؤال عن شيء ما (كالبحث مثلًا)، فلن يستخدموا إلا ما يمكنهم تذكّره، وهذا يعني ميّزات أقل فأقلّ مع مرور الوقت. إن كان على مستخدميك التّعامل مع قدر كبير من المعلومات، فقدّم لهم اقتراحات بفئات أو شيئًا مشابهًا يُساعدهم أن يتذكّروا أين يجدون ما يريدون. التعلم بطيء، العادات سريعة يُستخدم المُصطلح "Onboarding" لوصف التّعليمات الّتي نعرضها للمُستخدمين على شكل خطوات عند بداية استخدام الواجهة، وهي تساعد في إيجاد الميّزات الرئيسيّة وتجنّب الحيرة. لكن ما الّذي يحدث إذا استمرّ استخدامهم طيلة عامين؟ تُخلق العادات في أذهان المُستخدمين بسرعة شديدة، ولهذا عليك تصميم "طريقة سريعة" لتأدية المّهام الأساسيّة، والّتي قد تكون غير واضحة، وسينفق المُستخدمون المُتقدّمون بعض وقتهم لتعلّمها مقابل حصولهم على مستوى إنتاجيّة أعلى، اختصارات لوحة المفاتيح والقائمة الّتي تظهر بالنّقر بزرّ الفأرة الأيمن مثالان على ذلك، وحيلة "نقطة متبوعة بـ@" في تويتر مثال آخر. سنتحدّث في الدّرس القادم عن البيانات والإحصاءات واستخداماتها في تصميم تجربة المُستخدم. ترجمة بتصرّف للدّرس User Psychology How Experience Changes Experience من سلسلة Daily UX Crash Course لصاحبها Joel Marsh.
  17. قد تكون مهارة التصميم لُبّ تجربة المُستخدم، ولكن عليك أيضًا أن تفهم كيف يفكّر النّاس لتكون مصمّمًا ناجحًا، ولهذا سيكون حديثنا اليوم عن علم نفس المُستخدمين. فهرس سلسلة مدخل إلى تجربة المستخدم: مدخل إلى تجربة المستخدم User Experience فهم ودراسة المستخدمين في مجال تجربة المستخدم دراسة الشريحة المستهدفة في مجال تجربة المستخدم كيفية التصميم للأجهزة المختلفة هندسة المعلومات في تجربة المستخدم تعرف على أنماط التصميم في مجال تجربة المستخدم أشياء لا يمكن اعتبارها رسوما تخطيطية (Wireframes) في مجال تجربة المستخدم تعرف على الرسوم التخطيطية (Wireframes) في مجال تجربة المستخدم مفهوم الثقل المرئي (Visual Weight) والألوان في مجال تجربة المستخدم التكرار ومخالفة الأنماط في مجال تجربة المستخدم المحاذاة والقرب في مجال تجربة المستخدم تعرف على أساليب مسح الواجهة والتراتب المرئي في مجال تجربة المستخدم أساليب الإطلاع في مجال تجربة المستخدم: التصفح، البحث والاكتشاف تصميم هيكل صفحة الويب والعناصر الأساسية في مجال تجربة المستخدم الأزرار، النماذج والدعوات إلى الإجراء في مجال تجربة المستخدم استخدام علم النفس في مجال تجربة المستخدم لتكييف المستخدم وإقناعه (هذا الدرس) كيف تغير الخبرة من تجربة المستخدم؟ تصميم تجربة المستخدم من خلال بيانات وإحصائيات المستخدمين تعرف على أنواع المخططات الإحصائية في مجال تجربة المستخدم اختبارات أ/ب (A/B Test) في مجال تجربة المستخدم التكييف (Conditioning) إن كنت سمعت من قبل بتجربة بافلوف، فلا بد أن الفكرة مألوفة لك في سياقها العلميّ. الفكرة تنطبق على الحيوانات وكذلك على البشر. التكييف يعني أنّ الإنسان سيعيد فعل شيء ما ثانيةً عندما يتلقّى مُكافأة، وسيتفاداه عندما يتلقّى عقابًا. قد يبدو هذا واضحًا، لكن 99% من المصمّمين الّذي أعرفهم يتجاهلونه في عملهم، مع أنّه الطّريقة الوحيدة لجعل التّصميم مُسبّبًا للإدمان إن صحّ التعبير. لاحظ أنّنا نتحدّث عن مشاعر المكافأة والعقاب، لا الأشياء المادّيّة. عندما نقول "مكافأة المستخدم"، يتبادر لذهن معظم السّامعين أنّنا نقصد فرصة ربح iPhone أو تذاكر لمشاهدة فيلم أو ما شابه ذلك. نحن نتحدّث عن ملايين المُستخدمين هنا، فهذا غير عمليّ. المكافأة والعقاب الأكثر فاعليّة هما مجّانيّان، لأنّهما ببساطة شعور وليس شيئًا مادّيًّا. تخيّل معي أنّنا أتينا بك على منصّة وأخبرناك أمام خمسين ألفًا من الحاضرين أنّك من أفضل الأصدقاء ، وأنّ العالم أفضل بوجودك. هل سترغب في إعادة ذلك ثانية؟ ربّما. تخيّل الآن أنّنا أتينا بك على منصّة وأخبرنا الجميع أنّك أقلّ البشر خيرًا، بإجماع أصدقائك وعائلتك. هل سترغب في إعادة ذلك ثانية؟ لا، غالبًا! هذه أمثلة بعيدة عن الواقع قليلًا، ولكن لاحظ أنّنا لم نُعطك شيئًا ولم نحرمك شيئًا، بل الأمر تصوّر النّاس عنك فقط، والمشاعر الّتي يولّدها هذا التّصور قد تكون قويّة جدًّا. أنشئ حلقة دافع-نتيجة (Feedback Loop) إذًا كيف نستخدم التّكييف عمليًّا في التّصميم؟ الفكرة هي إنشاء حلقة غير مُنتهية من المشاعر والسلوكيّات، بحيث يصبح يُعطي شعور المكافأة بشكل مُستمرّ، هذا نموذج الحلقة: دافع > فعل > نتيجة > دافع لنقل مثًا أنّك أخذت صورة جميلة لطفلك، الآن لديك دافع لنشرها على فيسبوك بحيث يرى النّاس كم هو جميل طفلك، ولذلك تقوم بالفعل وتنشرها. يجب على فيسبوك تصميم طريقة لدفعك للقيام بهذا الفعل. بعد ذلك تتلقّى النّتيجة من أصدقائك الّذين يبدون إعجابهم بالصّورة ويكتبون تعليقات تُجاملك، بل ربّما تتلقّى رسالة بريد إلكتروني عن الموضوع. يجب على فيسبوك تصميم طريقة لتقديم هذه النّتيجة، والّتي بدورها تخلق دافعًا لنشر صورة أخرى مجدّدًا. هذه "الحلقة" ستستمر حتّى يتوقّف النّاس عن الإعجاب والتّعليق أو "يعاقبوك" بإبداء سخطهم على صورة شيطانك الصّغير هذا (سيناريو تخيليّ!) إذًا إليك الفكرة: إن صمّمت ميزة تمنح النّاس شعورًا إيجابيًّا، فسيعودون مرارًا لتنشيط هذا الشّعور، وإن كانت هذه الميزة تحقّق هدفك التّجاريّ، فقد صنعت مُنتجًا ناجحًا! كن حذرًا بخصوص العقاب: يجب على المُستخدم أن يحاول تفادي العقاب، فصمّم ميّزاتك على هذا النّحو، لا تحاول معاقبة المُستخدمين بإصرار، فهذا كفيل بفقدانهم، الحالة المثالية أن تجعل انقطاعهم عن فعل الأشياء الّتي تمنحهم شعورًا إيجابيًّا يؤدّي إلى انخفاض نقاطهم أو انتباههم أو مستوى الإنتاجيّة الّذي يريدونه. مثلًا: كانت هناك لعبة مزرعة (بدون ذكر أسماء!) فيها تصبح مزرعتك أكبر مع الوقت (مكافأة)، وإن توقّفت فترة طويلة عن اللّعب تبدأ محاصيلك بالجفاف والموت (عقاب)، ولكن يمكن أيضًا أن تدفع لتسريع الإنتاج وشراء أشياء جديدة لمزرعتك (مكافأة أكبر!) لا عجب أنّها من أنجح الألعاب في التّاريخ! احذر من تكيفك أنت! التكييف يطال الجميع، في كلّ مكان، ولكنّه مختلف النّوع من شخص لآخر، ولهذا لديك لونك المُفضّل، وتصميمك المُفضّل، ومأكولاتك المُفضّلة. لا تحسب أن الجميع يحبّون كلّ شيء تحبّه أنت! الإقناع (Persuasion) الإقناع موضوع مُعقّد. كتابي The Composite Persuasion يقع في 270 صفحة، يتحدّث عن كيف تجعل الأشياء مُقنعة، وهو على طوله ليس سوى "دورة مُكثّفة"! يمكن اعتبار هذا الجزء تتمّة لفقرة "الدّعوة إلى الإجراء" في الدّرس السّابق، كونه يعلّمك كيف تُنشئ نصوصًا ومقالات أكثر إقناعًا. إليك فكرتين أساسيّتين: للإقناع 8 مكوّنات عامّة، تكون أكثر فعاليّة عندما تُطبّق بترتيب مُحدّد، لأنّ كلّا منها يعتمد على ما سبقه. دوافع النّاس يمكن حصرها بـ14 دافعًا. سأشرح 4 منها وهي الأكثر شيوعًا في العالم الرقميّ. معادلة الإقناع بعد مقارنة 40 علمًا من أعلام الإقناع، وجدت أنّ أساليبهم تشترك في 8 صفات: قبل التفاعل السّمعة الطّيّبة: لن يفيدك شيء دون الثّقة، وفي الحالة المثالية عليك أن تبني سمعتك في الواقع، والنّقطة الأهم هي أن تتواصل مع جمهورك بأسلوب عالي القيمة، وفي عالم تجربة المُستخدم، ينطبق هذا على كلّ شيء، بدءًا من العلامة التّجاريّة الموثوقة، والصّدق في تسعير المُنتجات، وشهادات الزّبائن. لا تقل أنّ علامتك التّجاريّة مرموقة، بل أثبت ذلك بالأفعال. اعرف جمهورك: في عالم تجربة المُستخدم يعني هذا أن تُجري دراسات المُستخدمين لكي تعلم من تحاول إقناعهم وما اهتماماتهم. أثناء التفاعل كن مُنفتحًا وصريحًا: عليك أن تجذب انتباه المُستخدم مُباشرةً، ثمّ تتابع لتزيل أيّة اعتراضات واضحة قد تكون لديه، في عالم تجربة المُستخدم، قد يفيدك عنوان جميل أو صورة لافتة للنّظر فوق الطّيّة، لو كان السّعر موضع اعتراض، مثلًا، فليكن من المعلومات الأولى الّتي يمكن أن يراها المُستخدم، لا تفترض أنّهم سيتابعون القراءة حتى يصلوا إليه في النّهاية. عزّز شعور الألفة: اجعل المستخدم يألفك، مُستفيدًا بما يشترك فيه النّاس جميعًا، في عالم تجربة المستخدم، استخدم لغةً مألوفة، واعرض للزّائر ما يجمع بينه وبين زبائنك، أو اشرح الشّخص الرّئيسيّ في مقالك بطريقة تجعله قريبًا من المستخدم. ركّز على الهدف: عندما يصبح هدف المُستخدم واضحًا، استبعد أيّة معلومات قد تُشتّته، في عالم تجربة المستخدم قد يُفيدك إزالة القوائم والإعلانات خلال عمليّة الدّفع لكي لا تُشتّت المُستخدم عن الشّراء. أقنع: عندما تكون عمليّة الإقناع مُعقّدة، الجأ إلى دفع المعلومات على دفعات، من أبسطها إلى أعقدها، خطوةً بخطوة. هناك عدّة طرق لتحقيق ذلك، من بينها الانحياز المعرفيّ، والّذي يُساعد على تقديم المعلومات بصورة تجعلها أسهل قبولًا واستيعابًا. أتمّ الصّفقة: لا تقعّد الأمور عند انتهاء العمليّة، يكفي وضع زر "نشرّ" أو "تأكيد الشّراء" أو "المشاركة". بعد التفاعل لخّص الفكرة وأكّدها: لا تُنهِ عمليّة الإقناع بمجر انتهاء التّفاعل، بل اجعل النّاس يُشعرون بتقديرك لهم حتّى تحصل على ما تريد، في عالم تجربة المُستخدم، يمكن اللّجوء إلى إرسال رسالة بريد إلكتروني تذكّر المُستخدم بما يمكن فعله بمنتجه الجديد، أو قائمة بمقالات مُقترحَة، أو كم شخصًا أُعجب بمنشوره. الدوافع العامة هل سمعت بهرم ماسلو للحاجات الإنسانيّة؟ انسَه، فقد نسيه علماء النّفس منذ زمن، بينما ما يزال خبراء التّسويق يدرسونه في الجامعات! هناك 14 أمرًا يحتاجها الإنسان دومًا: تجنّب الموت، وتجنّب الألم، والهواء، والماء، والغذاء، والصّحّة البدنيّة، والنّوم، والجنس، والحب، وحماية الأبناء، والمكانة الاجتماعيّة، والانتساب، والعدالة، وفهم كلّ من هذه الأشياء بصورة أفضل. لكلّ هذه الأمور جمهور، وكلّ منها يُطلق ردود أفعال مختلفة الدّرجة، ولكن على الويب تكون المكانة الاجتماعيّة والانتساب والعدالة والفهم أكثرها فائدة، لأنّها مجرّد أفكار، كما أنّها غير محدودة، وبإمكانك خلقها من الصّفر، مجّانًا. المكانة الاجتماعيّة: هي المكوّن الرئيسيّة في عمليّة تقديم المنتج بشكل لعبة (gamification)، فهي طريقة لقياس مستواك بالنّسبة للآخرين. عندما تُصمّم نظامًا يُقدّم نقاطًا وجوائز رمزيّة وما شابهها، فإنّك تتحكّم بإدراك المُستخدمين لمكانتهم، يمكن أن تكون هذه الأمور أوسمة، أو إعجابات، أو مراحل في لعبة Candy Crush! سيكون لدى المستخدمين دافع أقوى للتفوّق على بعضهم، وإذا استطعت ربط هذه الإنجازات بأهدافك التّجاريّة، فإنك ستجني الأموال دون كلفة، سوى تعزيز هذه المشاعر. الانتساب: إن كنت مُشجّعًا مُخلصًا لفريق رياضيّ أو علامة تجاريّة، فإنّك تشعر بالفخر لكونك جزءًا من مُنظّمة أو مجموعة. هذا هو الانتساب، سبب انضمام النّاس إلى مجموعات فيسبوك، أو لبسهم ثيابًا مُعيّنة، أو إجرائهم اختبارًا لمعرّفة "أيّ شخصيّة من المسلسل الفلانيّ أنت؟!"، فهم مدفوعون للانتماء إلى أشياء مُعيّنة. صمّم ملتقىً يعزّز انتماء النّاس، وشاهد كيف يتجمّع النّاس في مجموعات وفئات. العدالة: هي فكرة أن يتلقّى كلّ مرء ما يستحق، سواء كان ذلك عقابًا أم ثوابًا. صمّم طريقة يتلقّى فيها الانتباه من يستحقّه، أو لعبةً يحارب فيها المُستخدمون الشّر، وستجدهم يفعلون ذلك بالضّبط. الفهم: لدى النّاس دافع لفهم كلّ من الدّوافع السّابقة أكثر (وهم يستحقّون أن يفهموها). إن حاولت أن تغيّر شيئًا أنفق عليه النّاس أوقاتهم في تعلّمه، كتصميم واجهتك، فقد تغضبهم، هل تذكر الصّفحات الغاضبة عندما فعل فيسبوك ذلك؟ هذا ما أقصده! ملاحظة: هل لاحظت أنّ المال ليس من بين الدّوافع المذكورة؟ هذا لأنّه ليس دافعًا بحدّ ذاته، فلو كان كذلك لكنت مُتحمّسًا لجني مال تعلم أنّك لن تنفقه، ولكنّك لست كذلك. نحن نشعر بدافع لكسب المكانة الّتي يصنعها المال، حتّى لو كان ذلك مجرّد نقاط في لعبة لا قيمة لها في العالم المادّيّ. ابحث عن أمثلة من الواقع تدعم هذا الدّوافع. سنتعلّم في الدّرس القادم كيف تُغيّر الخبرة من تجربة المستخدم، بين المُبتدئ والخبير. ترجمة بتصرّف للدّرسين User Psychology Conditioning و User Psychology Persuasion من سلسلة Daily UX Crash Course لصاحبها Joel Marsh.
  18. لن يطول الأمر قبل أن تحتاج إلى وسيلة لجمع المعلومات من مستخدميك، ولهذا سنبدأ حديثنا اليوم بالنّماذج (Forms). فهرس سلسلة مدخل إلى تجربة المستخدم: مدخل إلى تجربة المستخدم User Experience فهم ودراسة المستخدمين في مجال تجربة المستخدم دراسة الشريحة المستهدفة في مجال تجربة المستخدم كيفية التصميم للأجهزة المختلفة هندسة المعلومات في تجربة المستخدم تعرف على أنماط التصميم في مجال تجربة المستخدم أشياء لا يمكن اعتبارها رسوما تخطيطية (Wireframes) في مجال تجربة المستخدم تعرف على الرسوم التخطيطية (Wireframes) في مجال تجربة المستخدم مفهوم الثقل المرئي (Visual Weight) والألوان في مجال تجربة المستخدم التكرار ومخالفة الأنماط في مجال تجربة المستخدم المحاذاة والقرب في مجال تجربة المستخدم تعرف على أساليب مسح الواجهة والتراتب المرئي في مجال تجربة المستخدم أساليب الإطلاع في مجال تجربة المستخدم: التصفح، البحث والاكتشاف تصميم هيكل صفحة الويب والعناصر الأساسية في مجال تجربة المستخدم الأزرار، النماذج والدعوات إلى الإجراء في مجال تجربة المستخدم (هذا الدرس) استخدام علم النفس في مجال تجربة المستخدم لتكييف المستخدم وإقناعه كيف تغير الخبرة من تجربة المستخدم؟ تصميم تجربة المستخدم من خلال بيانات وإحصائيات المستخدمين تعرف على أنواع المخططات الإحصائية في مجال تجربة المستخدم اختبارات أ/ب (A/B Test) في مجال تجربة المستخدم النماذج سيتطلّب تصميم النّماذج وقتًا طويلًا، معظمه في الاهتمام بقابليّة استخدامها، فعادةً ما تسبّب النّماذج حيرة المُستخدم أو أنّه يُخطئ في استخدامها أو لا يستخدمها نهائيًّا، ومع ذلك تبقى النّماذج من أكثر أجزاء الموقع قيمة. إن لم تكن النّماذج أكثر أجزاء موقعك قيمة، فلم تستخدمها؟ هل نسيت أنّها تسبّب حيرة المُستخدم أو خطأه أو أنّه لا يستخدمها؟ صفحة واحدة طويلة أم عدة صفحات قصيرة؟ من أكثر الأسئلة الّتي يُكرّرها المُصمّمون والمُسوّقون: ما الحدّ الّذي تُعتبر بعده النّماذج طويلة جدًّا؟ كقاعدة عامّة حاول جعل النّماذج أقصر ما يمكن، ولكن لا تتردّد في تقسيمها إلى صفحات إن كان ذلك منطقيًّا، أو إن احتجت إلى حفظ المعلومات في خطوات، إن كنت تتوقّع أن يغادر المُستخدم الموقع أثناء ملء النّموذج. الأهمّ من ذلك أن يبدو الحقل بسيطًا، اجعل الأسئلة المرتبطة متقاربة، استبعد الأسئلة الّتي لا تحتاجها حقًّا، واستخدم بالضّبط عدد الصّفحات الذي تحتاجه (لا أكثر ولا أقل). أنواع الحقول غرض النّموذج هو الحصول على المعلومات من المستخدم، وهناك عدّة طرق لجمع هذه المعلومات، فاستخدم نوع الحقل الّذي يُعطيك أكثر المعلومات فائدة، سواء كان ذلك حقلًا نصيًّا عاديًّا أم علامة مُنزلقة (slider). لنقل أنّك تريد للمستخدم اختيار أنواعه المُفضّلة من الماعز (!)، يصلح هنا استخدام مربّعات الاختيار الواحد (Radio Buttons) أو المُتعدّد (Check Boxes)، فإذا كنت تريد إجابة أكثر اكتمالًا، فاختر الثّانية، وإلّا فاختر الأولى لأنها تُعطي نتيجة أكثر انتقائيّة. مسميات الحقول وتعليمات استخدام النموذج سنبدأ الحديث عن وظيفة المُسمّيات (labels)، فوظيفتها هي شرح ما يجب فعله في كلّ حقل في النّموذج، وعليك أن تجعلها قصيرة وواضحة وسهلة القراءة، وقريبة من الحقل المعنيّ، وهذا كفيل بحلّ 99% من مشاكل الحقول. قد تحتاج أحيانًا إلى إضافة تعليمات عن السؤال إن كان مُعقّدًا أو غير تقليديّ، أضف هنا شرحًا قصيرًا قرب الحقل إن كان بضع كلمات فقط، أو أضفه إلى جانب النّموذج بدلًا من كونه داخله إن كان أطول من ذلك، لكي لا يقطع سير عمليّة ملء الحقل على المُستخدمين الّذين يعرفون ما وظيفة الحقل. أنصحك بقراءة الكتاب Web Form Design لمؤلّفه Luke Wroblewski. التعامل مع أخطاء المستخدم ومنعها كثيرًا ما يُخطئ المُستخدمون في ملء النّماذج، ووظيفتنا منع ذلك ما أمكن، والتّعامل مع ما لا يمكن منعه بمرونة. يمكن منع الخطأ بجعل الحقول "ذكيّة"، فلو كان الحقل مُخصّصًا لرقم الهاتف، فاجعله ذكيًا بحيث يتعامل مع تنسيق رقم الهاتف في البلد المعنيّ (هذا يتطلّب تعاون المُطوّرين). يمكن أيضًا تفادي الأخطاء بإضافة بعض الأمثلة على تنسيق المعلومات المطلوب ضمن الحقل نفسه أو ضمن التّعليمات المُرافقة. عندما ينسى المُستخدم ملء حقل ما أو يخطئ، ينبغي عليك تنبيهه، وذلك بعرض علامة X مثلًا بجواره إن كان خاطئًا أو علامة "صح" إن كان صحيحًا، وهذا ما يُسمّى التّعامل مع الأخطاء في موضعها (inline error handling). تُستخدم هذه التقنيّة أيضًا في حقول كلمات المرور لبيان مدى قوّتها أثناء إدخالها. لا تستخدم هذه التّقنيّة إن لم يكن بإمكانك التّحقق من صحة البيانات، كما في حقل الاسم الكامل. عندما ينقر المُستخدم "التّالي" أو "تمّ"، تحقّق من كامل النّموذج بحثًا عن الأسئلة الّتي نسيها أو أخطأ فيها، اعرض المُشكلة بوضوح شديد وبيّن له سببها. نصيحة: تأكّد من أنّ بإمكان المُستخدم رؤية الخطأ من أسفل النّموذج، فلو كان عليه أن يُمرّر لأعلى ليُلاحظ الخطأ، فلن يفعل! السرعة مقابل الأخطاء هذه النّقطة متقدّمة بعض الشيء، لكنّها مفيدة للغاية. إذا كنت تطرح أسئلة تقليديّة على المُستخدم مثل اسمه وعنوان بريده الإلكترونيّ، فاجعل مُسمّيات الحقول في أعلى ويمين الحقل، فهذا يُسرّع إدخال المعلومات، لأنّه يُبقي كلّ شيء على محور التّفاعل. أمّا إن كانت الأسئلة مُعقّدة أو غير شائعة، فاجعل المُسمّيات على يمين كل حقل في الصّف ذاته، فهذا يجعل المُستخدم يتمهّل قليلًا في إدخال البيانات، ويخفّض احتمال الخطأ. اجعل زرّ "تمّ" على يسار (على يمين، إذا كان النموذج بالعربية) محور التّفاعل . إن كان الحقل يؤدّي إلى حذف شيء ما أو فقد بيانات قد تكون مهمّة، فاجعله على يمينه (أو على يساره، إن كان النّموذج بالعربية)، بحيث يتوقّف النّاس بحثًا عنه بدل نقره بطريقة لا شعورّية. الدعوات إلى الإجراء والتعليمات والمسميات هناك 4 مواضع يمكن أن يتدخّل فيها مُصمّم تجربة المُستخدم ليُبدي رأيه في الجمل المُستخدمة للتّواصل مع المُستخدمين، وأمّا في ما سوى ذلك، فمن الأفضل أن يُترك هذا الشأن لكتُّاب المُحتوى: الدّعوات إلى الإجراء (calls to action) التعليمات (instructions) المُسمّيات (labels) الشّروح الطّويلة الّتي تهدف لإقناع المُستخدم سنشرح في هذا الدّرس النّقاط الثلاث الأولى، أمّا الأخيرة فستكون في درس منفصل. الدعوات إلى الإجراء (Calls-to-Action) ويُقصد بها العناوين والنّصوص الّتي تكون بجانب الأزرار، وتدعو المُستخدم لفعل شيء ما، مثل "نزّل التّطبيق الآن!" أو "احصل على المميّزات المدفوعة مجّانًا!" أو ما شابهها، وقد يُفاجئك مدى التغيير الّذي تُحدثه العبارات المُتقنة الأسلوب في حالات كهذه. المُعادلة العامّة لعبارة ترويج جيّدة: فعل + فائدة + أجل/مكان قريب الفعل: ما تريد من المُستخدم فعله. الفائدة: ما سيحصل عليه المُستخدم (إن لم يفي الفعل بالمعنى) الأجل/المكان القريب: مدى زمنيّ مثل "الآن" أو مكان مثل "هنا"، الكلمة "مجانًا" قد تُعطي إحساسًا بالعجلة إن كان ذلك يُناسبك. التعليمات (Instructions) إن لم يكن واضحًا تمامًا ما يجب على المُستخدم فعله (وحتى وإن كان واضحًا) فقد ترغب بمساعدته، تحدّثنا في فقرات سابقة عن النّماذج وكيفيّة كتابة التّعليمات، فهي أكثر العناصر احتياجًا لها. يجب أن تكون التّعليمات قصيرة ومباشرة وحرفيّة، لا داعي لاستخدام مُصطلحات جزلة، أو كلمات تقنيّة، لا داعي للتّذاكي أو الاستهزاء أو المُزاح. أخبر المستخدم ما عليه فعله بالضّبط بأبسط الكلمات والعبارات، حدّثه وكأنّه طفل ذكيّ، أو كأنّه حديث عهد باللّغة، لا أقصد أن تكون العبارة غبيّة، بل واضحة. مثال عن جملة سيئة: "حلّق بفأرتك فوق الزّر الأصفر فور انتهائك من العمل!" مثال عن جملة سيّئة أيضًا: "كل المُدخلات في هذه المنطقة هي بيانات مطلوبة ويجب أن تُرسل بنجاح لبدء عمليّة إنشاء الحساب." مثال عن جملة غبيّة: "عليك أن تفخر بنفسك! فأنت بارعٌ في ملء النّماذج! حالما تنتهي من ملء هذه النّماذج، فعليك المتابعة إلى الزّر الأصفر أدناه ونقره، كدت تصل أيّها البطل!" مثال عن جملة جيّدة: "أجب على كلّ الأسئلة ثمّ انقر على الزّر الأصفر المُسمّى تمّ في نهاية هذه الصّفحة". المسميات (Labels) قد يكون مُغريًا جدًّا جعل المُسمّيات مُميّزة أو ذكيّة، لكن عليك أن تقاوم هذا الإغراء دومًا. استخدم الشّكل الأكثر شيوعًا وبساطة وسهولة من المُسمّى، لو كان المُسمّى يؤدّي إلى أكثر من نوع من الإجابات، فهو غير واضح. مثال عن مُسمّى سيّئ: "حيث يهفو القلب..." مثال عن مُسمّى أقل سوءًا: "مكان معيشتك" مثال عن مُسمّى أفضل: "العنوان" مثال عن المُسمّى الأفضل: "عنوان المنزل" تنطبق هذه القواعد على الأزرار أيضًا، وهو شيء يتجاهله كثيرٌ من المُصمّمين. إن تجاهلت العناوين والتّعليمات، هل يمكنك فهم وظيفة الأزرار؟ إن لم يكن الحال كذلك، فعليك تحسين المُسمّيات. أمثلة عن مُسمّيات سيّئة للأزرار: "نعم" و"موافق" أمثلة عن مُسمّيات جيّدة للأزرار: "حذف الحساب" و"حفظ التّغييرات" قد يبدو الحديث سهلًا، لكنّ الحياة العملية ستضعك في موقف ستضطّر فيه أن تقول لا للزّبون أو زملاء العمل عندما يطلبون جعل العبارات "أكثر روعة"! أثبت حجّتك باختبارات أ/ب إن اضطرت، ولكن لا تتراجع عن رأيك، فأحيانًا ما يحتاجه المستخدم هو عبارات بسيطة وواضحة، وليست "رائعة ومميّزة". الأزرار الرئيسية والثانوية كقاعدة عامّة ستحتاج نوعين من الأزرار فقط، لأنّ معظم الأفعال تقع في فئتين: أفعال رئيسيّة تخدم هدفك المطلوب أفعال ثانويّة لا تخدم الهدف الأزرار الرئيسية بعض الأفعال المُتاحة للمُستخدم تكون "مُنتجَة"، كالتّسجيل في الموقع أو الشّراء أو إرسال محتوى أو حفظه أو مُشاركته... فهي تُنتج أشياء لم تُوجد من قبل، وهذه نُسمّيها الأفعال الرئيسيّة (primary actions)، وهي ما نريد للمُستخدم فعله أكثر ما يمكن. يجب أن تكون الأزرار الّتي تؤدّي إلى أفعال رئيسيّة ظاهرة بوضوح، ويمكن تحقيق ذلك بتطبيق مبادئ التّصميم المرئيّ الّتي تعلّمناها في الدّروس السّابقة. تنسيق الأزرار الرئيسيّة: تباين عالٍ بالنّسبة للخلفيّة (لون أو درجة لونيّة مختلفة جدًّا) موضع الأزرار الرئيسيّة في الواجهة: على محور التّفاعل أو قربه بحيث يلاحظها المُستخدم تلقائيًّا. الأزرار الثانوية بعض الأفعال المُتاحة للمُستخدم تكون غير مُنتجة، كالإلغاء أو التخطّي، أو تفريغ النّموذج أو رفض عرضٍ ما وهكذا... فهي توقف أو تمنع إنشاء أشياء جديدة، وهذه نُسمّيها الأفعال الثّانوية، والّتي لا نريد للمستخدم أن يؤدّيها ولكنّنا نوفّرها من باب قابليّة الاستخدام. يجب أن تكون الأزرار الّتي تؤدّي إلى أفعال ثانوية أقل ظهورًا، لمنع النّقر عليها لا شعوريًّا. تنسيق الأزرار الثّانوية: تباين مُنخفض بالنّسبة للخلفيّة (لون أو درجة لونيّة متشابهة) موضع الأزرار الثّانويّة في الواجهة: بعيدة عن محور التفاعل بحيث لا يلاحظها المُستخدم إلّا إن كان يبحث عنها. استثناء: أهمية الفعل قد تكون بعض الأفعال الثّانويّة مهمّة، كحذف الحساب، وهذه يجب أن تكون ذات تنسيق رئيسيّ ولكن في موضع ثانويّ في الواجهة، وذلك لأنّنا نريد للمُستخدم أن يجدها ولكن نريد أيضًا أن يفكّر في نتائجها قبل تنفيذها. من المفيد أيضًا إعطاء هذه الأزرار لونًا يُشير إلى أهمّيتها (أحمر أو برتقاليّ أو أصفر...). الأزرار الخاصة قد يكون لديك نوع مُعيّن من الأزرار فريدٌ ضمن سائر تطبيقك أو موقعك، ويتطلّب انتباها خاصًّا، صمّم هذا الزّر بشكل خاصّ بحيث يبرز في الواجهة (الخروج عن النّمط)، مثلًا: زرّ "الإضافة إلى سلّة المشتريات" في Amazon، أو زرّ "Pin it" في Pinterest أو زرّ الإعجاب في فيسبوك. سنبدأ في الدّرس القادم الحديث عن الجوانب النّفسيّة لتجربة المُستخدم. ترجمة بتصرّف للدّروس Forms و Calls-to-Action, Instructions & Labels و Primary & Secondary Buttons من سلسلة Daily UX Crash Course لصاحبها Joel Marsh.
  19. بعد أن حدّدنا أهدافنا، وأنهينا أبحاثنا عن المستخدمين، وأسّسنا هندسة المعلومات، حان الوقت للبدء بالتّصميم! فهرس سلسلة مدخل إلى تجربة المستخدم: مدخل إلى تجربة المستخدم User Experience فهم ودراسة المستخدمين في مجال تجربة المستخدم دراسة الشريحة المستهدفة في مجال تجربة المستخدم كيفية التصميم للأجهزة المختلفة هندسة المعلومات في تجربة المستخدم تعرف على أنماط التصميم في مجال تجربة المستخدم أشياء لا يمكن اعتبارها رسوما تخطيطية (Wireframes) في مجال تجربة المستخدم تعرف على الرسوم التخطيطية (Wireframes) في مجال تجربة المستخدم مفهوم الثقل المرئي (Visual Weight) والألوان في مجال تجربة المستخدم التكرار ومخالفة الأنماط في مجال تجربة المستخدم المحاذاة والقرب في مجال تجربة المستخدم تعرف على أساليب مسح الواجهة والتراتب المرئي في مجال تجربة المستخدم أساليب الإطلاع في مجال تجربة المستخدم: التصفح، البحث والاكتشاف تصميم هيكل صفحة الويب والعناصر الأساسية في مجال تجربة المستخدم (هذا الدرس) الأزرار، النماذج والدعوات إلى الإجراء في مجال تجربة المستخدم استخدام علم النفس في مجال تجربة المستخدم لتكييف المستخدم وإقناعه كيف تغير الخبرة من تجربة المستخدم؟ تصميم تجربة المستخدم من خلال بيانات وإحصائيات المستخدمين تعرف على أنواع المخططات الإحصائية في مجال تجربة المستخدم اختبارات أ/ب (A/B Test) في مجال تجربة المستخدم هيكل الصفحة قد يكون من المُغري العمل على تنفيذ الرّسوم التّخطيطيّة واحدًا تلو الآخر، لكنّها طريقة غير فعّالة، بل ينبغي البدء بالأجزاء الكبيرة ثم إضافة التّفاصيل الصّغيرة أثناء العمل، والأجزاء الكبيرة هنا هي العناصر الّتي ستظهر في جميع الصّفحات، أي عناصر التّنقّل (navigation) وتذييل الصّفحة (footer). التذييل عادة ما يكون قائمة من الرّوابط الثّابتة ذات الموضوع العامّ وغير المهمّة بحيث لا تستحقّ أن توضع في القائمة الرئيسيّة. بعض المواقع تعتني أشد الاعتناء بمظهر التّذييل، وهذا أمرٌ حسن، ولكن إن احتوى التّذييل على روابط يحتاجها المستخدم لإنجاز مهمّته على الموقع، فالتّذييل ليس المكان المناسب لهذه الرّوابط. اسأل نفسك: "هل سيحتوي الموقع على صفحات فيها تمريرٌ غير منتهٍ (infinite scrolling)؟ وإن كانت الإجابة نعم، فتأكّد أنّ كلّ ما يحويه التّذييل متوفّر أيضًا في مكان آخر، فإن كان حقل اختيار اللّغة موجودًا في ذيل الصّفحة، وكلّما حاول المستخدم نقره ابتعد عنه للأسفل، فأنت مصمّم فاشل! عناصر التنقل هناك نوعان على الأقل من القوائم: رئيسيّة وفرعيّة. القائمة الرئيسيّة: إن كنت قد أسّست هندسة المعلومات بصورة صحيحة، فأنت تعرف الآن ما يجب أن تحويه هذه القائمة، فهي المستوى الأول من الرّوابط في خريطة الموقع (تحت الصّفحة الرئيسيّة). يجب أن يكون ترتيب عناصر القائمة من اليسار إلى اليمين (أو من اليمين إلى اليسار في حالة العربية مثلا) أو من أسفل إلى أعلى بحسب شعبيّة العنصر (وهذا يُقاس باهتمام المستخدمين، وليس بما يحلو لك!). إن كنت تصنع قائمة جديدة من الصّفر، افعل افضل ما يمكنك، ثم أخبر المُطوّرين أنّك ستحتاج لإعادة ترتيبها لاحقًا، وعندما يزداد عدد الزّوّار بصورة معتبرة، ادرس شعبيّة العناصر وأصلح الترتيب إن دعت الحاجة. القائمة الفرعيّة: وهي قائمة بالصّفحات الّتي تندرج تحت الصّفحة الحاليّة الّتي يراها المستخدم في خريطة الموقع، (أخبرني الحقيقة: لقد رسمت خريطة الموقع، صحيح؟). النّقطة الأكثر أهمّيّة في القوائم الفرعيّة هي أنّها يجب أن تكون في الحالات المثاليّة في الموضع ذاته في كلّ صفحة، حتّى وإن تغيّر الرّوابط، وهذا يسمح للمستخدمين بإيجادها بسرعة. لا تجعل القوائم الفرعيّة ضخمة يُفاجئني بعض المصمّمين عندما يحاولون إقناعي بقائمة فرعيّة عملاقة، فذلك يعني أن هندسة المعلومات (ومهندسها) في غاية السوء. رمي كلّ شيء في قائمة واحدة هو أكثر التّصاميم كسلًا في الكون، كن أفضل من ذلك. وجود هذا العدد الكبير من الرّوابط في القائمة يعني أنّه يجب التّخلّي عن بعضها! الخلاصة: أنشئ عناصر التنقل وتذييل جميع الصّفحات في تطبيقك قبل البدء بالمحتوى، ستكون لي شاكرًا فيما بعد. الطية (Fold) الطّيّة في التّصميم تعني الجزء من الصّفحة الّذي يراه المستخدم قبل أن يبدأ التمرير للأسفل، وهناك الكثير من الأفكار الخاطئة عنها. كلّ شيء فوق الطّيّة سيتلقّى أكبر قدر من المُشاهدات، ولكن يمكن (بحسب الدّراسات) أن تتوقّع نسبة من المستخدمين بين 60 و80 في المئة سيُمرّرون للأسفل إن كانوا يظنّون أنّهم سيجدون شيئًا مفيدًا. كلّ ما فوق الطّيّة يجب أن يُعلم المُستخدمين بما تحته، فإن لم يعلم المستخدم ما سيجده أدناها، فقد لا يتكلّف عناء التمرير. كن حذرًا: يشيع اليوم استخدام خلفيّة كبيرة على كامل القسم العلويّ من الصّفحة، فإن بدت الصّفحة وكأنّها تنتهي عند الطّيّة، فقد يغادر المُستخدم الموقع بدل أن يمرّر الصّفحة، وإن اضطررت لإضافة رسالة تخبره بأن "يمرّر للأسفل"، فتصميمك ضعيف. الصور يُعامل كثيرٌ من مصمّمي تجربة المُستخدم الصّور على أنّها بلا وظيفة، ولكنّ الصّور كثيرًا ما تقود عيني المُستخدم، ولذا يجب أن تفكّر فيها. بشكل خاصّ، تجذب صور النّاس الانتباه أكثر من أيّ شيء آخر في الواجهة. وكقاعدة عامّة، كلّما عزّزت الصّورة مشاعر المستخدم، كان تفاعله أكبر. نصيحة: في صور النّاس، حاول جعل الشّخص في الصّورة ينظر بالاتّجاه الّذي تريد المُستخدم أن ينظر نحوه، فهذا يصنع فرقًا كبيرًا. كلا الخريطتين الحراريّتين أعلاه تظهران التّخطيط نفسه، لكن إحداها تجعل الطّفل ينظر للعنوان، والأخرى تجعله ينظر نحو المُستخدم. تُظهر الخريطتان أن المُستخدمين ركّزوا على وجه الطّفل في الصّورة كثيرًا، لكن الثّانية جذبت انتباهًا أكبر للمحتوى النّصّي وصورة المُنتج والشّاعر. أي الصّورتين ستختار؟ العناوين بالإضافة لصور النّاس، تنجذب العينان نحو النّصوص الكبيرة عالية التّباين في الواجهة، فعندما تضيف عنوانًا كبيرًا إلى تصميمك، فهذا يعني أنّك اخترت الموضع الّذي سيبدأ المُستخدم مسح الصّفحة بعينيه منه. ولهذا ينبغي محاذاة العنوان مع النّص الأكثر أهمّية أسفله، فلو كان هذا المُحتوى غير مهمّ لجذب الانتباه بغير فائدة، وصرفه عن محتوى قد يكون أهمّ، ولو لم يُحاذَ النّص والعنوان لبحث المُستخدم عن نقطة أخرى يُركّز نظره عليها بعد قراءة العنوان. الخلاصة: ضع شيئًا يُركّز المستخدم عليه قبل أن يُمرّر الصّفحة. اجعل إمكانيّة التّمرير واضحة. اختر صور تثير المشاعر وتوجّه عينيّ المُستخدم. استفد من العناوين بتوجيه المستخدمين إلى المُحتوى المهمّ. محور التفاعل من أكثر الأسئلة شيوعًا في تصميم تجربة المُستخدم السؤال عن موضع الأزرار، هل تكون على اليمين أم على اليسار؟ الجواب ليس مُطلقًا، فالأمر يعتمد على موقع "الحوافّ" المرئيّة الّتي صنعتها. محور التّفاعل هو الحافّة التّخيليّة الّتي تتبعها عينك بصورة طبيعيّة، والعناصر الأقرب إلى محور التّفاعل تكون أكثر ظهورًا للمُستخدم. الفكرة بسيطة جدًّا: اهتمام الإنسان محدود، ولا يمكنه التّركيز إلا على شيء واحد في لحظة واحدة، فلو ركّز على جزء من المحتوى، ستكون الأجزاء الأخرى غير ظاهرة عمليًّا.إن لم تصدّقني، فشاهد هذا. اعثر على الحواف ستستخدم مبادئ التّصميم المرئيّ الّتي تعلّمناها في كل تصميماتك، فإذا توقّفت قليلًا ونظرت إلى تخطيط الصّفحة، ستجد أنّك خلقت "خطوطًا" أو "حوافّ" أو "قطعًا" في الصّفحة. قد تتشكّل هذه الحوافّ من محاذاة النّصوص أو الصّور أو تجميع العناصر في صفّ. كلّ حافّة هي محور تفاعل، ستتبع العين هذا المحور حتى ينقطع أو ينتهي. تركيز المُستخدم يكون مُنصبًّا على محور التّفاعل دومًا، وعندما يصرف اهتمامه عنه، فإنّه ينتقل إلى محور آخر. إن أردت أن ينقر الزُوار على شيء ما، فضعه على محور التّفاعل أو قربه، والعكس بالعكس. ترجمة -وبتصرّف- للدّروس Page Framework و The Fold, Images, & Headlines و The Axis of Interaction من سلسلة Daily UX Crash Course لصاحبها Joel Marsh.
  20. يستخدم النّاس المواقع والتّطبيقات لأسباب مُختلفة، فإن صمّمت لسلوك غير ما تريده، فلن تحصل على النّتائج الّتي تريدها. سنتعلّم اليوم الأساليب المُختلفة للنّاس في الاطّلاع على التّصميم: التّصفّح، والبحث، والاكتشاف. قد تعني هذه الكلمات أمورًا مختلفة في سياقات مُختلفة، ولهذا سنوضّح المقصود منها في هذا الدّرس: التصفّح يعني إلقاء نظرة "بحثًا عن أفكار"، كأن تذهب إلى متجر بلا هدف، وتخرج حاملًا بعض المُنتجات غير الضّروريّة. البحث هو أن تذهب إلى المتجر لشراء غرض مُحدّد. الاكتشاف هو أن تذهب إلى المتجر لشراء ذاك الغرض، وتعود به مع غرض آخر أعجبك. فهرس سلسلة مدخل إلى تجربة المستخدم: مدخل إلى تجربة المستخدم User Experience فهم ودراسة المستخدمين في مجال تجربة المستخدم دراسة الشريحة المستهدفة في مجال تجربة المستخدم كيفية التصميم للأجهزة المختلفة هندسة المعلومات في تجربة المستخدم تعرف على أنماط التصميم في مجال تجربة المستخدم أشياء لا يمكن اعتبارها رسوما تخطيطية (Wireframes) في مجال تجربة المستخدم تعرف على الرسوم التخطيطية (Wireframes) في مجال تجربة المستخدم مفهوم الثقل المرئي (Visual Weight) والألوان في مجال تجربة المستخدم التكرار ومخالفة الأنماط في مجال تجربة المستخدم المحاذاة والقرب في مجال تجربة المستخدم تعرف على أساليب مسح الواجهة والتراتب المرئي في مجال تجربة المستخدم أساليب الإطلاع في مجال تجربة المستخدم: التصفح، البحث والاكتشاف (هذا الدرس) تصميم هيكل صفحة الويب والعناصر الأساسية في مجال تجربة المستخدم الأزرار، النماذج والدعوات إلى الإجراء في مجال تجربة المستخدم استخدام علم النفس في مجال تجربة المستخدم لتكييف المستخدم وإقناعه كيف تغير الخبرة من تجربة المستخدم؟ تصميم تجربة المستخدم من خلال بيانات وإحصائيات المستخدمين تعرف على أنواع المخططات الإحصائية في مجال تجربة المستخدم اختبارات أ/ب (A/B Test) في مجال تجربة المستخدم التصفح عندما تزور متجرًا إلكترونيًّا لمجرّد أنّ منتجاتهم تبدو مغرية أو لأنّك تتابع "الموضة"، أو لأنّك تحلم بذلك اليوم الّذي ستقتني فيه تلك الحقيبة الثّمينة الّتي ستجعلك إنسانًا أفضل (!)، فأنت تتصفّح. المُستخدم المُتصفّح سيُلقي نظرة سريعة على معظم الصّور، واحدة تلو الأخرى، بدءا من أيمن وأعلى الصّفحة، قد يتجاهل بعض المنتجات، ولا بأس في ذلك، وقد ينقر على بعض الصّور الّتي تجذب انتباهه أكثر من غيرها. إذا أردت التّصميم بهدف التّصفّح، فاجعل المحتوى مُختصرًا ومرئيًّا، وسهِّل مسح الصّفحة بالعين، لا تحشر عناصر غير مهمّة في الصّفحة، بل ركّز على جوانب المُنتجات الّتي تلبّي مشاعر المستخدم، قدّم وصفًا يناسب هذه الحاجات، فلو كان يحبّ الأسماء التّجاريّة، فاعرض شعارات الشّركات بجوار المنتجات، وهلمّ جرًا. البحث عندما يحاول المُستخدم إيجاد شيء ما في ذهنه، فقد يبدو الأمر مُشابهًا للتصفّح، إلّا أنّ دراسات تتبّع العين تبيّن سلوكًا مختلفًا تمامًا. فالمستخدم الباحث سيتجاهل منتجات وصور كثيرةً، وسيُعينه تنظيم موقعك بصورة حسنة على تتبّع الخيارات بصورة منهجيّة، فهو لا يُريد أن يفوته أحدها. واجهة مثل واجهة موقع Pinterest غير مناسبة لهؤلاء المستخدمين لأنّها تبدو فوضويّة، ولكنّ إمكانيّة تصفيّة الخيارات قد تكون مفيدة. إذا أردت التّصميم بهدف البحث، فركّز على مزايا كلّ عنصر، فلو كان يبحث عن مُنتجٍ بصفات مُعيّنة فسيقف عند كل صورة فيها هذه الصّفات، أمّا غيرها فسيكون عقبة أمامه. أبرز المزايا الّتي تكون أساسيّة لمعظم المستخدمين، ولا شيء غيرها. تجاهل كون الموقع مليئًا بالمعلومات، فلا بأس في ذلك طالما كانت المعلومات مُفيدة. عندما يجد المُستخدم ما يُريد، فسينقر على المُنتج لقراءة معلومات أكثر أو لشرائه، وسيتوقّع معلومات مُفيدة مثل اسم المنتج وصوره وآراء المُستخدمين. الاكتشاف لنتفرض مثلًا أن لديك منتجًا رائعًا لم يجده مستخدمو موقعك، وتعتقد أنّهم قد يشترونه إن عُرض لهم، فكيف تخلق قابليّة الاكتشاف؟ قد تكون فكرتك عن الاكتشاف مخالفة تمامًا للواقع، فمرحبًا بك إلى عالم تجربة المُستخدم الغريب! فيما يلي فكرتان خاطئتان عن الاكتشاف: ستضيف المُنتج إلى القائمة الرّئيسيّة، أو تُنشئ إعلانات في أعلى الصّفحة تدعو لشرائه. تتوقّع أن مُستخدميك الأكثر وفاءًا سيجدون المنتج أوّلًا، لأنّه يقضون وقتًا أطول مع تصميمك. كلا الفكرتين خاطئتان. أوّلًا: لا ينقر المستخدمون على ما في القائمة إلا إن كانوا يبحثون عن شيء معيّن. لا أحد "يكتشف" من خلال القائمة. كما أن الإعلانات في أعلى الصّفحة غير مُجدية لأنّها لم تكن يومًا مُجدية! ألم تستخدم الإنترنت من قبل؟ ما الّذي سيجعل مستخدميك يُقبلون على هذا الإعلان على حين غرّة؟! ثانيًا: كلّما خَبِر مُستخدموك تصميمك، قلّت إمكانيّة اكتشافهم لأشياء جديدة، فالواقع أنّه ما من أحد سوى المُبتدئين يتصفّحون المواقع والتّطبيقات بحثًا عمّا يمكن إنجازه بها، وأمّا الخبراء فيعرفون ما يريدون، وكيف يُتِمّونه، فلم يتصفّحون؟ "إن أعجبك هذا، فقد يعجبك هذا أيضا..." بدلًا من ذلك، دع مستخدميك يجدون ما يبحثون عنه بالفعل، ثمّ اعرض عليهم أشياء جديدة مرتبطة بها بحيث يمكنهم "اكتشافها"، قد تعتقد أنّ هذا إخفاء لها، إلّا أنّه في الحقيقة أفضل ما يمكن فعله لضمان اكتشافها. في مواقع مثل Reddit، يأتي المستخدمون بحثًا عن أعلى المواضيع تقييمًا، وليس أحدثها، ولكن إن لم يُصوّت أحد على المواضيع الجديدة، لن يكون هناك مواضيع جديدة أعلى تقييمًا! ولهذا يضيف Reddit بعض المواضيع الجديدة من الفئات الّتي يحبّها الزّائر بين الأعلى تقييمًا، بحيث تحصل على تقييمات جديدة، لتعود الدّورة من جديد. كلّما فهمت مستخدميك، زاد فهمك لهدف تصميمك، ومن هنا تنبع أهميّة أبحاث المستخدمين. ترجمة بتصرّف للدّرس Browsing vs. Searching vs. Discovery من سلسلة Daily UX Crash Course لصاحبها Joel Marsh.
  21. من السهل أن ننجرف وراء فكرة أنّ المستخدم سيقرأ كلّ حرف نكتبه في واجهتنا، وسيُشاهد كل بكسل، لكنّ الحقيقة عكس ذلك، فالمستخدم يمسح الصّفحة بعينيه سريعًا، ثمّ يتوقّف عند جزء ما يلفت انتباهه. سنتعلّم اليوم عن أسلوبي مسح الواجهة: النّمط Z والنّمط F، والتّراتب المرئيّ. قد تظنّ أن تجربة استخدام تطبيق أو موقع مختلفة عن تجربة التّطبيقات والمواقع الأخرى، إلّا أنه في الحقيقة يمكن بسهولة توقّع نمط اطّلاع المستخدمين على الواجهة. فهرس سلسلة مدخل إلى تجربة المستخدم: مدخل إلى تجربة المستخدم User Experience فهم ودراسة المستخدمين في مجال تجربة المستخدم دراسة الشريحة المستهدفة في مجال تجربة المستخدم كيفية التصميم للأجهزة المختلفة هندسة المعلومات في تجربة المستخدم تعرف على أنماط التصميم في مجال تجربة المستخدم أشياء لا يمكن اعتبارها رسوما تخطيطية (Wireframes) في مجال تجربة المستخدم تعرف على الرسوم التخطيطية (Wireframes) في مجال تجربة المستخدم مفهوم الثقل المرئي (Visual Weight) والألوان في مجال تجربة المستخدم التكرار ومخالفة الأنماط في مجال تجربة المستخدم المحاذاة والقرب في مجال تجربة المستخدم تعرف على أساليب مسح الواجهة والتراتب المرئي في مجال تجربة المستخدم (هذا الدرس) أساليب الإطلاع في مجال تجربة المستخدم: التصفح، البحث والاكتشاف تصميم هيكل صفحة الويب والعناصر الأساسية في مجال تجربة المستخدم الأزرار، النماذج والدعوات إلى الإجراء في مجال تجربة المستخدم استخدام علم النفس في مجال تجربة المستخدم لتكييف المستخدم وإقناعه كيف تغير الخبرة من تجربة المستخدم؟ تصميم تجربة المستخدم من خلال بيانات وإحصائيات المستخدمين تعرف على أنواع المخططات الإحصائية في مجال تجربة المستخدم اختبارات أ/ب (A/B Test) في مجال تجربة المستخدم النمط Z لنبدأ بأكثر التّصميمات إثارةً للملل: صفحة نصّيّة في جريدة، كلّها قصّة واحدة، دون عناوين أو صور أو فواصل أو اقتباسات بخطّ كبير، لا شيء سوى النّص، من أوّلها إلى آخرها. في تصميم كهذا (أرجو أن لا يكون من صنعك!) سيمسح القارئ الصّفحة بما يُشابه حرف Z (معكوس في العربيّة). نريد من خلال ما تعلّمناه عن أنماط التّصميم المرئيّ أن نُحسّن من هذا التّصميم. لو أضفنا عنوانًا (ثقل مرئيّ)، وعمودًا واحد يتبعه (خطّ وهميّ) ثمّ جزأنا النّص على أقسام أصغر (تكرار) لحصلنا على ما يُشبه النّمط F الشّهير. النمط F تُظهر الصّورة أعلاه نتائج تتبّع العين، وهي تقنيّة تُسجّل موضع نظر المُستخدم، وكلّ ما أطال المُستخدم نظره في موضع ما، بدا هذا الموضع أكثر "حرارة" في الخريطة الحراريّة أعلاه. التّخطيطات المُشابهة للصّورة أعلاه تعطي نتائج مُشابهة للخريطة الحراريّة السّابقة. اكتسب مؤسّسو مجموعة Nielsen Norman بعض الشّهرة من خلال النّمط F، وعلى الرّغم أنّهم لم يأتوا بثورة مُشابهة منذ زمن، فإنّهم ما يزالون ينشرون مقالات كثيرة تستحقّ القراءة. هكذا يعمل النّمط F: ابدأ في الزّاوية العلويّة اليُسرى (لقارئي الإنكليزية، أو العلويّة اليُمنى لقارئي العربيّة)، كما في النّمط Z. اقرأ أو امسح العنوان أو السّطر الأوّل من النّصّ. امسح بنظرك القسم الأيسر (أو الأيمن بالعربيّة) مُتجّها للأسفل من العمود حتّى تجد شيئًا مثيرًا للاهتمام. اقرأ ما أثار اهتمامك بتأنٍّ. تابع المسح بنظرك نحو الأسفل. بتكرار هذه الخطوات مرارًا ستبدو الخريطة الحراريّة وكأنّها حرف F أو E (معكوسين للعربيّة)، ومن هنا جاءت التّسمية. ما المهم في ذلك؟ لاحظ كيف تستحوذ بعض أجزاء الصّفحة على اهتمام كبيرة بصورة طبيعيّة دون أجزاء أخرى قد يتجاهلها المُستخدم معظم الوقت، وهذا ما يُسمّى المناطق القويّة والضّعيفة في التّصميم. فزرّ يقع في أيمن وأعلى الصّفحة سيتلقّى نقرات أكبر من زرّ يقع في يسارها وأعلاها، والّذي سيتلقّى نقرات أكثر بدوره من زرّ يقع في يمينها وأسفلها، وأمّا أقلّها نقرًا فستكون الأزرار الموضوعة في أماكن عشوائيّة في منتصف الصّفحة، ما لم نفعل شيئًا يُحسّن من وضعها. اعلم أيضًا أنّ كل "قطعة" من المحتوى يمكن أن تُنشئ نمط F مستقلّ عن بقيّة القطع، فقد تحوي الصّفحة على أكثر من نمط F، وهذا موضوع متقدّم خارج عن حديثنا اليوم. التراتب المرئي (Visual Hierarchy) عندما تستخدم الخطوط للإشارة إلى أهمّية نصّ ما، وبعض الألوان لتمييز الأزرار، وتُضفي ثقلًا مرئيًّا على الأجزاء المُهمّة، فهذا يخلق تراتبًا مرئيًّا، أي تصميمًا يمكن للنّاس مسحه بسهولة، إذا تنتقل العين سريعًا من جزء مهمّ إلى جزء مهمّ آخر وهكذا... يعتقد بعض المصمّمين أن التّراتب المرئيّ أمرٌ جيّد لكونه يُعطي مظهرًا أفضل للتّصميم، وهذا صحيح، ولكنّه أيضًا يُعطي شعورًا مُريحًا لأنّه يسهلُ مسحه بالعين. هل تريد مشاهدة نتائج أكثر لمتابعة العين؟ ألقِ نظرة على صفحة متابعة العين الّتي أنشأتها على Pinterest سنتابع الحديث عن مواضيع متعلّقة بمتابعة العين في الدّرس القادم، ومن ذلك كيف يستخدم النّاس التّصميم بأساليب مختلفة، بين التّصفّح والبحث والاكتشاف. ترجمة بتصرّف للدّرس Z-Pattern, F-Pattern, and Visual Hierarchy من سلسلة Daily UX Crash Course لصاحبها Joel Marsh.
  22. سنشرح اليوم خامس مبادئ التّصميم المرئي وآخرها، والمتعلّقَ بكيفيّة تنظيم عناصر التّصميم ومنحها معنى، دون إضافة عناصر جديدة. الفكرة بسيطة لكنّها تؤثّر في كلّ ما تراه من حولك يوميًّا. سنودّع اليوم بطّاتنا المطاطيّة الّتي رافقتنا في دروسنا السّابقة، ولكنّ ليس قبل أن تشرح لنا اثنين من أهمّ مبادئ التّصميم المرئيّ. فهرس سلسلة مدخل إلى تجربة المستخدم: مدخل إلى تجربة المستخدم User Experience فهم ودراسة المستخدمين في مجال تجربة المستخدم دراسة الشريحة المستهدفة في مجال تجربة المستخدم كيفية التصميم للأجهزة المختلفة هندسة المعلومات في تجربة المستخدم تعرف على أنماط التصميم في مجال تجربة المستخدم أشياء لا يمكن اعتبارها رسوما تخطيطية (Wireframes) في مجال تجربة المستخدم تعرف على الرسوم التخطيطية (Wireframes) في مجال تجربة المستخدم مفهوم الثقل المرئي (Visual Weight) والألوان في مجال تجربة المستخدم التكرار ومخالفة الأنماط في مجال تجربة المستخدم المحاذاة والقرب في مجال تجربة المستخدم (هذا الدرس) تعرف على أساليب مسح الواجهة والتراتب المرئي في مجال تجربة المستخدم أساليب الإطلاع في مجال تجربة المستخدم: التصفح، البحث والاكتشاف تصميم هيكل صفحة الويب والعناصر الأساسية في مجال تجربة المستخدم الأزرار، النماذج والدعوات إلى الإجراء في مجال تجربة المستخدم استخدام علم النفس في مجال تجربة المستخدم لتكييف المستخدم وإقناعه كيف تغير الخبرة من تجربة المستخدم؟ تصميم تجربة المستخدم من خلال بيانات وإحصائيات المستخدمين تعرف على أنواع المخططات الإحصائية في مجال تجربة المستخدم اختبارات أ/ب (A/B Test) في مجال تجربة المستخدم المحاذاة (Alignment) تبدو البطّات القريبة مرتبطة ببعضها. نرى في الصّورة السابقة مجموعة من 6 بطّات جميلة، ونرى كذلك الكثير من العلاقات فيما بينها، وذلك بسبب كيفيّة مُحاذاتها: نرى صفّين نرى البطّتين في أقصى اليمين وأقصى اليسار على أنّهما "منفصلتان" تبدو البطّتان في الوسط أكثرها "انتظامًا" تبدو كلّ البطّات متّجهة باتّجاه واحد إن كنت ترى حركةً، فإن البطّة في أقصى اليسار تبدو متأخّرة عن رفيقاتها إن كنت ترى حركةً، فإن البطّة في أقصى اليمين تبدو وكأنّها تقود رفيقاتها جميع البطّات متطابقة، والاختلاف في إدراكنا يعود إلى أسلوب مُحاذاتها. يمكن مُحاذاة الأزرار المتشابهة الوظيفة، كما يمكن مُحاذاة مستويات مُختلفة من المستوى، أو تنظيم المعلومات في شبكة من الصّفوف والأعمدة لإيصال معنىً مُعقّد. القرب (Proximity) كلّما كانت البطّات أقرب إلى بعضها، بدت أكثر ارتباطًا. قرب الشيء أو بُعده عن شيء آخر يعطي انطباعًا عن مدى ارتباط هذين الشيئين. في الصّورة الثّانية نُشاهد 6 بطّات متماثلة ولكنّها لم تُحاذَ أفُقيًّا أو شاقوليًّا، ولكنّنا نُدرك وجود مجموعتين، إذ تبدو البطّات في كلّ مجموعة مرتبطة معًا كفريق أو عائلة، ولا شيء يُسبّب هذا الإحساس سوى قربها. في تصميماتك، اجعل العناصر المرتبطة فيما بينها أقرب بعضها من بعض، وأبعدها عن العناصر غير المرتبطة. مثلًا: عنوان وشرح مُختصر وزرّ (كدعوة لشراء أو تنزيل تطبيق)، هذه العناصر يجب أن تكون مُتقارَبة بحيث ينظر لها المستخدم على أنّها مجموعة، وهذا يُعفي المُستخدم من عناء قراءة كامل النّص لكي يدرك ارتباطه بالزّرّ. سنتعرّف في الدّروس القادمة على أنواع أنماط التّصميم والتّراتب المرئي (Visual Hierarchy). ترجمة بتصرّف للدّرس Alignment & Proximity من سلسلة Daily UX Crash Course لصاحبها Joel Marsh.
  23. سنتابع في هذا الدّرس الحديث عن مبادئ التّصميم المرئيّ، وسيكون موضوعنا عن الأنماط والتّكرار، ومتى يجب الخروج عن النّمطيّة. فهرس سلسلة مدخل إلى تجربة المستخدم: مدخل إلى تجربة المستخدم User Experience فهم ودراسة المستخدمين في مجال تجربة المستخدم دراسة الشريحة المستهدفة في مجال تجربة المستخدم كيفية التصميم للأجهزة المختلفة هندسة المعلومات في تجربة المستخدم تعرف على أنماط التصميم في مجال تجربة المستخدم أشياء لا يمكن اعتبارها رسوما تخطيطية (Wireframes) في مجال تجربة المستخدم تعرف على الرسوم التخطيطية (Wireframes) في مجال تجربة المستخدم مفهوم الثقل المرئي (Visual Weight) والألوان في مجال تجربة المستخدم التكرار ومخالفة الأنماط في مجال تجربة المستخدم (هذا الدرس) المحاذاة والقرب في مجال تجربة المستخدم تعرف على أساليب مسح الواجهة والتراتب المرئي في مجال تجربة المستخدم أساليب الإطلاع في مجال تجربة المستخدم: التصفح، البحث والاكتشاف تصميم هيكل صفحة الويب والعناصر الأساسية في مجال تجربة المستخدم الأزرار، النماذج والدعوات إلى الإجراء في مجال تجربة المستخدم استخدام علم النفس في مجال تجربة المستخدم لتكييف المستخدم وإقناعه كيف تغير الخبرة من تجربة المستخدم؟ تصميم تجربة المستخدم من خلال بيانات وإحصائيات المستخدمين تعرف على أنواع المخططات الإحصائية في مجال تجربة المستخدم اختبارات أ/ب (A/B Test) في مجال تجربة المستخدم تخلق هذه البطّات نمطًا، والنّمط يُغيّر إدراك النّاظر. يميل النّاظر إلى التّركيز في موضع مخالفة النّمط. يتعامل الدّماغ مع الأنماط والسّلاسل بمهارة فائقة، إذ يلاحظ بسرعة تكرّر شيء ما في الطّبيعة، ويميل إلى التّفكير بصورة مختلفة عن هذه الأشياء المُتكرّرة. تُظهر الصّورة الأولى أعلاه خمس بطّات متماثلة في صفّ واحد، إلّا أنّنا لا نرى خمس بطّات منفردة، بل صفًّا من البطّ، نعاملها على أنّها مجموعة أو سلسلة، وعادة نراها من اليمين إلى اليسار بحسب اتّجاه قراءتنا. لو كان صفّ البطّ هذا قائمة في موقع، لعاملناه بطريقة مماثلة، لذا توقّع أن ينقر النّاس على العناصر في يمين القائمة أكثر من تلك في يسارها. الخروج عن النمط تُظهر الصّورة الثّانية البّطات الخمس ذاتها، ولكنّ واحدة منها قررت الخروج وحدها، وهذا غيّر تمامًا من إدراكنا. نرى الآن صفّا من أربع بطّات، وواحدة منفردة، تظهر بارزة، ويصعب تجاهلها، مع أنّ البطّات جميعها مُتماثلة. لو كانت هذه البطّات قائمة، لكان الخيار الأوسط هو الأكثر نقرًا، لأنّ عيوننا تحدّق فيه، وستكون النّقرات على بداية القائمة (اليمين) أقلّ ممّا سبق، وإن بقيت أكثر ممّا في يسارها. فهم هذه الفكرة أمرٌ عظيم الأثر. قد تبدو الفكرة بسيطة وواضحة، ولكنّ تطبيقها على التّصميم يمكن أن يجعل المُستخدمين يُركّزون على الأزرار والخيارات المُهمّة. ولكن توخّ الحذر فقد يؤدّي الخروج عن النّمط إلى صرف نظر المُستخدم عمّا يهمّه، وعليك قبل الخروج عن النّمط أن تخلق هذا النّمط! اجمع مبادئ التصميم المرئي معا لخلق نمط أو سلسلة، ساوِ الثّقل المرئيّ واللّون، وعندها ستتجّه عينا المُستخدم من البداية إلى النّهاية. للخروج عن النمط، اختر عنصرًا ما تريد إبرازه كزرّ التّسجيل في الموقع، واجعل لونه أو حجمه أو شكله غير مُتوقّع، ومخالفًا لبقيّة النّمط، وعندها ستأتيك النّقرات! الخطوط والحواف الوهمية (Line Tension and Edge Tension) تعلّمنا إذن أن تكرار الأشياء يخلق نمطًا، إلّا أنّه يمكن أن يوحي بوجود "شكل" ما، ويؤثّر بدوره على اتّجاه عيني المُستخدم، وهذا ما نُسمّيه بالخطوط والحوافّ الوهميّة. أنت ترى صفّا من البطّ فيه فراغ. لم لا ترى 8 بطّات فقط؟ هل ترى 12 بطّة، أم مُستطيلًا من البطّ؟ هذه هي الحوافّ الوهميّة. (لم تملّ من رؤية البطّ بعد... صحيح؟) تنضوي هذه الفكرة تحت مُسمّى الشّدّ المرئي (Visual Tension) مفهوم يبدو بسيطًا جدًّا، ولكنّه عظيم التأثير، فالدّماغ البشريّ يُبالغ في التّعرّف على الأنماط لدرجة أن يرى أنماطًا غير موجودة! ويمكنك كمصمّم الاستفادة من ذلك. الخطوط الوهمية (Line Tension) تُظهر الصّورة الأولى في الفقرة السّابقة 8 بطّات في صفّ، ولكنّنا لا نرى 8 بطّات مُنفردة، بل خطًّا، وهذا من الشّدّ المرئيّ الّذي يعني إدراك وجود خطّ أو مسار غير موجود. ستتبع عيوننا هذا المسار لترى أين ينتهي، وهذا مفيدٌ للمصمّم. إن خرجنا عن هذا المسار (كأن نُنشئ فجوة)، فإنّه (كالخروج عن أيّ نمط) يجذب النّظر نحو الفجوة. الحواف الوهمية (Edge Tension) افترضنا حتّى الآن وجود خطّ واحد، لكن ماذا إن جمعنا عدّة خطوط؟ ستكون النّتيجة "شكلًا". في الصّورة الثّانية نلاحظ كيف رُتّبت البطّات بحيث تبدو وكأنّها تخلق زوايا في مستطيل، يمكن أن نعتبرها 12 بطّة، أو 4 مجموعات كلّ منها مكوّن من 3 بطّات، ولكنّ دماغك يميل إلى تفسيرها على أنّها مستطيل، وهذا ما يفعله. يمكن الآن وضع أشياء ضمن هذا المستطيل (مزيد من البط؟!) أو مساحات بين هذه الزّوايا، وكما في الخطوط الوهميّة، يجذب الفراغ النّظر. من جهة تخطيط الواجهات، قد يكون هذا الأسلوب مناسبًا للتركيز على العناصر الصّغيرة، كأسماء الحقول، أو يمكن إنشاء مسارات وهميّة تقود إلى الزّر المطلوب نقره، وهو اسلوب مُستخدم في الإعلانات القديمة. كما أنّه يمكن لهذا الأسلوب أن يزيد من بساطة الواجهة أو انسجامها لأنّ المسار أو المُستطيل ليس سوى مفهوم ذهني، ولكنّ النّظر إليه على أنّه 12 بطّة يعطي انطباعًا بالتّعقيد. اجمع مبادئ التصميم المرئي معا خلقنا فجوة في صفّ البطّ لجذب الانتباه، إلّا أنّه يمكن ملء الفراغ بعنصر ملوّن لإنشاء مسار مثل إنشاء تدرّج لونيّ على مجموعة عناصر في قائمة، أو يمكن إضافة ثقل مرئيّ إلى مجموعة من العناصر بُمعاملتها على أنّها شكل واحد بدل أجزاء منفصلة، وهي طريقة ممتازة للفت النّظر دون إضافة عناصر أخرى إلى الواجهة. ترجمة بتصرّف للدّرسين Repetition & Pattern-Breaking و Line Tension & Edge Tension من سلسلة Daily UX Crash Course لصاحبها Joel Marsh.
  24. يتحدّث هذا الدّرس عن اثنين من مبادئ التّصميم المرئيّ التّي تساعدك في توجيه انتباه المُستخدم، فبعض أجزاء التّصميم أكثر أهمّية من غيرها، ولكنّها قد لا تكون أوّل ما يلحظه المُستخدمون، وعلينا أن نُعين المُستخدم ليُلاحظها. فهرس سلسلة مدخل إلى تجربة المستخدم: مدخل إلى تجربة المستخدم User Experience فهم ودراسة المستخدمين في مجال تجربة المستخدم دراسة الشريحة المستهدفة في مجال تجربة المستخدم كيفية التصميم للأجهزة المختلفة هندسة المعلومات في تجربة المستخدم تعرف على أنماط التصميم في مجال تجربة المستخدم أشياء لا يمكن اعتبارها رسوما تخطيطية (Wireframes) في مجال تجربة المستخدم تعرف على الرسوم التخطيطية (Wireframes) في مجال تجربة المستخدم مفهوم الثقل المرئي (Visual Weight) والألوان في مجال تجربة المستخدم (هذا الدرس) التكرار ومخالفة الأنماط في مجال تجربة المستخدم المحاذاة والقرب في مجال تجربة المستخدم تعرف على أساليب مسح الواجهة والتراتب المرئي في مجال تجربة المستخدم أساليب الإطلاع في مجال تجربة المستخدم: التصفح، البحث والاكتشاف تصميم هيكل صفحة الويب والعناصر الأساسية في مجال تجربة المستخدم الأزرار، النماذج والدعوات إلى الإجراء في مجال تجربة المستخدم استخدام علم النفس في مجال تجربة المستخدم لتكييف المستخدم وإقناعه كيف تغير الخبرة من تجربة المستخدم؟ تصميم تجربة المستخدم من خلال بيانات وإحصائيات المستخدمين تعرف على أنواع المخططات الإحصائية في مجال تجربة المستخدم اختبارات أ/ب (A/B Test) في مجال تجربة المستخدم فكرة "الثّقل المرئيّ" (Visual Weight) بديهيّة نسبيًّا، فبعض العناصر تبدو "أثقل" من غيرها في تخطيط الواجهة، فتجذب الانتباه بصورة أسهل، وهذه الفكرة مهمّة لك كمصمّم لتجربة المُستخدم. وظيفتنا هي أن نُساعد المُستخدم على مُلاحظة الأشياء المُهمّة، وألّا نُشغله عن هدفه في الوقت ذاته. بإضافة ثقل مرئي لبعض عناصر التّصميم، يمكنك زيادة احتمال أن يراها المُستخدم، وبالتّالي تغيير ما سيقوم به بعد ذلك. تذكّر: الثقل المرئيّ مفهوم نسبيّ، وكلّ مبادئ التّصميم المرئيّ تقوم على مقارنة عنصر في التصميم بما حوله من عناصر. ولكي لا نطيل الحديث، إليكم نجم هذه السّلسلة من الدّروس: البطّة المطّاطيّة الأصيلة! التباين تجذب البطّة في المنتصف النّظر أكثر ممّا حولها، فالتّباين يؤثّر في الثّقل المرئيّ. هو الفرق بين العناصر الداكنة والعناصر الفاتحة، وكلما زاد الفرق بين عنصرين زاد التّباين بينهما. ما نريده في تجربة المُستخدم هو زيادة تباين العناصر المُهمّة، كالبطّة السّوداء في الصّورة أعلاه، فمعظم محتوى الصّورة فاتح اللّون، وهذا يؤدّي إلى زيادة مُلاحظة البطّة السّوداء، ولو كانت الصّورة سوداء في معظمها، لبدت البطّات البيضاء أكثر ثقلًا. لو كانت هذه البطّات أزرارًا في واجهة، لنقر معظم المُستخدمين الزّر الدّاكن. العمق والحجم يميل البشر إلى الانتباه إلى العناصر القريبة منهم في العالم المادّيّ أكثر من تلك البعيدة عنهم. وبالمثل فإنّنا نميل إلى فهم العناصر الأكبر حجمًا في العالم الرّقميّ على أنّها "أقرب" إلينا، كالبطّة الوسطى في الصّورة أعلاه، والعناصر الأصغر حجمًا على أنّها أبعد عنّا (كالبطّة المُشوّشة أعلاه). لو كانت هذه البطّات متماثلة الحجم، لنظرنا إليها غالبًا من اليمين إلى اليسار (كما نقرأ). يؤدّي استخدام تأثير التّشويش (blur) والظّلال إلى زيادة واقعيّة مفهوم العمق، والحجم يعطي هذا التأثير حتى لو كان التّصميم يتبع الأسلوب المُسطّح (flat design). كقاعدة عامّة، اجعل العناصر الأكثر أهمّية ذات حجم أكبر من تلك قليلة الأهميّة، وهذا يؤدّي إلى إنشاء تراتب مرئيّ ضمن الصّفحة يُسهّل فحصها بالعين، ويُساعدك على اختيار ما يُلاحظه المُستخدم أوّلًا. من هنا نُدرك خطأ فكرة "جعل الشّعار أكبر"، لأنّنا لا نريد للمستخدمين أن يُطيلوا النّظر في شعارنا بدل أن يشتروا شيئًا ما من الموقع! اللون لدينا في الحياة الواقعية نور الشمس، والأضواء الصّناعيّة، والحرارة والبرودة، والثياب، والأسماء التّجارية وكثير من العوامل المشابهة الّتي تؤثّر في إدراكنا للون، ونحن كمصمّمي تجربة المُستخدم علينا أن نفهم الألوان، وإن لم يكن من المطلوب التعمّق في تفاصيلها. أي هذه الألوان يبدو باردًا؟ وأيها يبدو وكأنّه تحذير؟ للألوان معنى. أي من هذه البطّات تبدو وكأنّها أقرب؟ يمكن للألوان أن "تتقدّم" أو "تتراجع". يمكن لنا أن نتعلّم بعض الأمور من البطّات في الصّورتين أعلاه. عادةً ما ننجز الرّسوم التّخطيطيّة (wireframes) بالأسود والأبيض، وهذا أمر حسن، لأنّه يسمح لنا بالتّركيز على الوظيفة، أمّا المظهر فهو مسؤوليّة مصمّمي الواجهة. إلّا أن الألوان في بعض الأحيان تكون ذات وظيفة، كألوان إشارات المرور، أو كأن يكون لون "المصّاصة" مطابقًا لطعمها، فهذا مهمّ! معنى الألوان في الصّورة الأولى ضمن الفقرة السّابقة، نرى ثلاث بطّات: زرقاء وصفراء وحمراء، وهي بطّات جميلة، ويمكن أن نُلاحظ مُباشرة أن لكلّ بطّة صبغة، ولكلّ من هذه الصّبغات "معنى" ما. لو كانت البطّات أزرارًا، فقد تكون: "تأكيد" و "إلغاء الأمر" و "حذف"، ولو كانت مؤشّرًا لامتلاء الخزّان، لكانت "مليء" و "نصف مليء" و"فارغ"، ولو كانت مؤشّرًا في فرن لكانت "بار" و "دافئ" و "حار". لعلّك أدركت الفكرة: البطّات متماثلة، لكنّ اللّون غيّر المعنى. إن لم تكن بحاجة للإشارة إلى الاختلاف في الوظيفة، فدع مصمّم الواجهة يختر الألوان، وإلّا فاجعل الألوان جزءًا من رسومك التّخطيطيّة. التراجع والتقدم يمكن للألوان كذلك أن تكون "صاخبة" أو "هادئة"، ففي الصّورة الثّانية نُشاهد بطّة حمراء واثنتين زرقاوين، تبدو الحمراء أقرب قليلًا، ولكنّها ليست كذلك. الجأ إلى هذه الحيلة في أزرار الشّراء حيث تبدو وكأنّها "تقفز" من الشّاشة، فالمستخدم يميل إلى نقر الألوان الأقرب. وبالعكس، قد ترغب أحيانًا بأن تُبقي بعض العناصر ظاهرة دون أن تُشتّت انتباه المستخدم، كالبطّتين الزّرقاوين، فهي تبدو "متراجعة"، وهذا الأسلوب مناسب لقائمة تبقى دومًا ظاهرة على الشّاشة، فلو كانت "صاخبة" لصرفت انتباه المُستخدم عن عناصر أهمّ. أبق رسومك بسيطة استخدم الألوان في الرّسوم التّخطيطيّة لبيان الوظيفة فقط، فلا داعي للمبالغة في استعمالها أو الحرص على تجميلها لعرضها أمام الزّبون، فقد يدخل معك في نقاش لا طائل من وراءه لاختيار ألوان أخرى. اجمع مبادئ التصميم المرئي معا يمكن استخدام الألوان مع ما تعلّمناه عن الثّقل المرئيّ، إذ يسهل ملاحظة العناصر الكبيرة، وأمّا العناصر الكبيرة حمراء اللّون فيستحيل تجاهلها! اجعل رسائل الخطأ والتّحذيرات حمراء وعالية التّباين، وأمّا إن كانت رسالة تأكيد لنجاح العمليّة، فيكفي أن تكون صغيرة الحجم بلون أخضر مُتراجعٍ. ترجمة بتصرّف للدّرسين Visual Weight, Contrast & Depth و Colour من سلسلة Daily UX Crash Course لصاحبها Joel Marsh.
  25. تتبادر إلى أذهان معظم النّاس عندما يسمعون عبارة "تجربة المستخدم" تلك المُخطّطات المكوّنة من مستطيلات وخطوط، والكثير منهم يظنّون -مُخطئين- أنّ هذه رسم المُخطّطات (الّتي نسمّيها wireframes) هي كلّ ما في تجربة المُستخدم. فهرس سلسلة مدخل إلى تجربة المستخدم: مدخل إلى تجربة المستخدم User Experience فهم ودراسة المستخدمين في مجال تجربة المستخدم دراسة الشريحة المستهدفة في مجال تجربة المستخدم كيفية التصميم للأجهزة المختلفة هندسة المعلومات في تجربة المستخدم تعرف على أنماط التصميم في مجال تجربة المستخدم أشياء لا يمكن اعتبارها رسوما تخطيطية (Wireframes) في مجال تجربة المستخدم تعرف على الرسوم التخطيطية (Wireframes) في مجال تجربة المستخدم (هذا الدرس) مفهوم الثقل المرئي (Visual Weight) والألوان في مجال تجربة المستخدم التكرار ومخالفة الأنماط في مجال تجربة المستخدم المحاذاة والقرب في مجال تجربة المستخدم تعرف على أساليب مسح الواجهة والتراتب المرئي في مجال تجربة المستخدم أساليب الإطلاع في مجال تجربة المستخدم: التصفح، البحث والاكتشاف تصميم هيكل صفحة الويب والعناصر الأساسية في مجال تجربة المستخدم الأزرار، النماذج والدعوات إلى الإجراء في مجال تجربة المستخدم استخدام علم النفس في مجال تجربة المستخدم لتكييف المستخدم وإقناعه كيف تغير الخبرة من تجربة المستخدم؟ تصميم تجربة المستخدم من خلال بيانات وإحصائيات المستخدمين تعرف على أنواع المخططات الإحصائية في مجال تجربة المستخدم اختبارات أ/ب (A/B Test) في مجال تجربة المستخدم ما هي الرسوم التخطيطية؟ إن كنت قد تابعت الدّروس الماضية من هذه السلسلة، فلعلّك تفهم الآن أنّ تجربة المُستخدم كجبل الجليد من حيث أنّ الجزء الظاهر منها ليس إلا جزءًا صغيرًا من المشكلة. قبل أن نبدأ الشّرح، أنصحك بالاطّلاع على مقالة أشياء لا يمكن اعتبارها رسوما تخطيطية لتصحّح بعض المفاهيم الخاطئة الّتي قد تعلّمتها بمفردك أو ضمن شركتك. الفكرة العامة الرسوم التّخطيطية هي مُستندات تقنيّة، كالّذي في الصّورة أعلاه (ولكنّها ليست دومًا حسن المنظر كهذا!)، وهي مكوّنة من خطوط ومستطيلات وأسماء، وربّما بعض الألوان. كثيرًا ما تُقارن الرّسوم التّخطيطيّة بالمُخطّطات الهندسيّة (blueprints) لأنّهما متقاربان في الهدف. فالمُخطّط الهندسيّ يُملي على البنّائين كيفيّة إنجاز خطّة المُهندس، وليس لون الجدران أو شكل الأثاث المُفضّل، وينبغي عليهم التّقيّد بما فيها بصورة جدّيّة، فهي ليست مُجرّد "اقتراح" أو "فكرة عامّة" أو "تصوّر سريع للمشروع". أمّا الرسوم السريعة الّتي تُنجز على الألواح أو خلال جلسات العصف الذّهنيّ فهي لا تُسمّى رسومًا تخطيطيّة لأنّها فقط تضع أساسًا لإنجاز الرّسوم المطلوبة فيما بعد، وهي مع ذلك لا تزال قيّمة. قد لا يستغرق الرّسم التّخطيطيّ أكثر من ساعة، لكنّ التّخطيط له قد يطول أسابيع أو شهورًا، ومن المهمّ أن تشرح ذلك لزبائنك وزملائك في العمل. إن كان مُصمّم الواجهات أو مُطوّرها لا يستطيع استخدام رسمك التّخطيطيّ بعد، فهو إذًا ليس رسمًا تخطيطيَّا، بل تصوّرا مبدئيًّا له. قد لا يكون درسنا طويلاً، ولكنّني سأتوقّف هنا، لأنّ الدروس القادمة ستشرح كيفيّة تحسين الرّسوم التّخطيطيّة بحيث تؤدّي إلى تصميم يعمل بصورة جيّدة، وليس فقط ذا مظهر جيّد. ترجمة بتصرّف للدرس ?What is a Wireframe من سلسلة Daily UX Crash Course لصاحبها Joel Marsh.