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

محمد بغات

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

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

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

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

    6

كل منشورات العضو محمد بغات

  1. دالة التوقيت (timing function) تتحكم في وتيرة وسرعة التغيرات التي تحدث إبّان الانتقال (transition). باستخدام دوال التوقيت، يمكن جعل الانتقالات أكثر حيوية. هذه المقالة جزء من سلسلة من المقالات حول التحريك في CSS: ما هي استخدامات الحركات؟ تجهيز بيئة العمل لإنشاء الحركات مدخل إلى الانتقالات: التنقل بين الحالات مدخل إلى الحركات: مفهوم الحركة تطبيق عملي: الانتقالات خاصيات الانتقالات دوال التوقيت الانتقالات المتعددة الانتقالات وجافاسكربت تطبيق عملي: التحريكات خاصيات الحركات تطبيق عملي: الإطارات المفتاحية الحركات المتعددة المتزامنة موجز الحركات رواية القصص عبر الحركات حرب النجوم إظهار المحتوى أثناء التمرير سهولة الوصول نهاية الرحلة فيما يلي مثال للانتقال باستخدام دالة توقيت خطية، إذ تتحرك جيئةً وذهابًا بوتيرة ثابتة. قارن هذا بهذا المثال الذي يستخدم دوال التوقيت المسماة cubic-bezier. سترى فرقًا كبيرًا في وتيرة الحركة. في هذا المثال، سنستخدم دالة cubic-bezier مخصصة: دالة توقيت cubic-bezier تجعل مقاربة cubic-bezier في هذا المثال التحريكات تتراجع إلى الوراء قليلاً قبل التحول بسرعة إلى الحالة الثانية، حيث تتجاوزها فعليًا، ثم تعود إليها. أمثلة CSS التي أعطيناها في البداية، وحالة التحويم (hover state) في كل الأمثلة تعتمد على دالة التوقيت. سنلقي نظرة على كل تلك الدوال، ونتعرف على كيفية تأثيرها على الطريقة التي تتحرك بها العناصر. إذا كنت ترغب بالتجريب على هذه الأمثلة، فقد أعددت مشروعًا على CodePen يمكنك الدخول إليه والتعديل عليه. Linear دالة التسارع الخطي Linear يتغير التسارع الخطي (linear transition) بمعدل ثابت من البداية إلى النهاية. ونظرًا لعدم وجود منحنى في الانتقال، فهو لا يتسارع أو يتباطأ. يمكن أن يكون هذا مفيدًا إن كنت تريد إنشاء تحريك يحتاج إلى حركة ثابتة، مثل مشهد خلفية نافذة قطار متحرك، أو الدوران الثابت. Ease-in التسارع المتباطئ عند الانطلاق ease-in تبدأ دالة التسارع المتباطئ عند الانطلاق (ease-in timing function) ببطء وتتسارع في نهاية الانتقال. بشكل مشابه لكرة تتدحرج من أعلى التل، حيث تبدأ ببطئ، وتنتهي بسرعة كبيرة في الأسفل. Ease-out التسارع المتباطئ عند التوقف ease-out دالة التسارع المتباطئ عند التوقف (ease-out timing function) تتصرف بشكل معاكس لدالة التسريع الداخلي. إذ تبدأ بسرعة، ثم تتباطئ في نهاية الانتقال. هذا مفيد في الحالات التي تحتاج فيها إلى إعطاء الزائر شعورًا بأنّ شيئًا ما يندفع من خارج الشاشة ثم يبطئ للتوقف. Ease-in-out دالة التسارع المتباطئ عند الانطلاق والتوقف Ease-in-out دالة التسارع المتباطئ عند الانطلاق والتوقف (ease-in-out timing function)، هي مزيج من دالتي التسريع الداخلي والخارجي. إذ تبدأ ببطء، وتتسارع خلال الجزء الأوسط من الانتقال، ثم تتباطأ عند النهاية. يمكن أن تمثّل سيارة تبدأ من حالة توقف تام، ثم تتسارع، ثم تبطئ قبل التوقف. كما قد تكون مفيدة لإنشاء تحريك يمثل عملية التحميل. Cubic-bezier دالة التسارع المخصص Cubic-bezier جميع دوال التوقيت التي رأيناها حتى الآن هي أمثلة لمنحنى cubic bezier. وهو منحنى يصف "شكل" دالة التوقيت. وهكذا، فإنّ تحديد دالة توقيت cubic-bezier يكافئ إنشاء دالة توقيت خاصة بنا. تتألف دوال توقيت cubic-bezier من 4 قيم، تمثل إحداثيتين (co-ordinates). صياغة cubic-bezier قد تبدو على الشكل التالي: transition-timing-function: cubic-bezier(1,-0.49,.13,1.09); الإحداثيتان هنا هما (‎1, -0.49) و (‎.13, 1.09). على الرسم البياني تبدو كما يلي: المصدر: http://cubic-bezier.com/#1,-0.49,.13,1.09 بدلاً من إنشاء المنحنيات يدويًا، فأنا أستخدم موقع cubic-bezier.com. سيمكّنك هذا الموقع من إنشاء بعض التأثيرات الرائعة. تصبح منحنيات cubic-bezier ممتعة عند استخدام قيم أكبر من 1، إذ من الممكن إنشاء انتقالات تتخطى الحدود وترتد. Steps دالة الانتقال الخطوي Steps في حين تعتمد معظم دوال التوقيت على المنحنيات، فإنّ الدالة steps تُقسّم عملية الانتقال إلى مجموعة من المراحل، وتقفز من مرحلة إلى التي تليها. ‎على سبيل المثال، إذا حددت القيمة steps(4)‎، فإنّ مدة الانتقال ستقُسّم إلى 4 قفزات منفصلة (انظر الصورة أعلاه). هذا مفيد للتحريكات المفاجئة. على سبيل المثال، دوّار التحميل (loading spinner)، أو شخصية متحركة للعبة فيديو. من خلال تعيين موضع الخلفية في بداية سلسلة الإطارات (frames)، يمكن استخدام دالة التوقيت steps للقفز من إطار لآخر، وإحداث شعور بالحركة. لمشاهدة مثال على ذلك، شاهد التحريك Twitter fave button. يمكنك أيضًا تحديد ما إذا كان الانتقال سيحتفظ بالإطار الأول لجزء من مدة الانتقال، أم أنه سيحتفظ بالإطار النهائي. الوضع الافتراضي هو الإطار النهائي، على أساس أنّ الإطار الأول قد ظهر بالفعل قبل أن يبدأ التحريك. يمكننا تحديد الخيار المناسب عند تحديد الخطوات: transition: all 2s steps(10, start); transition: all 2s steps(10, end); تمرين تتمة للتمرين السابق: حاول تغيير قيمة transition-timing-function، وشاهد كيف يغير ذلك الطريقة التي يظهر بها الانتقال. يمكنك أيضًا محاولة تغيير القيم في هذا المثال التوضيحي. من الناحية الفنية، فهي تحريك (animation) وليست انتقالًا (transition)، لكنّ دالة التوقيت تنطبق بنفس الطريقة. المصادر ترجمة وبتصرف للفصل timing functions من كتاب CSS Animation 101 لمؤلفه Donovan Hutchinson. اقرأ أيضًا المقالة التالية: الانتقالات المتعددة المقالة السابقة: خاصيات الانتقالات النسخة العربية الكاملة من كتاب: التحريك عبر CSS
  2. تعلّمنا في المقالة الماضية كيفية عمل الخاصية transition، وسنلقي في هذه المقالة نظرة على خاصيات الانتقالات. هذه المقالة جزء من سلسلة من المقالات حول التحريك في CSS: ما هي استخدامات الحركات؟ تجهيز بيئة العمل لإنشاء الحركات مدخل إلى الانتقالات: التنقل بين الحالات مدخل إلى الحركات: مفهوم الحركة تطبيق عملي: الانتقالات خاصيات الانتقالات دوال التوقيت الانتقالات المتعددة الانتقالات وجافاسكربت تطبيق عملي: التحريكات خاصيات الحركات تطبيق عملي: الإطارات المفتاحية الحركات المتعددة المتزامنة موجز الحركات رواية القصص عبر الحركات حرب النجوم إظهار المحتوى أثناء التمرير سهولة الوصول نهاية الرحلة الصياغة المختزلة مقابل الصياغة المطولة عند كتابة شيفرة CSS، يمكننا في كثير من الأحيان اختزال عدة خاصيات في خاصية واحدة مختزلة. على سبيل المثال، الصياغة المختزلة للخاصية padding تُكتب على النحو التالي: padding: 10px 20px 15px 25px; وهذا يكافئ: padding-top: 10px; padding-right: 20px; padding-bottom: 15px; padding-left: 25px; يمكننا بالطريقة نفسها كتابة الخاصية transition بشكل مختزل: transition: all 0.5s 1s linear; وهذا هو الشكل العام: transition: [property] [duration] [delay] [timing-function]; يمكن كتابة كل واحدة من هذه الخاصيات بشكل منفرد: transition-property: all; transition-duration: 0.5s; transition-delay: 1s; transition-timing-function: linear; لنلق نظرة على هذه الخاصيات. الخاصية transition-property عادةً ما ترد في بداية الصياغة المختزلة، وتمثلُ الخاصيةَ التي سيقوم المتصفح بتحريكها. لتغيير الخلفية على سبيل المثال، يمكن استخدام القيمة background. من الممكن أيضًا استخدام القيمة all، والتي تمثل جميع خاصيات CSS التي ينطبق عليها الانتقال. الخاصية transition-duration تمثل قيمةُ الخاصية transition-duration المدةَ التي يستغرقها الانتقال. فترة انتقال تساوي 3s (ثلاث ثوانٍ) ستكون أطول بثلاث مرات من فترة انتقال تساوي 1000ms. الخاصية transition-delay تخبر الخاصية transition-delay المتصفح بالانتظار قبل تطبيق الانتقال. هذه قيمة زمنية، ويمكن تحديدها بالثانية أو الميلي ثانية(ms). على سبيل المثال، 3s تكافئ ثلاث ثوان، و100ms تكافئ مئة ميلي ثانية. بشكل مكافئ، يمكنك كتابة تلك القيمة على شكل 0.1s. الأمر متروك لك. الخاصية transition-timing-function تستخدِم كل من الانتقالات (transitions) والتحريكات (animations) دوال التوقيت (timing functions). تحتاج دوال التوقيت إلى فقرة خاصة بها، لذا سنؤجل الحديث عنها إلى المقالة التالية. لكن بإيجاز، دوال التوقيت تعطي للتحريكات حيوية. الأشياء التي لا يطبّق الانتقال عليها رغم أنه يمكن استخدام الانتقالات على الخاصيات size و colour و border و background-position وغيرها، إلا أنّ هناك بعض الخاصيات التي لا يمكن تطبيق الانتقال عليها. فمثلًا، لا يمكن تطبيق الانتقال على عائلة الخطوط font-family، لأنّ هذا يعني أنّ على المتصفح إنشاء إطارات مفتاحية (keyframes) بين صورتين مختلفتين تمامًا من الخطوط. صور الخلفية المُنشأة باستخدام CSS، مثل التدرجات (generated gradients)، لا يمكن تطبيق التحريك على خاصياتها. لأنّ ذلك يعني أنّ المتصفح سيكون عليه استبدال صورة الخلفية بكل الإطارات المفتاحية للتحريك، لذلك فهو غير مدعوم. لكن يمكن تحريك خاصيات من قبيل opacity و background-position. وعن طريق تحريك صور الخلفية أو إخفائها، يمكنك إنشاء تأثيرات جذابة. يمكنك مشاهدة مثال Baymax، حيث يتم تحريك صورة الخلفية لإنشاء حركة. المصدر: http://[CSS](https://wiki.hsoub.com/CSS)animation.rocks/baymax يتم استخدام تأثير مماثل لإحداث تأثير اللمعان على الأزرار، حيث يتم تحريك تدرج الخلفية في مقدمة الزر. المصدر: https://[CSS](https://wiki.hsoub.com/CSS)animation.rocks/pseudo-elements/ تمرين لقد أنشأت مثالًا على Codepen لتجربة الانتقالات. المثال يقوم بالانتقال من شكل الماس (diamond shape) إلى شكل الدائرة. حاول تغيير بعض الخاصيات لمعرفة ما سيحدث. إذا كنت ترغب في التعمق أكثر في هذا الموضوع، فاضغط على الزر "Fork" لإنشاء نسختك الخاصة، ويمكنك بعد ذلك حفظ عملك في حساب Codepen الخاص بك. المصادر ترجمة وبتصرف للفصل transitions-properties من كتاب CSS Animation 101 لمؤلفه Donovan Hutchinson. اقرأ أيضًا المقالة التالية: دوال التوقيت المقال السابقة: تطبيق عملي: الانتقالات النسخة العربية الكاملة من كتاب: التحريك عبر CSS
  3. الآن وبعد أن قدمنا الخاصيتين transition و animation، فقد حان الوقت للتعمق أكثر في الانتقالات (transitions)، ومطالعة بعض الأمثلة. هذه المقالة جزء من سلسلة من المقالات حول التحريك في CSS: ما هي استخدامات الحركات؟ تجهيز بيئة العمل لإنشاء الحركات مدخل إلى الانتقالات: التنقل بين الحالات مدخل إلى الحركات: مفهوم الحركة تطبيق عملي: الانتقالات خاصيات الانتقالات دوال التوقيت الانتقالات المتعددة الانتقالات وجافاسكربت تطبيق عملي: التحريكات خاصيات الحركات تطبيق عملي: الإطارات المفتاحية الحركات المتعددة المتزامنة موجز الحركات رواية القصص عبر الحركات حرب النجوم إظهار المحتوى أثناء التمرير سهولة الوصول نهاية الرحلة الانتقالات تحدث الانتقالات في المتصفح عندما يتحوّل عنصر من حالة إلى أخرى. يرسم المتصفح الإطارات المفتاحية (keyframes) بين الحالة الابتدائية والحالة النهائية لإنشاء شعور بالحركة. الخاصية transition هي خاصية من خاصيات CSS. مثلها مثل الخاصيات height أو width أو border. يمكننا برمجة الانتقالات في CSS على النحو التالي: transition: background 0.5s linear; في هذه الحالة، نقول للمتصفح أنّ أيّ انتقال لخاصية الخلفية (background) سيستغرق نصف ثانية، إضافة إلى استخدام دالة التوقيت "الخطي" (linear). قد تتسبب الخاصية المذكورة أعلاه في تغيير خلفية الزر عند تمرير مؤشر الفأرة فوقه: button { background: white; transition: background 0.5s linear; } button:hover { background: green; } لاحظ الخاصية transition في المحدد button، والتي تقول للمتصفح أن يطبّق الانتقال على أي تغيير في الحالة، مثل التغيير الذي يحدث عن التحويم (hover)، وكذلك عند العودة إلى الحالة الأصلية بعد التحويم. إذا طبَّقنا الخاصية transition على حالة التحويم (hover) وحسب، فلن يتم الانتقال إلا إلى حالة التحويم، ولكن ليس عند العودة للحالة الأولى. إليك هذين المثالين التوضيحيَين. قد تجد أنّ هذه الأمثلة تحتوي على بعض الشيفرات غير المفهومة. سأعطي المزيد من التفاصيل في المقالات المقبلة، لكن لا تتردد في التجريب وتعديل القيم لمعرفة ما يحدث. مثال: انتقال الزر إليك مثالًا في CodePen يوضح تأثير التحويم (hover effect). في CodePen، يمكنك إجراء تغييرات على HTML و CSS ورؤية النتائج مباشرة. الشيء المهم الذي ينبغي التركيز عليه هو الخاصيات التي تبدأ بالبادئة transition-‎. transition-property: all; transition-duration: 0.4s; transition-timing-function: ease-out; تخبر هذه الشيفرة المتصفح بنوع الحركة التي ينبغي إنشاؤها عند الانتقال من حالة عدم التحويم (non-hover state)، إلى حالة التحويم (hover state). كما تُخبر المتصفح بالتحريك (الانتقال) التدريجي لكل الخصائص (الألوان والحجم والموضع) على مدار 0.4 ثانية. حاول تغيير بعض هذه القيم. على سبيل المثال، قم بتغيير "0.4s" إلى قيمة أطول، مثل "2s" (ثانيتين). كيف يبدو التحريك؟ يمكنك كذلك تغيير الخاصية من all إلى background. لإنشاء تأثير ممتع، حاول تغيير قيمة transition-timing-function من ease-out إلى: transition-timing-function: cubic-bezier(.59,-0.26,.33,1.42) دالة التوقيت cubic bezier تضفي على الحركة متعة وجاذبية. سنعود لدوال التوقيت بمزيد من التفاصيل في المقالات التالية. البادئات وتوافق المتصفحات لم أضمِّن بادئات المتصفحات (vendor prefixes) في شيفرات الأمثلة. وذلك لتسهيل قراءة الشيفرة، ولكن في حال كنت تستخدم الشيفرة في مرحلة الإنتاج فسيكون عليك إضافتها. أحب استخدام بادئ تلقائي Autoprefixer (وهو خيار متاح في Codepen، لتفعيله اضغط على أيقونة الإعدادات "cog" في قسم CSS)، ويمكن تفعيلها باستخدام أدوات البناء، مثل Grunt أو Gulp. بالمقابل، يمكنك كتابتها يدويًا على النحو التالي: -webkit-transition: ...; -moz-transition: ...; transition: ...; تمرين قم بتحرير الزر في المثال، وأضف أفكارك الخاصة. يمكنك تغيير الشكل (shape)، أو الحد (border)، أو أي خاصية أخرى. جرّب واستمتع، فالهدف هو التأكد من أنك تفهم كيف يؤثر الانتقال على تأثير التحويم الخاص بالعنصر. لمزيد من الإلهام، تحقَّق من تنسيق التحويم على هذا الرابط. هناك الكثير من الأمثلة الرائعة لاستلهام الأفكار. إذا كنت تريد التعمق في الموضوع أكثر، فأنشئ مشروعًا جديدًا في CodePen، مع إنشاء عنصر يتغير من حالة إلى أخرى عند التحويم عليه. حاول معرفة ما إذا كان يمكن أن يحتوي داخله عنصرًا يتحرك بمعدل مختلف. لا تقلق إذا لم تنجح في ذلك، سنغطي خاصيات التحريك بمزيد من التفاصيل في المقالات القادمة. المصادر ترجمة وبتصرف للفصل transitions in action من كتاب CSS Animation 101 لمؤلفه Donovan Hutchinson. اقرأ أيضًا المقالة التالية: خاصيات الانتقالات المقال السابقة: مدخل إلى الحركات: مفهوم الحركة النسخة العربية الكاملة من كتاب: التحريك عبر CSS
  4. لقد ناقشنا في المقالات السابقة استخدامات وفوائد التحريك (Animation)، ووجدنا بعض مصادر الإلهام، وألقينا نظرة على الأدوات والمواقع التي قد تكون مفيدة في التطوير، وتعرّفنا على كيفية تطبيق حركة عبر نقل عنصر من حالة إلى أخرى (transitions). هذه المقالة جزء من سلسلة من المقالات حول التحريك في CSS: ما هي استخدامات الحركات؟ تجهيز بيئة العمل لإنشاء الحركات مدخل إلى الانتقالات: التنقل بين الحالات مدخل إلى الحركات: مفهوم الحركة تطبيق عملي: الانتقالات خاصيات الانتقالات دوال التوقيت الانتقالات المتعددة الانتقالات وجافاسكربت تطبيق عملي: التحريكات خاصيات الحركات تطبيق عملي: الإطارات المفتاحية الحركات المتعددة المتزامنة موجز الحركات رواية القصص عبر الحركات حرب النجوم إظهار المحتوى أثناء التمرير سهولة الوصول نهاية الرحلة في هذه الفقرة ستتعرّف على الخاصية animation. التحريك في المتصفح الانتقال (Transition) والتحريك (animation) متشابهان. فكلاهما يأخذ شكل خاصية CSS، ويحدث خلال مدة معيّنة، إضافة إلى خصائص أخرى للتحكم في كيفية إنشاء المتصفح للحركة. في حين أنّ الانتقالات تدور حول جعل التغيير من الحالة "A" إلى الحالة "B" يبدوا سلسًا، فإنّ التحريكات هي وسيلة لوصف مراحل متعددة من الحركة. التحريكات مفيدة لإنجاز الحركات المعقدة في المتصفح. في المثال أعلاه، هناك 3 حالات (A و B و C). الانتقال سينتقل من A إلى C وحسب، في حين تسمح لنا التحريكات بتحديد مرحلة وسطية B، بحيث يتم المرور على كل الخطوات الثلاث. تتصرَّف التحريكات أيضًا بشكل مختلف قليلًا، إذ يمكنها أن تبدأ تلقائيًا. بينما قد يتطلب الانتقال إضافة صنف، أو تغييرًا في الحالة، مثل التحويم (hovering)، كما يمكن أن تبدأ التحريكات عند تحميل الصفحة. هذا يعني أنه في حال رغبت في سرد قصة ما، أو لفت الانتباه إلى شيء ما على الصفحة، فالتحريكات خيار جيد. أمثلة تعد حركة الزر "Save" التي نراها في Codepen مثالًا جيدًا على التحريكات. هذه الحركة تساعد الناس على ملاحظة الزر وتذكر ضرورة حفظ عملهم. يتكون التأثير من سلسلة من الإطارات المفتاحية (keyframes) التي تخبر المتصفح أن يهُزّ الزر من اليسار إلى اليمين. سنتعمّق أكثر في هذا الموضوع في فقرة الإطارات المفتاحية بشكل عملي . يمكننا أن نفعل الكثير من الأشياء عبر تحريك ونقل الإطارات المفتاحية عبر CSS. هذا مثال آخر ثلاثي الأبعاد: المثال CSS Mac Plus متوفر على موقع CodePen، وهذا دليل مفصل لكيفية تصميمه. الانتقال أم التحريك؟ تحدث الانتقالات عندما ينقل المتصفح عنصرًا من حالة إلى أخرى (من A إلى B) مما يوحي بحدوث حركة. يتم تطبيق ذلك عادةً نتيجة لحدث ما، مثل التحويم (hovering) فوق عنصر ما، أو إضافة صنف أو إزالته باستخدام JavaScript. التحريكات أكثر دينامية، وتحدث تلقائيًا، وتتيح لك إنشاء تسلسل من التحريكات عبر عدد من الإطارات المفتاحية، ويمكن أن تُنفّذ خلال دورات (loops). سنعود للخاصية animation لاحقًا. تمرين هل يمكنك التفكير في طرق لاستخدام التحريكات في صفحات الويب الخاصة بك؟ حاول أن تنتبه للتحريكات التي تحدث على المواقع التي تتصفحها. ابحث عن الأشياء التي تتحرك بطريقة مُلفتة. فعلى الأرجح أنها تحريكات. إذا قمت بتنزيل ملفي HTML و CSS الابتدائيَين، فقم بإلقاء نظرة على الخاصية 'animation'. على عكس الانتقالات، تحتاج التحريكات إلى جزء ثانٍ يسمى الإطارات المفتاحية (keyframes). حاول تغيير بعض القيم وشاهد ما يحدث. المصادر ترجمة وبتصرف للفصل animations من كتاب CSS Animation 101 لمؤلفه Donovan Hutchinson. اقرأ أيضًا المقالة التالية: تطبيق عملي: الانتقالات المقال السابقة: مدخل إلى الانتقالات: التنقل بين الحالات النسخة العربية الكاملة من كتاب: التحريك عبر CSS
  5. سنلقي في هذه الفقرة نظرة على خاصية الانتقال transition. هذه المقالة جزء من سلسلة من المقالات حول التحريك في CSS: ما هي استخدامات الحركات؟ تجهيز بيئة العمل لإنشاء الحركات مدخل إلى الانتقالات: التنقل بين الحالات مدخل إلى الحركات: مفهوم الحركة تطبيق عملي: الانتقالات خاصيات الانتقالات دوال التوقيت الانتقالات المتعددة الانتقالات وجافاسكربت تطبيق عملي: التحريكات خاصيات الحركات تطبيق عملي: الإطارات المفتاحية الحركات المتعددة المتزامنة موجز الحركات رواية القصص عبر الحركات حرب النجوم إظهار المحتوى أثناء التمرير سهولة الوصول نهاية الرحلة كانت المتصفحات فيما مضى بسيطة للغاية. فقبل وقت غير بعيد، لم تكن قادرة حتى على عرض الصور، أو التعامل إلا مع حفنة من الخطوط. ثم جاءت CSS، ومنحتنا القدرة على التحكم في شكل ومظهر صفحات الويب. التحريك (Animation) في المتصفحات ليس جديدًا. فقد وفرت لنا بعض مكتبات JavaScript مثل Flash و Canvas وغيرها طرقًا للتحريك، ولكن صارت CSS، في الآونة الأخيرة، خيارًا إضافيًا. الانتقالات إحدى الطرق التي تتيحها CSS للتحكم في التحريك في المتصفح هي باستخدام الخاصية 'transition'. بلغة المتصفحات، فإنّ الانتقال (transition) يعني حركة من حالة إلى أخرى. عندما نطبق عملية الانتقال على عنصر ما، فإننا نخبر المتصفح بأننا نريده أن ينقل، أو يحسب تلقائيًا، التغيير بين الحالات. على سبيل المثال، يمكننا تغيير تنسيق عنصر ما عند تمرير مؤشر الفأرة عليه، قم بتطبيق عملية الانتقال، وسينقل المتصفح التنسيق الأولي للعنصر إلى التنسيق الجديد نقلًا سلسًا مما يوحي بتطبيق حركة وحيوية على العنصر. خصائص الانتقال عندما نستخدم الانتقال على عنصر ما، فهناك الكثير من الخصائص التي يمكن أن تتحكم في كيفية الانتقال. يمكننا أن نجعله بطيئًا أو سريعًا، أو نؤخره، بل ويمكننا أن نتحكم في وتيرة التغيير باستخدام دوال التوقيت (timing functions). سوف نتطرق إلى ما يعنيه هذا في المقالة التالية. مثال آخر على الانتقالات المركبة: سنناقش قريبًا كيفية استخدام الانتقالات للقيام بمثل هذه التحريكات. مختصر القول الانتقال هو تغيير من حالة إلى أخرى. على سبيل المثال، عند تمرير مؤشر الفأرة فوق عنصر ما، فقد يتغير تنسيقه. الانتقالات تجعل التغيير يبدو كحركة متصلة وسلسة. تمرين كيف تبدو بيئة العمل خاصتك؟ قم بإلقاء نظرة على الشيفرة، وابحث عن الخاصية 'transition' في شيفرة CSS. هل يمكنك تخمين ما تفعله؟ في المرة القادمة التي تتصفح فيها الويب، ابحث عن أمثلة للانتقالات. ابحث عن التغييرات المثيرة للاهتمام، مثل ما يحدث عندما يُضاف عنصر جديد إلى الصفحة، أو عند تمرير مؤشر الفأرة فوق زر ما. ستجد أنّ الويب مليء بالتحريكات بمجرد أن تبدأ في البحث عنها. سنلقي في المقالة التالية نظرة عامة على الخاصية 'animation'، وكيف تختلف عن الخاصية 'transition'. المصادر ترجمة وبتصرف للفصل transitions من كتاب CSS Animation 101 لمؤلفه Donovan Hutchinson. اقرأ أيضًا المقالة التالية: مدخل إلى الحركات: مفهوم الحركة المقالة السابقة: تجهيز بيئة العمل لإنشاء الحركات النسخة العربية الكاملة من كتاب: التحريك عبر CSS
  6. سنتعلم في هذه الفقرة كيفية إنشاء تحريكات CSS ‏(CSS animations) ومشاهدتها في المتصفح. ولكن قبل البدء في كتابة الشيفرة، من الجيد أن نحدد سير العمل. هذه المقالة جزء من سلسلة من المقالات حول التحريك في CSS: ما هي استخدامات الحركات؟ تجهيز بيئة العمل لإنشاء الحركات مدخل إلى الانتقالات: التنقل بين الحالات مدخل إلى الحركات: مفهوم الحركة تطبيق عملي: الانتقالات خاصيات الانتقالات دوال التوقيت الانتقالات المتعددة الانتقالات وجافاسكربت تطبيق عملي: التحريكات خاصيات الحركات تطبيق عملي: الإطارات المفتاحية الحركات المتعددة المتزامنة موجز الحركات رواية القصص عبر الحركات حرب النجوم إظهار المحتوى أثناء التمرير سهولة الوصول نهاية الرحلة هناك طريقتان للتطوير: التطوير في المتصفح، والتطوير المحلي (offline). التطوير في المتصفح إنّ أبسط طريقة للقيام بالتجارب الصغيرة هي التطوير عبر الإنترنت. الموقع الذي أستخدمه غالبًا هو CodePen. موقع JS Fiddle يُعد خيارًا ممتازًا أيضًا. في بقية هذا الدرس، سأستخدم CodePen لكتابة الأمثلة، لذا فمن المهم أن تكون على دراية بطريقة عمله. CodePen عبارة عن منصة برمجية، والتي يمكنك أن تجري عليها تغييرات على شيفرة HTML و CSS و JavaScript، ورؤية النتائج على الفور. تنقسم الشاشة إلى أربعة أقسام، قسم المعاينة، وأقسام HTML و CSS و JavaScript. يوجد داخل كل قسم خيارات تسمح لك بإعداد اللغات (Sass بدلًا من CSS على سبيل المثال) وغيرها من الأشياء المفيدة. التطوير المحلي للمشاريع الأكبر، فأنا أفضل التطوير محليًا. هناك عدة طرق للقيام بذلك، والتي هي أكثر فعالية وسرعة من العمل في المتصفح مباشرة. الخيار الأبسط: إنشاء ملفّي HTML و CSS الخيار الأبسط هو إنشاء ملف HTML ‏(filename.html) وربطه بملف CSS ‏(filename.css) . هذه طريقة مقبولة، لكن يمكن أن تكون بطيئة، حيث سيكون عليك التنقل كثيرًا بين المتصفح والمحرر. لقد قمت بإنشاء مجموعة من ملفات HTML و CSS، يمكنك نسخها واستخدامها لبدء العمل. قم بتنزيلها من هنا. Dreamweaver / Macaw / Muse / Coda / Sublime يمكنك بالطبع استخدام أي أداة تجدها مريحة لإنشاء صفحات الويب. كل ما تحتاجه حقًا هو محرر نصوص. بعض الأدوات الأخرى تأتي مع إمكانية التحرير البصري، إذا كنت تفضل العمل بها، فافعل ذلك. أنا شخصيًا أوصي بالعمل مباشرة على الشيفرة. سيساعدك فهم طريقة عمل CSS على إصلاح المشاكل، أو إنشاء تأثيرات لا يمكن أن توفرها الأدوات البصرية. أداة البناء Gulp إذا كنت معتادًا على Github و Node ومعالجة الشيفرات، فقد ترغب في إعداد بيئة تطوير على جهازك. أنا أحب أداة Gulp. فكونُها تعتمد على Node، يجعلها سريعة جدًا. يمكن تجميع الوحدات لتحويل Sass إلى CSS والإصلاح التلقائي لدعم المتصفح، والمزامنة مع المتصفحات بحيث لا تحتاج إلى تحديث المتصفح في كل مرة تحدّث الشيفرة. إذا سبق واستخدمت Grunt، أو أدوات البناء الأخرى، فسيكون سير العمل مألوفًا. لقد قمت بإنشاء مستودع Github لجعل التطوير المحلي أسرع. إذا كنت متعودًا استخدامَ Git، فزُر صفحة المستودع، وطالع الملف readme الذي يحتوي إرشادات الإعداد. يمكنك تحسينه ومشاركته إذا رغبت في ذلك. مختصر القول أثناء تعلُّمك للتحريك في CSS، لا تتردد في التعديل على الشيفرة وتجربة أمور جديدة. قد ترغب في استضافة الشيفرة بنفسك، أو قد تُفضِّل استخدام CodePen، الأمر يعود لك. تأكد من أنه يمكنك تحويل الأفكار إلى شيفرة بسلاسة. تمرين سجّل في CodePen. أضف بعض شيفرات HTML و CSS، وانظر بنفسك كيف تتغير النتائج على الفور. من الجيد أيضًا مطالعة بعض أمثلة CodePen على الصفحة الرئيسية، وتعلم كيفية عملها. اختياري: إذا كنت ترغب في تجربة التطوير المحلي، فقم بتنزيل ملفات البدء المحلية: الخيار الأساسي: ملفات HTML و CSS لبدء المشروع متقدم: ملفات البدء Gulp & Sass المصادر ترجمة وبتصرف للفصل creative-environments من كتاب CSS Animation 101 لمؤلفه Donovan Hutchinson. اقرأ أيضًا المقالة التالية: مدخل إلى الانتقالات: التنقل بين الحالات المقالة السابقة: ما هي استخدامات الحركات؟ النسخة العربية الكاملة من كتاب: التحريك عبر CSS
  7. هذه المقالة جزء من سلسلة من المقالات حول التحريك في CSS: ما هي استخدامات الحركات؟ تجهيز بيئة العمل لإنشاء الحركات مدخل إلى الانتقالات: التنقل بين الحالات مدخل إلى الحركات: مفهوم الحركة تطبيق عملي: الانتقالات خاصيات الانتقالات دوال التوقيت الانتقالات المتعددة الانتقالات وجافاسكربت تطبيق عملي: التحريكات خاصيات الحركات تطبيق عملي: الإطارات المفتاحية الحركات المتعددة المتزامنة موجز الحركات رواية القصص عبر الحركات حرب النجوم إظهار المحتوى أثناء التمرير سهولة الوصول نهاية الرحلة قبل الدخول في الجانب الفني من التحريك (Animation) في CSS، سنناقش سبب القيام بالتحريك في المقام الأول. أبلغُ من الكلمات التحريك يمكن أن يوصل المعلومات بكفاءة، ويمكن استخدامه لجذب الانتباه، ولكن في جميع الحالات، فالهدف النهائي هو التواصل. إدراج الحركة في التصميمات يجعلها أكثر كفاءة في إيصال المعلومة. الصور المتحركة أبلغ من الكلمات الملفوظة أو المكتوبة. التحريك المعبّر والملائم يمكن أن يضيف جاذبية إلى تصاميمنا، و يعطي مصداقية إلى عملنا. ذلك لأننا اعتدنا على رؤية الحركة طوال الوقت في العالم "الحقيقي". لذلك، فإدخال الحركة إلى عملنا يجعله أكثر حيوية. مع التحسن المستمر في دعم المتصفحات للتحريك، فقد أصبحت خيارًا قابلًا للتطبيق أكثر من أي وقت مضى. من نواح كثيرة، التحريك قد يكون بنفس أهمية الخطوط التي نستخدمها، والتخطيطات التي ننشئها. ما المقصود بالتحريك في مجال تطوير المواقع؟ للتحريك فائدتان رئيسيتان: توصيل المعلومات، وجذب الانتباه. يمكننا إيجاد عدة طرق لاستخدام هاتين الميزتين أثناء تصميم المواقع. يمكن أن يكون التحريك معبرًا، كما يحدث عندما يتململ زر الحفظ في CodePen ليذكرنا بضرورة حفظ عملنا: نحن ماهرون في رصد الحركة. لذلك، فإنّ إضافة القليل من الحركة إلى عناصر الصفحة يمكن أن يضفي على الصفحة الحيوية. يمكننا أيضًا استخدام التحريك لتقديم المحتوى على الصفحة: يمكننا عن طريق تحريك المعلومات على الصفحة أن نعرض للقُرَّاء معلومة إضافية قد لا يمكن إيصالها بالمحتوى الساكن. يلفت التحريك الانتباه إلى المحتوى الجديد المُضاف، ويرسم سياق تلك المعلومة الجديدة. فسيظهر المحتوى الجديد فجأة بدون تقنية التحريك، وقد يظنّ القارئ أنه كان هناك طوال الوقت. يمكننا استخدام التحريك لرواية قصة: ‎‏ما سبق مقتبس من فيديو تعليمي للعبة "Portal". كتابة القصص لا يحتاج دائمًا إلى أن يكون حرفيًا. إذ يمكننا إضافة حركة معبّرة، مثل إظهار تغييرات البيانات في المخطط. بحيث يمكن للبيانات نفسها أن تروي قصة عبر الحركة. مع القوة العظيمة تأتي المسؤولية التحريك يفتح أمامنا آفاقًا كثيرة. إلا أنّ وجود الكثير من الأشياء المتحركة على الصفحة قد يشتت الانتباه. من الأفضل تجنّب التحريك عندما لا يكون ضروريًا، بحيث تكون لكل حركة تضيفها تأثير إيجابي. قد يعني هذا الاكتفاء بتحريك عنصر صغير فقط على صفحتك. فالأفضل ما قل ودل كما قيل. رغم كل شيء، إذا كنت ترغب في خلق تأثير بصري جذاب عبر القيام بالكثير من التحريك، فيمكنك القيام بذلك. لكن احرص على أن يكون ذلك بعيدًا عن المحتوى الذي تريد من المشاهدين أن يركزوا عليه. يمكنك فعل ذلك عبر جعل التحريك يحدث مرة واحدة فقط، بدلاً من تكراره باستمرار، أو بإيقاف التحريك عندما يبدأ الناس في تمرير الصفحة. مصدر إلهام التحريك له تاريخ طويل وغني. كتبت مؤخرًا منشورًا حول مبادئ التحريك على الويب بعنوان مبادئ التحريك في صفحات الويب باستخدام CSS. المبادئ مستمدة من كتاب ديزني (1981) Illusion of Life: Disney Animation. إذا كنت ترغب في التعمق في موضوع التحريك، فابحث عن مقاطع الفيديو التي تخص Animator’s Survival Kit videos. يوتيوب مليء بمصادر الإلهام والأفكار. للحصول على أمثلة متميزة وإبداعية، خصص بعض الوقت لتصفح Hover States. يعرض هذا الموقع جميع أنواع الأمثلة المبدعة للتحريك على الشبكة. موقع Dribbble.com مفيد أيضًا. على سبيل المثال، إليك مثالًا رائعًا من Dribbble يعرض مبادئ التصميم الخاصة بجوجل (Google’s Material Design principles. البحث عن "animation" هو أحد الطرق الجيدة للعثور على أفكار ملهمة. أتحقق كذلك بانتظام من مستجدات موقع CodePen. فهو مصدر رائع لصور وأمثلة التحريك. ملخص التحريك تقنية نافعة ومفيدة للغاية إذا استُخدِم بشكل صحيح، يمكن أن يكون أداةً مفيدةً في التصميم استخدمه لجذب الانتباه أو توصيل المعلومات لا تبالغ فيه إذا كنت تريد أن تتميز، فالتحريك قد يساعدك على ذلك تمرين فكّر في عملك ومشاريعك، وكيف يمكن أن يساعدك التحريك. يتحمس البعض ويبدؤون بتحريك كل شيء على الصفحة، حاول البحث عن طرق للاستفادة من التحريك بحيث تساعد زوارَك على فهم المحتوى بشكل أفضل. هل تريد أن توصل إلى زوار صفحتك شعورًا بالحركة والحيوية؟ هل هناك تغيير مفاجئ في صفحتك يحدث بسرعة وبشكل غير ملحوظ بحيث يمكن أن يُعرض بشكل أفضل عبر التحريك السلس؟ أخيرًا، ألق نظرة على مواقع مثل Hover States و Little Big Details و Dribbble. هذه المواقع ستساعدك كلما تحيّرت وأُغلِق عليك. المصادر ترجمة وبتصرف للفصل why animate من كتاب CSS Animation 101 لمؤلفه Donovan Hutchinson. اقرأ أيضًا المقالة التالية: تجهيز بيئة العمل لإنشاء الحركات النسخة العربية الكاملة من كتاب: التحريك عبر CSS
  8. السؤال الذي قد يتبادر إلى أذهان مُطوّري الجافا هو "ما الذي عليّ أن أتعلمه الآن؟". هناك مجموعة من اللغات التي تستحق الاعتبار، مثل Clojure, Rust أو Haskell. ولكن ماذا لو كنت تريد أن تتعلم شيئًا يساعدك على دفع الفواتير وهو فوق ذلك سهل الاستخدام؟ Kotlin قد يكون خيارك الأفضل، وسنحاول في هذه المقالة أن نشرح لماذا. ما هو Kotlin؟ لغة برمجة مُطوّرة من قبل JetBrains، والذين كانوا وراء فكرة IntelliJ IDEA IDE بالإضافة إلى أشياء أخرى. بديل بسيط ومرن لجافا تتلاءم جيدًا مع أكواد جافا الموجودة تُترجم لجافا bytecode تعمل على JVM كما تُترجم أيضًا لجافا سكريبت إن كنت قد قرأت وثائقها فستلاحظ مجموعة من الأمور المهمة: تتيح لك القيام بالكثير من الأشياء بأكواد قليلة تحل الكثير من المشاكل الموجودة في جافا تساعدك على الاستمرار في استخدام النظام البيئي ecosystem المعتاد لجافا تتيح لك برمجة الواجهة الأمامية والخلفية بنفس اللغة 100٪ متوافقة مع الجافا أداؤها جيد مقارنةً بالبدائل (Clojure, Scala) لا تضيف إلّا طبقةً رقيقة من التّعقيد على جافا يبدو هذا جيدًا، أليس كذلك؟ فيما يلي سنرى بعض الأمثلة لمقارنتها بالجافا. عناصر القِيَم مقابل بيانات الأصناف Value objects vs data classes ما تراه هنا هو كائن جافا قديم ((POJO مع الأنماط المعتادة: public class HexagonValueObject { private final int x; private final int y; private final int z; public HexagonValueObject(int x, int y, int z) { this.x = x; this.y = y; this.z = z; } public int getX() { return x; } public int getY() { return y; } public int getZ() { return z; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; HexagonValueObject hexagon = (HexagonValueObject) o; return getX() == hexagon.getX() && getY() == hexagon.getY() && getZ() == hexagon.getZ(); } @Override public int hashCode() { return Objects.hash(getX(), getY(), getZ()); } @Override public String toString() { return "HexagonValueObject{" + "x=" + x + ", y=" + y + ", z=" + z + '}'; } إنشاء عناصر القِيَم هو عملية مرهقة حتى باستخدام المكتبات مثل Lombok) Lombok يتطلب تثبيت ملحقة في بيئة العمل IDE الخاصة بك من أجل أن يعمل، وهو أمر قد لا يكون ممكنًا في جميع بيئات التطوير، يمكن تجاوز هذا المشكل بأدوات مثل Delombok، ولكنه اختراق أكثر منه حل للمشكلة)، على الأقل IDEA (أو Eclipse) يمنحك بعض المساعدة في توليد الكثير من تلك الوظائف، ولكنّ إضافة حقل ونسيان تعديل الوظيفة equals سيؤدي إلى مفاجآت سيئة. دعونا ننظر الآن إلى الكود المقابل في Kotlin: data class HexagonDataClass(val x: Int, val y: Int, val z: Int) مذهل! لقد اختصرنا الكثير من الكتابة بالمقارنة مع نسخة جافا. بيانات الأصناف في Kotlin تعطيك equals + hashCode و toString بالإضافة إلى المُحصّلات والمُعيِّنات (getters and setters). يمكنك أيضًا نسخها، مما يخلق كائنًا جديدًا مع إعادة كتابة بعض حقوله. حشو سلاسل النصوص String interpolation التعامل مع سلاسل النصوص في الجافا مرهق. ولكن يمكن تبسيطه باستخدام String.format ومع ذلك ستبقى قبيحة. public class JavaUser { private final String name; private final int age; public String toHumanReadableFormat() { return "JavaUser{" + "name='" + name + '\'' + ", age=" + age + '}'; } public String toHumanReadableFormatWithStringFormat() { return String.format("JavaUser{name='%s', age=%s}", name, age); } } Kotlin وجد حلّا لهذا، حيث أضاف مفهوم حشو السلاسل، مما بسّط استخدام المتغيرات في السلاسل الحرفية. ومكّن أيضًا استدعاء الوظائف منها! class KotlinUser(val name: String, val age: Int) { fun toHumanReadableFormat() = "JavaUser{name='$name', age=$age}" fun toHumanReadableFormatWithMethodCall() = "JavaUser{name='${name.capitalize()}', age=$age}" } دوال التمديد Extension functions كتابة المُلقّمات decorators في جافا يمكن أن يكون صعبًا، كما أنّها ليست مثالية. إذا كنت تريد أن تكتب مُلقّمًا، والذي يمكن استخدامه مع جميع الأصناف التي تقدّم List فلا يمكنك ببساطة استخدامه في الملقم خاصتك لأنّه سيحتاج منك أن تقدم الكثير من الوظائف الأخرى، لذلك ستكون مضطرًّا لتمديد AbstractList. public class ListPresenterDecorator<T> extends AbstractList<T> { private List<T> list; public ListPresenterDecorator(List<T> list) { this.list = list; } public String present() { return list.stream() .map(Object::toString) .collect(Collectors.joining(", ")); } @Override public T get(int index) { return list.get(index); } @Override public int size() { return list.size(); } } إذا كنت بحاجة إلى تلقيم شيء ما ولكنه لا يوفر أصنافًا أساسية base classes مفيدةً مثل AbstractList أو الصنف final، فأنت لا تستطيع ذلك ببساطة. لكن وظائف التمديد Extension methods يمكن أن تعطيك الحل! fun <T> List<T>.present() = this.joinToString(", ") تتصرف هذه الوظيفة كمُلقّم لجميع الأصناف Lists. لو قارنّا هذا ببديله في الجافا فإنّ هذ السطر الصغير أبسط بكثير، والأكثر من ذلك سوف يعمل أيضًا مع الأصناف final. حاول ألّا تسيء استخدامها وحسب. التحقق من العدمية Null safety التحقق من القيم المعدومة null ينطوي على الكثير من التعابير المنطقية والأنماط. مع ظهور الجافا، يمكنك أخيرًا التعامل مع هذه المشكلة بواسطة الصنف Optional، لكن ماذا لو كان مرجعOptional معدومًا null؟ ستحصل على NullPointerException، فلا زلنا بعد مرور 20 عام على ظهور جافا لا نستطيع تحديد العنصر الذي كان معدومًا. نأخذ المثال التالي: public class JavaUser { static class Address { String city; } private final String firstName; private final String lastName; private final List<Address> addresses; /** * If you want to make sure nothing is `null` * you have to check everything. */ public static String getFirstCity(JavaUser user) { if(user != null && user.addresses != null && !user.addresses.isEmpty()) { for(Address address : user.addresses) { if(address.city != null) { return address.city; } } } throw new IllegalArgumentException("This User has no cities!"); } } مع Kotlin، لديك العديد من الخيارات. إذا كان مشروعك متداخلًا مع مشاريع جافا يمكنك استخدام عامل التحقق من العدميّة (؟)null safety operator: data class KotlinUserWithNulls(val firstName: String?, // String? means that it is either a String object or a null val lastName: String?, val addresses: List<Address> = listOf()) { data class Address(val city: String?) companion object { fun fetchFirstCity(user: KotlinUserWithNulls?): String? { user?.addresses?.forEach { it.city?.let { return it } } return null } } } الكود الموجود بعد ؟ لن يُنفّذ إلا إن كان المعامل الأيسر operand غير معدوم not null. الدالة let تنشئ ارتباطًا محليًّا للكائن المُستدعى بحيث أنّ it سوف تشير إلى it.city. أما إن كان عملك مفصولًا عن الجافا فأقترح الابتعاد عن كل ما له علاقة بـ null data class KotlinUserWithoutNulls(val firstName: String, // this parameter can't be null val lastName: String, val addresses: List<Address> = listOf()) { data class Address(val city: String) companion object { fun fetchFirstCity(user: KotlinUserWithNulls) = user.addresses.first().city } } إذا لم يكن هناك أي استخدام لـ null (لا وجود لـ ؟) فكل شيء سيصبح أكثر بساطةً. استنباط النوع Type Inference Kotlin يدعم استنباط الأنواع، مما يعني أنه قادر على معرفة الأنواع من سياقها. أي مثل العامل الماسي diamond notation في الجافا <>! نأخذ المثال التالي: public class JavaUser { // ... private final String firstName; private final String lastName; private final List<Address> addresses; public Address getFirstAddress() { Address firstAddress = addresses.get(0); return firstAddress; } } هذا يبدو نفسه تقريبًا في Kotlin: data class KotlinUser(val firstName: String, val lastName: String, val addresses: List<Address> = listOf()) { data class Address(val city: String) /** * This is the same as in `JavaUser`. */ fun getFirstAddressNoInference(): Address { val firstAddress: Address = addresses.first() return firstAddress } } وذلك حتى تدع Kotlin تكتشف أنواع المتغيرات الخاصة بك: /** * Here the type of `firstAddress` is inferred from the context. */ fun getFirstAddressInferred(): Address { val firstAddress = addresses.first() return firstAddress } أو حتى الوظائف: /** * Here the return type is inferred. Note that * if a method consists of only one statement * you can omit the curly braces. */ fun getFirstAddress() = addresses.first() لا داعي للتحقق من الاستثناءات No checked exceptions ربما رأيت الكود التالي مليون مرة من قبل: public class JavaLineLoader { public List<String> loadLines(String path) { List<String> lines = new ArrayList<>(); try(BufferedReader br = new BufferedReader(new FileReader(path))) { String line; while((line = br.readLine()) != null) { lines.add(line); } } catch (IOException e) { e.printStackTrace(); } return lines; } } لأصحاب المدرسة القديمة في (IO in Java). لاحظوا التعليمة try في الكود أعلاه، الأمر نفسه سيُكتب هكذا في Kotlin: class KotlinLineLoader { fun loadLines(path: String) = File(path).readLines() } هناك بضعة أشياء ينبغي الانتباه إليها، أولًا: Kotlin ألغى التحقق من الاستثناءات. ثانيًا: Kotlin أضاف استخدام الكائنات من نوع Closeable أي: يمكنك أن تلاحظ أيضًا أنّ دالة التمديد (readLines) أضيفت إلى الصنف File. هذا النمط موجود بكثرة في Kotlin. إذا كنت قد استخدمت Guava أو Apache Commons أو شيء مماثل من قبل، فغالبًا سترى وظائف مشتركة تضاف منها إلى الصنف JDK كدالة تمديد. وهذا قد يكون مفيدًا لصحتك (أعصابك على الأقل). دعم لامبدا Lambda support دعونا نلقي نظرة على دعم lambda في الجافا: public class JavaFilterOperation { private List<String> items; @FunctionalInterface interface FilterOperation { Boolean filter(String element); } private List<String> filterBy(FilterOperation fn) { return items.stream() .filter(fn::filter) // applying the function .collect(Collectors.toList()); } public void doFilter() { filterBy((element) -> { return element.length() > 0; // calling the function with an actual lambda }); } } بما أنّه لا توجد قاعدة محددة لكتابة أنواع معاملات الوظائف method parameter types، فسيكون علينا إنشاء واجهة لها بأنفسنا. لاحظ أننا يمكن أن نستخدم Function<String, Boolean> هنا، ولكنها لن تعمل إلّا على الدوال ذات المعامل الواحد! هناك بعض الواجهات في JDK لحل هذه المشكلة، ولكن الكود سيكون مربكًا وغير واضح (مثلًا، ما دور BiFunction هنا؟)، Kotlin يحسّن هذا قليلًا: class KotlinFilterOperation { private val items = listOf<String>() fun filterBy(fn: (String) -> Boolean) = items.filter(fn) fun doFilter() { filterBy(String::isNotEmpty) // note the exension function `isNotEmpty` added to `String`! } } Kotlin يضيف قاعدةً نحويةً من أجل تمرير الدوال كمعاملات: (ParamType1, ...ParamTypeN) -> ReturnType. Kotlin يوفر مراجع للوظائف والحقول، كما يمكنك أيضًا التأشير إلى وظيفة من كائن ملموس! باستخدام المثال أعلاه يمكنني التأشير إلى الوظيفة filterBy من عيّنة instance ملموسة، هكذا: val reference = KotlinFilterOperation()::filterBy البرمجة الوظيفيّة Functional programming البرمجة الوظيفيّة أصبح لها ضجة في الوقت الحاضر. ومع جافا 8، قدمت Oracle مقاربتها لهذا الموضوع: الواجهة البرمجية Stream (Stream API). وهي تعمل كالتالي: public class JavaUser { // ... public static Set<String> fetchCitiesOfUsers( List<JavaUser> users) { return users.stream() .flatMap(user -> user.addresses.stream()) .map(JavaUser.Address::getCity) .collect(Collectors.toSet()); } } المقابل في Kotlin مشابه إلى حد ما، ولكن باختلافات دقيقة: fun fetchCitiesOfUsers(users: List<KotlinUser>) = users .flatMap(KotlinUser::addresses) .map(Address::city) .toSet() لا يوجد تحويل صريح للتسلسلات streams بما أنّ كل تجميعات Kotlin تدعمها. كنتيجة مباشرة لذلك، لن يكون عليك تمرير لامبدا لـ flatMap. تجميع النتيجة يحدث تلقائيًّا (لا حاجة لاستدعاء الوظيفة *Collectors.to). واستخدامنا لـ toSet هنا سببه الوحيد أننا نريد إرجاع عنصر من نوع Set. وإلا فيمكن حذف.toSet() توافقية جافا وKotlin يمكن أن يكون هذا معقدًا لمعظم النّاس، لكن JetBrains تعاملت مع هذا الأمر بالشكل الصحيح: public class KotlinInterop { public void helloJava() { System.out.println("Hello from Java!"); } public void helloKotlin() { JavaInterop.createInstance().helloKotlin(); } } class JavaInterop { fun helloJava() { KotlinInterop().helloJava() } fun helloKotlin() { println("Hello from Kotlin!") } companion object { @JvmStatic fun createInstance() = JavaInterop() } } التداخل هنا سلس وسهل. جافا وKotlin يمكن أن يتعايشا معًا في نفس المشروع، كما يقدم Kotlin مجموعة من التعبيرات (مثل @JvmStatic) لتسهيل استدعاء كود Kotlin من الجافا دون أية مشاكل. قد تكون قد لاحظت من هذه الأمثلة أن Kotlin يستعير الأفكار الجيدة من جافا ويُحسّنها ويحاول أن يختصر عليك الوقت والعمل إلى أدنى حد ممكن. الأنباء الأخيرة حول اعتزام جوجل جعل Kotlin واحدةً من اللغات المعتمدة علىAndroid تدعم هذا التوجه أيضًا. أشياء لتخبر بها مديرك: إذا كنت ترغب في إعطاء Kotlin فرصة فإليك بعض النصائح التي ستساعدك عند التفاوض مع مديرك أو زملائك في الفريق: Kotlin خرج من عالم الصناعة، وليس من العالم الأكاديمي. فهو يحل المشاكل التي يواجها المبرمجون اليوم. حر ومفتوح المصدر يأتي ومعه أداة مفيدة لتحويل جافا إلى Kotlin يمكنك مزج Kotlin وجافا دون جهد يُذكر يمكنك استخدام جميع أدوات وأُطُر عمل جافا الموجودة Kotlin مدعوم من قبل أفضل بيئة عمل في السوق (مع نسخة مجانية) من السهل قراءته، فحتى المبرمجون غير المتخصصون في Kotlin يمكنهم مراجعة الأكواد المكتوبة به لا تحتاج إلى رهن مشروعك بـ Kotlin: يمكنك البدء في المرحلة الأولى بكتابة الاختبارات فيه. من غير المرجح أن تتخلى JetBrains عن Kotlin لأنه يُقوّي مبيعاتها Kotlin لديه مجتمع حيوي، كما يمكنك بسهولة أن تساهم في Kotlin وتقترح ميزات جديدة باستخدام KEEP إذن، هل يستحق Kotlin الضجة المثارة حوله؟ الحكم لك. ترجمة -وبتصرّف- للمقال Kotlin is the new Java لكاتبه Adam Arold
  9. ملاحظة: يفترض هذا الدليل أنك تستخدم مصرّف Babel، كما يتطلّب استخدام إعدادات babel-preset-airbnb المسبقة أو ما يماثلها. ويفترض أيضًا أنّك ثبّت ترقيعات متعدّدة (Polyfills/Shims)، عبر airbnb-browser-shims أو ما يماثلها. أنواع البيانات الأنواع البدائية (Primitives) عندما تتعامل مع نوع بدائي فأنت تعمل مباشرةً على قيمته. سلاسل المحارف string، الأعداد number، القيم المنطقية boolean، القيمة المعدومة null، القيمة غير المعرَّفة undefined، الرموز symbol، الأعداد الصحيحة الكبيرة bigint. const foo = 1; let bar = foo; bar = 9; console.log(foo, bar); // => 1, 9 لا يمكن ترقيع النوعين symbol وbigint بدقّة، لذا ينبغي ألّا تُستخدَم عند استهداف المتصفحات/البيئات التي لا تدعمها تلقائيّا. الأنواع المركبة (Complex) عند التعامل مع نوع مركّب فأنت تعمل على مرجعٍ لقيمته. الكائنات object، المصفوفات array، الدوال function. const foo = [1, 2]; const bar = foo; bar[0] = 9; console.log(foo[0], bar[0]); // => 9, 9 المراجع References استخدم const لجميع مراجعك. وتجنب استخدام var. استخدم قاعدتي prefer-const وno-const-assign في ESlint. لماذا؟ لأنّ هذا سيضمن لك ألّا تعيد تعيين مراجعك، وهو ما يمكن أن يؤدي إلى أخطاء، ويُصعّب فهم الشفرة البرمجية. // سيئ var a = 1; var b = 2; // جيّد const a = 1; const b = 2; إن كنت مضطرًّا لإعادة تعيين المراجع، استخدم let بدلاً من var. استخدم قاعدة no-var في ESlint. لماذا؟ لأن نطاق تعريف let محدود بالكتلة البرمجية (Block-scoped) وليس محدودًا داخل الدالة (Function-scoped) كما هو الحال مع var. // سيئ var count = 1; if (true) { count += 1; } // جيد، استخدم let. let count = 1; if (true) { count += 1; } تذكر أن نطاق كل من let و constمحدود بالكتلة. // لا توجد المتغيّرات المصرَّح عنها ب let وconst إلّا بداخل الكتل المُصرَّح فيها { let a = 1; const b = 1; } console.log(a); // خطأ في المرجع ReferenceError console.log(b); // خطأ في المرجع ReferenceError الكائنات Objects استخدم صياغة تصنيف النوع (Literal syntax) لإنشاء الكائنات. استخدم قاعدة no-new-object في ESLint. // سيئ const item = new Object(); // جيّد const item = {}; استخدم أسماء محسوبة للخاصيّات عند إنشاء كائنات بأسماء خاصيّات ديناميكية. لماذا؟ لأن ذلك سيسمح لك بتعريف جميع خاصيّات الكائن في مكان واحد. function getKey(k) { return `a key named ${k}`; } // سيئ const obj = { id: 5, name: 'San Francisco', }; obj[getKey('enabled')] = true; // جيّد const obj = { id: 5, name: 'San Francisco', [getKey('enabled')]: true, }; استخدم أسلوب التعريف المختصر لتوابع الكائن. (قاعدة object-shorthand في ESLint). // سيئ const atom = { value: 1, addValue: function (value) { return atom.value + value; }, }; // جيّد const atom = { value: 1, addValue(value) { return atom.value + value; }, }; استخدم التعريف المختصر لقيمة الخاصية (قاعدة object-shorthand في ESLint). لماذا؟ لأنه أقصر وأوضح. const lukeSkywalker = 'Luke Skywalker'; // سيئ const obj = { lukeSkywalker: lukeSkywalker, }; // جيّد const obj = { lukeSkywalker, }; اجمع الخاصيّات المُختصرة في بداية التصريح بالكائن (Object declaration). لماذا؟ لأنّ هذه الطريقة تسهّل معرفة أي الخاصيّات تستخدم الاختصار. const anakinSkywalker = 'Anakin Skywalker'; const lukeSkywalker = 'Luke Skywalker'; // سيئ const obj = { episodeOne: 1, twoJediWalkIntoACantina: 2, lukeSkywalker, episodeThree: 3, mayTheFourth: 4, anakinSkywalker, }; // جيّد const obj = { lukeSkywalker, anakinSkywalker, episodeOne: 1, twoJediWalkIntoACantina: 2, episodeThree: 3, mayTheFourth: 4, }; لا تضع بين علامات التنصيص إلّا الخاصيّات التي لها أسماء غير صالحة. لماذا؟ بشكل عام، لأنّه أسهل للقراءة ويُحسّن وضوح الكود، كما أنّه يسهُل استخدامه من قبل محركات الجافا سكريبت. // سيئ const سيئ = { 'foo': 3, 'bar': 4, 'data-blah': 5, }; // جيّد const جيّد = { foo: 3, bar: 4, 'data-blah': 5, }; لا تستدع توابع Object.prototype ، مثل hasOwnProperty، propertyIsEnumerable، و isPrototypeOf، لا تستدعها مباشرة. استخدم قاعدة no-prototype-builtins في ESLint. لماذا؟ لأنّ خاصيّات الكائن قد تغطّي تلك التوابع - انظر مثلًا إلى {hasOwnProperty: false} – علاوة على أن الكائن قد يكون معدومًا (Object.create(null)). // سيئ console.log(object.hasOwnProperty(key)); // جيّد console.log(Object.prototype.hasOwnProperty.call(object, key)); // أفضل const has = Object.prototype.hasOwnProperty; // cache the lookup once, in module scope. /* أو */ import has from 'has'; // https://www.npmjs.com/package/has // ... console.log(has.call(object, key)); يفضل تطبيق عامل التمديد (Spread operator) على الكائن بدلًا من استخدام التابع Object.assign إن كنت تريد النسخ السطحي (Shallow-copy) للكائنات. أو يمكنك استخدام عامل الاستناد (Rest operator) للحصول على كائن جديد مع حذف خصائص معينة. // سيئ جدًّا const original = { a: 1, b: 2 }; const copy = Object.assign(original, { c: 3 }); // يتسبّب في التعديل على الكائن `original` delete copy.a; // الأمر نفسه هنا // سيئ const original = { a: 1, b: 2 }; const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 } // جيّد const original = { a: 1, b: 2 }; const copy = { ...original, c: 3 }; // copy => { a: 1, b: 2, c: 3 } const { a, ...noA } = copy; // noA => { b: 2, c: 3 } المصفوفات Arrays استخدم صياغة تصنيف النوع (Literal syntax) لإنشاء المصفوفة (قاعدة no-array-constructor في ESLint). // سيئ const items = new Array(); // جيّد const items = []; استخدم Array#push بدلًا من الإسناد المباشر لإضافة عناصر إلى المصفوفة. const someStack = []; // سيئ someStack[someStack.length] = 'abracadabra'; // جيّد someStack.push('abracadabra'); استخدم تمديد المصفوفات ... (Array spreads) لنسخ المصفوفات. // سيئ const len = items.length; const itemsCopy = []; let i; for (i = 0; i < len; i += 1) { itemsCopy[i] = items[i]; } // جيّد const itemsCopy = [...items]; استخدم عامل التمديد ... بدلًا من التابع Array.from لتحويل كائن مُكرَّر (Iterable) إلى مصفوفة. const foo = document.querySelectorAll('.foo'); // جيّد const nodes = Array.from(foo); // أفضل const nodes = [...foo]; استخدم التابع Array.from بدلًا من عامل التمديد ... لتحويل كائن شبيه بالمصفوفات إلى مصفوفة. const arrLike = { 0: 'foo', 1: 'bar', 2: 'baz', length: 3 }; // سيئ const arr = Array.prototype.slice.call(arrLike); // جيّد const arr = Array.from(arrLike); استخدم التابع Array.from بدلًا من عامل التمديد ... لتطبيق الدالة map على الكائنات المُكرّرة، بهدف تجنّب خلق مصفوفة مؤقتة. // سيئ const baz = [...foo].map(bar); // جيّد const baz = Array.from(foo, bar); استخدم التعليمة return في رد نداء توابع المصفوفات (Method callbacks). لا ضير في حذف التعليمة return إن كان متن الدالة يتكون من تعليمة واحدة من دون آثار جانبية. استخدم قاعدة array-callback-return في ESLint. // جيّد [1, 2, 3].map((x) => { const y = x + 1; return x * y; }); // جيّد [1, 2, 3].map(x => x + 1); // سيئ -عدم وجود قيمة مُرجَعة يعني أن "acc" يصبح غير معرّف بعد عملية التكرار الأولى [[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => { const flatten = memo.concat(item); memo[index] = flatten; }); // جيّد [[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => { const flatten = memo.concat(item); memo[index] = flatten; return flatten; }); // سيئ inbox.filter((msg) => { const { subject, author } = msg; if (subject === 'Mockingbird') { return author === 'Harper Lee'; } else { return false; } }); // جيّد inbox.filter((msg) => { const { subject, author } = msg; if (subject === 'Mockingbird') { return author === 'Harper Lee'; } return false; }); استخدم سطرًا جديدًا بعد معقوفة الفتح وقبل معقوفة إغلاق المصفوفة إذا كان المصفوفة متعددة الأسطر. // سيئ const arr = [ [0, 1], [2, 3], [4, 5], ]; const objectInArray = [{ id: 1, }, { id: 2, }]; const numberInArray = [ 1, 2, ]; // جيّد const arr = [[0, 1], [2, 3], [4, 5]]; const objectInArray = [ { id: 1, }, { id: 2, }, ]; const numberInArray = [ 1, 2, ]; التفكيك Destructuring استخدم تفكيك الكائن عند التعامل مع عدة خاصيّات للكائن. لماذا؟ التفكيك يُعفيك من الحاجة إلى إنشاء مراجع مؤقتة لتلك الخصائص. // سيئ function getFullName(user) { const firstName = user.firstName; const lastName = user.lastName; return `${firstName} ${lastName}`; } // جيّد function getFullName(user) { const { firstName, lastName } = user; return `${firstName} ${lastName}`; } // أفضل function getFullName({ firstName, lastName }) { return `${firstName} ${lastName}`; } استخدم تفكيك المصفوفات. const arr = [1, 2, 3, 4]; // سيئ const first = arr[0]; const second = arr[1]; // جيّد const [first, second] = arr; استخدم تفكيك الكائن لأجل إرجاع أكثر من قيمة، وليس تفكيك المصفوفات. لماذا؟ يمكنك إضافة خاصيات جديدة مع الوقت وتغيير الترتيب دون مشاكل. // سيئ function processInput(input) { // then a miracle occurs return [left, right, top, bottom]; } // تحتاج إلى أخذ ترتيب البيانات المُرجَعة في الحسبان const [left, __, top] = processInput(input); // جيّد function processInput(input) { // then a miracle occurs return { left, right, top, bottom }; } // تختار البيانات التي تحتاجها فقط const { left, top } = processInput(input); السلاسل النصية Strings استخدم علامات التنصيص المفردة '' لتحديد السلاسل النصيّة (القاعدة quotes في ESLint). // سيئ const name = "Capt. Janeway"; // سيئ - يجب أن تحتوي القوالب مصنَّفة النوع على حشو (Interpolation) أو أسطر جديدة. const name = `Capt. Janeway`; // جيّد const name = 'Capt. Janeway'; ينبغي ألّا تُكتَب النصوص التي يتجاوز طولها 100 حرف على أسطر متعددة باستخدام ضمّ النصوص (Concatenation). لماذا؟ النصوص التي فيها أخطاء تكون مزعجةً وتجعل الكود أقل قابلية للبحث. // سيئ const errorMessage = 'This is a super long error that was thrown because \ of Batman. When you stop to think about how Batman had anything to do \ with this, you would get nowhere \ fast.'; // سيئ const errorMessage = 'This is a super long error that was thrown because ' + 'of Batman. When you stop to think about how Batman had anything to do ' + 'with this, you would get nowhere fast.'; // جيّد const errorMessage = 'This is a super long error that was thrown because of Batman. When you stop to think about how Batman had anything to do with this, you would get nowhere fast.'; عند بناء النصوص برمجيًّا، استخدم القوالب النصيّة بدلًا من ضمّ النصوص (قاعدة prefer-template template-curly-spacing في ESLint). لماذا؟ تجعل القوالب النصيّة الشفرة أكثر مقروئية وإيجازًا، مع أسطر جديدة مناسبة وميزات حشو النصوص. // سيئ function sayHi(name) { return 'How are you, ' + name + '?'; } // سيئ function sayHi(name) { return ['How are you, ', name, '?'].join(); } // سيئ function sayHi(name) { return `How are you, ${ name }?`; } // جيّد function sayHi(name) { return `How are you, ${name}?`; } لا تطبّق أبدًا ()eval على النصوص، لأنها تتسبّب في الكثير من الثغرات. استخدم قاعدة no-eval في ESLint. تجنب تخليص الأحرف (Escape characters) في النصوص قدر الإمكان. لماذا؟ الخط المائل العكسي يضر بالمقروئية، وبالتالي يجب ألًا يُستخدم إلا عند الضرورة. // سيئ const foo = '\'this\' \i\s \"quoted\"'; // جيّد const foo = '\'this\' is "quoted"'; const foo = `my name is '${name}'`; الدوال استخدم الدوال العبارات (Function expressions) المُسماة بدلًا من التصريح بالدوال (قاعدة func-style في ESLint). لماذا؟ يضع محرَك جافاسكريبت الدوال المُصرَّح بها في بداية النطاق (تُعرَف هذه العملية بالرفع Hoist)، وبالتالي يمكن استخدام الدالة قبل تعريفها في الملف. يعني ذلك أنه سيكون من السهل الإحالة إلى الدالة قبل أن تُعرَّف في الملف، وهو ما يضر المقروئية وقابلية الصيانة. إذا وجدت أن تعريف دالة ما كبير أو معقد حدّ الإرباك، فربما حان الوقت لوضعه في وحدة خاصة به! لا تنس أن تُسمّي العبارة صراحة، بغض النظر عما إذا كان الاسم مستنتجًا من المتغير الحاوي (كما هو الحال غالبًا في المتصفحات الحديثة أو عند استخدام مصرّفات مثل Babel) أم لا. تلغي هذه الطريقة أي افتراضات حول مكدس النداء إلى الخطأ Error’s call stack // سيئ function foo() { // ... } // سيئ const foo = function () { // ... }; // جيّد // اسم العبارة الدالة مغاير لاسم الاستدعاء الذي يحيل إلى المتغيّر const short = function longUniqueMoreDescriptiveLexicalFoo() { // ... }; ضع الدوال العبارات فورية الاستدعاء (Immediately-invoked Function Expression، أو IIFE اختصارًا)، ضعها بين قوسين. استخدم قاعدة wrap-iife في ESLint. لماذا؟ الدوال العباراة فورية الاستدعاء هي وحدة منفردة، لذلك وضعها، هي وعبارة استدعائها، بين قوسين يجعل الشفرة واضحة. وإن كان من المستبعد جدّا أن تحتاج الدوال فورية الاستدعاء (IIFE) في مشاريع تكثر من استخدام الوحدات الوظيفية (Modules). // immediately-invoked function expression (IIFE) (function () { console.log('Welcome to the Internet. Please follow me.'); }()); لا تصرّح أبدًا بالدوال في كتلة غير خاصة بالدوال (مثل if، وwhile، …إلخ). أسند الدالة إلى متغير بدلًا من ذلك. تسمح المتصفحات بالتصريح بالدالة في تلك الكتل، إلّا أنها ستفسرها بطرق مختلفة، وهو أمر لا يحبه المبرمجون. استخدم قاعدة no-loop-func في ESLint. ملحوظة: يعرّف معيار ECMA-262 الكتلة (Block) بأنها قائمة من التعليمات البرمجية (Statements). والتصريح بدالة (Function decleration) ليس تعليمة // سيئ if (currentUser) { function test() { console.log('Nope.'); } } // جيّد let test; if (currentUser) { test = () => { console.log('Yup.'); }; } لا تسمّي معاملًا بالاسم arguments. لأنه سيأخذ الأسبقية على الكائن arguments الذي يُحدد تلقائيّا في نطاق كل دالة. // سيئ function foo(name, options, arguments) { // ... } // جيّد function foo(name, options, args) { // ... } لا تستخدم أبدًا المعامل arguments، واستخدم بدلًا منه عامل التمديد (...). لماذا؟ عامل التمديد واضح في تحديد الوسائط التي تريد سحبها. بالإضافة إلى ذلك، وسائط عامل التمديد هي مصفوفة حقيقة، وليست شبيهة بالمصفوفة مثل الكائن arguments. استخدم قاعدة prefer-rest-params في ESLint. // سيئ function concatenateAll() { const args = Array.prototype.slice.call(arguments); return args.join(''); } // جيّد function concatenateAll(...args) { return args.join(''); } استخدم الصيغة الافتراضية للمعاملات بدلًا من التعديل على وسائط الدالة. // سيئ جدّا function handleThings(opts) { // لا! يجب ألّا نعدّل على وسائط الدالة. // أمر سيئ آخر: إذا كانت قيمة الوسيط opts تساوي القيمة المنطقية false فسيُسنَد كائن إلى الوسيط opts، وهو ما قد تريده إلّا أنه يتسبب في ثغرات تصعب ملاحظتها opts = opts || {}; // ... } // سيئ أيضا function handleThings(opts) { if (opts === void 0) { opts = {}; } // ... } // جيّد function handleThings(opts = {}) { // ... } تجنب الآثار الجانبية في المعاملات الافتراضية. لماذا؟ لأنّها مربكة. var b = 1; // سيئ function count(a = b++) { console.log(a); } count(); // 1 count(); // 2 count(3); // 3 count(); // 3 ضع دائمًا المعاملات الافتراضية في الأخير. // سيئ function handleThings(opts = {}, name) { // ... } // جيّد function handleThings(name, opts = {}) { // ... } لا تستخدم أبدًا منشئ الدوال (Function constructor) لإنشاء دالة جديدة. استخدم قاعدة no-new-func في ESLint. لماذا؟ إنشاء دالة بهذه الطريقة يتضمّن تقييم نص كما تفعل ()eval، وهو ما يفتح نقاط ضعف. // سيئ var add = new Function('a', 'b', 'return a + b'); // سيئ كذلك var subtract = Function('a', 'b', 'return a - b'); ضع مسافات في توقيع الدالة. استخدم قاعدتي space-before-function-paren وspace-before-blocks في ESLint. لماذا؟ الاتساق أمر جيد، كما لن تكون مضطرًا لإضافة أو إزالة مسافة عند إضافة أو إزالة اسم. // سيئ const f = function(){}; const g = function (){}; const h = function() {}; // جيّد const x = function () {}; const y = function a() {}; لا تعدّل أبدًا على المعاملات. استخدم قاعدة no-param-reassign في ESLint. لماذا؟ يمكن للتعديل على الكائنات التي مُرُرت كمعاملات أن يتسبب في آثار جانبية غير مرغوب فيها في المستدعي الأصلي. // سيئ function f1(obj) { obj.key = 1; } // جيّد function f2(obj) { const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1; } لا تُعد إسناد المعاملات. استخدم قاعدة no-param-reassign في ESLint. لماذا؟ إعادة إسناد المعاملات يمكن أن يؤدي إلى سلوك غير متوقع، خصوصًا عند التعامل مع الكائن arguments. كما يمكن أن يسبب مشاكل في الأداء، خصوصا في V8. // سيئ function f1(a) { a = 1; // ... } function f2(a) { if (!a) { a = 1; } // ... } // جيّد function f3(a) { const b = a || 1; // ... } function f4(a = 1) { // ... } من الأفضل استخدام استخدام عامل التمديد (...) لاستدعاء الدوال متغيرة عدد الوسائط (Variadic functions). استخدم قاعدة prefer-spread في ESLint. لماذا؟ لأنها أوضح، فلست مضطرًّا لتجهيز السياق، كما لا يمكنك أن تجمع بسهولة new مع apply. // سيئ const x = [1, 2, 3, 4, 5]; console.log.apply(console, x); // جيّد const x = [1, 2, 3, 4, 5]; console.log(...x); // سيئ new (Function.prototype.bind.apply(Date, [null, 2016, 8, 5])); // جيّد new Date(...[2016, 8, 5]); الدوال التي لها توقيعات أو استدعاءات متعددة الأسطر، ينبغي أن تكون مسافاتها البادئة تمامًا مثل كل القوائم متعددة الأسطر الأخرى في هذا الدليل، بمعنى أن كل عنصر في سطر، مع فاصلة زائدة بعد العنصر الأخير. // سيئ function foo(bar, baz, quux) { // ... } // جيّد function foo( bar, baz, quux, ) { // ... } // سيئ console.log(foo, bar, baz); // جيّد console.log( foo, bar, baz, ); الدوال السهمية Arrow Functions استخدم صيغة الدالة السهمية عندما تكون مضطرًّا لاستخدام دالة مجهولة (Anonymous function)، مثلًا عند تمرير ردّ نداء (Callback) على السطر. استخدم قاعدتي prefer-arrow-callback وarrow-spacing في ESlint. لماذا؟ لأنها تخلق نسخة من الدالة تُنفَّذ في السياق this، وهو عادةً ما ترغب فيه، كما أنها أكثر إيجازا. متى تتخلّى عنها؟ إذا كانت لديك دالة معقدة، فيمكنك نقل وظيفتها إلى دالة عبارة مُسمّاة. // سيئ [1, 2, 3].map(function (x) { const y = x + 1; return x * y; }); // جيّد [1, 2, 3].map((x) => { const y = x + 1; return x * y; }); إن كان متن الدالة يتكون من تعليمة واحدة تُرجِع تعبيرًا دون آثار جانبية، فاحذف القوسين المعقوصيْن ({}) واستخدم الإرجاع الضمني (بدون التعليمة return). خلافًا لذلك، أبق على الأقواس المعقوصة واستخدم التعليمة return. استعن بقاعدتي arrow-parens و arrow-body-style في ESLint. لماذا؟ تسهيل قراءة الشفرة عند استخدام دوال بالتسلسل. // سيئ [1, 2, 3].map(number => { const nextNumber = number + 1; `A string containing the ${nextNumber}.`; }); // جيّد [1, 2, 3].map(number => `A string containing the ${number}.`); // جيّد [1, 2, 3].map((number) => { const nextNumber = number + 1; return `A string containing the ${nextNumber}.`; }); // جيّد [1, 2, 3].map((number, index) => ({ [index]: number, })); // لا يوجد إرجاع ضمني لكن توجد آثار جانبية function foo(callback) { const val = callback(); if (val === true) { // افعل شيئًا هنا إذا كان رد النداء إيجابيا } } let bool = false; // سيئ foo(() => bool = true); // جيّد foo(() => { bool = true; }); في حال امتد التعبير عبر عدة أسطر، ضعه بين قوسين لمقروئية أكبر. لماذا؟ لتوضيح أين تبدأ وأين تنتهي الدالة. // سيئ ['get', 'post', 'put'].map(httpMethod => Object.prototype.hasOwnProperty.call( httpMagicObjectWithAVeryLongName, httpMethod, ) ); // جيّد ['get', 'post', 'put'].map(httpMethod => ( Object.prototype.hasOwnProperty.call( httpMagicObjectWithAVeryLongName, httpMethod, ) )); أضف دائمًا أقواسًا حول الوسائط من أجل الوضوح والتناسق. استعن بقاعدة arrow-parens في ESlint. لماذا؟ تقليل الأخطاء عند إضافة وسائط أو حذفها. // سيّئ [1, 2, 3].map(x => x * x); // جيّد [1, 2, 3].map((x) => x * x); // سيّئ [1, 2, 3].map(number => ( `A long string with the ${number}. It’s so long that we don’t want it to take up space on the .map line!` )); // جيّد [1, 2, 3].map((number) => ( `A long string with the ${number}. It’s so long that we don’t want it to take up space on the .map line!` )); // سيّئ [1, 2, 3].map(x => { const y = x + 1; return x * y; }); // جيّد [1, 2, 3].map((x) => { const y = x + 1; return x * y; }); تجنب الخلط بين صياغة الدوال السهمية (=>) وبين عوامل المقارنة (<= , >=). استعن بقاعدة no-confusing-arrow. // سيئ const itemHeight = item => item.height > 256 ? item.largeSize : item.smallSize; // سيئ const itemHeight = (item) => item.height > 256 ? item.largeSize : item.smallSize; // جيّد const itemHeight = item => (item.height > 256 ? item.largeSize : item.smallSize); // جيّد const itemHeight = (item) => { const { height, largeSize, smallSize } = item; return height > 256 ? largeSize : smallSize; }; افرض موقع متن الدوال السهميّة عن طريق الإرجاع الضمني (Implicit return). استعن بقاعدة implicit-arrow-linebreak. // سيّئ (foo) => bar; (foo) => (bar); // جيّد (foo) => bar; (foo) => (bar); (foo) => ( bar ) الأصناف والمُنشِئات Classes & Constructors استخدم دائمًا الكلمة المفتاحية class. وتجنب التعامل مع الخاصيّة prototype مباشرة. لماذا؟ العبارة class أكثر إيجازا ووضوحا. // سيئ function Queue(contents = []) { this.queue = [...contents]; } Queue.prototype.pop = function () { const value = this.queue[0]; this.queue.splice(0, 1); return value; }; // جيّد class Queue { constructor(contents = []) { this.queue = [...contents]; } pop() { const value = this.queue[0]; this.queue.splice(0, 1); return value; } } استخدم الكلمة المفتاحية extends للتوارث بين الأصناف. لماذا؟ لأنها وسيلة مدمجة لوارثة الوظائف من النموذج الأولي دون التسبب بمشاكل عند استخدام العامل instanceof. // سيئ const inherits = require('inherits'); function PeekableQueue(contents) { Queue.apply(this, contents); } inherits(PeekableQueue, Queue); PeekableQueue.prototype.peek = function () { return this.queue[0]; }; // جيّد class PeekableQueue extends Queue { peek() { return this.queue[0]; } } يمكن للتوابع أن تُرجع الكائن this للمساعدة استخدام التوابع بالتسلسل. // سيئ Jedi.prototype.jump = function () { this.jumping = true; return true; }; Jedi.prototype.setHeight = function (height) { this.height = height; }; const luke = new Jedi(); luke.jump(); // => true luke.setHeight(20); // => undefined // جيّد class Jedi { jump() { this.jumping = true; return this; } setHeight(height) { this.height = height; return this; } } const luke = new Jedi(); luke.jump() .setHeight(20); لا بأس في تخصيص التابع ()toString، لكن تأكد من أنه يعمل بسلاسة ولا تتسبب في أي آثار جانبية. class Jedi { constructor(options = {}) { this.name = options.name || 'no name'; } getName() { return this.name; } toString() { return `Jedi - ${this.getName()}`; } } توجد منشئات افتراضية للأصناف يُلجَا إليها إنْ لم يُحدّد منشئ سلفا. لذا، فلا حاجة لمنشئات فارغة أو منشئات تقتصر على الإنابة عن الصنف الأب. استعن بقاعدة no-useless-constructor في ESLint. // سيئ class Jedi { constructor() {} getName() { return this.name; } } // سيئ class Rey extends Jedi { constructor(...args) { super(...args); } } // جيّد class Rey extends Jedi { constructor(...args) { super(...args); this.name = 'Rey'; } } تجنب تكرار عناصر الصنف. استعن بقاعدة no-dupe-class-members في ESLint. لماذا؟ في حال التصريح المكرر لعنصر من عناصر الصنف، فالقيمة الأخيرة فقط هي التي ستُعتمَد. وجود التكرار يعني بالضرورة وجود علّة في الشفرة البرمجية. // سيئ class Foo { bar() { return 1; } bar() { return 2; } } // جيّد class Foo { bar() { return 1; } } // جيّد class Foo { bar() { return 2; } } يجب أن تستخدم توابع الصنف الكائن this أو تُدرَج في تابع ثابت (Static) إلّا إذا استدعت مكتبة خارجية أو إطار عمل استخدام توابع غير ثابتة. كون التابع مرتبطًا بنظير كائن (Instance) يجب أن يشير إلى أنه يتصرف بسلوك مختلف حسب خاصيّات الكائن المستقبل. استعن بالقاعدة class-methods-use-this. // سيّئ class Foo { bar() { console.log('bar'); } } // جيّد، استخدام this class Foo { bar() { console.log(this.bar); } } // جيّد، المنشئ مستثنى من القاعدة constructor() { // ... } } // جيّد، يفترض ألا تستخدم التوابع الثابتة الكائن this class Foo { static bar() { console.log('bar'); } } الوحدات Modules استخدم دائمًا الكلمتيْن المفتاحيتيْن (import/export) لاستيراد أو تصدير الوحدات، بدلًا من الطرق الأخرى غير القياسية. يمكنك دائمًا تصريف الوحدات المفضلة لديك إلى شفرة جافاسكريبت (Transpile). لماذا؟ الوحدات هي المستقبل، دعونا نُدشن المستقبل منذ الآن. // سيئ const AirbnbStyleGuide = require('./AirbnbStyleGuide'); module.exports = AirbnbStyleGuide.es6; // مقبول import AirbnbStyleGuide from './AirbnbStyleGuide'; export default AirbnbStyleGuide.es6; // أفضل import { es6 } from './AirbnbStyleGuide'; export default es6; لا تعتمد على محارف البدل (Wild card) في استيراد الوحدات (الاستيراد بالجملة). لماذا؟ لتتأكد من أنّ لديك تصديرًا افتراضيّا واحدا. // سيئ import * as AirbnbStyleGuide from './AirbnbStyleGuide'; // جيّد import AirbnbStyleGuide from './AirbnbStyleGuide'; لا تُصدّر مباشرةً من الاستيراد. لماذا؟ على الرغم من أن الكتابة في سطر واحد تكون أوجز، إلّا أن وجود طريقة واضحة واحدة للاستيراد وأخرى للتصدير يجعل الأمور متسقة. // سيئ // filename es6.js export { es6 as default } from './AirbnbStyleGuide'; // جيّد // filename es6.js import { es6 } from './AirbnbStyleGuide'; export default es6; استورد من مسار معيّن في مكان واحد فقط. استعن بقاعدة no-duplicate-imports. لماذا؟ وجود عدة أسطر تستورد من المسار نفسه يمكن أن يجعل الشفرة أقل قابلية للصيانة. // سيئ import foo from 'foo'; // … some other imports … // import { named1, named2 } from 'foo'; // جيّد import foo, { named1, named2 } from 'foo'; // جيّد import foo, { named1, named2, } from 'foo'; لا تُصدر الارتباطات المتحوّلة (Mutable bindings). استعن بقاعدة no-duplicate-imports. لماذا؟ يُفضّل عمومًا تجنب العناصر المتحولة، ولكن على وجه الخصوص عند تصدير الارتباطات المتحولة. على الرغم من أن هذا الأسلوب قد يكون ضروريّا في حالات خاصة، إلّا أنّ القاعدة الأساسية هي ألّا تُصدرَ إلا المراجع الثابتة. // سيئ let foo = 3; export { foo }; // جيّد const foo = 3; export { foo }; في الوحدات التي فيها تصدير واحد فقط، يفضل استخدام التصدير الافتراضي بدلًا من التصدير المسمى named export. استعن بقاعدة import/prefer-default-export. لماذا؟ لتشجيع استخدام الملفات التي لا تصدر إلًا شيئًا واحدًا فقط، لأن ذلك أفضل وأسهل للقراءة والصيانة. // سيئ export function foo() {} // جيّد export default function foo() {} ضع كل عبارات import فوق عبارات الاستيراد الأخرى. استعن بقاعدة import/first في ESLint. لماذا؟ بما أن محرّك جافاسكريبت يرفع تعليمات import إلى أعلى النطاق (Hoisted)، فوضعها كلها في الجزء العلوي يمنع أي سلوك غير متوقع. // سيئ import foo from 'foo'; foo.init(); import bar from 'bar'; // جيّد import foo from 'foo'; import bar from 'bar'; foo.init(); يجب أن توضع مسافة بادئة قبل عناصر الاستيراد ذي الأسطر المتعدّدة، مثله مثل المصفوفات متعددة الأسطر والكائنات مصنّفة النوع (Object literals). استعن بالقاعدة object-curly-newline. لماذا؟ تتبع الأقواس المعقوصة قواهد المسافات البادئة ذاتها التي تتبعها كتل الأقواس المعقوصة الأخرى في هذا الدليل. الأمر نفسه ينطبق على الفواصل الزائدة (Trailing commas). // سيئ import {longNameA, longNameB, longNameC, longNameD, longNameE} from 'path'; // جيّد import { longNameA, longNameB, longNameC, longNameD, longNameE, } from 'path'; لا تسمح بصيغة Webpack للتحميل في تعليمات استيراد الوحدات. استعن بالقاعدة import/no-webpack-loader-syntax. لماذا؟ لأن استخدام صيغة Webpack في الاستيراد تجعل الشفرة معتمدة على محزّم (Bundler). من الأفضل استخدام صيغة Webpack في الملف webpack.config.js. // سيئ import fooSass from 'css!sass!foo.scss'; import barCss from 'style!css!bar.css'; // جيّد import fooSass from 'foo.scss'; import barCss from 'bar.css'; لا تُضمّن امتداد ملفات جافاسكريبت في الاستيراد. استعن بالقاعدة import/extensions. لماذا؟ لأن تضمين الامتدادات يصعّب إعادة كتابة الشفرة، ويضع لدى جميع العملاء تفاصيل غير مناسبة عن الوحدة التي تستوردها. // سيّئ import foo from './foo.js'; import bar from './bar.jsx'; import baz from './baz/index.jsx'; // جيّد import foo from './foo'; import bar from './bar'; import baz from './baz'; المُكرّرات والمولّدات لا تستخدم المكررات (Iterators). من الأفضل استخدام دوال الدرجات العليا لجافاسكريبت بدلًا من الحلقات مثلfor-in أو for-of. لماذا؟ هذا يتماشى مع قاعدة انعدام التحول (Immutable rule) التي ننتهجها. التعامل مع الدوال التي تُرجع قيمًا أسهل للفهم مقارنة بالدوال ذات الآثار الجانبية. استعن بقاعدتي no-iterator وno-restricted-syntax. استخدم الدوال ()map / every() / filter()، find() / findIndex() / reduce() / some / للمرور على المصفوفات، والتوابع ()Object.keys / Object.values() / Object.entries لإنتاج مصفوفات حتى تتمكن من المرور على عناصر الكائن. const numbers = [1, 2, 3, 4, 5]; // سيئ let sum = 0; for (let num of numbers) { sum += num; } sum === 15; // جيّد let sum = 0; numbers.forEach((num) => { sum += num; }); sum === 15; // أفضل (استخدام فعالية الدوال، برمجة وظيفية) const sum = numbers.reduce((total, num) => total + num, 0); sum === 15; // سيئ const increasedByOne = []; for (let i = 0; i < numbers.length; i++) { increasedByOne.push(numbers[i] + 1); } // جيّد const increasedByOne = []; numbers.forEach((num) => { increasedByOne.push(num + 1); }); // أفضل (الاعتماد على التصور الوظيفي) const increasedByOne = numbers.map(num => num + 1); لا تستخدم المولدات (Generators) في الوقت الراهن. لماذا؟ لأنها لا تُصرَّف على نحو جيّد إلى ES5. إذا كان عليك استخدام المُولدات، أو إذا أردت تجاهل نصيحتنا، تأكد من أن تواقيع دوالها متباعدة بشكل صحيح. استعن بالقاعدة generator-star-spacing. لماذا؟ function و * هما كلمتان مفتاحيتان من نفس المفهوم، فالكلمة المفتاحية * ليست تعديلًا للكلمة المفتاحية function، و function* مختلفة عن function. // سيئ function * foo() { // ... } // سيئ const bar = function * () { // ... }; // سيئ const baz = function *() { // ... }; // سيئ const quux = function*() { // ... }; // سيئ function*foo() { // ... } // سيئ function *foo() { // ... } // سيئ جدّا function * foo() { // ... } // سيئ جدّا const wat = function * () { // ... }; // جيّد function* foo() { // ... } // جيّد const foo = function* () { // ... }; الخصائص استخدم أسلوب الترميز بالنقطة (Dot notation) عند الدخول إلى الخصائص. const luke = { jedi: true, age: 28, }; // سيئ const isJedi = luke['jedi']; // جيّد const isJedi = luke.jedi; استخدام المعقوفتين [] عند الدخول إلى الخاصيّات بواسطة متغير. const luke = { jedi: true, age: 28, }; function getProp(prop) { return luke[prop]; } const isJedi = getProp('jedi'); استخدام العامل الأسي ** عند حساب الأسّ. // سيئ const binary = Math.pow(2, 10); // جيّد const binary = 2 ** 10; المتغيرات استخدم دومًا const أو let للتصريح بالمتغيرات. عدم القيام بذلك سيؤدي إلى إنشاء متغيرات عامة، في حين نريد تجنب تلويث فضاء الأسماء العام (Global namespace). استعن بقاعدتيْ no-undef وprefer-const. // سيئ superPower = new SuperPower(); // جيّد const superPower = new SuperPower(); صرّح باستخدام const أو let لكل متغيّر على حدة. استعن بقاعدة one-var في ESlint. لماذا؟ من الأسهل إضافة متغيرات جديدة بهذه الطريقة، ولن تكون مضطرا لإبدال الفاصلة المنقوطة ; بفاصلة , كل ما أردت إضافة متغيّر جديد، وستتخلّص من التعديلات المقتصرة على علامات التنقيط. ستتمكّن كذلك من المرور خلال التنقيح (Debugging) على كل متغيّر على حدة، بدلًا من القفز عليها كلها في وقت واحد. // سيئ const items = getItems(), goSportsTeam = true, dragonball = 'z'; // سيئ (قارن بالتصريح فوقه لتعثر على الخطأ) const items = getItems(), goSportsTeam = true; dragonball = 'z'; // جيّد const items = getItems(); const goSportsTeam = true; const dragonball = 'z'; جمّع كل التصريحات بالكلمة المفتاحية const ثم بعدها التصريحات بالكلمة المفتاحية let. لماذا؟ سيكون هذا مفيدًا لاحقًا عندما تحتاج إلى إسناد متغير اعتمادًا على متغير مُسنَد مسبقا. // سيئ let i, len, dragonball, items = getItems(), goSportsTeam = true; // سيئ let i; const items = getItems(); let dragonball; const goSportsTeam = true; let len; // جيّد const goSportsTeam = true; const items = getItems(); let dragonball; let i; let length; أسند المتغيرات حيث تحتاج لذلك، لكن ضعها في مكان مناسب. لماذا؟ المتغيّرات المصرّح عنها بـ let أو const ذات نطاق كتلي (Block scoped) وليست ذات نطاق دالّي (Function scoped). // سيئ، لا حاجة لنداء الدالة function checkName(hasName) { const name = getName(); if (hasName === 'test') { return false; } if (name === 'test') { this.setName(''); return false; } return name; } // جيّد function checkName(hasName) { if (hasName === 'test') { return false; } const name = getName(); if (name === 'test') { this.setName(''); return false; } return name; } تجنب الإسناد المتسلسل للمتغيّرات. استعن no-multi-assign بقاعدة في ESLint. لماذا؟ لأن الإسناد المتسلسل ينتج متغيرات عامة. // سيئ (function example() { // يفسّر جافاسكريبت التعليمة أدناه على النحو التالي // let a = ( b = ( c = 1 ) ); // لا يُطبّق التصريح بـ let إلا على المتغيّر a، // المتغيّران b وc يصبحان عامين. let a = b = c = 1; }()); console.log(a); // يتسبّب في خطأ في المرجع ReferenceError console.log(b); // 1 console.log(c); // 1 // جيّد (function example() { let a = 1; let b = a; let c = a; }()); console.log(a); // يتسبّب في خطأ في المرجع ReferenceError console.log(b); // يتسبّب في خطأ في المرجع ReferenceError console.log(c); // يتسبّب في خطأ في المرجع ReferenceError // الأمر نفسه ينطبق على `const` تجنب استخدام عمليات الزيادة والإنقاص الأحادية (++, --). استعن بقاعدة no-plusplus. لماذا؟ تخضع عمليات الزيادة والإنقاص الأحادية، حسب وثائق ESLint، لقاعدة الإدراج التلقائي للفواصل المنقوطة، ويمكن أن تتسبب في حدوث أخطاء صامتة مع زيادة أو إنقاص قيمة ضمن التطبيق. كما أنه من الأكثر وضوحًا استخدام num += 1 بدلًا من num++ أو num ++ لتغيير قيم المتغيّرات. تجنُّب استخدام عامل الزيادة والإنقاص الأحادي سيجنبك الزيادة (أو الإنقاص) قبل إجراء عملية، وهو ما يمكن أيضًا أن يسبب سلوكًا غير متوقع في برامجك. // سيئ const array = [1, 2, 3]; let num = 1; num++; --num; let sum = 0; let truthyCount = 0; for (let i = 0; i < array.length; i++) { let value = array[i]; sum += value; if (value) { truthyCount++; } } // جيّد const array = [1, 2, 3]; let num = 1; num += 1; num -= 1; const sum = array.reduce((a, b) => a + b, 0); const truthyCount = array.filter(Boolean).length; تجنّب إدراج الأسطر قبل علامة الإسناد = أو بعدها. أحط القيمة بقوسيْن إنْ كان الإسناد يخرق قاعدة max-len (طول السطر). استعن بقاعدة operator-linebreak. لماذا لأن الأسطر حول = يمكن أن تعتّم على قيمة الإسناد. // سيّئ const foo = superLongLongLongLongLongLongLongLongFunctionName(); // سيّئ const foo = 'superLongLongLongLongLongLongLongLongString'; // جيّد const foo = ( superLongLongLongLongLongLongLongLongFunctionName() ); // جيّد const foo = 'superLongLongLongLongLongLongLongLongString'; لا تسمح بمتغيّرات غير مستخدمة. استعن بالقاعدة no-unused-vars. لماذا؟ المتغيّرات المُصرّح بها غير المستخدمة في أي جزء من الشفرة هي في الغالب خطأ ناتج عن إعادة هيكلة غير مكتملة. تأخذ هذه المتغيّرات مساحة من الشفرة ويمكن أن تتسبّب في خلط لدى من يقرأه. // سيّئ var some_unused_var = 42; // متغيّرات يقتصر استخدامها على تغيير قيمتها لا تعد مستخدمة. var y = 10; y = 5; // قراءة المتغيّر فقط من أجل التعديل عليه لا يعدّ استخداما var z = 0; z = z + 1; // وسائط غير مستخدمة في الدالة function getX(x, y) { return x; } // جيّد function getXPlusY(x, y) { return x + y; } var x = 1; var y = a + 2; alert(getXPlusY(x, y)); // لا تنطبق القاعدة هنا على المتغيّر type الذي على الرغم من أنه غير مستخدم إلا أن له علاقة بالمتغيّر المُفكَّك // هذه طريقة لاستخراج كائن بإسقاط المفاتيح المحدّدة. var { type, ...coords } = data; // يطابق الكائنُ coords الكائنَ data، غير أنه لا توجد فيه الخاصيّة type التي استخرجناها منه. الرفع إلى أعلى النطاق Hoisting تُرفَع المتغيّرات المُصرَّح عنها بالكلمة المفتاحية var إلى أعلى نطاق الدالة المحتوية الأقرب، إلّا أن هذا الأمر لا ينطبق على عمليّات الإسناد. للمتغيّرات المُعرَّفة بالكلمتيْن المفتاحيتيْن const و let ميزة جديدة تُسمى المناطق الميتة الظرفية (Temporal dead zones)؛ لذا من المهم أن تعرف لماذا لم يعد استخدام الدالة typeof آمنا. // نعرف أن التعليمات التالية لن تعمل (على فرض // أنه لا يوجد متغيّر باسم notDefined) function example() { console.log(notDefined); // يتسبب في خطأ ReferenceError } // تستطيع التصريح بمتغيّر بعد الإحالة إليه // لأن التصريح سيُرفَع إلى أعلى النطاق // ملحوظة: إسناد القيمة true إلى المتغيّر لا يُرفَع // إلى أعلى النطاق. function example() { console.log(declaredButNotAssigned); // => undefined var declaredButNotAssigned = true; } // يرفع مفسّر جافاسكريبت التصريح بالمتغيّر // إلى أعلى النطاق، وهو ما يعني أنه بإمكاننا // إعادة كتابة المثال السابق على النحو التالي function example() { let declaredButNotAssigned; console.log(declaredButNotAssigned); // => undefined declaredButNotAssigned = true; } // استخدام const وlet function example() { console.log(declaredButNotAssigned); // تتسبّب في خطأ ReferenceError console.log(typeof declaredButNotAssigned); // تتسبّب في خطأ ReferenceError const declaredButNotAssigned = true; } يُرفَع اسم متغيّر الدوال العبارات غير المُسمّاة إلى أعلى النطاق، بخلاف إسناد الدالة. function example() { console.log(anonymous); // => undefined anonymous(); // => TypeError anonymous is not a function var anonymous = function () { console.log('anonymous function expression'); }; } في الدوال العبارات المُسماة يُرفع اسم المتغير، بخلاف اسم الدالة أو متنها. function example() { console.log(named); // => undefined named(); // => TypeError named is not a function superPower(); // => ReferenceError superPower is not defined var named = function superPower() { console.log('Flying'); }; } // الأمر نفسه يحدث عندما يكون اسم الدالة // هو نفسه اسم المتغيّر. function example() { console.log(named); // => undefined named(); // => TypeError named is not a function var named = function named() { console.log('named'); }; } يرفع التصريح بالدوال اسم الدالة ومتنها إلى أعلى النطاق. function example() { superPower(); // => Flying function superPower() { console.log('Flying'); } } عوامل المقارنة والمساواة استخدم === و !== بدلًا من == و !=. تقيّم التعليمات الشرطية مثل العبارة if العبارات عبر آلية الإجبار (Coercion) عن طريق الدالة المجرَّدة ToBoolean والتي تتبع دائمًا القواعد البسيطة التالية: تُمنَح للكائنات القيمة true تُعيَّن للنوع Undefined القيمة false تُحدَّد للعبارة Null القيمة false البيانات من النوع المنطقي (Booleans) تُعطى لها القيمة المنطقية الموافقة إذا كانت قيمة العدد تساوي 0+ أو 0- أو NaN تحدّد قيمته المنطقية بـ false، وإلّا يأخذ القيمة true إذا كانت سلسلة المحارف (String) خاوية ("") تُعيَّن قيمتها إلى false، وإلاّ تأخذ القيمة true. if ([0] && []) { // true // المصفوفة (حتى وإنْ كانت فارغة) هي كائن، والكائنات تُقيّم بالقيمة true } استخدم الاختصارات عند التعامل مع قيم منطقية، بالمقابل استخدم المقارنات الصريحة للنصوص والأرقام. // سيئ if (isValid === true) { // ... } // جيّد if (isValid) { // ... } // سيئ if (name) { // ... } // جيّد if (name !== '') { // ... } // سيئ if (collection.length) { // ... } // جيّد if (collection.length > 0) { // ... } استخدم الأقواس المعقوصة ({}) لإنشاء الكتل في البنود case و default التي تحتوي التصريح بمتغيّرات (على سبيل المثال let، const ، function ، وclass). استعن بالقاعدة no-case-declarations. لماذا؟ التصريحات مرئية في كامل كتلة switch، ولكن لا يُعاد تعيينها إلّا عندما تُسنَد لها قيمة، وهو ما لا يحدث إلا عند بلوغ بند case، وهو ما يسبب مشاكل عندما يحاول أكثر من بند case تعريف الشيء نفسه. // سيئ switch (foo) { case 1: let x = 1; break; case 2: const y = 2; break; case 3: function f() { // ... } break; default: class C {} } // جيّد switch (foo) { case 1: { let x = 1; break; } case 2: { const y = 2; break; } case 3: { function f() { // ... } break; } case 4: bar(); break; default: { class C {} } } لا ينبغي أن تتداخل عوامل المقارنة الثلاثية Ternaries، وفي الغالب ينبغي أن تكون على سطر واحد. استعن بقاعدة no-nested-ternary. // سيئ const foo = maybe1 > maybe2 ? "bar" : value1 > value2 ? "baz" : null; // التقسيم إلى عبارتين تستخدمان مقارنات ثلاثية const maybeNull = value1 > value2 ? 'baz' : null; // أفضل const foo = maybe1 > maybe2 ? 'bar' : maybeNull; // الأفضل const foo = maybe1 > maybe2 ? 'bar' : maybeNull; تجنب استخدام المقارنات الثلاثية التي لا لزوم لها. استعن بقاعدة no-unneeded-ternary. // سيئ const foo = a ? a : b; const bar = c ? true : false; const baz = c ? false : true; // جيّد const foo = a || b; const bar = !!c; const baz = !c; استخدم الأقواس عند مزج العمليات، والاستثناء الوحيد هي العمليات الحسابية القياسية (+، -، *، /) بما أنّ الأسبقية فيها مفهومة جيّدا. ننصح بوضع / و* بين قوسين، لأنّ الأسبقية بينهما قد تكون غير واضحة عند المزج بينهما. استعن بقاعدة no-mixed-operators. لماذا؟ هذا يحسّن المقروئية ويوضّح قصد المطوّر. // سيئ const foo = a && b < 0 || c > 0 || d + 1 === 0; // سيئ const bar = a ** b - 5 % d; // سيئ // one may be confused into thinking (a || b) && c if (a || b && c) { return d; } // جيّد const foo = (a && b < 0) || c > 0 || (d + 1 === 0); // جيّد const bar = (a ** b) - (5 % d); // جيّد if (a || (b && c)) { return d; } // جيّد const bar = a + b / c * d; الكتل Blocks استخدم الأقواس المعقوصة للكتل متعددة الأسطر. // سيئ if (test) return false; // جيّد if (test) return false; // جيّد if (test) { return false; } // سيئ function foo() { return false; } // جيّد function bar() { return false; } إذا كنت تستخدم الكتل متعددة الأسطر مع if و else ، ضع else على السطر نفسه الذي يوجد عليه القوس المعقوص الذي يغلق كتلة if. // سيئ if (test) { thing1(); thing2(); } else { thing3(); } // جيّد if (test) { thing1(); thing2(); } else { thing3(); } إذا كانت كتلة if تُرجع قيمة دائمًا (أي تنتهي بالتعليمة return)، فكتلة else المتعلّقة بها غير ضرورية. تعليمة return في كتلة else if موالية لكتلة if تحتوي على return يمكن تقسيمها إلى عدة كتل if. استعن بقاعدة no-else-return. // سيئ function foo() { if (x) { return x; } else { return y; } } // سيئ function cats() { if (x) { return x; } else if (y) { return y; } } // سيئ function dogs() { if (x) { return x; } else { if (y) { return y; } } } // جيّد function foo() { if (x) { return x; } return y; } // جيّد function cats() { if (x) { return x; } if (y) { return y; } } //جيّد function dogs(x) { if (x) { if (z) { return y; } } else { return z; } } تعليمات التحكم Control Statements إذا تجاوزت عبارات التحكم (if, while …) الحد الأقصى لطول السطر، فيمكن وضع كل شرط (أو مجموعة من الشروط) في سطر جديد. كما يجب أن يكون العامل المنطقي في بداية السطر. لماذا؟ وضع العوامل في بداية السطر يحافظ على محاذاة العوامل ويتبع نمطًا مماثلا لتسلسل التوابع. كما يحسّن القراءة من خلال توضيح العمليات المنطقية المعقدة. // سيئ if ((foo === 123 || bar === 'abc') && doesItLookGoodWhenItBecomesThatLong() && isThisReallyHappening()) { thing1(); } // سيئ if (foo === 123 && bar === 'abc') { thing1(); } // سيئ if (foo === 123 && bar === 'abc') { thing1(); } // سيئ if ( foo === 123 && bar === 'abc' ) { thing1(); } // جيّد if ( foo === 123 && bar === 'abc' ) { thing1(); } // جيّد if ( (foo === 123 || bar === "abc") && doesItLookجيّدWhenItBecomesThatLong() && isThisReallyHappening() ) { thing1(); } // جيّد if (foo === 123 && bar === 'abc') { thing1(); } لا تستخدم عوامل الاختيار بدلًا من تعليمات التحكم // سيّئ !isRunning && startRunning(); // جيّد if (!isRunning) { startRunning(); } التعليقات استخدم /** ... */ للتعليقات متعددة الأسطر. // سيئ // make() returns a new element // based on the passed in tag name // // @param {String} tag // @return {Element} element function make(tag) { // ... return element; } // جيّد /** * make() returns a new element * based on the passed-in tag name */ function make(tag) { // ... return element; } استخدم // لأجل التعليقات ذات السطر الواحد. ضع التعليقات ذات السطر الواحد على سطر جديد فوق التعليمة البرمجية موضوع التعليق. وضع سطرًا فارغًا قبل التعليق إلا إذا كان على السطر الأول من كتلة. // سيئ const active = true; // is current tab // جيّد // is current tab const active = true; // سيئ function getType() { console.log('fetching type...'); // set the default type to 'no type' const type = this.type || 'no type'; return type; } // جيّد function getType() { console.log('fetching type...'); // set the default type to 'no type' const type = this.type || 'no type'; return type; } // جيّد أيضا function getType() { // set the default type to 'no type' const type = this.type || 'no type'; return type; } ابدأ كل التعليقات بمسافة لجعلها أسهل للقراءة. استعت بقاعدة spaced-comment. // سيئ //is current tab const active = true; // جيّد // is current tab const active = true; // سيئ /** *make() returns a new element *based on the passed-in tag name */ function make(tag) { // ... return element; } // جيّد /** * make() returns a new element * based on the passed-in tag name */ function make(tag) { // ... return element; } ابدأ التعليقات التي تهدف إلى لفت انتباه المطورين الآخرين إلى أنّ هناك مشكلةً تحتاج إلى المراجعة بالكلمات FIXME أو TODO، أو إذا كنت تقترح حلّا لتنفيذه. تختلف هذه عن التعليقات العادية لأنها قابلة للتنفيذ. الإجراءات قد تكون: FIXME: -- need to figure this out أو TODO: -- need to implement مثلا. استخدم // FIXME: للإشارة إلى مشكلة. class Calculator extends Abacus { constructor() { super(); // FIXME: shouldn’t use a global here total = 0; } } استخدم // TODO: لاقتراح حل لمشكلة. class Calculator extends Abacus { constructor() { super(); // TODO: total should be configurable by an options param this.total = 0; } } المسافات Whitespace أضف فراغيْن بسيطيْن (زر المسافة مرتيْن) للمسافات البادئة. استعن بقاعدة indent. // سيئ function foo() { ∙∙∙∙let name; } // سيئ function bar() { ∙let name; } // جيّد function baz() { ∙∙let name; } ضع مسافة واحدة قبل القوس المعقوص الأول. استعن بالقاعدة space-before-blocks. // سيئ function test(){ console.log('test'); } // جيّد function test() { console.log('test'); } // سيئ dog.set('attr',{ age: '1 year', breed: 'Bernese Mountain Dog', }); // جيّد dog.set('attr', { age: '1 year', breed: 'Bernese Mountain Dog', }); ضع مسافةً قبل القوس الفاتح في تعليمات التحكم (if, while …). لا تضع أي مسافات بين لائحة الوسائط Arguments واسم الدالة عند استدعاء دالة أو التصريح بها. استعن بقاعدة keyword-spacing. // سيئ if(isJedi) { fight (); } // جيّد if (isJedi) { fight(); } // سيئ function fight () { console.log ('Swooosh!'); } // جيّد function fight() { console.log('Swooosh!'); } ضع مسافات بين العوامل. استعن بقاعدة space-infix-ops. // سيئ const x=y+5; // جيّد const x = y + 5; أنهِ الملفات بمحرف الرجوع إلى السطر. استعن بقاعدة eol-last // سيئ import { es6 } from './AirbnbStyleGuide'; // ... export default es6; // سيئ import { es6 } from './AirbnbStyleGuide'; // ... export default es6;↵ ↵ // جيّد import { es6 } from './AirbnbStyleGuide'; // ... export default es6;↵ استخدم المسافة البادئة عند استخدام سلسة طويلة من التوابع (أكثر من اثنتين). استخدم نقطة في البداية، لكي تبين أنّ السطر هو استدعاء لتابع، وليس تعليمةً جديدة. استعن بالقاعدتيْن newline-per-chained-call وno-whitespace-before-property. // سيئ $('#items').find('.selected').highlight().end().find('.open').updateCount(); // سيئ $('#items'). find('.selected'). highlight(). end(). find('.open'). updateCount(); // جيّد $('#items') .find('.selected') .highlight() .end() .find('.open') .updateCount(); // سيئ const leds = stage.selectAll('.led').data(data).enter().append('svg:svg').classed('led', true) .attr('width', (radius + margin) * 2).append('svg:g') .attr('transform', `translate(${radius + margin},${radius + margin})`) .call(tron.led); // جيّد const leds = stage.selectAll('.led') .data(data) .enter().append('svg:svg') .classed('led', true) .attr('width', (radius + margin) * 2) .append('svg:g') .attr('transform', `translate(${radius + margin},${radius + margin})`) .call(tron.led); // جيّد const leds = stage.selectAll('.led').data(data); ضع سطرًا فارغًا بعد الكتل وقبل التعليمة الموالية. // سيئ if (foo) { return bar; } return baz; // جيّد if (foo) { return bar; } return baz; // سيئ const obj = { foo() { }, bar() { }, }; return obj; // جيّد const obj = { foo() { }, bar() { }, }; return obj; // سيئ const arr = [ function foo() { }, function bar() { }, ]; return arr; // جيّد const arr = [ function foo() { }, function bar() { }, ]; return arr; لا تحش الكتل بأسطر فارغة. استعن بالقاعدة padded-blocks. // سيئ function bar() { console.log(foo); } // سيئ if (baz) { console.log(qux); } else { console.log(foo); } // سيئ class Foo { constructor(bar) { this.bar = bar; } } // جيّد function bar() { console.log(foo); } // جيّد if (baz) { console.log(qux); } else { console.log(foo); } لا تستخدم عدة أسطر فارغة لحشو شفرتك البرمجية. استعن بالقاعدة no-multiple-empty-lines. // سيّئ class Person { constructor(fullName, email, birthday) { this.fullName = fullName; this.email = email; this.setAge(birthday); } setAge(birthday) { const today = new Date(); const age = this.getAge(today, birthday); this.age = age; } getAge(today, birthday) { // .. } } // جيّد class Person { constructor(fullName, email, birthday) { this.fullName = fullName; this.email = email; this.setAge(birthday); } setAge(birthday) { const today = new Date(); const age = getAge(today, birthday); this.age = age; } getAge(today, birthday) { // .. } } لا تضف مسافات داخل الأقواس. استعن بالقاعدة space-in-parens. // سيئ function bar( foo ) { return foo; } // جيّد function bar(foo) { return foo; } // سيئ if ( foo ) { console.log(foo); } // جيّد if (foo) { console.log(foo); } لا تضف مسافات داخل الأقواس المعكوفة ([]). استعن بالقاعدة array-bracket-spacing. // سيئ const foo = [ 1, 2, 3 ]; console.log(foo[ 0 ]); // جيّد const foo = [1, 2, 3]; console.log(foo[0]); أضف مساحات داخل الأقواس المعقوصة. استعن بالقاعدة object-curly-spacing. // سيئ const foo = {clark: 'kent'}; // جيّد const foo = { clark: 'kent' }; تجنب التعليمات البرمجية التي يتجاوز طولها 100 محرف (باحتساب المسافات). النصوص الطويلة - حسب قاعدة مذكورة أعلاه - مستثناة من هذه القاعدة، وينبغي ألّا تُفكك. استعن بالقاعدة max-len. لماذا؟ لضمان سهولة القراءة والصيانة. // سيئ const foo = jsonData && jsonData.foo && jsonData.foo.bar && jsonData.foo.bar.baz && jsonData.foo.bar.baz.quux && jsonData.foo.bar.baz.quux.xyzzy; // سيئ $.ajax({ method: 'POST', url: 'https://airbnb.com/', data: { name: 'John' } }).done(() => console.log('Congratulations!')).fail(() => console.log('You have failed this city.')); // جيّد const foo = jsonData && jsonData.foo && jsonData.foo.bar && jsonData.foo.bar.baz && jsonData.foo.bar.baz.quux && jsonData.foo.bar.baz.quux.xyzzy; // جيّد $.ajax({ method: 'POST', url: 'https://airbnb.com/', data: { name: 'John' }, }) .done(() => console.log('Congratulations!')) .fail(() => console.log('You have failed this city.')); افرض مسافات متناسقة بعد القوس المعقوص البادئ لكتلة وبعد القوس المعقوص البادئ لكتلة موالية لها على السطر نفسه. الأمر نفسه ينطبق على الأقواس المعقوصة المكمّلة للكتلتيْن. استعن بقاعدة block-spacing. // سيّئ function foo() {return true;} if (foo) { bar = 0;} // جيّد function foo() { return true; } if (foo) { bar = 0; } تجنّب المسافات قبل الفواصل وافرض مسافة بعد الفاصلة. استعن بالقاعدة comma-spacing. // جيّد var foo = 1,bar = 2; var arr = [1 , 2]; // سيّئ var foo = 1, bar = 2; var arr = [1, 2]; افرض عدم استخدام المسافات داخل الأقواس المعكوفة لخاصيّة محسوبة. استعن بالقاعدة computed-property-spacing. // سيّئ obj[foo ] obj[ 'foo'] var x = {[ b ]: a} obj[foo[ bar ]] // جيّد obj[foo] obj['foo'] var x = { [b]: a } obj[foo[bar]] تجنّب المسافات بين دالة واستدعائها. استعن بالقاعدة func-call-spacing. // سيئ func (); func (); // جيّد func(); افرض المسافات بين المفاتيح والقيم في الخاصيّات المصنّفة النوع في الكائنات. استعن بالقاعدة key-spacing. // سيّئ var obj = { foo : 42 }; var obj2 = { foo:42 }; // جيّد var obj = { foo: 42 }; تجنّب المسافات الباقية بعد نهاية الأسطر. استعن بالقاعدة no-trailing-spaces. تجنّب عدة أسطر فارغة، ولاتسمح إلا بسطر واحد جديد في نهاية الملفات. تجنّب كذلك وجود سطر جديد في بداية الملفات. استعن بالقاعدة no-multiple-empty-lines. // سيّئ - عدة أسطر فارغة. var x = 1; var y = 2; // سيّئ - سطران جديدان بعد نهاية الملف var x = 1; var y = 2; // سيّئ - سطر جديد في بداية الملف var x = 1; var y = 2; // جيّد var x = 1; var y = 2; الفواصل تجنب الفواصل في البداية. استعن بالقاعدة comma-style. // سيئ const story = [ once , upon , aTime ]; // جيّد const story = [ once, upon, aTime, ]; // سيئ const hero = { firstName: 'Ada' , lastName: 'Lovelace' , birthYear: 1815 , superPower: 'computers' }; // جيّد const hero = { firstName: 'Ada', lastName: 'Lovelace', birthYear: 1815, superPower: 'computers', }; استخدم فاصلة إضافية. استعن بالقاعدة comma-dangle. لماذا؟ هذا يؤدي إلى توضيح الاختلافات بين الشفرات البرمجية في إيداعات Git . علاوة على ذلك، تحذف مصرّفات مثل Babel الفواصل الزائدة في الشفرة الناتجة عن التصريف، ممّا يعني أنه لا داعي للقلق من مشكلة الفاصلة المُجرجَرة (Trailing comma) في المتصفحات القديمة. // سيئ، فرق بين إيداعين في Git بدون فاصلة إضافية const hero = { firstName: 'Florence', - lastName: 'Nightingale' + lastName: 'Nightingale', + inventorOf: ['coxcomb chart', 'modern nursing'] }; // جيّد، فرق بين إيداعين في Git بوجود فاصلة إضافية const hero = { firstName: 'Florence', lastName: 'Nightingale', + inventorOf: ['coxcomb chart', 'modern nursing'], }; // سيئ const hero = { firstName: 'Dana', lastName: 'Scully' }; const heroes = [ 'Batman', 'Superman' ]; // جيّد const hero = { firstName: 'Dana', lastName: 'Scully', }; const heroes = [ 'Batman', 'Superman', ]; // سيئ function createHero( firstName, lastName, inventorOf ) { // does nothing } // جيّد function createHero( firstName, lastName, inventorOf, ) { // does nothing } // جيّد (انتبه إلى أنه يجب ألا تظهر فاصلة بعد عامل الاستناد Rest element) function createHero( firstName, lastName, inventorOf, ...heroArgs ) { // does nothing } // سيئ createHero( firstName, lastName, inventorOf ); // جيّد createHero( firstName, lastName, inventorOf, ); // جيّد (انتبه إلى أنه يجب ألا تظهر فاصلة بعد عامل الاستناد Rest element) createHero( firstName, lastName, inventorOf, ...heroArgs ); الفاصلة المنقوطة Semicolons استخدم الفاصلة المنقوطة. استعن بالقاعدة semi. لماذا؟ عندما يصادف مفسّر جافاسكريبت عودة إلى السطر بدون فاصلة منقوطة، فإنه يستخدم مجموعة من القواعد تسمى الإدراج التلقائي للفاصلة المنقوطة (Automatic Semicolon Insertion) لتحديد ما إذا كان يجب احتساب نهاية السطر على أنها نهاية للتعليمة البرمجية، ويُدرِج، كما يوحي الاسم، فاصلة منقوطة في الشفرة البرمجية قبل نهاية السطر إذا رأى أن التعليمة قد انتهت. الإدراج التلقائي للفاصلة المنقوطة ترافقه سلوكيات شاذة، قد تتسبّب في إساءة فهم الشفرة البرمجية. تصبح هذه القواعد أكثر تعقيدًا مع إضافة ميزات جديدة إلى جافاسكريبت. سيساعد الوضوح في إنهاء التعليمات البرمجية وإعداد أداة جودة الشفرة (مثل ESLint) لتحديد الفواصل المنقوطة المفقودة في تجنب تلك المشاكل. // سيئ، تنتج عنه استثناءات (Exceptions) const luke = {} const leia = {} [luke, leia].forEach(jedi => jedi.father = 'vader') // سيئ، تنتج عنه استثناءات (Exceptions) const reaction = "No! That's impossible!" (async function meanwhileOnTheFalcon(){ // handle `leia`, `lando`, `chewie`, `r2`, `c3p0` // ... }()) // سيّئ، يُرجع `undefined` بدلًا من القيمة الموجودة في السطر الموالي // يحدث هذا دائمًا عندما تكون التعليمة return مفصولة بسطر عن القمية المرجَعة // وذلك بسبب حدوث الإدراج التلقائي للفاصلة المنقوطة function foo() { return 'search your feelings, you know it to be foo' } // جيّد const luke = {}; const leia = {}; [luke, leia].forEach((jedi) => { jedi.father = 'vader'; }); // جيّد const reaction = "No! That's impossible!"; (async function meanwhileOnTheFalcon(){ // handle `leia`, `lando`, `chewie`, `r2`, `c3p0` // ... }()); // جيّد function foo() { return 'search your feelings, you know it to be foo'; } التحويل بين أنواع البيانات وفرض نوع معيّن Type Casting & Coercion حوّل نوع البيانات في بداية التعليمة. سلاسل المحارف: // => this.reviewScore = 9; // سيئ const totalScore = new String(this.reviewScore); // المتغيّر totalScore كائن "object" وليس سلسلة محارف "string" // سيئ const totalScore = this.reviewScore + ''; // يستدعي التابع this.reviewScore.valueOf() // سيئ const totalScore = this.reviewScore.toString(); // لا تضمن إرجاع سلسلة محارف // جيّد const totalScore = String(this.reviewScore); بالنسبة للأعداد: استخدم Number لأجل التحويل، أما لتحليل النصوص فاستخدم دائمًا التابع parseInt مع أساس radix . استعن بالقاعدتيْن radix وno-new-wrappers. const inputValue = '4'; // سيئ const val = new Number(inputValue); // سيئ const val = +inputValue; // سيئ const val = inputValue >> 0; // سيئ const val = parseInt(inputValue); // جيّد const val = Number(inputValue); // جيّد const val = parseInt(inputValue, 10); ضع تعليقًا يشرح ما الذي تفعله ولماذا إذا كنت مضطرّا لاستعمال parseInt واحتجت إلى استخدام Bitshift لأسباب تتعلق بالأداء. // جيّد /** * parseInt was the reason my code was slow. * Bitshifting the String to coerce it to a * Number made it a lot faster. */ const val = inputValue >> 0; احذر عند استخدام عمليات الإزاحة bitshift ، فالأعداد مُمثلة بقيم من 64 بتا. ولكنّ عمليات الإزاحة دائما تُرجع أعدادًا ممثلة بقيم من 32 بت (المرجع). يمكن أن تؤدي الإزاحة إلى نتائج غير متوقعة عند استخدام قيم عددية أكبر من 32 بتا. أكبر قيمة للأعداد ذات الإشارة الممثلة على 32 بتا هي 2,147,483,647. (نقاش). 2147483647 >> 0; // => 2147483647 2147483648 >> 0; // => -2147483648 2147483649 >> 0; // => -2147483647 القيم المنطقية: const age = 0; // سيئ const hasAge = new Boolean(age); // جيّد const hasAge = Boolean(age); // best const hasAge = !!age; اصطلاحات التسمية تجنب الأسماء المكونة من حرف واحد. استخدم أسماء معبّرة. استعن بالقاعدة id-length. // سيئ function q() { // ... } // جيّد function query() { // ... } استخدم أسلوب camelCase لتسمية الكائنات، والدوال، والنظائر (Instances). استعن بالقاعدة camelCase. // سيئ const OBJEcttsssss = {}; const this_is_my_object = {}; function c() {} // جيّد const thisIsMyObject = {}; function thisIsMyFunction() {} لا تستخدم أسلوب التسمية PascalCase إلا عند تسمية المُنشئات أو الأصناف. استعن بالدالة new-cap. // سيئ function user(options) { this.name = options.name; } const سيئ = new user({ name: 'nope', }); // جيّد class User { constructor(options) { this.name = options.name; } } const جيّد = new User({ name: 'yup', }); لا تستخدم العارضة السفلية في البداية أو النهاية. استعن بالقاعدة camelcno-underscore-danglease. لماذا؟ لا يوجد في جافاسكريبت مفهوم الخصوصية عندما يتعلّق الأمر بالخاصيّات أو التوابع. على الرغم من أن الكثيرين ينظرون إلى وضع العارضة السفلية في بداية الاسم على أنه اصطلاح يعني “خاص”، إلّا أن هذه الخاصيّات عامة كلها، وعلى هذا النحو، فهي جزء من الواجهة البرمجية API العمومية. هذا الاصطلاح قد يؤدي بالمطورين للاعتقاد خطأً بأن التغيير لن يؤثّر سلبًا على الشفرة البرمجية، أو أنه ليست هناك حاجة للاختبار. إن بدا لك هذا الشرح طويلًا، فتذكر هذه الجملة: إذا كنت تريد لشيء أن يكون “خاصّا”، فيجب ألا تضعه في مكان ملحوظ. // سيئ this.__firstName__ = 'Panda'; this.firstName_ = 'Panda'; this._firstName = 'Panda'; // جيّد this.firstName = 'Panda'; // جيّد، في البيئات التي يكون WeakMaps متوفّرًا فيها، راجع الرابط التالي // see https://kangax.github.io/compat-table/es6/#test-WeakMap const firstNames = new WeakMap(); firstNames.set(this, 'Panda'); لا تحفظ مرجعًا إلى this. واستخدم الدوال السهمية أو التابع Function#bind . // سيئ function foo() { const self = this; return function () { console.log(self); }; } // سيئ function foo() { const that = this; return function () { console.log(that); }; } // جيّد function foo() { return () => { console.log(this); }; } يجب أن يتطابق اسم الملف القاعدي (Base filename) تمامًا مع اسم التصدير الافتراضي. // file 1 contents class CheckBox { // ... } export default CheckBox; // file 2 contents export default function fortyTwo() { return 42; } // file 3 contents export default function insideDirectory() {} // in some other file // سيئ import CheckBox from './checkBox'; // PascalCase import/export, camelCase filename import FortyTwo from './FortyTwo'; // PascalCase import/filename, camelCase export import InsideDirectory from './InsideDirectory'; // PascalCase import/filename, camelCase export // سيئ import CheckBox from './check_box'; // PascalCase import/export, snake_case filename import forty_two from './forty_two'; // snake_case import/filename, camelCase export import inside_directory from './inside_directory'; // snake_case import, camelCase export import index from './inside_directory/index'; // requiring the index file explicitly import insideDirectory from './insideDirectory/index'; // requiring the index file explicitly // جيّد import CheckBox from './CheckBox'; // PascalCase export/import/filename import fortyTwo from './fortyTwo'; // camelCase export/import/filename import insideDirectory from './insideDirectory'; // camelCase export/import/directory name/implicit "index" // ^ supports both insideDirectory.js and insideDirectory/index.js استخدم أسلوب التسمية camelCase عندما التصدير الافتراضي لدالة. يجب أن يكون اسم الملف الخاص بك مطابقًا لاسم دالتك. function makeStyleGuide() { // ... } export default makeStyleGuide; استخدم أسلوب التسمية PascalCase عندما تصدّر منشئًا أو صنفًا أو صنفًا أو مكتبة دوال أو كائن مجرّدا. const AirbnbStyleGuide = { es6: { }, }; export default AirbnbStyleGuide; يجب أن تكون كل حروف المختصرات والكلمات المنحوتة إما مكتوبة بأحرف كبيرة وإما بأحرف صغيرة. لماذا؟ تهدف التسميات لتسهيل قراءة الشفرة على الإنسان، وليس لاسترضاء خوارزميات الكمبيوتر. // سيئ import SmsContainer from './containers/SmsContainer'; // سيئ const HttpRequests = [ // ... ]; // جيّد import SMSContainer from './containers/SMSContainer'; // جيّد const HTTPRequests = [ // ... ]; // جيّد أيضا const httpRequests = [ // ... ]; // أفضل import TextMessageContainer from './containers/TextMessageContainer'; // أفضل const requests = [ // ... ]; اختياريًّا، يمكنك كتابة ثابت بحروف كبيرة إذا تحقّقت الشروط التالية: 1) التصدير، 2) التصريح بالكلمة const ، أي أنه لا يمكن إعادة إسناده، 3) يمكن للمبرمج أن يثق أنه لم يتغيّر لا هو ولا الخاصيّات المتفرّعة عنه. لماذا؟ هذه أداة إضافية للحالات التي يكون المبرمج فيها غير متأكد من أن المتغيّر ستتغيّر قيمته. تخبر المتغيّرات المكتوبة بحروف كبيرة (مثل UPPERCASE_VARIABLES) المبرمج أن بإمكانه الوثوق من أن تلك الثوابت (وخاصيّاتها) لن تتغيّر قيمتها. هل ينطبق الأمر على كل المتغيّرات المعرّفة بالكلمة const؟ لا حاجة لذلك، وبالتالي يجب ألا تُستخدَم الحروف الكبيرة في تسمية الثوابت داخل ملف، ولكنها يجب أن تُستخدَم للثوابت المُصدَّرة. ماذا عن الكائنات المُصدَّرة؟ استخدم الحروف الكبيرة في المستوى الأعلى من التصدير (مثلًا EXPORTED_OBJECT.key) وتأكّد من أن الخاصيّات المتفرّعة كلها لا تتغيّر. // سيّئ const PRIVATE_VARIABLE = 'should not be unnecessarily uppercased within a file'; // سيّئ export const THING_TO_BE_CHANGED = 'should obviously not be uppercased'; // سيّئ export let REASSIGNABLE_VARIABLE = 'do not use let with uppercase variables'; // --- // مرخَّص به، لكنه لا يضيف قيمة دلالية export const apiKey = 'SOMEKEY'; // أفضل في أغلب الحالات export const API_KEY = 'SOMEKEY'; // --- // سيّء، حروف كبيرة غير ضرورية في الاسم مع انعدام القيمة الدلالية export const MAPPING = { KEY: 'value' }; // جيّد export const MAPPING = { key: 'value' }; المسترجعات (Accessors) ليس مفروضًا وجود توابع الاسترجاع للوصول إلى الخاصيّات. لا تستخدم توابع الاسترجاع أو التعديل التي توفّرها جافاسكريبت لأنّ لها آثارًا جانبيةً غير متوقعة، ويصعب اختبارها وصيانتها والتعامل معها. إنْ أردت استخدام المسترجعات (أو المعدّلات) فمن الجيّد استخدام التوابع ()getVal و('setVal('hello لهذا الغرض. // سيئ class Dragon { get age() { // ... } set age(value) { // ... } } // جيّد class Dragon { getAge() { // ... } setAge(value) { // ... } } استخدم()isVal أو ()hasVal للخاصيّات والتوابع المنطقية. // سيئ if (!dragon.age()) { return false; } // جيّد if (!dragon.hasAge()) { return false; } لا ضير في إنشاء دوال ()get و ()set، ولكن يجب أن تكون متسقة. class Jedi { constructor(options = {}) { const lightsaber = options.lightsaber || 'blue'; this.set('lightsaber', lightsaber); } set(key, val) { this[key] = val; } get(key) { return this[key]; } } الأحداث عند ربط حمولات البيانات بالأحداث (سواء كانت أحداث DOM أوأحداث مكتبات خاصّة مثل Backbone)، مرّر كائنًا مصنّف النوع (معروف أيضًا باسم “hash”) بدلًا من قيمة خام. سيسمح ذلك لاحقًا بإضافة المزيد من البيانات إلى حمولة الحدث دون الحاجة إلى إيجاد وتحديث كل معالجات الحدث. على سبيل المثال، بدلًا من: // سيئ $(this).trigger('listingUpdated', listing.id); // ... $(this).on('listingUpdated', (e, listingID) => { // do something with listingID }); من الأفضل استخدام: // جيّد $(this).trigger('listingUpdated', { listingID: listing.id }); // ... $(this).on('listingUpdated', (e, data) => { // do something with data.listingID }); jQuery ضع السابقة $ قبل متغيرات jQuery . // سيئ const sidebar = $('.sidebar'); // جيّد const $sidebar = $('.sidebar'); // جيّد const $sidebarBtn = $('.sidebar-btn'); أضف عمليات البحث المؤقت في jQuery إلى التخبئة (Cache). // سيئ function setSidebar() { $('.sidebar').hide(); // ... $('.sidebar').css({ 'background-color': 'pink', }); } // جيّد function setSidebar() { const $sidebar = $('.sidebar'); $sidebar.hide(); // ... $sidebar.css({ 'background-color': 'pink', }); } بالنسبة لاستعلامات DOM استخدم ‎$('.sidebar ul')‎ أو parent > child $('.sidebar > ul')‎. راجع jsperf. استخدم التابع find في الاستعلام عن الكائنات في نطاق jQuery. // سيئ $('ul', '.sidebar').hide(); // سيئ $('.sidebar').find('ul').hide(); // جيّد $('.sidebar ul').hide(); // جيّد $('.sidebar > ul').hide(); // جيّد $sidebar.find('ul').hide(); المكتبة القياسية تحوي المكتبة القياسية أدوات مساعدة لم تعد تُستخدم ولكن يُيقى عليها للتوافق مع المتصفحات القديمة. استخدم التابع Number.isNaN بدلًا من التابع العام isNaN. استعن بالقاعدة no-restricted-globals. لماذا؟ يحوّل التابع isNaN القيم غير العددية إلى أعداد، ويُرجع القيمة true لأي شيء يتحوّل إلى NaN. إذا كان السلوك هو ما ترغب فيه، فكن صريحًا في ذلك. // سيئ isNaN('1.2'); // false isNaN('1.2.3'); // true // جيّد Number.isNaN('1.2.3'); // false Number.isNaN(Number('1.2.3')); // true استخدم التابع Number.isFinite بدلًا من التابع العام isFinite. استعن بالقاعدة no-restricted-globals. لماذا؟ isFinite تحوّل القيم غير العددية إلى أعداد، وتُرجع القيمة true لأي شيء يتحوّل إلى عدد منته. إذا كان السلوك هو ما ترغب فيه، فكن صريحًا في ذلك. // سيئ isFinite('2e3'); // true // جيّد Number.isFinite('2e3'); // false Number.isFinite(parseInt('2e3', 10)); // true الاختبار يجب أن تكتب اختبارات، وليس أساسيًّا الإطار الذي تستخدمه لذلك. المهم أن تكتبها. احرص على كتابة العديد من الدوال البسيطة والصغيرة، وقلّل من استخدام البيانات المتحوّلة . كن حذرًا عند استخدام أصناف stubs و mocks لأنها يمكن أن تجعل اختباراتك أكثر هشاشة. نستخدم mocha وjest في Airbnb. يُستخدَم tape كذلك من حين لآخر في وحدات صغيرة ومعزولة. محاولة اختبار 100٪ من الشفرة هو هدف جيد، حتى لو لم يكن دائمًا عمليّا. كلما أصلحت خللًا، قم بكتابة اختبار ارتداد (Regression test). فمن المؤكد أنّه بدونه ستعود الثغرات مجدّدا. ترجمة - وبتصرّف - للمقال Airbnb JavaScript Style Guide
  10. التخزين الكائني Object storage هي وسيلة شائعة لتخزين ومعالجة الأصول الثابتة مثل الصوت والصور والنصوص وملفات PDF، وأنواع أخرى من البيانات غير المُهيكلة. مقدمو الخدمات السحابية يعرضون خدمات التخزين الكائني بالإضافة إلى التخزين المحلي أو التخزين الكُتلي Block storage، والذي يُستخدم لتخزين ملفات التطبيقات الديناميكية وقواعد البيانات. يمكنك مطالعة المقال مقارنة بين خدمات التخزين الكائني والتخزين الكتلي لمعرفة حالات الاستخدام والاختلافات بين المقاربتين. Spaces هي خدمة تخزين كائني تقدمها DigitalOcean. فبالإضافة إلى إمكانية تسجيل الدخول ورفع وإدارة وحذف الملفات المخزنة بواسطة لوحة التحكم، يمكنك أيضًا الوصول إلى مساحة تخزين DigitalOcean عبر سطر الأوامر وعبر الواجهة البرمجية Spaces API. في هذا الدرس، سوف نُنشئ تطبيق Node.js يسمح للمستخدمين برفع الملفات إلى مساحة التخزين في DigitalOcean عن طريق ملء استمارة في الواجهة الأمامية من الموقع. المتطلبات الأساسية لمتابعة هذا الدرس، سوف تحتاج: مساحة تخزين في DigitalOcean، إضافة إلى مفتاح وصول access keyومفتاح وصول سرّي لحسابك. يمكنك مطالعة الدليل How To Create a DigitalOcean Space and API Keyلإعداد حساب على DigitalOcean، وإنشاء مساحة التخزين خاصتك وإنشاء مفتاح API ورمز سري. تحتاج كذلك إلى تثبيت Node.js و npmعلى جهازك. يمكنك الذهاب إلى الموقع الرسمي لـ Node.js لتثبيت الإصدار المناسب لنظام التشغيل الخاص بك. يجب أن يكون لديك الآن حساب DigitalOcean ومساحة تخزين مع مفتاح الوصول access key إضافة إلى Node.js وnpm مُثبتين على جهازك. إضافة مفاتيح الوصول إلى ملف الاعتماد Credentials File مساحات التخزين في DigitalOcean متوافقة مع الواجهة البرمجية API لـ Amazon Simple Storage Service (S3) ، سنستخدم الخدمة SDK AWS الخاصة بجافاسكريبت في Node.js لربط الاتصال بمساحة التخزين التي أنشأناها. الخطوة الأولى هي إنشاء ملف الاعتماد credentials file، لوضع مفتاح الوصول access key ومفتاح الوصول السري secret access keyالذي حصلت عليه عند إنشاء مساحة التخزين الخاصة بك في DigitalOcean. سيتم وضع الملف في aws/credentials./~ على الماك و اللينكس، أو في C:\Users\USERNAME\.aws\credentials على ويندوز. افتح مُوجه الأوامر command prompt، تأكد من أنك في مجلد المستخدمين Users directory الخاص بك، وأنك تملك صلاحيات استخدام sudo، ثم قم بإنشاء مجلد .aws مع تضمينه ملف الاعتماد. sudo mkdir .aws && touch .aws/credentials افتح الملف، وألصق الكود البرمجي التالي داخله، مع استبدال your_access_key و your_secret_key بمفاتيحك الخاصة. credentials [default] aws_access_key_id=your_access_key aws_secret_access_key=your_secret_key الآن سوف تتم المصادقة على دخولك إلى مساحة التخزين Spaces عبر SDK AWS، ويمكننا الآن الانتقال إلى إنشاء التطبيق. تثبيت ارتباطات Node.js للبدء، قم بإنشاء مجلد لوضع تطبيق Node.js فيه ثم اذهب إليه. في هذا المثال التوضيحي، سوف نقوم بإنشاء مشروعنا في spaces-node-app في المجلد sites. mkdir sites/spaces-node-app && cd sites/spaces-node-app قم بإنشاء ملف جديد package.json لأجل مشروعك. وألصق داخله الكود أدناه. package.json { "name": "spaces-node-app", "version": "1.0.0", "main": "server.js", "scripts": { "start": "node server.js" }, "license": "MIT" } الملف package.json سيتضمّن الاسم، رقم الإصدار وترخيص استعمال التطبيق. أما الحقلscripts فسيسمح لنا بتشغيل خادم Node.js بكتابة npm start بدلًا من node server.js. سوف نقوم بتثبيت كل ارتباطاتنا dependencies بالتعليمة npm install، تليها أسماء الارتباطات الأربع في مشروعنا. npm install aws-sdk express multer multer-s3 بعد تشغيل هذا الأمر، سيتم تحديث الملف package.json. هذه الارتباطات ستساعدنا على ربط الاتصال بالواجهة البرمجية لمساحة التخزين فيDigitalOcean ، وإنشاء خادم الويب، والتعامل مع رفع الملفات. ● aws-sdk - ستسمح لنا AWS SDK لجافاسكريبت بالوصول إلى S3 من خلال الواجهة البرمجية لجافاسكريبت (JavaScript API). ● express - Express هي بيئة تطوير للشبكة تسمح لنا بإعداد الخادم بسرعة وكفاءة. ● multer - Multer هو برنامج وسيط لمعالجة رفع الملفات. ● multer-s3 - Multer S3 يمدد رفع الملفات لـ S3 object storage، والتي هي في حالتنا، مساحة تخزين DigitalOcean. الآن وبعد أن أعددنا مكان وارتباطات مشروعنا، يمكن إعداد الخادم والمنظر الأمامي front-end views. إنشاء الواجهة الأمامية للتطبيق أولًا، سننشئ عدة ملفات لأجل المناظر العامّة public views لتطبيقنا. وهي التي سيراها المستخدم على الواجهة الأمامية. قم بإنشاء مجلد اسمهpublic يتضمن index.html, success.html و error.html. حيث ستحتوي كل هذه الملفات الثّلاث على هيكل HTML أدناه، لكن مع محتويات مختلفة في body. قم بكتابة الكود البرمجي التالي في كل ملف. <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>DigitalOcean Spaces Tutorial</title> <link rel="stylesheet" href="./style.css"> </head> <body> <!-- contents will go here --> </body> </html> أكتب رسالة خطأ في body داخل error.html. error.html ... <h1>Something went wrong!</h1> <p>File was not uploaded successfully.</p> ... أكتب رسالة نجاح فيbody داخل success.html. success.html ... <h1>Success!</h1> <p>File uploaded successfully.</p> ... في index.html، سنقوم بإنشاء استمارة HTML بـ multipart/form-data. وستتكون من مُدخل input بسيط وزر الإرسال submit. index.html ... <h1>DigitalOcean Spaces Tutorial</h1> <p>Please select a file and submit the form to upload an asset to your Dig-italOcean Space.</p> <form method="post" enctype="multipart/form-data" action="/upload"> <label for="file">Upload a file</label> <input type="file" name="upload"> <input type="submit" class="button"> </form> ... وأخيرا، سنقوم بإنشاء ملف style.css وإضافة تنسيقات CSS لجعل التطبيق سهل القراءة. style.css html { font-family: sans-serif; line-height: 1.5; color: #333; } body { margin: 0 auto; max-width: 500px; } label, input { display: block; margin: 5px 0; } بهذه الملفات الثلاث، لدينا الآن استمارة لرفع الملفات والتي ستشكل ملامح الصفحة الرئيسية لتطبيقنا الصغير هذا، كما لدينا أيضًا صفحة للنجاح والخطأ لأجل المستخدم. إعداد Express Server Environment لقد أنشأنا كل الملفات الخاصة بالواجهة الأمامية لتطبيقنا، لكننا حاليا لم نقم بعدُ بإعداد أي خادم أو أي طريقة لعرضها. سنقوم بإعداد خادمNode بواسطة Express web framework. في المجلد الجذري للمشروع، قم بإنشاء ملف server.js. وفي الجزء العلوي، قم برفع أربعة ارتباطات بواسطة الدالة ()require. كما سنُوجه تطبيقنا عبر عيّنة app من express. server.js // Load dependencies const aws = require('aws-sdk'); const express = require('express'); const multer = require('multer'); const multerS3 = require('multer-s3'); const app = express(); الواجهة الأمامية موجودة في المجلدpublic ، لذلك ضع الإعدادات تحت الارتباطات مباشرة. server.js ... // Views in public directory app.use(express.static('public')); سوف نوجّه index.html, success.html و error.html نسبة إلى جذر الخادم root of the server. server.js ... // Main, error and success views app.get('/', function (request, response) { response.sendFile(__dirname + '/public/index.html'); }); app.get("/success", function (request, response) { response.sendFile(__dirname + '/public/success.html'); }); app.get("/error", function (request, response) { response.sendFile(__dirname + '/public/error.html'); }); وأخيرا، سوف نخبر الخادمَ أيّ منفذport يجب أن يُصغي إليه. في هذا المثال، سنستخدم 3001, ولكن يمكنك تعيين أي منفذ متاح. server.js ... app.listen(3001, function () { console.log('Server listening on port 3001.'); }); احفظ server.js وشغّل الخادم. يمكنك القيام بذلك عن طريق تشغيل node server.js، أو بـ npm start، الاختصار الذي وضعناه في package.json. npm start Output > node server.js Server listening on port 3001. تصفّح http://localhost:3001، وسوف ترى استمارة الرفع، لأننا جعلنا index.html جذرَ الخادم. يمكنك أيضًا تصفّح http://localhost:3001/success و http://localhost:3001/error للتحقق من أن تلك الصفحات تُوجّه بشكل صحيح. رفع ملف إلى مساحة التخزين بواسطة Multer الآن بعد أن أعددنا بيئة الخادم بشكل صحيح، الخطوة الأخيرة هي دمج الاستمارة مع Multer وMulter S3 لأجل رفع الملف إلى مساحة التخزين. يمكنك استخدام ()new aws.S3 لربط الاتصال بـعميل أمازون( S3 (Amazon S3 client. ولاستخدامه مع Spaces DigitalOcean، سنحتاج إلى تحديد نقطة مرجعيةendpoint جديدة للتأكد من أنه يرفع الملفات إلى المكان الصحيح. في وقت كتابة هذا الدرس، nyc3 هي المنطقة الوحيدة المتاحة لـمساحة التخزين Spaces. في الملف server.js، انتقل مرة أخرى إلى أعلى وقم بإلصاق الأكواد التالية مباشرة تحت التصريح بالثوابت. server.js ... const app = express(); // Set S3 endpoint to DigitalOcean Spaces const spacesEndpoint = new aws.Endpoint('nyc3.digitaloceanspaces.com'); const s3 = new aws.S3({ endpoint: spacesEndpoint }); باستخدام المثال الموجود في وثائق multer-S3، سوف نقوم بإنشاء دالة upload، وإحالة اسم مساحة التخزين خاصتك إلى الخاصية bucket. قم بإعطاء القيمة acl للخاصية public-read لضمان أن يكون الملف في متناول الجميع. لكن إن تركت تلك الخاصية فارغة فستأخذ القيمية الافتراضية private، مما يجعل الملفات غير مُتاحة على شبكة الإنترنت. server.js ... // Change bucket property to your Space name const upload = multer({ storage: multerS3({ s3: s3, bucket: 'your-space-here', acl: 'public-read', key: function (request, file, cb) { console.log(file); cb(null, file.originalname); } }) }).array('upload', 1); بعد اكتمال الدالة upload، الخطوة الأخيرة هي وَصل استمارة الرفع بالكود البرمجي لإرسال الملف وتوجيه المستخدم وفقا لذلك. انتقل إلى أسفل الملف server.js، وألصق هذا الكود مباشرة فوق الوظيفة ()app.listen في نهاية الملف. server.js ... app.post('/upload', function (request, response, next) { upload(request, response, function (error) { if (error) { console.log(error); return response.redirect("/error"); } console.log('File uploaded successfully.'); response.redirect("/success"); }); }); عندما ينقر المستخدم على submit سيتم إرسال استعلامPOST إلى upload/, سيكون Node في حالة إصغاء/انتظار لـ POST. وسيستدعي الدالة()upload. وفي حال العثور على خطأ، فستُعيد العبارة الشرطية توجيه المستخدم إلى الصفحة error/. أما إذا مرّت الأمور بشكل جيد، فستتم إعادة توجيه المستخدم إلى الصفحة success/، وسيتم رفع الملف إلى مساحة التخزين خاصتك. هذا هو الكود الكامل لـ server.js. server.js // Load dependencies const aws = require('aws-sdk'); const express = require('express'); const multer = require('multer'); const multerS3 = require('multer-s3'); const app = express(); // Set S3 endpoint to DigitalOcean Spaces const spacesEndpoint = new aws.Endpoint('nyc3.digitaloceanspaces.com'); const s3 = new aws.S3({ endpoint: spacesEndpoint }); // Change bucket property to your Space name const upload = multer({ storage: multerS3({ s3: s3, bucket: 'your-space-here', acl: 'public-read', key: function (request, file, cb) { console.log(file); cb(null, file.originalname); } }) }).array('upload', 1); // Views in public directory app.use(express.static('public')); // Main, error and success views app.get('/', function (request, response) { response.sendFile(__dirname + '/public/index.html'); }); app.get("/success", function (request, response) { response.sendFile(__dirname + '/public/success.html'); }); app.get("/error", function (request, response) { response.sendFile(__dirname + '/public/error.html'); }); app.post('/upload', function (request, response, next) { upload(request, response, function (error) { if (error) { console.log(error); return response.redirect("/error"); } console.log('File uploaded successfully.'); response.redirect("/success"); }); }); app.listen(3001, function () { console.log('Server listening on port 3001.'); }); أوقف خادم Node عبر الضغط على CONTROL + C في موجه الأوامر، وأعد تشغيله لضمان تطبيق التغييرات الجديدة. npm start انتقل إلى جذر المشروع، اختر ملفَا، وقم بتفعيل الاستمارة. إذا تم تعيين كل شيء بشكل صحيح، سيتم توجيهك إلى صفحة النجاح success page، وسوف يكون الملف متاحًا للعموم في مساحة التخزين خاصتك في DigitalOcean. بافتراض أن الملف الذي قمت برفعه هو test.txt، فإن عنوان الملف سيكون: https://your-space-here.nyc3.digitaloceanspaces.com/test.txt من الأسباب الشائعة لفشل العمليات هي الاعتمادات credentials الخاطئة، وضع ملفات الاعتماد في أماكن غير صحيحة أو استخدام اسم bucket غير صحيح. الخلاصة تهانينا، لقد أنشأت تطبيق Node.js وExpress لرفع الأصول الثابتة للتخزين الكائني object storage. يمكن أن تقوم ببعض التعديلات والتجارب على التطبيق من هنا. لا بد من أخذ احتياطات إضافية مثل المصادقة authentication لضمان سير جيد لهذا النوع من التطبيقات، وتبقى هذه نقطة انطلاق جيدة لجعل تطبيق الويب خاصتك يعمل بسلاسة معDigitalOcean Spaces . ترجمة -وبتصرّف- للمقال How To Upload a File to Object Storage with Node لصاحبته Tania Rascia
  11. مقدمة Node.js هي منصة لجافا سكريبت للبرمجة متعددة الأغراض والتي تسمح للمستخدمين ببناء تطبيقات الشبكة بسرعة. من خلال استخدام جافا سكريبت على كل من الواجهة الأمامية والخلفية، سيكون التطوير أكثر اتساقا وسيُصمم داخل النظام نفسه. في هذا الدليل، ستتعلم كيفية تثبيت Node.js على خادم Debian 8. يحتوي Debian 8 على إصدار لـ Node.js في مجلداته الافتراضية، ولكن ذلك الإصدار قديم في الأغلب، سنقوم باستكشاف طريقتين لتثبيت أحدث إصدارات Node.js على نظامك. المتطلبات الأساسية لمتابعة هذا الدرس، ستحتاج إلى خادم Debian 8 مع مستخدم غير كامل الصلاحيات non-root user ويملك امتيازات sudo. كيفية التثبيت باستخدام PPA أسرع وأسهل وسيلة للحصول على أحدث إصدار من Node.js على خادمك هي بإضافة PPA (personal package archive) الخاص بـ NodeSource. سوف يشتمل على عدد أكبر من تحديثات Node.js مقارنة بمستودعات Debian الرسمية. كما أنه يتيح لك الاختيار بين Node.js v4.x (النسخة القديمة المدعومة على المدى الطويل, مدعومة حتى أبريل 2017)، v6.x (نسخة أحدث من LTS، والتي ستُدعم حتى أبريل 2018)، و Node.js v7.x (النسخة الحالية قيد التطوير). أولا، قم بتثبيت PPA للحصول على محتوياته. تأكد من أنك في المجلد الرئيسي home directory، استخدم curl لاستخراج النص البرمجي لتثبيت الإصدار المفضل لديك، مع استبدال 6.x برقم الإصدار الصحيح: cd ~ curl -sL https://deb.nodesource.com/setup_6.x -o nodesource_setup.sh يمكنك فحص محتويات هذا النص البرمجي بواسطة nano (أو محرر النصوص المفضل لديك): nano nodesource_setup.sh وقم بتشغيل البرنامج النصي عقِب الأمر sudo: sudo bash nodesource_setup.sh سيتم إضافة PPA إلى إعداداتك وسوف يتم تحديث حزمتك المحلية المُخزنة تلقائيًا. بعد تشغيل برنامج التنصيب من nodesource، يمكنك تثبيت حزمة Node.js بنفس الطريقة التي اتبعتها أعلاه: sudo apt-get install nodejs الحزمة nodejs تحتوي رُقامة nodejs (nodejs binary ) إضافة إلى npm، لذلك لا تحتاج إلى تثبيت npm بشكل منفصل. ولكن لكي تعمل بعض حُزم npm (مثل تلك التي تتطلب ترجمة التعليمات البرمجية من المصدر)، فستحتاج إلى تثبيت الحزمة build-essential: sudo apt-get install build-essential كيفية التثبيت بواسطة nvm بدل تثبيت Node.js بواسطة apt، يمكنك استعمال أداة خاصة تسمّى nvm (Node.js version manager). فباستخدام nvm، يمكنك تثبيت عدة إصدارات متكاملة من Node.js ما سيسمح لك بضبط بيئة العمل بشكل أسهل. كما ستعطيك إمكانية الوصول إلى أحدث إصدارات Node.js، ولكن ستسمح لك أيضا باستهداف الإصدارات السابقة التي قد يعتمد عليها تطبيقك. بدايةً سوف نحتاج إلى الحصول على حزم البرمجيات من مستودعات Debian والتي ستسمح لنا ببناء الحُزم المصدرية source packages. التعليمة nvm ستستخدم تلك الأدوات لبناء المكونات الضرورية: sudo apt-get update sudo apt-get install build-essential libssl-dev حالما يتم تثبيت الحُزم الضرورية، يمكنك حذف سكريبت التثبيتnvm من صفحة المشروع علىGitHub , رقم الإصدار قد يكون مختلفًا، ولكن بشكل عام، يمكنك تحميله بـ curl: curl -sL https://raw.githubusercontent.com/creationix/nvm/v0.32.0/install.sh -o install_nvm.sh طالع سكريبت التثبيت بواسطة nano: nano install_nvm.sh قم بتشغيل النص البرمجي بواسطة bash: bash install_nvm.sh النص البرمجي سيقوم بتثبيت البرنامج في مجلد في ~/.nvm. كما سيقوم بإضافة الأسطر اللازمة للملف ~/.profile لجعل التعليمة nvm متاحًا. لكسب إمكانية الوصول إلى التعليمةnvm وكذلك وظائفها، ستحتاج إلى تسجيل الخروج ثم تسجيل الدخول مرة أخرى، أو يمكنك إضافة المصدر ~/.profile حتى يتم اعتبار التغييرات في الجلسةsession الحالية: source ~/.profile الآن وبعد تثبيت nvm، يمكنك تثبيت أحد إصدارات Node.js بشكل منفصل. لمعرفة إصدارات Node.js المتوفرة للتثبيت، قم بكتابة: nvm ls-remote Output ... v6.8.0 v6.8.1 v6.9.0 (LTS: Boron) v6.9.1 (LTS: Boron) v6.9.2 (Latest LTS: Boron) v7.0.0 v7.1.0 v7.2.0 كما ترون، فأحدث إصدار في وقت كتابة هذه السطور هو v7.2.0، ولكن v6.9.2 هو آخر الإصدارات المدعومة على المدى الطويل. يمكنك تثبيته بكتابة: nvm install 6.9.2 سوف ترى المخرجات التالية: Computing checksum with sha256sum Checksums matched! Now using node v6.9.2 (npm v3.10.9) Creating default alias: default -> 6.9.2 (-> v6.9.2) عادة، سيتحولnvm إلى استخدام الإصدار المثبت حديثًا. لكن يمكنك أن تخبرnvm صراحة باستخدام النسخة التي حمّلناها للتو عن طريق كتابة: nvm use 6.9.2 يمكنك مشاهدة النسخة المستخدمة حاليا عن طريق كتابة في واجهة الأوامر: node -v Output v6.9.2 إن كان لديك عدة إصدارات من Node.js، يمكنك معرفة أيّ منها تم تثبيته بكتابة: nvm ls إذا كنت ترغب في جعل أحد هذه الإصدارات نسختك الافتراضية، يمكنك كتابة: nvm alias default 6.9.2 هذا الإصدار سيتم اختياره تلقائيًا عند فتح جلسة عمل جديدة على المِطراف terminal. يمكنك أيضًا الإحالة إليه باستخدام الاسم default هكذا: nvm use default كل نسخة من Node.js ستُتابع حُزمها الخاصة بها، وسيكونnpm متاحًا لإدارتها. يمكنك وضع حُزم تثبيت npm في المجلد ./node_modules الخاص بمشروعNode.js باستخدام الصيغة الطبيعية normal format. على سبيل المثال، بالنسبة للوحدة express: npm install express إذا كنت ترغب بتثبيته بشكل كلّي (جعْله متاحا للمشاريع الأخرى التي تستخدم نفس إصدار Node.js)، يمكنك إضافة العلم –g كما يلي: npm install -g express هذا سيثبّت الحزمة في: ~/.nvm/node_version/lib/node_modules/package_name التثبيت الكلي سيتيح لك تشغيل التعليمات من سطر الأوامر، ولكن سيكون عليك ربط الحزمة من مجالك المحلي local sphere للوصول إليه من داخل برنامج ما: npm link express يمكنك معرفة المزيد حول الخيارات المتاحة لك مع nvm بكتابة: nvm help خلاصة كما رأيت، هناك عدة طرق لتثبيت وتشغيل Node.js على خادم Debian 8. ظروفك ستملي عليك أيّ الطرق المذكورة أعلاه ستكون أفضل لك. وفي حين أن النسخة المحزومة packaged في مستودع Ubuntu هي الأسهل، إلّا أن طريقةnvm أكثر مرونة بالتأكيد. ترجمة -وبتصرّف- للمقال How To Install Node.js on Debian 8 لصاحبه Brian Hogan
  12. في هذا الدرس سنتعلّم كيفية استخدام بيئات التطوير ومكتبات أندرويد الشهيرة التي تستخدم التأشير annotation processing في Kotlin. يوجد في عالم الأندرويد العديد من بيئات التطوير الشهيرة التي تبسّط التطوير. يمكنك استخدام نفس البيئات لأجل التطوير في Kotlin كما تفعل في جافا. يقدم هذا الدرس بعض الأمثلة ويسلّط الضوء على الفروقات في الإعدادات. سنلقي نظرة على Dagger, Butterknife, Data Binding, Auto-parcel و DBFlow (بقية بيئات التطوير يمكن إعدادها بشكل مماثل). كل بيئات التطوير هذه تعمل بنظام التأشير annotation processing: حيث تقوم بالتأشير على الشيفرة البرمجيّة وسيتمّ توليد أكواد جاهزة لأجلك. تقلّل التأشيرات من الإسهاب غير الضروري وتبسّط الشيفرة البرمجية، وإن كنت تريد أن تفهم ما الذي يحدث فعلا في وقت التنفيذ runtime، يمكنك إلقاء نظرة على الشيفرة التي تم إنشاؤها. تذكّر أن كل بيئات التطوير هذه تولّد الأكواد في جافا، وليس Kotlin. في Kotlin تقوم بتحديد الارتباطات (dependencies بطريقة مماثلة لجافا باستخدام Kotlin Annotation processing tool (kapt بدلاً من annotationProcessor. Dagger Dagger هو بيئة تطوير تحقن الارتباطات dependency injection. إن لم يكن مألوفًا لديك، يمكنك القراءة عنه في دليل المستخدم. لقد حوّلنا مثال القهوة المذكور في هذا الدليل إلى Kotlin، ويمكنك إيجاد النتيجة هنا. الشيفرة البرمجية لـ Kotlin تبدو مشابهة إلى حد كبير للشيفرة الأصليّة. يمكنك تصفح المثال كله في ملف واحد. كما هو الحال في جافا، يمكنك استخدامInject@ للتأشير على المنشئ constructor المستخدم من قبل Dagger لإنشاء عيّنات instancesمن صنف ما. لدى Kotlin أسلوب مختصر للإعلان عن خاصية ما وعن معامل (parameter) منشئ ما في نفس الوقت. ولكي تؤشّر على المنشئ، استخدم الكلمة constructor بشكل صريح وضَع التأشير Inject@ قبله: class Thermosiphon @Inject constructor( private val heater: Heater ) : Pump { // ... } التأشير على الوظائف methods يبدو تمامًا بنفس الشكل. في المثال التالي Binds@ تحدّد أنّ الكائن Thermosiphon سيستخدم حيثما كان Pump مطلوبًا، تحدّد Provides@ طريقة بناء Heater، كما توضّح Singleton@ أنّ Heater نفسه ينبغي أن يستخدم في كل مكان: @Module abstract class PumpModule { @Binds abstract fun providePump(pump: Thermosiphon): Pump } @Module(includes = arrayOf(PumpModule::class)) class DripCoffeeModule { @Provides @Singleton fun provideHeater(): Heater = ElectricHeater() } الأصناف المؤشّرة بـ Module@ تحدّد كيفية توفير مختلف الكائنات. انتبه إلى أنه عند تمرير معامل تأشير (annotation argument) كمعامل vararg، فسيكون عليك أن تغلّفه بـ arrayOf، كما هو الحال في (Module(includes = arrayOf(PumpModule::class)@ أعلاه. لكي يتمّ توليد تطبيق محقون الارتباط (dependency-injected implementation) لأجل نوع ما, قم بالتأشير عليه بـ Component@. الصّنف المُولّد سيكون له اسم هذا النوع مسبوقًا بـ Dagger، مثل DaggerCoffeeShop أدناه: @Singleton @Component(modules = arrayOf(DripCoffeeModule::class)) interface CoffeeShop { fun maker(): CoffeeMaker } fun main(args: Array<String>) { val coffee = DaggerCoffeeShop.builder().build() coffee.maker().brew() } يقوم Dagger بتوليد تطبيق لـ CoffeeShop والذي يسمح لك بالحصول على صنف CoffeeMaker محقون بالكامل. يمكنك التنقل ومشاهدة تطبيق DaggerCoffeeShop إذا قمت بفتح المشروع في بيئة التطوير. لاحظنا أن التأشير على الأكواد البرمجية لم يتغيّر تقريبًا عند التحوّل إلى Kotlin. الآن دعونا نرى التغييرات التي ينبغي إدخالها على نص البناء البرمجي. في جافا تقوم بربط Dagger بـ annotationProcessor (أو apt): dependencies { ... annotationProcessor "com.google.dagger:dagger-compiler:$dagger-version" } في Kotlin عليك إضافة ملحقة kotlin-kapt لإتاحة kapt, ثم قم باستبدال annotationProcessor بـ kapt: apply plugin: 'kotlin-kapt' dependencies { ... kapt "com.google.dagger:dagger-compiler:$dagger-version" } هذا كل شيء! لاحظ أن kapt يعتني بملفات جافا كذلك، لذلك لا تحتاج الإبقاء على الارتباط بـ annotationProcessor. النص البرمجي الكامل لبناء لهذا المشروع يمكن العثور عليه هنا . يمكنك أيضا الاطّلاع على الكود البرمجي المحوَّل لأندرويد. ButterKnife يسمح ButterKnife بربط العروض views بالحقول مباشرة بدلاً من استدعاء findViewById. لاحظ أن ملحقات أندرويد الإضافية لـ Kotlin (مُدمجة تلقائيًا في ملحقة Kotlin في أندرويد ستوديو) تحل المسألة نفسها: استبدال findViewById بكود برمجي موجز وواضح. ربّما عليك استخدامه إلا إذا كنت تستخدم ButterKnife سلفًا ولا تنوي الهجرة. يمكنك استخدام ButterKnife مع Kotlin بنفس طريقة استخدامه مع جافا. دعونا نرى أوّلا التغيرات التي طرأت على نص البناء البرمجي في Gradle، ومن ثم تسليط الضوء على بعض الاختلافات في الكود البرمجي. في ارتباطات Gradle استخدم الملحقة kotlin-kapt وقم باستبدال annotationProcessor بـ kapt: apply plugin: 'kotlin-kapt' dependencies { ... compile "com.jakewharton:butterknife:$butterknife-version" kapt "com.jakewharton:butterknife-compiler:$butterknife-version" } لقد قمنا بتحويل عيّنة ButterKnife إلى Kotlin. يمكن العثور على الكود الناتج هنا. دعونا نلقي نظرة أكثر عليه لمعرفة ما الذي تغيّر. في جافا تؤشّر على الحقل وتربطه بالعرض view المقابل: @BindView(R2.id.title) TextView title; في Kotlin لا يمكنك العمل مع الحقول مباشرة، ولكن بدل ذلك يمكنك العمل مع الخصائص. حيث تؤشّر على الخاصّية: @BindView(R2.id.title) lateinit var title: TextView يتم تعريف التأشير BindView@ ليتم تطبيقه على الحقول فقط، مُترجم Kotlin يدرك هذا ويقوم بالتأشير على الحقل المقابل تلقائيا عندما تقوم بالتأشير على كامل الخاصيّة. لاحظ كيف أن مُعدِّل lateinit يسمح بالتصريح بأنواع غير معدومةnon-null مُبتدَأً (initialized) بعد إنشاء الكائن (بعد استدعاء المنشئ). بدون lateinit سيكون عليك أن تُصرّح بنوع ذي قيمة معدومةً (nullable type) وتقوم بإضافة تحقيقات إضافية لاختبار العدميّة nullability. يمكنك أيضًا إعداد الوظائف كمُصغيات listeners، باستخدام تأشيرات ButterKnife: @OnClick(R2.id.hello) internal fun sayHello() { Toast.makeText(this, "Hello, views!", LENGTH_SHORT).show() } يحدد هذا الكود إجراءً ليتم تنفيذه عند النقر على الزر “hello”. لاحظ أنه باستخدام صيغة lambdas فإن الكود يبدو موجزًا عند كتابته مباشرةً في Kotlin: hello.setOnClickListener { toast("Hello, views!") } الدالّة toast مُعرّفة في المكتبة Anko . ربط البيانات مكتبة ربط البيانات تسمح لك بربط بيانات تطبيقك بالخُطاطةlayouts بطريقة موجزة. يمكنك إتاحة المكتبة باستخدام نفس الإعدادات في جافا: android { ... dataBinding { enabled = true } } لجعله يعمل مع أصناف Kotlin قم بإضافة الارتباط kapt: apply plugin: 'kotlin-kapt' dependencies { kapt "com.android.databinding:compiler:$android_plugin_version" } عند التبديل إلى Kotlin، لا تتغير ملفات خطاطةxml على الإطلاق. على سبيل المثال، يمكنك استخدام variableفي data لوصف متغير يمكن استخدامه في الخطاطة. يمكنك تعريف متغير من نوع Kotlin: <data> <variable name="data" type="org.example.kotlin.databinding.WeatherData"/> </data> يمكنك استخدام الصيغة {}@ لكتابة العبارات، بحيث يمكن الإحالة إلى خصائص Kotlin : <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@{data.imageUrl}" android:contentDescription="@string/image" /> لاحظ أن لغة التعبير عن ربط البيانات تستخدم نفس القواعد للإحالة إلى الخصائص كما هو الحال في Kotlin، أي: data.imageUrl . في Kotlin يمكنك كتابة v.prop بدلا من ()v.getProp حتى لو كانت ()getProp من وظائف جافا. وبالمثل، بدلًا من استدعاء مُحدِّدٍ setter مباشرة، فيمكنك استخدام الإحالة: class MainActivity : AppCompatActivity() { // ... override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main) binding.data = weather // the same as // binding.setData(weather) } } يمكنك ربط مُصغي لإطلاق إجراء ما عندما يحدث حدث معين: <Button android:text="@string/next" android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="startOtherActivity" /> هنا startOtherActivity هي وظيفة محددة في النشاط الرئيسي MainActivity: class MainActivity : AppCompatActivity() { // ... fun startOtherActivity(view: View) = startActivity<OtherActivity>() }   يستخدم هذا المثال الدالة startActivity التي تنشئ نيّة intent بدون بيانات وتبدأ نشاطًا جديدًا، والذي يأتي من المكتبة Anko. لأجل تمرير البيانات يمكنك كتابة (startActivity<OtherActivity>("KEY" to "VALUE". لاحظ أنه بدلًا من الإعلان باستخدام lambdas في xml كما في المثال التالي، يمكنك ربط الإجراءات مباشرةً في الكود البرمجي: <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="@{() -> presenter.onSaveClick(task)}" /> // the same logic written in Kotlin code button.setOnClickListener { presenter.onSaveClick(task) } في السّطر الأخير تم تحديد button بالرّجوع إلى رقم تعريفها id باستخدام ملحقة أندرويد لـ Kotlin. يمكنك استخدام هذه الملحقة كبديل وهو ما سيسمح لك بالحفاظ على منطق الربط في الكود البرمجي والحصول على نص برمجي موجز في نفس الوقت. يمكنك العثور على مشروع تمثيليّ هنا. DBFlow DBFlow هي مكتبةSQLite تسمح بتبسيط التفاعل مع قواعد البيانات. كما تعتمد بشكل كبير على التأشير. لاستخدامها مع Kotlin قم بإعداد ارتباطات التأشير باستخدام kapt: apply plugin: 'kotlin-kapt' dependencies { kapt "com.github.raizlabs.dbflow:dbflow-processor:$dbflow_version" compile "com.github.raizlabs.dbflow:dbflow-core:$dbflow_version" compile "com.github.raizlabs.dbflow:dbflow:$dbflow_version" } إن كان تطبيقك يستخدم بالفعل DBFlow، فيمكنك إقحام Kotlin في المشروع بدون مشاكل. يمكنك التّحويل التّدريجي لأكوادك البرمجية الموجودة إلى Kotlin (لضمان ترجمة كل شيء بشكل متناسق). الكود البرمجي المُحوّل لا يختلف كثيرا عن نظيره في جافا. على سبيل المثال، التصريح بجدول يبدو مشابهًا لجافا مع فارق صغير يتمثّل في أنّ القيم الافتراضية للخصائص يجب تحديدها صراحة: @Table(name="users", database = AppDatabase::class) class User : BaseModel() { @PrimaryKey(autoincrement = true) @Column(name = "id") var id: Long = 0 @Column var name: String? = null } بالإضافة إلى تحويل الوظائف الموجودة إلى Kotlin، يمكنك أيضًا الاستفادة من الدعم الخاص بـ Kotlin. على سبيل المثال، يمكن أن تقوم بتعريف الجداول كـ أصناف بيانات data classes. @Table(database = KotlinDatabase::class) data class User(@PrimaryKey var id: Long = 0, @Column var name: String? = null) يعرّف DBFlow مجموعة من الإضافات لجعل استخدامه في Kotlin أكثر سلاسةً، بحيث يمكنك إدراجه في الارتباطات خاصتك: dependencies { compile "com.github.raizlabs.dbflow:dbflow-kotlinextensions:$dbflow_version" } هذا سيمنحك وسيلة للتعبير عن الاستعلامات بأسلوب مشابه لـ C#/LINQ ، استخدم lambdas لكتابة أكواد أبسط بكثير لأجل العمليّات المتزامنة وغيرها. التّحزيم التلقائي Auto-Parcel يسمح التحزيم التلقائي بتوليد قيم محزومة Parcelable من الأصناف المؤشّرة بـ AutoValue@. عند تحديد الارتباطات dependency استخدم مرةً أخرى kapt كمُؤشّر لمعالجة ملفات Kotlin: apply plugin: 'kotlin-kapt' dependencies { ... kapt "frankiesardo:auto-parcel:$latest-version" } يمكنك العثور على النّموذج المُحوّل من هنا. يمكنك التأشير على أصناف Kotlin بـ AutoValue@. دعونا نلقي نظرة على الصّنف المحوّلAddress الذي سيتمّ توليد صياغته المحزومة Parcelable implementation : @AutoValue abstract class Address : Parcelable { abstract fun coordinates(): DoubleArray abstract fun cityName(): String companion object { fun create(coordinates: DoubleArray, cityName: String): Address { return builder().coordinates(coordinates).cityName(cityName).build() } fun builder(): Builder = `$AutoValue_Address`.Builder() } @AutoValue.Builder interface Builder { fun coordinates(x: DoubleArray): Builder fun cityName(x: String): Builder fun build(): Address } } ليس لـ Kotlin وظائف ثابتة static methods، لذلك ينبغي تضمينها داخل كائن مرافق. إذا كنت مصرًّا على استخدامها من داخل الأكواد البرمجية لجافا، قم بالتأشير عليهم بـ JvmStatic. إن كنت ترغب في الوصول إلى صنف أو وظيفة من جافا باسم غير صالح في Kotlin، يمكنك تمرير الاسم داخل علامتي تنصيص (``)، كما هو الحال مع الصّنف التي تم إنشاؤه AutoValue_Address$. عمومًا الكود البرمجي المُحوّل يبدو مشابها كثيرا لكود جافا الأصلي. ترجمة -وبتصرّف- للمقال Android Frameworks Using Annotation Processing من توثيقيات KOTLIN هذا المقال منشور تحت رخصة Apache 2 license
  13. في هذا الدرس، ستتعلم كيفية استخدام خُطّافات (Git (Git hooks لأتمتة نشر بيئة الإنتاج لتطبيقات Rails على خادم أوبونتو 14.04 عن بُعد. باستخدام خُطّافات Git ستتمكن من نشر التطبيقات عن طريق دفع التغييرات إلى خادم الإنتاج production server، وبدلًا من أن تقوم بكل شيء يدويًّا (مثل ترحيل قاعدة البيانات) فالاستعانة بأحد أشكال النشر الآلي، مثل خُطّافات Git، سيوفر عليك الكثير من الوقت على المدى الطويل. في هذا الدرس سنستخدم خُطّافGit من نوعpost-receive ، بالإضافة إلىPuma كخادم للتطبيق،Nginx كوكيل عكسي لـ Puma و PostgreSQL كقاعدة بيانات. المتطلبات الأساسية سوف تحتاج صلاحيات مستخدم غير جذري non-root والذي يملك امتيازات مستخدم أساسي superuser على خادم أوبونتو. في هذا المثال، سيكون اسم المستخدم deploy. يمكنك تعلم كيفية فعل ذلك في هذا الدرس: الإعداد الابتدائي لخادوم أوبنتو 14.04. إذا كنت ترغب في النشر دون الحاجة لإدخال كلمة المرور، فتأكد من إعداد مفاتيح SSH. سوف تحتاج إلى تثبيت Ruby على خادمك. إذا لم تكن قد فعلت ذلك سلفًا، يمكنك تثبيته جنبًا إلى جنب مع Rails باستخدام rbenv أو RVM. سوف تحتاج أيضًا إلى تطبيق Rails مُدار في مستودع git على جهازك. إذا لم يكن لديك تطبيق في git، فسوف نقدم لك تطبيقًا بسيطًا كمثال لتعمل عليه. لنبدأ على بركة الله. تثبيت PostgreSQL معظم بيئات Rails تستخدم PostgreSQL كقاعدة بيانات، لذلك عليك تثبيته على خادمك الآن. على خادم الإنتاج، قم بتحديث apt-get: sudo apt-get update ثم قم بتثبيت PostgreSQL بهذه التعليمات: sudo apt-get install postgresql postgresql-contrib libpq-dev إنشاء قاعدة بيانات الإنتاج الخاصة بالمستخدم لإبقاء الأمور بسيطةً، سنسمي قاعدة بيانات الإنتاج الخاصة بالمستخدم بنفس اسم التطبيق خاصتك. على سبيل المثال، إذا كان اسم تطبيقك “appname”، فيجب عليك إنشاء مستخدم PostgreSQL بهذه الطريقة: sudo -u postgres createuser -s appname لتعيين كلمة مرور لقاعدة بيانات المستخدم، ادخُل سطر أوامر PostgreSQL هكذا: sudo -u postgres psql بعد ذلك قم بتعيين كلمة المرور لقاعدة بيانات المستخدم “appname” هكذا: \password appname قم بإدخال كلمة المرور التي تريد ثم قم بتأكيدها. اخرج من سطر أوامر PostgreSQL بهذه التعليمة: \q الآن نحن على استعداد لتزويد تطبيقك بمعلومات الاتصال الخاصة بقاعدة البيانات. إعداد تطبيق Rails على جهاز التطوير خاصتك، ستقوم بإعداد تطبيقك لأجل النشر. اختياري: إنشاء تطبيق Rails إن كان لديك تطبيق Rails جاهز للنشر. فيمكنك تخطي هذا القسم والقيام بالتغييرات المناسبة لاحقًا. أمّا إن لم يكن لديك تطبيق جاهز، فإن الخطوة الأولى هي إنشاء تطبيق Rails جديدة. هذه التعليمات ستنشئ تطبيق Rails جديد تحت اسم “appname” في المجلد الرئيسي. لا تتردد في استبدال “appname” بالاسم الذي تريد: cd ~ rails new appname ثم تحوّل إلى مجلد التطبيق: cd appname لأجل تطبيقنا هذا، سوف نقوم بتوليد سقالة scaffold controller لكي يجد تطبيقنا شيءً ليعرضه: rails generate scaffold Task title:string note:text لنتأكدْ الآن من أن تطبيقنا موجود في مستودعgit . تهيئة Git Repo إن لم يكن تطبيقك موجودًا بالفعل في مستودع git لسبب ما، قم بتهيئته وإجراء إلزام أولي initial commit. قم بالتحوّل إلى مجلد التطبيق. في مثالنا، التطبيق يسمى " appname" وهو موضوع في المجلد الرئيسي home directory: cd ~/appname git init git add -A git commit -m 'initial commit' الآن دعونا نُجهّز تطبيقنا لربط الاتصال بقاعدة بيانات الإنتاج لـ PostgreSQL. تحديث إعدادات قاعدة البيانات تحوّل إلى مجلد تطبيقك إن لم تكن بالفعل هناك. في مثالنا، التطبيق يسمى “appname” وهو موضوع في المجلد الرئيسي home directory: cd ~/appname الآن افتح ملف إعداد قاعدة البيانات في محرر النصوص المفضل لديك: vi config/database.yml اعثر على مقطع الإنتاج production section في إعدادات قاعدة بيانات تطبيقك، وقم باستبداله بمعلومات الاتصال بقاعدة بيانات الإنتاج خاصتك. من المفروض أن يبدو كشيء من هذا القبيل (قم باستبدال القيم عند الاقتضاء): config/database.yml excerpt production: <<: *default host: localhost adapter: postgresql encoding: utf8 database: appname_production pool: 5 username: <%= ENV['APPNAME_DATABASE_USER'] %> password: <%= ENV['APPNAME_DATABASE_PASSWORD'] %> احفظ واخرج. هذا الملف يؤكد على أن بيئة الإنتاج الخاصة بالتطبيق ينبغي أن تستخدم قاعدة بيانات PostgreSQL تحت مُسمّى “appname_production” على المضيف المحلي localhost. لاحظ أنه تم إحالة اسم المستخدم وكلمة مرور قاعدة البيانات إلى متغيرات البيئة environment variables. سنقوم بتحديدها على الخادم في وقت لاحق. تحديث Gemfile إذا لم يكن لدى Gemfile خاصتك المكتبة pg (PostgreSQL adapter gem)، ولم تكن المكتبة Puma مُحددة، فيجب عليك إضافتهما الآن. افتح Gemfile الخاص بتطبيقك في المحرّر المفضل لديك: vi Gemfile أضف الأسطر التالية إلىGemfile : Gemfile excerpt group :production do gem 'pg' gem 'puma' end احفظ واخرج. سيحدد هذا النص البرمجي أن بيئة الإنتاج production environment يجب أن تستخدم المكتبات pgوpuma : إعداد Puma قبل إعداد Puma، يجب عليك أن تتحقق من عدد وحدات المعالجة المركزية التي يملكها خادمك. يمكنك بسهولة فعل ذلك على خادمك بهذه التعليمة: grep -c processor /proc/cpuinfo الآن، على جهاز التطوير خاصتك، قم بإضافة إعدادات Puma إلى الإعداد config/puma.rb . افتح الملف في محرر النصوص: vi config/puma.rb انسخ وألصق هذه الإعدادات في الملف: config/puma.rb # Change to match your CPU core count workers 2 # Min and Max threads per worker threads 1, 6 app_dir = File.expand_path("../..", __FILE__) shared_dir = "#{app_dir}/shared" # Default to production rails_env = ENV['RAILS_ENV'] || "production" environment rails_env # Set up socket location bind "unix://#{shared_dir}/sockets/puma.sock" # Logging stdout_redirect "#{shared_dir}/log/puma.stdout.log", "#{shared_dir}/log/puma.stderr.log", true # Set master PID and state locations pidfile "#{shared_dir}/pids/puma.pid" state_path "#{shared_dir}/pids/puma.state" activate_control_app on_worker_boot do require "active_record" ActiveRecord::Base.connection.disconnect! rescue Ac-tiveRecord::ConnectionNotEstablished Ac-tiveRecord::Base.establish_connection(YAML.load_file("#{app_dir}/config/database.yml")[rails_env]) end قم بتغيير العددworkers إلى عدد وحدات المعالجة المركزية لخادمك. يفترض المثال أن لديك اثنان. احفظ واخرج. الآن تم إعداد Puma بموضعlocation تطبيقك وموضع مقبسه socket والمذكرات logs ومعرّفات العمليات PIDS. لا تتردد في تعديل الملف، أو إضافة الخيارات التي تناسبك. ألزمCommit التغييرات الأخيرة: git add -A git commit -m 'added pg and puma' قبل الاستمرار، قم بتوليد المفتاح السري والذي سيتم استخدامه لبيئة الإنتاج الخاصة بتطبيقك: rake secret rake secret sample output: 29cc5419f6b0ee6b03b717392c28f5869eff0d136d8ae388c68424c6e5dbe52c1afea8fbec305b057f4b071db1646473c1f9a62f803ab8386456ad3b29b14b89 سوف تنسخ المُخرجات وتستخدمها لتحديد القيمة SECRET_KEY_BASE الخاصة بتطبيقك في الخطوة التالية. إنشاء النص البرمجي لإطلاق Puma سنقوم بإنشاء نص برمجي للإطلاق (Upstart init script). حتى نتمكن من تشغيل وإيقاف Puma بسهولة، وللتأكد من أنه سيبدأ عند بدء التشغيل. على خادم الإنتاج خاصتك، حمّل أداة Jungle Upstart من مستودع Puma على GitHub وضعها في المجلد الرئيسي: cd ~ wget https://raw.githubusercontent.com/puma/puma/master/tools/jungle/upstart/puma-manager.conf wget https://raw.githubusercontent.com/puma/puma/master/tools/jungle/upstart/puma.conf الآن افتح الملف puma.conf حتى تتمكن من تحرير إعدادات النشر الخاصة بمستخدمPuma : vi puma.conf ابحث عن السطرين الذين يحددان setuid و setgid، و قم باستبدال “apps” باسم النشر الخاص بالمستخدم أو المجموعة خاصتك. على سبيل المثال، إذا كان اسم مستخدم النشر “deploy”، فينبغي أن تكون الأسطر هكذا: puma.conf excerpt 1 of 2 setuid deploy setgid deploy الآن ابحث عن السطر الذي يحتوي:exec /bin/bash <<'EOT'. أضف الأسطر التالية تحته، وتأكد من استبدال اسم المستخدم وكلمة المرور الخاصة ب PostgreSQL، وأضف كذلك rake secret الذي قمت بإنشائه سابقًا: puma.conf excerpt 2 of 2 export APPNAME_DATABASE_USER='appname' export APPNAME_DATABASE_PASSWORD='appname_password' export SECRET_KEY_BASE='rake_secret_generated_above' احفظ واخرج. الآن انسخ النصوص في مجلد خدمات الإطلاق Upstart services: sudo cp puma.conf puma-manager.conf /etc/init النص البرمجي puma-manager.conf يُحدد /etc/puma.conf كمرجع لمعرفة التطبيقات التي يجب إدارتها. دعونا ننشئ ونحرّر هذا الملف الآن: sudo vi /etc/puma.conf كل أسطر هذا الملف يجب أن تتضمن مسارات التطبيقات التي تريد من Puma أن يُديرها. سنقوم بنشر تطبيقنا في مجلد يُسمى “appname” داخل المجلد الرئيسي. في هذا المثال، سيكون كما يلي (تأكد من تعديل المسار ليتناسب مع المكان الذي يتواجد فيه تطبيقك): /etc/puma.conf /home/deploy/appname احفظ واخرج الآن تمّ إعداد تطبيقك لينطلق عند بدء التشغيل بمساعدة Upstart, وهذا يعني أن تطبيقك سيبدأ حتى بعد إعادة إقلاع خادمك. لا تنسى أننا لم ننشر التطبيق حتى الآن، لذلك لسنا جاهزين لتشغيله بعد. تثبيت وإعداد Nginx لجعل التطبيق متاحًا على شبكة الإنترنت، يجب أن تستخدم Nginx كخادم. قم بتثبيت Nginx باستخدام apt-get: sudo apt-get install nginx الآن افتح كتلة الخادم الافتراضي default server block بمحرر النصوص: sudo vi /etc/nginx/sites-available/default استبدل محتويات الملف بالتعليمات البرمجية التالية. تأكد من استبدال الأجزاء الملوّنة باسم المستخدم واسم التطبيق المناسبين. /etc/nginx/sites-available/default upstream app { # Path to Puma SOCK file, as defined previously server unix:/home/deploy/appname/shared/sockets/puma.sock fail_timeout=0; } server { listen 80; server_name localhost; root /home/deploy/appname/public; try_files $uri/index.html $uri @app; location @app { proxy_pass http://app; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_redirect off; } error_page 500 502 503 504 /500.html; client_max_body_size 4G; keepalive_timeout 10; } احفظ واخرج. سيقوم هذا النص البرمجي بإعداد Nginx كوكيل عكسي، لذلك طلبات HTTP ستُرسل إلى الخادم Puma عبر مقبس يونيكس Unix socket. لا تتردد في إجراء التغييرات التي تراها مناسبةً. لن نقوم بإعادة تشغيل Nginx, فالتطبيق غير موجود بعدُ على الخادم. سنقوم بإعداد التطبيق فيما يلي. إعداد مستودع الإنتاج (git (Prepare Production Git Remote على خادم الإنتاج، قم بتثبيت git بواسطة apt-get: sudo apt-get install git ثم قم بإنشاء مجلد للمستودع البعيد remote repository. سنقوم بإنشاء مجلد git أوّلي في المجلد الرئيسي وسنسميه “appname_production”. يمكنك تسمية المستودع البعيد كما تريد (ولكن لا تضعه في ~/appnameلأنه المكان الذي سننشر فيه التطبيق): mkdir ~/appname_production cd ~/appname_production git init –bare بما أن هذا المستودع أوّلي، فلا يوجد مجلّد عمل بعدُ وجميع الملفات الموجودة في .git موجودة في المجلد الرئيسي نفسه. نحن بحاجة إلى إنشاء خُطّاف git من نوعpost-receive ، والذي هو النص البرمجي الذي سيتم تشغيله عندما يتلقى خادم الإنتاج دفعةً من git(git push). افتح الملف hooks/post-receive في محرر النصوص: vi hooks/post-receive انسخ وألصق النص التالي في الملف post-receive: hooks/post-receive #!/bin/bash GIT_DIR=/home/deploy/appname_production WORK_TREE=/home/deploy/appname export APPNAME_DATABASE_USER='appname' export APPNAME_DATABASE_PASSWORD='appname_password' export RAILS_ENV=production . ~/.bash_profile while read oldrev newrev ref do if [[ $ref =~ .*/master$ ]]; then echo "Master ref received. Deploying master branch to produc-tion..." mkdir -p $WORK_TREE git --work-tree=$WORK_TREE --git-dir=$GIT_DIR checkout -f mkdir -p $WORK_TREE/shared/pids $WORK_TREE/shared/sockets $WORK_TREE/shared/log # start deploy tasks cd $WORK_TREE bundle install rake db:create rake db:migrate rake assets:precompile sudo restart puma-manager sudo service nginx restart # end deploy tasks echo "Git hooks deploy complete" else echo "Ref $ref successfully received. Doing nothing: only the mas-ter branch may be deployed on this server." fi done تأكد من تحديث القيم التالية: GIT_DIR :مجلد المستودع الأولي لـ (git (bare git repository الذي قمت بإنشائه في وقت سابق WORK_TREE : المجلد حيث تريد نشر تطبيقك (يجب أن يتطابق مع الموضع الذي قمت بتحديده في إعدادات Puma) APPNAME_DATABASE_USER :اسم مستخدم PostgreSQL (ضروري لمهام rake ) APPNAME_DATABASE_PASSWORD : كلمة مرور PostgreSQL (ضروري لمهام rake ) بعد ذلك، يجب عليك مراجعة التعليمات الموجودة بين التعليقين # start deploy tasks و # end deploy tasks. هذه هي التعليمات التي سيتم تشغيلها في كل مرة يتم دفع push الشعبة الرئيسية master branch إلى مستودع الإنتاج في(git (appname_production. إذا تركتها كما هي، فسيحاول الخادم القيام بما يلي بالنسبة لبيئة الإنتاج الخاصة بتطبيقك: تشغيل المُحزّم bundler إنشاء قاعدة بيانات ترحيل قاعدة البيانات الترجمة الأوليةPrecompile للأصول assets إعادة تشغيل Puma إعادة تشغيل Nginx إذا كنت ترغب في إجراء أية تغييرات أو أي إضافات للتحقق من الأخطاء، لا تتردد في القيام بذلك. بمجرد الانتهاء من مراجعة النص البرمجي احفظه واخرج. بعد ذلك، اجعل البرنامج النصي قابلًا للتنفيذ: chmod +x hooks/post-receive Sudo بلا كلمة مرور Passwordless Sudo لأن الخُطّاف post-receive يحتاج إلى تشغيل تعليماتsudo ، فسنسمح للمستخدم deploy باستخدام sudo بدون كلمة مرور(استبدل اسم المستخدمdeploy في حال اخترت اسمًا مختلفًا): sudo sh -c 'echo "deploy ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/90-deploy' هذا سيسمح للمستخدم deploy بتشغيل التعليمة sudo دون الحاجة لإعطاء كلمة المرور. ربما تريد أن تُقيّد التعليمات التي يمكن للمستخدمdeploy القيام بها. وكحد أدنى، عليك استخدام مفتاح المصادقة SSH كما عليك تعطيل المصادقة بكلمة المرور password authentication. إضافة Production Git Remote الآن بعد أن أعددنا كل شيء لخادم الإنتاج، دعونا نضيف production git remote لمستودع التطبيق خاصتنا. على جهاز التطوير خاصتك، تأكد من أنك في مجلد التطبيق: cd ~/appname ثم قم بإضافة مستودع git بعيد (git remote) جديد تحت اسم “production” والذي يشير إلى مستودع git الأولي appname_production الذي أنشأته على خادم الإنتاج. استبدل اسم المستخدم (deploy) وعنوان الـ IP الخاص بالخادم واسم المستودع البعيد (appname_production): git remote add production deploy@production_server_public_IP:appname_production لقد صار تطبيقك الآن جاهزًا للنشر بواسطة git push. النشر للإنتاج Deploy to Production بعد كل الإعدادات التي قمنا بها، يمكنك الآن نشر تطبيقك على الخادم خاصتك عن طريق تشغيل تعليمات git التالية: git push production master هذا سيدفع push شعبتك الرئيسية المحلية local master branch إلى مستودع الإنتاج البعيد production remote الذي قمت بإنشائه سابقًا. عندما يتلقى production remote أمر الدفع، فسينفّذ النصَّ البرمجي post-receive الذي أعددناه في وقت سابق. إذا قمت بكل شيء بشكل صحيح، فيجب أن يكون تطبيقك متاحًا الآن على عنوان الـ IP العام لخادم الإنتاج خاصتك. إذا كنت تستخدم التطبيق التعليمي لهذا الدرس، فمن المفروض أن تكون قادرًا على الوصول إلى http://production_server_IP/tasks من أيّ متصفح و من المفروض أن ترى شيئًا من هذا القبيل: الخلاصة في أي وقت تقوم بإجراء تغيير على تطبيقك، يمكنك تشغيل نفس التعليمة git push للنشر على خادم الإنتاج خاصتك. هذا لوحده من المفروض أن يوفر عليك الكثير من الوقت على مدى عمر المشروع. لقد شمل هذا الدرس فقط الخطّافات من نوع “post-receive”، ولكن هناك عدة أنواع أخرى من الخطّافات التي يمكن أن تساعدك على تحسين أتمتة عملية النشر. ترجمة -وبتصرّف- للمقال How To Deploy a Rails App with Git Hooks on Ubuntu 14.04 لصاحبه Mitchell Anicas
  14. تم تصميم خطاطة CSS الشّبكيّة لتعمل هي وباقي أجزاء CSS كجزء من نظام شامل لإنجاز التخطيط layout. في هذا الدّرس سنشرح كيف أنّ الشّبكة grid تتّسق مع التقنيات الأخرى التي ربّما تستخدمها سلفًا. Grid و flexbox الفرق الأساسي بين خطاطة CSS الشّبكيّة وخطاطة CSS flexbox هي أنّ flexbox تمّ تصميمه لأجل الخطاطات أحادية البعد، بمعنى خطاطة يُمكن أن تُسلك في صفّ أو عمود. أمّا الشّبكةGrid فقد تمّ تصميمها لأجل الخطاطات ثنائيّة الأبعاد، بمعنى خطاطة تَسري في الصّفوف والأعمدة في نفس الوقت. كلا المواصفتين القياسيتينspecifications تتشاركان في بعض السّمات، وإن كنت قد تعلّمت من قبل كيفيّة استخدام flexbox فسترى بعض أوجه التشابه التي ستساعدك في تعلّم مفهوم الشّبكة بسرعة. الخطاطة أحاديّة البعد vs الخطاطة ثنائية البعد سنستخدم مثال بسيط يمكن أن يُوضّح الفرق بين الخطاطة أحادية البعد أو ثنائية الأبعاد. في المثال الأول، سنستخدم flexbox لنَظم مجموعة من المربّعات. سيكون لدينا خمسة أبناء في الوعاء container، وسنُعيّن للخاصيّةflex قيمًا بحيث يمكنها أن تنبسط وتنقبض في حدود هامش 200 بكسل. كما سنحدّد للخاصّية flex-wrap القيمةwrap ، بحيث إذا صارت المساحة في الوعاء أضيق من أن تستوعب المرونة الأساسيّة flex basis، فسيتمّ وضع العناصر في صفّ جديد. <div class="wrapper"> <div>One</div> <div>Two</div> <div>Three</div> <div>Four</div> <div>Five</div> </div> .wrapper { display: flex; flex-wrap: wrap; } .wrapper > div { flex: 1 1 200px; } يمكنك أن ترى في الصورة أن اثنين من العناصر انتظما في سطر جديد. هذان العنصران يتقاسمان الحيّز المتاح ولا يصطفّان تحت العناصر الموجودة أعلاها. يحدث هذا لأنّه عندما تُغلّف العناصر المرنة flex items، فإنّ كل صف جديد (أو عمود عند العمل بالأعمدة) يصبح وعاءً مرنًا جديدًا. حيث أنّ توزيع المساحة يحدث على طول الصف. أحد الأسئلة الشائعة هو كيفية جعل هذه العناصر تصطفّ. هنا نحتاج استخدام الخطاطة ثنائيّة الأبعاد: لأنّك إن كنت تريد ضبط المحاذاة عبر الصفوف والأعمدة، فستحتاج إلى الشّبكة grid . نفس الخطاطة لكن مع شبكات CSS في هذا المثال، سنُنشئ نفس الخطاطة باستخدام الشّبكة. هذه المرة لدينا ثلاثة مدارج للأعمدة column tracks. لسنا بحاجة لوضع أي شيء للعناصر نفسها. حيث سينتظمون تلقائيا واحد في كل خلية من الشّبكة التي تم إنشاؤها. وكما هو واضح فقد بقيت في شبكة صارمة strict grid، حيث يصطفّون في صفوف وأعمدة. ومع وجود خمسة عناصر، فستكون هناك فجوة في نهاية الصف 2. <div class="wrapper"> <div>One</div> <div>Two</div> <div>Three</div> <div>Four</div> <div>Five</div> </div> .wrapper { display: grid; grid-template-columns: repeat(3، 1fr); } سؤال بسيط يجب أن تسأله عند المفاضلة بين الشّبكة و flexbox وهو: هل أنا بحاجة فقط لضبط الخطاطة على صعيد الصّفوف وحدها أو الأعمدة وحدها – استخدم إذًا flexbox هل أحتاج لضبط الخطاطة على صعيد الصفوف والأعمدة معًا – استخدم إذًا الشّبكة محتوى خارجيّ أم خطاطة داخليّة؟ بالإضافة إلى مسألة التفريق بين البعد الواحد والأبعاد الثّنائية، هناك طريقة أخرى لتقرّر ما إذا كان عليك استخدام flexbox أو الشّبكة لأجل التخطيط. يتكيّف flexbox مع حجم المحتوى. فمن الحالات المثالية لاستخدام flexbox هي عندما يكون لديك مجموعة من العناصر التي تريد توزيعها بالتساوي في الوعاء. بحيث يتمّ تحديد المساحة التي سيشغلها كل عنصر على حسب حجم المحتوى. وإذا انتظمت العناصر في سطر جديد، فستتوزع على أساس أحجامها والمساحة المتاحة في ذلك السّطر. الشّبكة تعمل في الاتجاه الداخلي للخطاطة layout in، فعند استخدام خطاطة CSS الشّبكيّة يتمّ إنشاء خطاطة ثم توضع العناصر فيها، وفي حال استخدام قواعد auto-placement ستوضع العناصر في خلايا الشّبكة وفقا للشّبكة الصارمة strict grid. هناك إمكانيّة لإنشاء مدارج tracks تتكيّف مع حجم المحتوى، ولكنّها ستغيّر أيضا كامل المدرج. إذا كنت تستخدم flexbox وتشعر أنّك تفتقد بعض المرونة، فربّما عليك استخدام خطاطة CSS الشّبكيّة. على سبيل المثال إذا حدّدت عرض width عنصر مرن (flex item) بنسبة مئوية لجعله يصطف مع العناصر الأخرى في الصّف الأعلى. في هذه الحالة، فمن المحتمل أن يكون استخدام الشّبكة خيارًا أفضل لك. محاذاة المربّع Box alignment لعلّ من أكثر سمات flexbox إثارة هي أنه مكّننا لأول مرة من التحكم في المحاذاة بشكل صحيح. فقد جعل من السهل توسيط مربع على الصفحة. ويمكن للعناصر المرنة الامتداد على كامل ارتفاع الوعاء، وهذا يعني أنّه صار من الممكن الحصول على أعمدة متساوية الارتفاع. وهي أشياء كنّا نريد القيام به منذ فترة طويلة جدا، كما أنها جاءت مرفقة بجميع أنواع الحيل لمساعدتنا على خلق التأثير البصري الذي نريد. خصائص المحاذاة المحدّدة في المواصفات القياسيّة specification لـ flexbox قد تمّ إضافتها إلى مواصفات قياسيّة جديدة تسمّى Box Alignment Level 3 . وهذا يعني أنّه يمكن استخدامها في مواصفات قياسيّة أخرى، بما في ذلك الخُطاطة الشّبكيّة. وفي المستقبل، قد تنطبق أيضًا على أساليب التخطيط الأخرى. إليك الآن مثالًا بسيطًا لمقارنة flexbox والشّبكة. في المثال الأول، والذي يستخدم flexbox، لدينا وعاء يحتوي ثلاثة عناصر. تمّ تعيين خاصيّة min-height للغلافwrapper ، لتحديد ارتفاع الوعاء المرن flex container. سنعطي أيضا للخاصيّة align-items الخاصّة بالوعاء المرن القيمة flex-end، وبالتالي فإن العناصر سوف تصطفّ في نهاية الوعاء. كما أنّنا أيضا سنحدد الخاصية align-self لـ box1 حتّى تستبدل السّلوك الافتراضي وتمتد على كامل ارتفاع الوعاء وعلى box2 حتّى يصطفّ في بداية الوعاء المرن. <div class="wrapper"> <div class="box1">One</div> <div class="box2">Two</div> <div class="box3">Three</div> </div> .wrapper { display: flex; align-items: flex-end; min-height: 200px; } .box1 { align-self: stretch; } .box2 { align-self: flex-start; } المحاذاة في شبكات CSS يستخدم المثال الثاني الشّبكة لإنشاء نفس التّصميم. هذه المرة سنستخدم خصائص مربع المحاذاة التي تنطبق على الخطاطة الشبكيّة. لذلك سنستخدم start وend لأجل المحاذاة بدلًا من flex-start وflex-end. في حالة الخطاطة الشبكيّة، فنحن نحاذي العناصر داخل الحيّز الخاص بهم الشّبكة. وهو في هذه الحالة خليّة واحدة، ولكن لا شيء يمنع أن يكون الحيّز مكوّنًا من عدة خلايا. <div class="wrapper"> <div class="box1">One</div> <div class="box2">Two</div> <div class="box3">Three</div> </div> .wrapper { display: grid; grid-template-columns: repeat(3،1fr); align-items: end; grid-auto-rows: 200px; } .box1 { align-self: stretch; } .box2 { align-self: start; } الوحدة fr و flex-basis لقد رأينا سابقا كيف نستخدم الوحدةfr لأجل تخصيص نسبة من المساحة المتوفرة في وعاء الشّبكة لأجل مدرج الشّبكة grid tracks. عندما تُستخدم الوحدة fr مع الدالّة ()minmax فيمكن أن تعطينا سلوكًا مشابهًا جدًا للخاصيةflex في flexbox وفي الوقت نفسه تُمكّننا من إنشاء خطاطة في بعدين. لو عدنا إلى المثال الذي أوضحنا فيه الفرق بين الخطاطات أحادية وثنائيّة الأبعاد، فيمكنك أن ترى أن هناك فرقا في الطريقة التي تتكيّف وتتجاوب بها الخطاطتان. فمع الخطاطة المرنة، إذا غيّرنا حجم النافذة توسيعًا وتصغيرًا، سيتكيّف flexbox بشكل جيد بتعديل عدد العناصر في كل صف وفقا للمساحة المتاحة. إن كان لدينا ما يكفي من المساحة فكل العناصر الخمسة ستنتظم في صف واحد، أمّا إن كان الوعاء ضيّقًا فقد لا نجد مساحة لأكثر من واحد. على سبيل المقارنة، النسخة الشبكيّة دائما ما يكون لديها ثلاثة مدارج للأعمدة column tracks. والمدارج نفسها سوف تنبسط وتنقبض، ولكن ستكون هناك دائما ثلاثة أعمدة كما طلبنا عند تعريف الشّبكة. الملء التلقائي لمدارج الشّبكة Auto-filling grid tracks يمكننا خلق تأثير مماثل لـ flexbox، دون التفريط في إمكانيّة الحفاظ على المحتوى مرتبًا في صفوف وأعمدة صارمة، من خلال إنشاء تسلسل المدارج track listing باستخدام العبارةrepeat وخاصّيتي auto-fill و auto-fit. في المثال التالي، سنستخدم auto-fill بدلًا من استخدام الأعداد الصحيحة في العبارة repeat وسنحدّد تسلسل المدارج track listing بـ 200 بكسل. وهذا يعني أن الشّبكة سوف تنشئ أكبر عدد من مدارج الأعمدة ذات 200 بكسل التي يمكن احتواؤها في الوعاء. <div class="wrapper"> <div>One</div> <div>Two</div> <div>Three</div> </div> .wrapper { display: grid; grid-template-columns: repeat(auto-fill، 200px); } عدد مرن من المدارج الأمر هنا مختلف بعض الشيء عن flexbox. ففي مثال flexbox، كانت العناصر أكبر من 200 بكسل قبل التغليف wrapping. يمكننا تحقيق نفس الشّيء في الشّبكة من خلال الجمع بين auto-fill و ()minmax . في المثال التالي، سنُنشئ مدارج تُملأ تلقائيا بواسطة minmax. هدفنا أن نجعل الحدّ الأدنى لحجم المدارج لا يقلّ عن 200 بكسل، سنعيّن الحد الأقصى عند1fr . وبمجرّد أن يخمّن المتصفح كم “200 بكسل” سوف يتّسع لها الوعاء، مع احتساب فجوات الشبكة، فسوف يعتبر الحدّ الأقصى المحدّد 1fr كتعليمات لتوزيع المساحة المتبقية بين العناصر. <div class="wrapper"> <div>One</div> <div>Two</div> <div>Three</div> </div> .wrapper { display: grid; grid-template-columns: repeat(auto-fill، minmax(200px، 1fr)); } لدينا الآن القدرة على إنشاء شبكة بعدد مرن من المدارج المرنة (flexible number of flexible tracks)، ولكن مع محاذاة العناصر التي وُضعت في الشّبكة في الصفوف والأعمدة. الشّبكة والعناصر مُطلقة التموضِع Grid and absolutely positioned elements تتفاعل الشّبكة مع العناصر مطلقة التّموضع، وهو أمر يمكن أن يكون مفيدًا إذا كنّا نرغب في وضع عنصر داخل شبكة أو حيّز من الشبكة. تحدد المواصفات القياسيّة السلوك الافتراضي عندما يكون وعاء الشّبكة كتلة احتواء containing block وحيث يكون وعاء الشّبكة أبًا للعنصر مطلق التّمَوضُع absolutely positioned. وعاء الشبكة ككتلة احتواء لجعل وعاء الشّبكة كتلة احتواء نحتاج إلى إعطاء القيمة relative للخاصيةposition الخاصّة بالوعاء، بالضّبط كما تفعل عادة لصنع كتلة احتواء لأيّ من العناصر الأخرى مُطلقة التموضع absolutely positioned. بعد فعل ذلك، إن حدّدنا لعنصر من الِشبكة position: absolute فإنه سيأخذ نفس الحجم الذي تأخذه كتلة الاحتواء الخاصّة بوعاء الشّبكة، أمّا في حال كان لهذا العنصر الخاصيّة grid position، فسيأخذ حيّز الشّبكة التي وُضع فيها. في المثال أدناه لدينا غلاف يحتوي أربعة عناصر. العنصر الثالث مطلق التموضع وأيضا موضوع على الشّبكة باستخدامline-based placement. وعاء الشّبكة لديه position: relative وكذلك سياق التموضع positioning context لذلك العنصر. <div class="wrapper"> <div class="box1">One</div> <div class="box2">Two</div> <div class="box3"> This block is absolutely positioned. In this example the grid container is the containing block and so the absolute positioning offset values are calculated in from the outer edges of the area it has been placed into. </div> <div class="box4">Four</div> </div> .wrapper { display: grid; grid-template-columns: repeat(4،1fr); grid-auto-rows: 200px; grid-gap: 20px; position: relative; } .box3 { grid-column-start: 2; grid-column-end: 4; grid-row-start: 1; grid-row-end: 3; position: absolute; top: 40px; left: 40px; } يمكنك أن ترى أن العنصر قد أخذ حيّزًا من الصّف الثاني وحتى الرابع، ثمّ بدأ بعد السّطر 1. مع تحديد احداثيّته باستخدام الخصائصtop و left. كما تمّ إخراجه من الانسيابflow كما هي عادة العناصر مطلقة التموضع كما تضعهم قواعدautoplacement في نفس المساحة. كما لم يتمّ إنشاء صفّ جديد حتّى يمتد العنصر إلى سطر الصّف 3. إن أزلت position: absolute من قواعد .box3 يمكنك أن ترى كيف سيُعرض بدون تحديد الموضع. وعاء الشّبكة كأبٍ A grid container as parent إذا كان لدى الابن مُطلَق التّموضع وعاء شبكيّ grid container كأبٍ ولكنّ ذلك الوعاء لا يخلق سياق تموضع positioning context جديد، فهذا يعني أنه قد أُخرج من الانسيابflow كما في المثال السابق. سياق التموضع positioning context سيكون كالسياق الذي أنشأه العنصر كما هو شائع في أساليب التخطيط الأخرى. في حالتنا، إذا أزلنا position: relative من الغلاف أعلاه، سياق التموضع سيكون من viewport ، كما هو موضح في هذه الصورة. لم يعد يشارك هذا العنصر مرّة أخرى في الخطاطة الشّبكيّة من حيث التحجيم أو عندما تحدّد مواضع العناصر الأخرى بشكل تلقائي. مع حيّز الشّبكة كأب With a grid area as the parent إذا وٌضع العنصر مطلق التموضع داخل حيّز الشّبكة فيمكنك حينها إنشاء سياق تموضع على الحيّز. في المثال أدناه لدينا شبكة كما قبل ولكن هذه المرة سنُدرج عنصرا داخل .box3 في الشّبكة. سنعيَن القيمةrelative للخاصيّةposition الخاصة بـ .box3 ثم سنحدّد موضع العنصر الفرعي بواسطة خصائص الإزاحة offset properties. في هذه الحالة، سياق التّموضع سيكون هو حيّز الشّبكة. <div class="wrapper"> <div class="box1">One</div> <div class="box2">Two</div> <div class="box3">Three <div class="abspos"> This block is absolutely positioned. In this example the grid area is the containing block and so the absolute positioning offset values are calculated in from the outer edges of the grid area. </div> </div> <div class="box4">Four</div> </div> .wrapper { display: grid; grid-template-columns: repeat(4،1fr); grid-auto-rows: 200px; grid-gap: 20px; } .box3 { grid-column-start: 2; grid-column-end: 4; grid-row-start: 1; grid-row-end: 3; position: relative; } .abspos { position: absolute; top: 40px; left: 40px; background-color: rgba(255،255،255،.5); border: 1px solid rgba(0،0،0،0.5); color: #000; padding: 10px; } الشّبكة و display: contents سنقوم بإطلالة أخيرة على إحدى المواصفات القياسيّة للتّخطيط والتي تستحقّ التنويه وهي التفاعل بين خطاطة CSS الشّبكيّة وdisplay: contents. قيمةcontents للخاصيةdisplay هي قيمة جديدة موضّحة في المواصفات القياسيّة (Display specification ) كما يلي: "إنّ العنصر نفسه لا يولّد أية مربّعات، ولكنّ بإمكان أبنائه وأشباه العناصر pseudo-elements توليد المربّعات كالمعتاد. ولغرض توليد المربعات والتخطيط، يجب أن يعامل العنصر كما لو أنه قد تم استبداله بأبنائه وبأشباه العناصر في شجرة وثيقة" إذا حدّدت لعنصر ما display: contents فإنّ المربع الذي سينشئه سيختفي، أمّا مربّعات الأبناء فستظهر كما لو أنها قد ارتفعت لمستوى أعلى. وهذا يعني أن أبناء عناصر الشّبكة يمكن أن يصبحوا بدورهم عناصر للشّبكة. إن بدا هذا غريبا فإليك مثالًا بسيطًا. في الوسوم التالية، لدينا شبكة بحيث سيشمل أوّل عناصرها جميع مدارج الأعمدة الثلاث. كما تحتوي ثلاثة عناصر متداخلة. وبما أنّ هؤلاء العناصر ليسوا أبناءً مباشرين، فإنهم لن يصبحوا جزءا من الخطاطة الشّبكيّة لذك فالخاصّيةdisplay ستكون خطاطة عادية regular block layout. <div class="wrapper"> <div class="box box1"> <div class="nested">a</div> <div class="nested">b</div> <div class="nested">c</div> </div> <div class="box box2">Two</div> <div class="box box3">Three</div> <div class="box box4">Four</div> <div class="box box5">Five</div> </div> .wrapper { display: grid; grid-template-columns: repeat(3، 1fr); grid-auto-rows: minmax(100px، auto); } .box1 { grid-column-start: 1; grid-column-end: 4; } إذا أضفنا display: contents إلى قواعدbox1، فإنّ المربع الخاصّ بذلك العنصر سيختفي والعناصر الفرعية ستصبح الآن عناصر للشّبكة وستنساب وفق قواعدauto-placement . <div class="wrapper"> <div class="box box1"> <div class="nested">a</div> <div class="nested">b</div> <div class="nested">c</div> </div> <div class="box box2">Two</div> <div class="box box3">Three</div> <div class="box box4">Four</div> <div class="box box5">Five</div> </div> .wrapper { display: grid; grid-template-columns: repeat(3، 1fr); grid-auto-rows: minmax(100px، auto); } .box1 { grid-column-start: 1; grid-column-end: 4; display: contents; } يمكن أن تكون هذه طريقة لجعل العناصر الفرعيّة في الشّبكة تتصرّف كأنها جزء من الشّبكة، وهذا قد يساعدنا على الالتفاف على بعض المشاكل والتي سيتم حلها بواسطة subgrids بمُجرّد تقديمها. يمكنك أيضا استخدام display: contents بطريقة مماثلة مع flexbox لتمكين العناصر الفرعيّة من أن تصبح عناصر مرنة. كما رأينا خلال هذا الدليل، خطاطة CSS الشّبكيّة هي مجرّد أداة واحدة من الأدوات المتاحة لك. لا تتردّد في دمجها مع الطرق الأخرى للتّخطيط لتحصل على التأثيرات التي تصبو إليها. ترجمة -وبتصرّف- للمقال Relationship of grid layout to other layout methods لأصحابه المساهمين (kan199041, VladPavel15, mfluehr, NouranMahmoud, bgates, Pickles-Spill, jarrodn, AleshaOleg, teoli, rachelandrew) حقوق الصورة البارزة محفوظة لـ Freepik
  15. يصف هذا الدرس كيفية استخدام إضافات أندرويد فيKotlin لتحسين دعم تطوير أندرويد. في هذا الدرس سوف نستعرض الخطوات اللازمة لاستخدام ملحقات أندرويد الإضافية في لغة البرمجة Kotlin، لتعزيز تجربة التطوير في أندرويد. ربط العرض View Binding الخلفية كل مطوري أندرويد يعرفون جيدًا الدالة ()findViewById. والتي هي من دون أدنى شك، مصدر لكثير من المتاعب والأخطاء المحتملة والشيفرات السيئة والتي يصعب قراءتها وصيانتها. صحيح أن هناك العديد من المكتبات المتاحة لتوفير حلول لهذه المشكلة، إلّا أن هذه المكتبات تتطلب حقول تأشير annotating fields لكل عنصر معروض من نوع View. توفر لنا ملحقات أندرويد الإضافية لـ Kotlin تجربة مماثلة لما توفره بعض تلك المكتبات، دون أن نكون في حاجة إلى كتابة شيفرات إضافية. في الأساس، هذا يسمح لنا بكتابة الشيفرة التالية: // Using R.layout.activity_main from the 'main' source set import kotlinx.android.synthetic.main.activity_main.* class MyActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // Instead of findViewById<TextView>(R.id.textView) textView.setText("Hello, world!") } } textView هي خاصية إضافية لـ Activity، ولها نفس النوع المعلن في activity_main.xml (أي TextView). استخدام إضافات أندرويد لـKotlin إعداد الارتباطات Configuring the Dependency سنستخدم في هذا الدرس Gradle، لكن يمكنك تحقيق نفس النتائج باستخدام IntelliJ IDEA project structure أو Maven. إضافات أندرويد هي جزء من ملحقة Kotlin الخاصة بكل من IntelliJ IDEA وAndroid Studio. لذلك لا تحتاج إلى تثبيت ملحقات إضافية. كل ما تحتاجه هو إتاحة ملحقة Gradle لأندرويد في ملف الوحدة build.gradle: apply plugin: 'kotlin-android-extensions' استيراد الخصائص التركيبية synthetic properties من الملائم استيراد جميع خصائص الودجةwidget) ) لخطاطة (layout) معينة دفعة واحدة: import kotlinx.android.synthetic.main.<layout>.* وهكذا إذا كان اسم ملف الخطاطة هو activity_main.xml، فسنقوم باستيراد kotlinx.android.synthetic.main.activity_main.* إن كنّا نريد أن نستدعي الخصائص التركيبية على View، فيجب علينا أيضًا استيراد kotlinx.android.synthetic.main.activity_main.view.* وبمجرد أن نفعل ذلك، يمكننا حينها استدعاء الإضافات المقابلة والتي هي اسماء خصائص سُمّيت على إثر عناصر العرضviews الموجودة في ملف XML. فعلى سبيل المثال، بالنسبة لهذا العرض: <TextView android:id="@+id/hello" android:layout_width="fill_parent" android:layout_height="wrap_content"/> ستكون هناك خاصية اسمها hello: activity.hello.text = "Hello World!" الوضع التجريبي Experimental Mode تشمل الملحقات الإضافية لأندرويد العديد من الميزات التجريبية مثل دعم LayoutContainer ومولّدات تقديم الصنف Parcelable (Parcelable implementation generator). هذه الميزات لا تُعتبر جاهزة بعدُ للإنتاج، لذلك نحتاج إلى التحوّل للوضع التجريبي في build.gradle من أجل استخدامها: androidExtensions { experimental = true } دعم LayoutContainer تدعم الملحقات الإضافية لأندرويد أنواع مختلفة من الحاويات containers. وأبسط تلك الحاويات Activity، Fragment وView. ولكن يمكنك أن تحوّل (افتراضيًا) أي صنف إلى حاوية لإضافات أندرويد من خلال تطبيق الواجهةLayoutContainer ، على سبيل المثال: import kotlinx.android.extensions.LayoutContainer class ViewHolder(override val containerView: View) : ViewHolder(containerView), LayoutContainer { fun setup(title: String) { itemTitle.text = "Hello World!" } } لاحظ أنك تحتاج إلى التحوّل إلى الوضع التجريبي لاستخدام LayoutContainer. دعم النكهات Flavor Support تدعم ملحقات أندرويد الإضافية نكهات أندرويد ((Android flavors. لنفترض أن لديك نكهة اسمها free في ملف build.gradle خاصّتك: android { productFlavors { free { versionName "1.0-free" } } } يمكنك استيراد كافة الخصائص التركيبية للخطاطة free/res/layout/activity_free.xml بإضافة هذا الاستيراد: import kotlinx.android.synthetic.free.activity_free.* في الوضع التجريبي، يمكنك تحديد أي اسم آخر (وليس فقط flavor)، على سبيل المثال freeDebug أو freeRelease يصلحان كذلك. التخزين المؤقت لعناصرView استدعاء ()findViewById يمكن أن يكون بطيئًا، خصوصًا في حالة تشعبات العرض (view hierarchies) الكبيرة، لذلك تحاول إضافات أندرويد التقليل من عدد مرّات استدعاء ()findViewById بواسطة التخزين المؤقت للعروض في الحاويات. افتراضيًا، اضافات أندرويد تضيف دالة تخزين مؤقت مخفية وحقل تخزين إلى كل حاوية (Activity، Fragment، View أو LayoutContainer implementation) مكتوبة بـ Kotlin. التابع method)) صغير جدًا لذلك لا يزيد حجم APK كثيرًا. في المثال التالي، يتم استدعاء ()findViewById مرة واحدة فقط: class MyActivity : Activity() fun MyActivity.a() { textView.text = "Hidden view" textView.visibility = View.INVISIBLE } لكن في الحالة التالية: fun Activity.b() { textView.text = "Hidden view" textView.visibility = View.INVISIBLE } لا يمكننا أن نعرف ما إذا كان سيتم استدعاء هذه الدالة في أنشطة مصادرنا فقط أم أيضا في كل أنشطة جافا. لهذا السبب، لن نستخدم التخزين المؤقت هنا، حتى لو تم تمرير أحد عيّنات instance الصنف MyActivity من المثال السابق كمستقبِل. تغيير استراتيجية التخزين المؤقت للصنف View يمكنك تغيير استراتيجية التخزين المؤقت بشكل شامل أو بالنسبة لكل حاوية على حدة. وهذا أيضًا يتطلب التحول إلى الوضع التجريبي. يتم تحديد استراتيجية التخزين المؤقت الشاملة للمشروع في ملف build.gradle: androidExtensions { defaultCacheImplementation = "HASH_MAP" // also SPARSE_ARRAY, NONE } افتراضيًا، الملحقات الإضافية لأندرويد تستخدم HashMap كمرجع احتياطي للتخزين، ولكن يمكنك التبديل لتطبيق SparseArray، أو إيقاف التخزين المؤقت وحسب. هذا الأخير مفيد بشكل خاص إن أردت الاكتفاء باستخدام الجزء المقسّم Parcelable من إضافات Android. يمكنك أيضًا التأشير على حاوية ما بـ ContainerOptions@ لتغيير استراتيجية التخزين المؤقت: import kotlinx.android.extensions.ContainerOptions @ContainerOptions(cache = CacheImplementation.NO_CACHE) class MyActivity : Activity() fun MyActivity.a() { // findViewById() will be called twice textView.text = "Hidden view" textView.visibility = View.INVISIBLE } Parcelable بدءًا من الإصدار Kotlin 1.1.4، وفّرت الملحقات الإضافية لأندرويد مولّدات تطبيق للصنف Parcelable كميزة تجريبية. إتاحة دعم Parcelable قم بتطبيق ملحقة Gradle المسمّاة kotlin-android-extensions كما هو موضح [أعلاه] (#إعداد الارتباطات) وقم بتشغيل الوضع التجريبي. كيفية الاستخدام قم بالتأشير على الصنف بـ Parcelize@، وسيتم إنشاء تطبيق Parcelable تلقائيًا. import kotlinx.android.parcel.Parcelize @Parcelize class User(val firstName: String, val lastName: String, val age: Int): Parcelable يتطلّب Parcelize@ التصريح بجميع الخصائص المتسلسلة في المنشئ constructor الأولي. ستقوم إضافات أندرويد بإطلاق تحذير على كل الخصائص ذات الحقول المصرّح بها في جسم الصّنف، كما أنّه لا يمكن تطبيق Parcelize@ إذا لم تكن كل معاملات المنشئ الأوّلية خصائصًا. إن كان صنفك يتطلب تسلسلاً منطقيًا أكثر تقدمًا، فيمكنك كتابته داخل صنف مرافق: @Parcelize data class Value(val firstName: String, val lastName: String, val age: Int) : Parcelable { private companion object : Parceler<User> { override fun User.write(parcel: Parcel, flags: Int) { // Custom write implementation } override fun create(parcel: Parcel): User { // Custom read implementation } } } الأنواع المدعومة يدعم Parcelize@ طيفًا واسعًا من الأنواع: الأنواع الأولية Primitive types (ونسخها المغلّفة boxed versions). Objects وenums . String، CharSequence. Exception. Size، SizeF، Bundle، IBinder، IInterface، FileDescriptor SparseArray، SparseIntArray، SparseLongArray، SparseBooleanArray. كل الأنواع المتسلسلة Serializable (حتى Date مدعوم) وتطبيقات Parcelable. تجميعات كل الأنواع المدعومة: List (مُحالة على ArrayList)، و Set (مُحالة على LinkedHashSet)، و Map (مُحالة على LinkedHashMap). بالإضافة إلى عدد من التطبيقات الملموسة: ArrayList، LinkedList، SortedSet، NavigableSet، HashSet، LinkedHashSet، TreeSet، SortedMap، NavigableMap، HashMap، LinkedHashMap، TreeMap، ConcurrentHashMap. الجداول التي تحتوي الأنواع المدعومة. النسخ الفارغة Nullable versions من كل الأنواع المدعومة. تخصيص الـ Parcelers حتى إن لم يكن النوع مدعوما مباشرة، يمكنك كتابة كائن Parceler لأجل دعمه. class ExternalClass(val value: Int) object ExternalClassParceler : Parceler<ExternalClass> { override fun create(parcel: Parcel) = ExternalClass(parcel.readInt()) override fun ExternalClass.write(parcel: Parcel, flags: Int) { parcel.writeInt(value) } } أمّا عناصر Parcelers الخارجية يمكن تطبيقها باستخدام التأشيرات TypeParceler@ أو WriteWith@: // Class-local parceler @Parcelable @TypeParceler<ExternalClass, ExternalClassParceler>() class MyClass(val external: ExternalClass) // Property-local parceler @Parcelable class MyClass(@TypeParceler<ExternalClass, ExternalClassParceler>() val external: ExternalClass) // Type-local parceler @Parcelable class MyClass(val external: @WriteWith<ExternalClassParceler>() ExternalClass) ترجمة -وبتصرّف- للمقال Kotlin Android Extensions لصاحبه Yan Zhulanow
  16. مقدمة Node.js هو بيئة عمل مفتوحة المصدر لجافا سكريبت تعمل وقت التشغيل لبناء تطبيقات الخادم والشبكات بسهولة. تعمل المنصة على لينكس, OS X, FreeBSD وويندوز. يمكن تشغيل تطبيقات Node.js من سطر الأوامر، ولكن سنركز على تشغيلها كخدمة، بحيث سيتم إعادة تشغيلها تلقائيًا في حال حدوث فشل أو في حال إعادة تشغيل الحاسوب، كما يمكن استخدامها بأمان في بيئة الإنتاج. في هذا الدرس، سوف نغطّي موضوع إنشاء بيئة عمل Node.js جاهزة للإنتاج على خادم Debian 8. هذا الخادم سيشغّل تطبيقًا لـ Node.js سيكون مُدارًا بواسطة PM2، ويوفر للمستخدمين وصولًا آمنًا إلى التطبيق عبر Nginx reverse proxy. المتطلبات الأساسية يفترض هذا الدليل أن لديك خادم Debian 8، بصلاحيات مستخدم غير جذري non-root user ومع امتيازات sudo. ونفترض كذلك أن لديك اسم نطاق يشير إلى عنوان IP العام للخادم. دعونا نبدأ بتثبيت بيئة العمل Node.js على خادمك. تثبيت Node.js سوف نقوم بتثبيت أحدث إصدارات Node.js المدعومة على المدى الطويل، وذلك باستخدام أرشيف الحزمة NodeSource. أولًا، تحتاج إلى تثبيت NodeSource PPA لأجل الوصول إلى محتوياته. تأكد من أنك في المجلد الأساسي home directory، وقم باستخدامcurl للحصول على برنامج التثبيت النصي من مستودعات 6.X Node.js: cd ~ curl -sL https://deb.nodesource.com/setup_6.x -o nodesource_setup.sh يمكنك فحص محتويات هذا النص البرمجي بواسطةnano (أو محرر النصوص المفضل لديك): nano nodesource_setup.sh وقم بتشغيل النص البرمجي عقب sudo: sudo bash nodesource_setup.sh سيتم إضافة PPA إلى إعداداتك وسوف يتم تحديث حزمتك المحلية المُخزنة تلقائيًا. بعد تشغيل برنامج التنصيب من nodesource، يمكنك تثبيت حزمة Node.js بنفس الطريقة التي اتبعتها أعلاه: sudo apt-get install nodejs الحزمة nodejs تحتوي رُقامة nodejs (nodejs binary ) إضافة إلى npm، لذلك لا تحتاج إلى تثبيت npm بشكل منفصل. ولكن لكي تعمل بعض حُزم npm (مثل تلك التي تتطلب ترجمة التعليمات البرمجية من المصدر)، فستحتاج إلى تثبيت الحزمة build-essential: sudo apt-get install build-essential لقد تم تثبيت بيئة العمل Node.js، وصارت جاهزة لتشغيل تطبيقاتنا! لذلك دعونا الآن نكتب تطبيقًا بـ Node.js إنشاء تطبيق Node.js سنكتب تطبيق Hello World والذي سيُرجع ببساطة الجملة “Hello World” لكل طلبات HTTP. هذا تطبيق تعليمي بسيط من شأنه أن يساعدك على التعامل مع Node.js، يمكنك بعد ذلك استبداله بتطبيقاتك الخاصة. فقط تذكر أن تعدّل تطبيقك لكي يُصغي إلى منافذ وعناوين IP المناسبة. الشيفرة البرمجية لـ Hello World أولًا، قم بإنشاء وفتح تطبيق Node.js لأجل التحرير. في هذا الدرس، سوف نستخدم nano لتحرير تطبيق تعليمي يُسمّى hello.js: cd ~ nano hello.js أدرج التعليمات البرمجية التالي في الملف. يمكنك استبدال المنفذ 8080، في كلا الموقعين (تأكد من استخدام منفذ غير أساسي non-admin، أي 1024 أو أكبر): hello.js #!/usr/bin/env nodejs var http = require('http'); http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n'); }).listen(8080, 'localhost'); console.log('Server running at http://localhost:8080/'); الآن احفظ واخرج. هذا التطبيق سيُصغي إلى العنوان المحدد(localhost) والمنفذ (8080)، ويُرجع “Hello World” مع كود النجاح (HTTP success) يساوي200 . وبما أننا نُصغي على localhost، فإن العملاء البعيدين remote clients لن يكونوا قادرين على ربط الاتصال بتطبيقنا. اختبار التطبيق لتتمكن من اختبار التطبيق، اجعل hello.js ملفًّا تنفيذيًا: chmod +x ./hello.js وقم بتشغيله هكذا: ./hello.js Output Server running at http://localhost:8080/ لاختبار التطبيق، قم بفتح جلسة على المطراف terminal على الخادم الخاص بك، ثم اربط االاتصال بـlocalhost بواسطة curl: curl http://localhost:8080 إن رأيت المخرجات التالية، فهذا يعني أن التطبيق يعمل بشكل صحيح ويُصغي للعنوان والمنفذ الصحيحين: Output Hello World إن لم ترى المخرجات المناسبة، تأكد من أن تطبيق Node.js قيد التشغيل، وأنه يٌصغي للعنوان والمنفذ الصحيح. بمجرد أن تتيقن بأنه يعمل، أوقف التطبيق (إذا لم تكن قد فعلت من قبل) عن طريق الضغط على Ctrl + C. تثبيت PM2 سنقوم الآن بتثبيتPM2 ، والذي هو مدير العمليات process manager لتطبيقات Node.js. يوفر PM2 وسيلة سهلة لإدارة وإخفاء daemonize التطبيقات (أي تشغيلها كخدمة في الخلفية.( سوف نستخدم npm، وهي حزمة لإدارة وحدات Nodeالتي تُثبّت مع Node.js، لتثبيت PM2 على الخادم استخدم هذا الأمر: sudo npm install -g pm2 الخيار -g يقول لـnpm أنّ عليه تثبيت الوحدة بشكل كلي globally، بحيث تكون متاحة على نطاق النظام كله. إدارة التطبيق عبر PM2 PM2 بسيط وسهل الاستخدام. سوف نغطي فيما يلي بعض استخداماته الأساسية. بدء التطبيق أول شيء سنفعله هو استخدام التعليمة pm2 start لبدء تشغيل التطبيق hello.js في الخلفية: pm2 start hello.js هذا يضيف أيضًا تطبيقك إلى لائحة عمليات PM2، والتي تُحدَّث في كل مرة تبدأ تشغيل التطبيق: Output [PM2] Spawning PM2 daemon [PM2] PM2 Successfully daemonized [PM2] Starting hello.js in fork_mode (1 instance) [PM2] Done. ┌──────────┬────┬──────┬──────┬────────┬─────────┬────────┬─────────────┬──────────┐ │ App name │ id │ mode │ pid │ status │ restart │ uptime │ memory │ watching │ ├──────────┼────┼──────┼──────┼────────┼─────────┼────────┼─────────────┼──────────┤ │ hello │ 0 │ fork │ 3524 │ online │ 0 │ 0s │ 21.566 MB │ disabled │ └──────────┴────┴──────┴──────┴────────┴─────────┴────────┴─────────────┴──────────┘ Use `pm2 show <id|name>` to get more details about an app كما ترى، يُعيّن PM2 تلقائيا اسم التطبيق (بناءً على اسم الملف دون الامتداد .js). ورقم تعريفPM2 . كما يحفظ PM2 معلومات أخرى، من قبيل معرّف العمليةPID ، وحالته الراهنة، واستخدام الذاكرة. سيتم إعادة تشغيل التطبيقات التي تعمل تحت PM2 تلقائيا إذا تعطل التطبيق أو أُوقف، لكي نجعل التطبيق يشتغل تلقائيًا عند بدء أو إعادة التشغيل ينبغي اتخاذ خطوة إضافية. ولحسن الحظ، يوفّرPM2 وسيلةً سهلةً للقيام بذلك، وهي التعليمة الفرعية startup. تقوم التعليمة الفرعية startup بإنشاء وإعداد برنامج نصي لإطلاق PM2 والعمليات المُدارة من قبله عند بدء تشغيل الخادم. يجب عليك أيضًا تحديد المنصة الذي تعمل عليها، والتي هي Ubuntu، في حالتنا: pm2 startup system السطر الأخير من المخرجات سوف يتضمّن تعليمة عليك تشغيلها بامتيازات المستخدم الجذري superuser. Output [PM2] You have to run this command as root. Execute the following command: sudo env PATH=$PATH:/usr/bin /usr/local/lib/node_modules/pm2/bin/pm2 startup systemd -u sammy --hp /home/Sammy قم بتشغيل التعليمة التي تم إنشاؤها (مماثلة للمخرجات الملوّنة أعلاه، ولكن استخدم اسم المستخدم الخاص بك بدلًا من sammy) لجعل PM2 يبدأ مع بداية التشغيل (استخدم التعليمة من المخرجات التي لديك): sudo env PATH=$PATH:/usr/bin /usr/local/lib/node_modules/pm2/bin/pm2 startup systemd -u sammy --hp /home/sammy هذا سوف يُنشئ systemd unit والتي ستُشغّل PM2 للمستخدم خاصتك عند بدء التشغيل. عينة pm2 هذه ستشغّل بدورهاhello.js . يمكنك التحقق من حالة الوحدة systemd بواسطة systemctl: systemctl status pm2 لمزيد من التفاصيل عن systemd، طالع مقال أساسيات Systemd: العمل مع الخدمات، الوحدات Units، واليوميات Journal استخدامات أخرى لـ PM2 (اختياري) يوفرPM2 العديد من التعليمات الفرعية التي تسمح لك بإدارة أو البحث عن معلومات حول تطبيقاتك. لاحظ أن تشغيل PM2 دون أي معاملات arguments سيؤدي إلى عرض صفحة مساعدة تتضمن أمثلة على الاستخدام والتي تغطي استخدامات PM2 بتفاصيل أكثر مما هو موجود في هذا الدرس. يمكنك إيقاف التطبيق بهذه التعليمة (حدّد App name أو id الخاص بـPM2 ): pm2 stop app_name_or_id يمكنك إعادة تشغيل التطبيق بهذه التعليمة (حدّد App name أو id الخاص بـPM2 ): pm2 restart app_name_or_id ويمكن أيضًا مطالعة قائمة من التطبيقات المُدارة حاليًا من قبل PM2 بالتعليمة الفرعيةlist . pm2 list يمكنك الحصول على مزيد من المعلومات حول تطبيق معين باستخدام التعليمة الفرعية info (حدّد App name أو id الخاص بـPM2 ): pm2 info example يمكن الوصول إلى مراقب عمليات PM2 بالتعليمة الفرعية monit. سيتم عرض حالة التطبيق ووحدة المعالجة المركزية CPU واستخدام الذاكرة: pm2 monit الآن وبعد أن قمنا بتشغيل Node.js وإدارته بواسطة PM2، دعونا نبدأ إعداد الوكيل العكسيreverse proxy . إعداد Nginx ليكون الوكيل العكسي للخادم Reverse Proxy Server الآن وبعد أن بدأ تطبيقك يشتغل ويُصغي إلى المضيف المحلي localhost، تحتاج إلى إعداد وسيلة ليتمكن المستخدمون من الوصول إليه. سوف نقوم بإنشاء خادم Nginx كوكيل عكسي لهذا الغرض. في هذا الدرس ستتعلم كيفية إعداد خادم Nginx من الصفر. إن سبق وقمت بإعداد خادم Nginx، فيمكنك الاكتفاء بنسخ location في server block من اختيارك (تأكد من أن المحل location لا يتداخل مع المحتوى الموجود على الخادم). أولًا، قم بتثبيت Nginx باستخدام apt-get: sudo apt-get install nginx الآن افتح ملف الإعدادات الافتراضي لـ server block لأجل تحريره: sudo nano /etc/nginx/sites-available/default احذف كل ما هو موجود في الملف وقم بإدراج الإعدادات التالية. تأكد من استبدال اسم النطاق خاصتك في الموجّه server_name. بالإضافة إلى ذلك، قم بتغيير المنفذ (8080) إن كان تطبيقك مُعدًّا للإصغاء إلى منفذ آخر: /etc/nginx/sites-available/default server { listen 80; server_name example.com; location / { proxy_pass http://localhost:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; } } هذا سيقوم بإعداد الخادم بحيث يرد على الطلبات requests على مستوى الجذر root. إن افترضنا مثلًا أن خادمنا مُتوفر فيexample.com ، فإن الدخول إلى http://example.com/ عبر متصفح الإنترنت سيبعث الطلب إلى hello.js الذي يُصغي إلى المنفذ 8080 في المضيف المحلي. يمكنك إضافة كتل محل location blocks إضافية لنفس كتلة الخادم server block لإتاحة إمكانية الوصول إلى التطبيقات الأخرى على نفس الخادم. على سبيل المثال، إن كنت تُشغّل تطبيقًا آخرًا لـ Node.js على المنفذ 8081، يمكنك إضافة كتلة المحل location block التالية للسماح بالوصول إليها عبر http://example.com/app2 : Nginx Configuration — Additional Locations location /app2 { proxy_pass http://localhost:8081; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; } بمجرد الانتهاء من إضافة كتل المحل لتطبيقاتك، قم بالحفظ واخرج. تأكد من أنك لم ترتكب أي أخطاء نحوية syntax errors أثناء الكتابة: sudo nginx –t بعد ذلك، قم بإعادة تشغيل Nginx: sudo systemctl restart nginx بعد ذلك، قم بترخيص المرور لـ Nginx عبر جدار حماية، إن كان متاحًا. إذا كنت تستخدم ufw ، يمكنك استخدام التعليمة التالية: sudo ufw allow 'Nginx Full' يمكنك بواسطةufw التحقق من الحالة باستخدام التعليمة التالية: sudo ufw status إذا كنت تستخدم IPTables بدلًا من ذلك، يمكنك ترخيص المرور لـ Nginx باستخدام التعليمة التالية: sudo iptables -I INPUT -p tcp -m tcp --dport 80 -j ACCEPT يمكنك دائمًا التحقق من حالة IPTables باستخدام التعليمة التالية: sudo iptables –S على افتراض أن تطبيق Node.js الخاص بك قيد التشغيل، وأن إعدادات Nginx وتطبيقاتك صحيحة، فسيكون بإمكانك الآن الوصول إلى تطبيقك عبر وكيل عكسي لـ Nginx. جرّب ذلك بنفسك عن طريق الدخول إلى عنوان الخادم الخاص بك (الـ IP أو اسم النطاق) الخلاصة تهانينا! لقد نجحت في جعل تطبيق Node.js الخاص بك يعمل في خلفية وكيل عكسي لـ Nginx على خادم Debian 8. إعدادات الوكيل العكسي هاته مرنة بما فيه الكفاية لتمكين المستخدمين من الوصول إلى التطبيقات الأخرى أو محتويات صفحات الأنترنت الثابتة التي تريد مشاركتها. حظًا سعيدًا في عملك على Node.js. ترجمة -وبتصرّف- للمقال How To Set Up a Node.js Application for Production on Debian 8 لصاحبته Lisa Tagliaferri
  17. جميعنا – نحن مصمّمي المواقع –تعلّمنا أن نبقى بعيدًا عن الخُطاطات layouts المستندة على الجداول. لهذا غالبًا ما ننساها ونهملها إلى أن نجد أنفسنا محتاجين إليها. هذا الدرس سيأخذك خطوة بخطوة لإنشاء جدول بيانات أنيق وسلس يحتوي مقارنة بين مميّزات عدة درّاجات نارية من طرازHarley Davidson . سنقوم ببناء الجدول حصرًا بـ HTML ثمّ سنصقله بـ CSS لإنشاء جدول HTML جميل وواضح. الجدول سيقارن ميزات ثلاثة نماذج من الدراجات النارية من طراز Harley Davidson Sportster. سيتمّ بناء الجدول بـ HTML ثمّ سنُنسّقه بـ CSS لجعل البيانات واضحة. مشاهدة الجدول النّهائي سنحتاج إلى عدّة ملفات لأجل تصميم هذا المشروع التّعليمي. وهي: صورة PNG سنستخدمها كخلفية، وصورة كبيرة بالأبيض والأسود لملء الخلفيّة، وشعار Harley Davidson إضافة إلى صور نماذج الدرّاجات النارية الثلاث التي سنقارنها. الملفّ عبارة عن HTML عاديّ. حيث يبدأ بـ DOCTYPE، عنوان الصفحة ورابط لـ CSS. يبدأ محتوى الصفحة بـ <H1>، والذي سيتم لاحقا تحويله إلى شعار HD، بعد ذلك سنضيف وعاء div لمساعدتنا على توسيط المحتوى. بعد ذلك سندرج <table> متبوعًا بـ <THEAD> لتحديد العناوين والتّرويسات headings في جدولنا. يحتوي <THEAD> على صفّين، أحدهما يتضمّن صور الدرّاجات والآخر يتضمّن عناوين كل نموذج في وسم <H2>. الجدول يحتوي ثلاثة أعمدة، ولكن الخلايا الأولى في الترويسة فارغة لذلك سنضيف الحرف الرّابط &nbsp;. لمساعدتنا على تنسيق الجدول وتحسين مقروئية البيانات وفي نفس الوقت سنضيف في الكود أصنافًا classes إلى الخلايا. بعد إغلاق <THEAD> يبدأ <TBODY>. وبينما تُستخدم <th> داخل <THEAD>، فإنّ <td> تستخدم داخل <TBODY>. كل سلسة من الخلايا مُتضمَّنة داخل صفّ واحد، وصفوفنا تساعدنا في تحديد الأعمدة. كما ستتم إضافة كافّة صفوف البيانات لإنهاء HTML. HTML النّهائي <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Harley Davidson Sportster Motorcycle Model Comparison</title> <link href="style.css" rel="stylesheet" /> </head> <body> <h1>Harley Davidson Motorcycles</h1> <div id="container"> <table> <thead> <tr> <th> </th> <th class="iron"><img src="images/iron.jpg" alt="Harley Davidson Iron 883" /></th> <th class="nightster"><img src="images/nightster.jpg" alt="Harley Davidson Nightster" /></th> <th class="fortyeight"><img src="images/forty-eight.jpg" alt="Harley Davidson Forty-Eight" /></th> </tr> <tr> <th> </th> <th class="iron"><h2>Iron 883</h2></th> <th class="nightster"><h2>Nightster</h2></th> <th class="fortyeight"><h2>Forty-Eight</h2></th> </tr> </thead> <tbody> <tr> <td class="feature">Engine</td> <td class="iron">883cc</td> <td class="nightster">1202cc</td> <td class="fortyeight">1202cc</td> </tr> <tr> <td class="feature">Torque</td> <td class="iron">70Nm</td> <td class="nightster">98Nm</td> <td class="fortyeight">98Nm</td> </tr> <tr> <td class="feature">Exhaust</td> <td class="iron">Chrome, staggered shorty exhaust with dual mufflers</td> <td class="nightster">Chrome, slash-cut ex-haust with dual mufflers</td> <td class="fortyeight">Chrome, staggered shorty exhaust with dual slash-cut mufflers</td> </tr> <tr> <td class="feature">Wheels</td> <td class="iron">Black, 13-Spoke Cast Alumi-num</td> <td class="nightster">Black, Laced Steel</td> <td class="fortyeight">Black, Laced Steel</td> </tr> <tr> <td class="feature">Ground Clearance</td> <td class="iron">120mm</td> <td class="nightster">130mm</td> <td class="fortyeight">100mm</td> </tr> <tr> <td class="feature">Price</td> <td class="iron">£6,699</td> <td class="nightster">£8,099</td> <td class="fortyeight">£8,849</td> </tr> </tbody> </table> </div> </body> </html> يبدأ CSS بإزالة وتعويض تنسيق المتصفّح الافتراضي، ثم يقوم بتعيين التنسيق الكلّي للصّفحة. بعد ذلك نضيف صورة الخلفية إلى جسم الصّفحة. أمّا خصائص الخط العّام فستكون16px Georgia رمادي. بعد ذلك سيتمّ تحويل <H1> إلى شعار Harley Davidson باستخدام تقنية استبدال الصورة، ثم سنضع الوعاء div في وسط الصفحة. تُستخدم صورة الخلفية النّمطية للملء. وبعدها سنحدّد قيم الخاصّية box-shadow لـ CSS3 لمحاكاة تأثير الظّل المُنسدل في فوتوشوب. افتراضيًا سيعرض الجدول فجوات صغيرة بين خلايا الجدول. التصميم الذي نبتغي يتطلّب هامشًا بين الأعمدة ولكن دون ثغرات بين الصّفوف. تسمح لنا الخاصيّة border-spacing بضبط التباعد على المحورين Y و X. ستتمّ إضافة الهامش الداخلي Padding, كما سيتمّ توسيط النص في جميع العناصر <th> و <td>، ثم سنستثني الخلايا ذات الصّنف “feature”، حيث سنحاذي النصّوص فيها إلى اليسار. كما ستُمنح هذه الخلايا عرضًاwidth محدّدًا لتغيير تناسب الجدول لجعل ذلك العمود الأكبر من بين بقيّة الأعمدة. أعطينا لكلّ الأصناف “iron”، “nightster” و “fortyeight” خلفيّة بيضاء شفّافة باستخدام RGBa. كان بإمكاننا أن نستخدم صنفًا واحدًا لجميع هذه الخلايا، ولكنّ الأصناف المعيّنة ستساعدنا على التنقل في بيانات الجدول في الكود البرمجي. لأجل إضافة لمسة أخيرة على الجدول، سنضيف نفس الملء الشفّاف إلى صفوف الجدول، ولكن فقط عندما يطوف عليها مؤشّر الفأرة. هذا التأثير البسيط يعزّز سهولة استخدام الجدول، ممّا يساعد المستخدم على مطالعة ومقارنة البيانات. CSS الكامل body, div, h1, h2, h3, h4, h5, h6, p, ul, ol, li, dl, dt, dd, img, form, fieldset, input, textarea, blockquote, table, tr, td, th { margin: 0; padding: 0; border: 0; } body { background: #000 url(images/bg.jpg) center top fixed no-repeat; font: 16px Georgia, Serif; color: #ccc; } h1 { width: 168px; height: 130px; margin: 30px auto; position: relative; background: url(images/harley-davidson.png); text-indent: -9999px; } #container { width: 940px; margin: -115px auto; padding: 110px 10px 50px 10px; background: url(images/bg-pattern.png); box-shadow: 0px 0px 15px #000; } table { border-spacing: 10px 0px; } th, td { text-align: center; padding: 10px; } .feature { width: 183px; text-align: right; font-size: 24px; font-weight: normal; color: #fff; } .iron, .nightster, .fortyeight { background: rgba(255,255,255,0.05); } h2 { font-size: 24px; font-weight: normal; color: #fff; } tr:hover { background: rgba(255,255,255,0.05); } thead tr:hover { background: none; } جدول HTML / CSS النهائي تحقّق بنفسك من المثال لرؤية الشكل النهائي للجدول بكلّ التّأثيرات التي ترافقه. استخدام الشفافية من ملفّات PNG24 وصيغة التّلوينRGBa ساعد على إنشاء تصميم أنيق عندما دُمج مع صورة الخلفية الكبيرة. عمومًا تقنيات الجدول البسيطة هذه يمكن استخدامها في أيّ مشروع لعرض بياناتك الجدولية بطريقة واضحة وسهلة الفهم. ترجمة -وبتصرّف- للمقال How To Create a Slick Features Table in HTML & CSS لصاحبه iggy
  18. في هذا الدرس ستتعلّم كيفيّة إنشاء تطبيق Kotlin بسيط لأندرويد باستخدام Android Studio. تثبيت ملحقة Kotlin تمّ إدماج ملحقة Kotlin مع Android Studio بدءًا من النسخة 3.0. إن كنت تستخدم إصدارًا سابقًا، فستحتاج تثبيت ملحقة Kotlin. اذهب إلى File | Settings | Plugins | Install JetBrains plugin… بعد ذلك ابحث عنKotlin وقم بتثبيته. إن كنت تبحث في الشاشة " Welcome to Android Studio"، فقم باختيار Configure | Plugins | Install JetBrains plugin… عليك إعادة تشغيل بيئة التطوير بعد الانتهاء. إنشاء مشروع من السهل للغاية البدء في استخدام Kotlin لتطوير أندرويد. في هذا الدرس سنعمل على Android Studio. ولكن إن كنت تستخدم Intellij IDEA مع أندرويد، فالعمليّة هي نفسها تقريبًا. لنقم أوّلاً بإنشاء مشروع جديد. اختر Start a new Android Studio project أو File | New project. سيساعدك صندوق الحوار التالي في عملية إنشاء مشروع جديد. ستحتاج إلى اختيار اسم للمشروع وتحديد أيّ إصدارات Android SDK قمت بتثبيتها. معظم الخيارات يمكن إبقاؤها على قيمها الافتراضية، لذلك يمكنك الضغط على Enter مرارًا. تسميّة المشروع: يوفر Android Studio 3.0 خيارًا لتمكين دعم Kotlin على هذه الشاشة. يمكنك التحقق من هذا الخيار وتجاوز الخطوة " Configuring Kotlin in the project" أدناه. اختيار إصدار الأندرويد: اختر إنشاء النشاط الذي سيتم تجهيزه لك: قم بتسمية النّشاط: في Android Studio 3.0، يمكنك أن تختار إنشاء النشاط في Kotlin على الفور، حتى تتمكن من تجاوز الخطوة “Converting Java code to Kotlin”. الإصدارات السابقة تخلق النشاط في Java، ويمكنك بعدها استخدام أداة التحويل الآلي لتحويله. بشكل عام، أسهل طريقة للبدء في استخدام Kotlin هي بالتحويل التلقائي لنشاط جافا إلى نشاط Kotlin. يرجى ملاحظة أنه بدلاً من البحث في الوثائق عن وسيلة جديدة للتعبير عن نمط برمجي قديم، يمكنك كتابته بالجافا، ثم نسخ-لصق كود الجافا في ملف Kotlin، وسيقترح عليك IntelliJ IDEA (أو Android Studio) تحويله. تحويل كود الجافا إلى Kotlin افتح ملف MainActivity.java. ثم قم باستدعاء الإجراء Convert Java File to Kotlin File. يمكنك القيام بذلك بعدّة طرق أسهلها استدعاء Find Action ثمّ البدء في كتابة اسم الإجراء (كما هو مبيّن في المقتطف أدناه). أو يمكنك بدلا من ذلك استدعاء هذا الخيار Code | Convert Java File to Kotlin File من القائمة أو استخدام الاختصار المقابل (يمكنك العثور عليه في عنصر القائمة). بعد التحويل من المفروض أن تحصل على نشاط مكتوب بـ Kotlin. إعداد Kotlin في المشروع إن بدأت في تحرير هذا الملف، Android Studio سيُلمّح لك في الموجّه prompt بأنّ Kotlin لم يتم إعداده بعد لكي تقوم بإعداده. أو بدلاً من ذلك، يمكنك أن تبدأ الإعداد بأن تختار Tools | Kotlin | Configure Kotlin in Project من القائمة الرئيسيّة. ثم ستتم مطالبتك بتحديد إصدار Kotlin. قم باختيار أحدث الإصدارات المتاحة في قائمة الإصدارات المثبّتة. بعد إعداد Kotlin، ينبغي تحديث ملف التطبيق build.gradle. وحينها يمكنك أن ترى أنه تمّت إضافة ملحقة التطبيق: "kotlin-android" ومكتبتها المتعلّقة kotlin-stdlib. الخطوة الأخيرة هي مزامنة المشروع. يمكنك الضغط على “Sync Now” في الموجّه أو استدعاء الإجراء Sync Project with Gradle Files. بناء ونشر تطبيقات Kotlin لأندرويد أنت الآن جاهز لإنشاء التطبيق وتشغيله على محاكٍ أو جهاز. وهذا يجري تمامًا بنفس الطريقة في جافا. يمكنك إطلاق التطبيق والتوقيع عليه على غرار ما تفعله في تطبيقات أندرويد المكتوبة بلغة جافا. حجم ملف تشغيل Kotlin صغير: فحجم المكتبة في حدود 932 KB (الإصدار 1.2.10). وهذا يعني أنّ Kotlin لا يضيف إلا قدرًا قليلًا إلى حجم الملف apk. يُنتج مترجم Kotlin رُقامات byte-code، وبالتالي فليس هناك حقّا أيّ فرق من حيث الشكل والمظهر بين التطبيقات المكتوبة بـ Kotlin وتلك المكتوبة بلغة جافا. من توثيقيات Kotlin
  19. السلام عليكم, بعد بحث مطول عن أفضل بيئة عمل لتطوير تطبيقات الهاتف استقريت على اختيار ionic. لكن قبل أن أبدأ تعلمه يراودني هاجس يؤرق كل مبرمج عربي, وهو مشكلة دعم اللغة العربية. سؤالي هو: هل يمكن إنشاء تطبيقات على ionic باللغة العربية دون مشاكل,
  20. شكرا أخي أحمد ماذا عن الخيار الثاني html/css/javascript/jquiry هل يمكنني الاكتفاء بها لبرمجة التطبيقات أم ان فيها نفس مشكل بايثون
  21. السلام عليكم, أريد أن أدخل عالم برمجة التطبيقات, وقد احترت كيف أفعل ذلك, بحثت كثيرا على الأنترنت وكانت معظم الإيجابات أني سأحتاج الجافا (والتي أكرهها) لبرمجة تطبيقات الأندرويد و وسويفت لأجل منتجات آبل. أرجوا من الإخوان الذين سبقوني في هذا المجال أن يبينوا لي الطريق وأجرهم على الله. لدي سؤالان: 1 - بايثون هو لغتي المفضلة, مستواي فيه متوسط, لكن بإمكاني أن أواصل التعلم , فهل يمكنني أن أبرمج به تطبيقات تعمل على الهاتف وسطح المكتب بنفس الكود؟ وهل علي أن أستخدم أدوات مساعدة؟ 2 - لدي معرفة لا بأس بها ب html/css/javascript/jquiry , وقد قرأت في بعض المقالات أنه بالإمكان برمجة تطبيقات تعمل على كل المنصات فقط بهذه اللغات. فهل هذا صحيح؟ إن كان بالإمكان البرمجة بهما معا, فأي منهما أفضل في نظرك؟ أخذا بعين الاعتبار المعايير التالية: - العمل على كل المنصات (أندرويد, ios, سطح المكتب) - دعم اللغة العربية - السرعة في إنجاز المشروع أيضا سأكون ممتنا لمن يضع كتبا أو مراجع, تحياتي
  22. شكرا أخي الكريم, شرح رائع وسلس. لدي مشكلة مع مستندات جوجل, وهي أن التحويل أحيانا إلى صيغة DOCX . لا يتم بشكل صحيح, حيث تكون هنالك مشاكل في موضع الأشكال والصور. هل هناك حل لهذا. شكرا.
×
×
  • أضف...