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

لوحة المتصدرين

  1. آية ايمش

    آية ايمش

    الأعضاء


    • نقاط

      2

    • المساهمات

      6


  2. عمر الوريكات

    عمر الوريكات

    الأعضاء


    • نقاط

      1

    • المساهمات

      43


  3. محمد أحمد العيل

    • نقاط

      1

    • المساهمات

      308


  4. أكاديميّة حسوب

    • نقاط

      1

    • المساهمات

      5187


المحتوى الأكثر حصولًا على سمعة جيدة

المحتوى الأعلى تقييمًا في 01/07/16 في كل الموقع

  1. كيف توفر أفضل دعم فنّي باستخدام التعابير والجُمل المناسبة؟ يمكن ﻻختيار الكلمات أن يصنع فارقًا كبيرًا عندما يتعلق الأمر بتغيير الطريقة التي يتصرف أو يشعر بها الناس. ففي أواخر العام الماضي، غيَّرت فيس بوك الخيار «إخفاء/تصنيف كمُزعج» إلى خيار «ﻻ أريد أن أرى ذلك». ماذا كانت النتيجة؟ زيادة 58% في عدد الأشخاص الذين بلغّوا عن المنشورات التي ظهرت في صفحة الأخبار (feed)، فقط بسبب تغيير بعض الكلمات! وينطبق المبدأ نفسه على خدمة العملاء، يمكنك باستخدام العبارات المناسبة في تعاملك أن تصنع فارقًا كبيرًا وتجعل تجربة الزبائن مثمرة. سنقدم لك فيما يلي قائمةً من ست عبارات ﻻستخدامها لتقديم خدمة أفضل للعملاء وبناء علاقات أعمق مع الزبائن. ستة عبارات من أجل تقديم أفضل خدمة للزبائن 1. «لا أعرف، لكن سأبحث عن الحل من أجلك» قبل بضع سنوات، كنت أنتقل من شقتي في سان فرانسيسكو وكنت أحتاج لإلغاء اشتراكي بخدمة الإنترنت في منزلي القديم. اتصلت بمزوّد خدمة الإنترنت وأخبرتهم بما أريد فعله، عندها اكتشفت أنه تصنيف هذه الشّركة كأسوأ مزوّد خدمة في أميركا. والحوار الذي جرى بيني وبينهم دليل على ذلك: موظف خدمة الزبائن: «ستحتاج للاتصال بقسم الحسابات» أنا: «حسنًا، هل تستطيع أن تحوِّل اتصالي إليهم من فضلك؟» موظف خدمة الزبائن: «ﻻ نستطيع ذلك، يمكنك اﻻتصال بهذا الرقم بنفسك» وفعلا قمت بالاتصال بقسم الحسابات... موظف خدمة الزبائن الآخر: «عليك اﻻتصال بقسم "Retention" وهذا رقمهم...» قضيت 45 دقيقةً أخرى في الانتظار قبل أن أستسلم في ذلك اليوم؛ ولم تبادر الشركة بتقديم المساعدة إلا بعد طرح الشكوى على تويتر. السبب الوحيد لبقائي زبونًا لدى تلك الشركة هو أنه ﻻ يوجد لدي خيار آخر (للاتصال بالإنترنت). أما بالنسبة لمعظم الشركات، فيمتلك الزبائن خياراتٍ أخرى إن تمت معاملتهم كما فعلت تلك الشركة. في استطلاع عن خدمة العملاء في عام 2011، سألت شركة أميركية العملاءَ عن أكثر العبارات التي أغضبتهم بالتعامل مع خدمة الزبائن وكانت العبارة الفائزة هي...؟ لن تكون خدمة العملاء جيدةً بمعرفة الإجابة الصحيحة دومًا فقط؛ وإنما في كثيرٍ من الأحيان عليك البحث عن الإجابة الصحيحة وتقديمها للعميل. 2. «سأشعر بنفس الأمر لو كنت مكانك» هناك الكثير من الأبحاث عن أهمية إظهار التعاطف في خدمة العملاء. ولكن يمكن تلخيص الموضوع بمنشور من سطر واحد كتبه سيث غودين (Seth Godin) في مدونته أبسط سؤال محبط لخدمة العملاء هو: لقد مررنا بمثل هذا الموقف سواءً كان ذلك في إطار دعم العملاء أو بحوارٍ مع أحد الأصدقاء أو أحد أفراد الأسرة؛ فلا فائدة من الحوار مع شخص لم يفهم ما هو سبب غضبك أو خيبة أملك. لهذا السبب من المهم جدًا عدم الاكتفاء التعاطف فقط ولكن يجب أيضًا إيصال هذا الشعور إلى عميلك بتقديم المساعدة. 3. «سأكون سعيدًا لمساعدتك في ذلك» وجد الباحثان Andrew Newberg و Mark Robert Waldman في كتابهما «Words Can Change Your Brain» أنَّ استخدام -وسماع- كلمات إيجابيةً ستُغيِّر من الطريقة التي نرى بها العالم من حولنا. يمكنك جعل الزبائن (ونفسك) تشعر بإيجابيةٍ أكثر عندما تستعمل كلماتٍ إيجابية. اتخذ 95% من الزبائن حول العالم قرارًا، إما بالتخلي عن التعامل مع شركةٍ ما، أو بالشكوى للآخرين عنها، بسبب تجربة سيئة لخدمة الزبائن. يمكن لأفعالٍ بسيطةٍ مثل إضافة كلمات أكثر قوةً وإيجابيةً عند تفاعل خدمة الزبائن مع العملاء أن تصنع فارقًا كبيرًا. فعندما يُرسِل لك عميلٌ بريدًا إلكترونيًا حول مشكلةٍ ما، فبدلًا من أن ترد عليه بقولك: «سألقي نظرة على المُشكل»؛ يمكنك أن تخبره بأنَّك ستكون سعيدًا بمساعدته. 4. «سنعلمك بآخر التطورات في اليوم الفلاني/الساعة الفلانية» إذا أرسل العميل رسالة إلكترونية يستفسر إن جدّ أي جديد حول تذكرة الدّعم الفنّي التي أرسلها من قبل فهذا يُعتبر في حد ذاته فشلًا ذريعًا لقسم الدّعم الفنّي. في اختبارٍ في Groove، وجدنا أن العملاء الذين يعيدون التواصل معنا يكون تقييمهم لرضاهم عن الخدمة بمعدَّل 10% أقل من العملاء الذين لا يقومون بذلك. شيئان عليك فعلهما لتجنب طلبات الاستفسار عما حصل في طلبيات الزبائن: التأكد أننا نُطلِع العميل على المستجدات باستمرار (مرةً واحدةً في اليوم على الأقل). دع العميل يعرف بالضبط متى يكون من المتوقع أن نتواصل معه. بينما لا نستطيع دائمًا أن نَعِدَ بإيجاد «حل» قبل وقت معين؛ لكن يمكننا دومًا أن نعد بإطلاع العميل على المستجدات؛ فالإيفاء بهذا الوعد يساهم بإعلام الزبون عن وضع تذكرته، ويُشكِّل فرصةً لبناء الثقة بينكما. 5. «نقدر لك إعلامنا بالمشكلة» وفقًا لسبر آراء أجرته Lee Resources International، يتقدم زبونٌ واحدٌ من أصل 26 زبونًا (الذين يعانون من نفس المشكلة) بشكوى؛ ولهذا تعني كل شكوى من أحد العملاء أن العشرات من الزبائن الآخرين يواجهون نفس المشكلة ولكنهم لم يخبروك بذلك. وهذا يعني أن حل المشكلة لعميل واحد يمكن أن يفيد العشرات غيره من الزبائن في نفس الوقت. هذه فرصةٌ كبيرةٌ وخدمةٌ منحكم إياها العملاء الذين قرروا إرسال رسالة لكم وإعلامكم بالمشكلة التي واجهتهم. في كتاب Dale Carnegie الشهير «How to Win Friends & Influence People» (كيف تكسب الأصدقاء وتُؤثّر في النّاس)، كانت واحدة من التقنيات الأساسية المذكورة فيه هي «يجب أن تكون سخيًا بإظهار التقدير للآخرين». لذا ﻻ تُفوِّت أيّة فرصة لتوجيه الشكر للزبائن لبناء علاقات أفضل معهم (أو أي شخصٍ آخر). 6. «هل يمكنني المساعدة بأي شيء آخر» على الرغم من بذل قصارى جهدنا، والنوايا الحسنة التي لدينا، إﻻ أننا ﻻ نصل دائمًا إلى مبتغانا في تقديم أفضل خدمة للزبائن. في الواقع، تُشير إحدى الإحصائيات أنه على الرغم من أنَّ 94% من المتاجر الإلكترونية على الإنترنت (online retailer) توفّر خدمة الدّعم الفنّي عبر البريد الإلكتروني؛ إلا أنها تُجيب إجابةً غير صحيحةٍ على 27% من الاستفسارات. وعلى الرغم من أنّك تسعى جاهدًا لتقديم إجابات دقيقة، وقد لا تكون نسبة إجاباتك الخاطئة بهذا الارتفاع؛ إلا أنَّ إجاباتك قد لا تكون مُفيدة جدًا للسائل. المشكلة كما تبينها الأبحاث (كما في إحصائية AmEx في القسم السابق) أنَّ معظم الأشخاص لا يتحدثون عن المشكلات التي تواجههم؛ لذا عندما لا تقدم لهم الفائدة المرجوة بإجابتك، فلن يعاودوا مراسلتك ولن يطلبوا منك توضيحًا أو مساعدةً أخرى. وهذا ما يجعل هذه العبارة من أفضل العبارات التي تستخدمها؛ بأن تترك الباب مفتوحًا أمام العملاء وتدعوهم للتفاعل، وتترك لهم فرصة إعلامك إذا لم تحل مشكلتهم. أضف هذه العبارات إلى قاموسك لاستخدامها في خدمة الزبائن اليوم تضمين هذه العبارات والمصطلحات في تفاعلات خدمة العملاء هي مكسبٌ فوري! فلن تأخذ منك وقتًا طويلًا لتنفيذها، لكنك ستكسب زبونًا سعيدًا بالتعامل معك وراضٍ عن الخدمة، ومخلصًا لشركتك. ماذا عنك أنت؟ هل وجدت العبارات التي ذكرناها أعلاه مفيدةً لإرضاء الزبائن؟ هل هناك عبارات شائعةٌ تجعلك تنفر من خدمة الزبائن؟ ترجمة -وبتصرّف- للمقال Word Choice Matters: Six Phrases That Will Change the Way You Do Customer Service لصاحبه Len Markidan.
    2 نقاط
  2. سنتعلم في هذا الدرس كيفية استخدام أدوات برنامج إنكسكيب الأساسية لرسم صندوق هدية ثلاثي الأبعاد عن طريق برنامج inkscape والذي سيكون بالشكل التالي: افتح ملفا جديدا ثم اختر المسار: File > Document properties وألغ علامة الصح عند عبارة إظهار إطار الصفحة (Show page border) من تبويب Page. ملاحظة: لتصغير وتكبير مساحة العمل، نختار من لوحة المفاتيح علامة الزائد (+) للتكبير وعلامة الناقص (-) للتصغير أو عن طريق المسار: View > Zoom نبدأ الآن برسم الصندوق ثلاثي الأبعاد. نرسم مربعا مع الضغط على زر ctrl أثناء الرسم لنحافظ على أبعاد المربع، ثم نضغط على زر التحديد والتحويل (select and transform) ثم بالضغط عليه مجددا تظهر أسهم التدوير. نختار سهم التدوير في الزاوية اليمنى أو اليسرى العلوية كما هو موضع في الصورة مع الضغط على ctrl أثناء التدوير إلى أن يصبح الشكل كما في الصورة التالية: ثم بالضغط مجددا تظهر أسهم التصغير والتكبير فنقوم بالضغط المستمر على السهم العلوي ونسحب إلى الأسفل إلى أن يصل للبعد المناسب الذي نرغب أن تكون عليه قاعدة الصندوق. نكرر الشكل الناتج عن طريق تحديد الشكل ثم الضغط على ctrl+d أو عن طريق اختيار الزر الأيمن ثم Duplicate. بعد ذلك نحرك النسخة الجديدة من الشكل للأسفل مع الضغط على ctrl ليبقى بموازاة الشكل الأصلي ونضعه حيث نرغب أن يكون ارتفاع الصندوق. نختار أداة الرسم (Bezier) ومن الشريط العلوي نحدد خيار (snap cusp nodes) ليساعدنا على اختيار نقاط التقاطع (العقد) (nodes) في زوايا الشكل بدقة. وبالأداة نرسم شكلا كالتالي: ثم نكرر الشكل (Duplicate) وبالضغط على خيار الانعكاس الأفقي (Flip horizontal) من الشريط العلوي في الصورة المحدد أو اختصاره (H): حصلنا على شكل الصندوق سنزيل الحدود عن الطريق تحديد جميع الأشكال ثم نختار خيار التعبئة والحدود الخارجية (fill and stroke) وذلك عن طريق المسار: Object > fill and stroke أو بالضغط على shift+ctrl+f. ثم نختار تبويب رسم الحدود (stroke paint) ونضغط على الخيار (x). نلون الصندوق بالألوان التالية التي اخترناها من قائمة الألوان بالأسفل: نحدد الأجزاء الثلاثة الظاهرة لنا عن طريق تحديدها قطعةً قطعة مع الضغط على زر shift أثناد التحديد، ثم بالضغط على زر ctrl نرفعها قليلا للأعلى؛ ليظهر لنا الجزء المختفي وسنتركه في مكانه إلى حين الحاجة إليه. بعد أن أتممنا عمل الصندوق سنعمل على إنشاء غطاء الصندوق وذلك بتحديد أجزائه التي لوناها بتدرجات الرمادي، ثم تكرار هذه الأجزاء عن طريق ctrl+d ورفعها لمساحة كافية أعلى الصندوق. نقوم بتحديد ارتفاع الغطاء (الشكلين المتقابلين) على جهة اليمين واليسار وتكرارهما مجددا عن طريق ctrl+d وتغيير لونهما لتمييزهما عن الشكل الأصلي ثم بالضغط على ctrl ننزلهما أسفل إلى القدر الذي نرغب أن يتوقف فيه ارتفاع الغطاء كما في الصورة: ثم بتحديد الارتفاع الأيمن مع نسخته المكررة منه نختار المسار التالي: Path > Difference أو بالضغط على ctrl+-: ونفعل الشيء نفسه مع الارتفاع جهة اليسار، ليظهر لنا الغطاء بهذا الشكل: نلغي خيار (snap cusp nodes) الذي سبق أن فعلناه ثم نكرر الارتفاع الذي كوناه لغطاء الصندوق (الشكلين المتقابلين) ونلونه باللون الأسود لنعمل ظلا للغطاء. وعن طريق خيار الإرسال للخلف (page down) المحدد في الصورة نرسل المستطيلين الأسودين للخلف لثلاثة مرات. ومن تبويب (fill and stroke) سنختار الشفافية (opacity) من 70-90 والضبابية blur 2.5 تقريبا. ملاحظة: حتى لا يظهر الظل على الحواف جهة اليمين واليسار نقوم بإزاحة الظل (المستطيل) الأيمن إلى اليسار خطوتين والظل (المستطيل) الأيسر إلى اليمين خطوتين . بالتحديد على جميع الأشكال المكونة لغطاء الصندوق نحركه لمكانه المناسب على الصندوق مع الضغط على زر ctrl لنحافظ على المحاذاة ليعطينا هذا الشكل: أنهينا عمل الصندوق وغطاءه وبقي أن نزينه بالشريطة لتكتمل هديتنا. نحدد الأجزاء اليمنى الظاهرة لنا من الصندوق مع سقف الصندوق العلوي ونقوم بتكرارها وسحبها بعيدا عن الصندوق ونلونه كما في الصورة: نكرر المربع العلوي المحدد في الصورة ونغير لونه لنميزه عن الأصل ونكبر حجمه ونحركه حيث يتكون عندها عرض الشريطة التي نرغب أن نزين الصندوق بها كما في الصورة: ثم نقوم بالضغط على: Path > difference أو بالضغط على ctrl+-. نفعل الشيء نفسه مع بقية الأشكال كما في الصور: نحدد القطع الثلاثة التي أنشأناها ثم نكررها ونضغط على خيار الانعكاس الأفقي (Flip horizontal): لتكون الشريطة في منتصف الصندوق نحاذيها عن طريق استخدام تبويب المحاذاة (Align and Distribute) وذلك عن طريق اتباع على المسار التالي: Object > Align and Distribute أو بالضغط على shift+ctrl+A. من القائمة الموجودة في التبويب نحدد (آخر تحديد) (last selected) ثم نختار قطعة الشريطة التي نرغب بمحاذاتها أولا ثم الشكل الذي سنجعلها في منتصفه ثم نضغط على الخيارات: التمركز على المحور العمودي (center on vertical axis)التمركز على المحور الأفقي (center on horizontal axis)المحددة في الصورة: ونكرر ذلك مع جميع قطع الشريطة التي أنشأناها لتظهر بهذه الطريقة: ملاحظة: نرسل قطع الشريطة الظاهرة على ارتفاع الصندوق مرتين إلى الخلف عن طريق page down المحدد في الصورة: بقي أن نرسم عقدة الشريطة لتكتمل شريطتنا. نختار أداة الرسم (Bezier) ثم نرسم شكل مثلث ثم نتبع الخطوات التالية: نختار أداة تحرير الشكل (node tool) المحددة في الصورة، ونحدد العقدة (node) في الطرف العلوي للمثلث. نختار من الشريط العلوي خيار جعل العقدة المختارة متساوية (make selected nodes symmetric) لنحصل على انحناءة مكان العقدة. نفعل الشيء نفسه مع العقدة السفلية ثم نحدد الطرف المنحني للعقدة. لنضيف لها (node) من علامة (+) في الشريط العلوي. نحدد النقطة الجديد التي أنشأناها ونختار من الشريط العلوي (object to path). نحرك النقطة لداخل الشكل يمينا. نلون الشكل ونزيل التحديد ونكرره ونعكسه أفقيا ونحركها كالتالي: نرسم شكلا مربعا ونجعل حوافه مستديرة وذلك عن طريق تحريك الدائرة في الزاوية العلوية للمربع إلى الأسفل: ثم نحركه لوسط العقدة ونزيد من قتامة لونه عن طريق ملف حوار التعبئة: بقيت الزوائد التي تمتد من عقدة الشريطة. نرسم مستطيلا ثم بالضغط على أداة (node tool) ثم (object to bath) نحدد ضلع المربع الأقل ارتفاعا ثم نضيف (node) كما فعلنا مسبقا مع العقدة وذلك بالضغط على خيار (insert new nodes) ثم نضغط على نقطة التقاطع الجديدة التي أنشأناها (node) ونحركها للداخل. نكرر الشكل مجددا، بعد ذلك باستخدام أسهم التدوير والتحجيم نصل للشكل التالي: نحدد أجزاء العقدة التي أنشأناها وبالضغط على ctrl+G نجعلها في مجموعة (group). لتكون العقدة متناسقة مع بعد الصندوق سنقوم بعمل الخطوات التالية: نختار سهام التدوير ونحدد السهم المتوسط الأيسر ونحركه للأعلى. نختار السهم العلوي المتوسط من أسهم التدوير ونحركه يمينا. نقوم بتصغير الشكل ليتناسب مع حجم الصندوق. يمكن التعديل على العقدة لنعطيها بعدا وبروزا حيث نغير اللون للون أفتح، ثم نقوم بتكرار العقدتين ونجعلها أكثر قتامة ثم بأداة (node tool) نعدل على الشكل لنصل لهذه الصورة عن طريق حذف العقد (nodes) الزائدة وتصغير الزوائد الممتدة من عقدة الشريطة إلى أن نصل لما يلي: نضيف ثنيتين صغيرتين في أطراف عقدة الشريطة بأداة (node tool). نقوم بتلوين الثنيتين بلون أقتم ونزيل الحدود، لتظهر بالشكل التالي: اكتملت الهدية ولله الحمد ولم يتبق لنا إلا أن نتلاعب بألوانها ونضيف بعض الظلال ليكون صندوقنا أكثر جمالا وأقرب للواقعية. نكرر كل جزء من شكل الشريطة مجددا بما فيها العقدة ونحوله للأسود أو الرمادي القاتم مع جعل الضبابية (blur) بمقدار 1.9 تقريبا والشفافية opacity 50 نرسل الأشكال المكررة من الشريطة للخلف بعد تحديدها. ولا ننس المربع الذي تركناه في البداية حيث نكبر حجمه بالضغط على ctrl+shift ثم نسحبه إلى أسفل الصندوق مع تحديد الخيارات كما في الصورة: هذه هي النتيجة النهائية لهذا الدرس: مع إمكانية إضافة أفكار جديدة وتغيير الألوان والأحجام:
    1 نقطة
  3. في هذا الدرس سوف تتعلم كيفية إنشاء قائمة تنقل (navigation) دائرية الشكل باستخدام CSS Transforms. سوف أريك كيف تقوم بذلك خطوة بخطوة وسوف أقوم بشرح الحسابات والمنطق البسيط وراء هذه الطريقة حتى يتسنى لك فهم كيفية عمل هذه الطريقة. وكما ذكرت قبل قليل فسوف يكون هناك بعض الحسابات، ولكن لا تقلق فلن يكون هناك أي شيء معقد أو يصعب فهمه وسوف أشرح كل شيء خطوة بخطوة. بنية HTML بما أننا سنقوم ببناء قائمة تنقل فسوف نبدأ بالبنية المعتادة لأي قائمة، أي أننا سوف نحتاج إلى عنصر div ليحتوي على قائمة العناصر المتمثلة بعنصر ul وسوف نحتاج أيضًا إلى عناصر القائمة المتمثلة بعناصر li كما أننا سوف نحتاج إلى زر (button) ليعمل على فتح وإغلاق القائمة عند الضغط عليه. وسوف نحتاج في المثال الأول إلى شيء اضافي وهو عنصر div ليعمل كغشاء (overlay) يمنعنا من الضغط على أي مكان آخر في الصفحة طالما أنّ القائمة مفتوحة. ملاحظة: جميع الأيقونات المستخدمة في هذا الدرس هي من خدمة Font Awesome. شرح بعض الحسابات خلف CSS Transforms أفضل طريقة للشرح هنا ستكون باستعمال الصور بدلًا من الكلمات، لذلك سوف أبدأ بالمنطق الكامن وراء هذه الطريقة كما أننا سنقوم بتطبيق بعض الحسابات وبعد أن ننتهي من الشرح سوف نتوجه إلى جزء التكويد مما سيسمح لك بأن تعرف ما الذي تفعله كل واحدة من خصائص CSS التي سوف نستعملها. لنبدأ أولًا بتعريف ما هو المقصود بالزاوية المركزية. أنظر إلى الصورة التالية متبوعة بشرح بسيط: لنفرض أنك تريد توزيع عناصر القائمة على نصف دائرة كما هو الحال بالنسبة لما نريد إنشائه هنا، ولنفرض أيضًا أنّ لدينا 6 عناصر، إذًا كل زاوية سوف يكون لها زاوية مركزية بقيمة: 180 درجة / 6 = 30 درجة ماذا لو أردت توزيع العناصر على دائرة كاملة؟ بسيطة كل ما سنقوم بفعله هو التالي: 360 درجة / 6 = 60 درجة وهكذا دواليك. إذًا فكل ما يجب علينا فعله هو حساب الزاوية المركزية التي تناسبنا وبعدها نبدأ بتطبيق بعض الحسابات على خصائص CSS Transforms لنقوم بتطبيق هذه الزوايا عليها. وحتى يكون بامكاننا إنشاء زاوية مساوية للزوايا المركزية التي نريدها فإننا سوف نحتاج إلى حرف/تمييل (skew) العناصر وذلك باستخدام دالّة ()skew، وسوف تكون القيمة كالتالي: 90 درجة - x درجة ، بحيث تكون x هي الزاوية المركزية التي نريدها ولكن في هذه الحالة فإنّ جميع محتوى عناصر القائمة سوف تتم إمالتها وسوف تظهر بشكل غير مناسب، لذلك يجب علينا أن نقوم بتعديلها (أي نعمل لها unskew) حتى يظهر كل شيء بشكل جيد. يمكنك الدخول إلى هذا المثال لرؤية كيف يتم تطبيق التحويلات (transforms) إلى عناصر القائمة خطوة بخطوة حتى يتضح لك ما الذي سوف نقوم بفعله في الشيفرة البرمجية (ضع في الحسبان أن التسلسل الموجود في المثال قد يختلف بشكل بسيط عن الخطوات الفعلية التي سوف نتبعها في هذا الدرس). وهذه لقطات لكل خطوة سوف تراها في هذا المثال: الحالة الأولية: الخطوة الأولى: الخطوة الثانية: الخطوة الثالثة: الخطوة الرابعة: الخطوة الخامسة: الخطوة السادسة: إذًا هذا ما سوف نفعله: سوف نحتاج إلى موضعة عناصر القائمة بشكل مطلق (absolute positioning) داخل الحاوي الخاص بها. سوف نستعمل الخاصية transform-origin على كل عنصر وبالقيمة bottom right corner . بعد ذلك سوف نقوم بتحريك العناصر إلى الأعلى وإلى اليسار بشكل كافٍ حتى تتطابق مراكز التحريك الخاصة بها مع مركز العنصر الذي يحويها. سوف نقوم بتدوير العناصر في مواضعها باستخدام المعادلة التالية: كل عنصر له index بقيمة i سوف يتم تدويره بقيمة i*x حيث أن x كما سبق وذكرنا هي قيمة الزاوية المركزية. بعد ذلك نقوم بتمييلها (skew) للحصول على الزاوية المركزية التي نريدها (باستخدام المعادلة الموجودة في الأعلى). في مثالنا سوف يكون هناك 5 عناصر مما يعني وجود 5 زوايا مركزية وبالتالي سوف نقوم بتغطية الجزء العلوي فقط من الدائرة، واعتمادًا على المعادلات التي طرحناها سابقًا فسوف تكون الزاوية المركزية لكل عنصر هي 36 درجة (180 / 5) ولكننا سوف نجعل الزاوية المركزية تساوي 40 درجة مما يمنحنا منطقة أكبر قابلة للنقر، وبالتالي يكون مجموع الزوايا هو 40*5=200 وهو رقم أكبر من 180. في هذه الحالة سوف نحتاج إلى تدوير العناصر عكس عقارب الساعة بقيمة (200-180)/2 لنتأكد من أنها متوازنة على كلا الجانبين. قمنا إلى هذه النقطة بإنشاء الزوايا المركزية المناسبة، ولكن تمييل عناصر القائمة أدى أيضًا إلى تمييل المحتوى الذي بداخلها وبالتالي تشويهه، لذلك سوف نحتاج إلى تطبيق قاعدة رياضية أخيرة حتى نتأكد من ظهور المحتوى بشكل سليم وغير مشوه، وهذه القاعدة هي: نقوم بتمييل عناصر a الموجودة داخل عناصر القائمة وذلك بتطبيق قيمة معاكسة للقيمة المستخدمة لتمييل عناصر القائمة وبعد ذلك نقوم بتدويرها بالقيمة: -[90 - (x/2)] (لاحظ أن القيمة سالبة) ، مرة أخرى x هي الزاوية المركزية إذًا بما أن الزاوية المركزية هي 40 درجة فإننا سوف نحتاج إلى تمييل العناصر aبالقيمة -40 درجة وتدويرها بالقيمة -70 درجة (90 - (40/2)). سوف يتم موضعة عناصر a بشكل مطلق داخل الحاوي الخاص بها (الحاوي هنا هي عناصر القائمة) وسوف يتم إعطاء عناصر القائمة الخاصية overflow: hidden مما يعني أنّه سوف يتم قطع جزء من كل عنصر من عناصر a، وحتى نتأكد بأنّ محتوى العناصر (سواء كانت نص أو أيقونة) يبقى ضمن المحتوى الظاهر منها فإننا سوف نستعمل الخاصية text-align: center. وهذه هي كل الحسابات التي سوف نحتاجها إلى الآن. تبقى علينا الآن أن نقوم باستعمال تنسيقات CSS المناسبة حتى يصبح كل شيء بشكله المناسب. تنسيقات CSS سوف نقوم أولًا بتنسيق المثال الأول. سوف نستخدم Modernizr حتى نستطيع تحديد المتصفحات التي تدعم CSS Transforms والمتصفحات التي لا تدعمها ونقوم بتوفير fallback للمتصفحات القديمة التي لا تدعمها. لنبدأ أولًا بتنسيق العنصر الذي سوف يحتوي على القائمة ، بحيث سوف يكون ثابت إلى الأسفل ومتوسط للصفحة وسوف يكون متقلصًا/مخفيًا ويظهر/يتمدد عندما يتم النقر على الزر. .csstransforms .cn-wrapper { font-size:1em; width: 26em; height: 26em; overflow: hidden; position: fixed; z-index: 10; bottom: -13em; left: 50%; border-radius: 50%; margin-left: -13em; transform: scale(0.1); transition: all .3s ease; } /* class applied to the container via JavaScript that will scale the navigation up */ .csstransforms .opened-nav { border-radius: 50%; transform: scale(1); } سوف نقوم كذلك بتنسيق الزر الذي سوف يفتح ويغلق القائمة: .cn-button { border:none; background:none; color: white; text-align: Center; font-size: 1.5em; padding-bottom: 1em; height: 3.5em; width: 3.5em; background-color: #111; position: fixed; left: 50%; margin-left: -1.75em; bottom: -1.75em; border-radius: 50%; cursor: pointer; z-index: 11 } .cn-button:hover, .cn-button:active, .cn-button:focus{ background-color: #222; } عندما يتم فتح القائمة فإنه سوف يظهر غطاء/غشاء شفاف يغطي الصفحة، وهذه هي التنسيقات الخاصة به: .cn-overlay{ width:100%; height:100%; background-color: rgba(0,0,0,0.6); position:fixed; top:0; left:0; bottom:0; right:0; opacity:0; transition: all .3s ease; z-index:2; pointer-events:none; } /* Class added to the overlay via JavaScript to show it when navigation is open */ .cn-overlay.on-overlay{ pointer-events:auto; opacity:1; } سوف نقوم الآن بتنسيق عناصر القائمة وعناصر a الموجودة بداخلها وذلك بتطبيق المنطق والالحسابات التي تحدثنا عنها في البداية: .csstransforms .cn-wrapper li { position: absolute; font-size: 1.5em; width: 10em; height: 10em; transform-origin: 100% 100%; o verflow: hidden; left: 50%; top: 50%; margin-top: -1.3em; margin-left: -10em; transition: border .3s ease; } .csstransforms .cn-wrapper li a { display: block; font-size: 1.18em; height: 14.5em; width: 14.5em; position: absolute; bottom: -7.25em; right: -7.25em; border-radius: 50%; text-decoration: none; color: #fff; padding-top: 1.8em; text-align: center; transform: skew(-50deg) rotate(-70deg) scale(1); transition: opacity 0.3s, color 0.3s; } .csstransforms .cn-wrapper li a span { font-size: 1.1em; opacity: 0.7; } /* for a central angle x, the list items must be skewed by 90-x degrees in our case x=40deg so skew angle is 50deg items should be rotated by x, minus (sum of angles - 180)2s (for this demo) */ .csstransforms .cn-wrapper li:first-child { transform: rotate(-10deg) skew(50deg); } .csstransforms .cn-wrapper li:nth-child(2) { transform: rotate(30deg) skew(50deg); } .csstransforms .cn-wrapper li:nth-child(3) { transform: rotate(70deg) skew(50deg) } .csstransforms .cn-wrapper li:nth-child(4) { transform: rotate(110deg) skew(50deg); } .csstransforms .cn-wrapper li:nth-child(5) { transform: rotate(150deg) skew(50deg); } .csstransforms .cn-wrapper li:nth-child(odd) a { background-color: #a11313; background-color: hsla(0, 88%, 63%, 1); } .csstransforms .cn-wrapper li:nth-child(even) a { background-color: #a61414; background-color: hsla(0, 88%, 65%, 1); } /* active style */ .csstransforms .cn-wrapper li.active a { background-color: #b31515; background-color: hsla(0, 88%, 70%, 1); } /* hover style */ .csstransforms .cn-wrapper li:not(.active) a:hover, .csstransforms .cn-wrapper li:not(.active) a:active, .csstransforms .cn-wrapper li:not(.active) a:focus { background-color: #b31515; background-color: hsla(0, 88%, 70%, 1); } .csstransforms .cn-wrapper li:not(.active) a:focus { position: fixed; /* fix the "displacement" bug in webkit browsers when using tab key */ } سوف نقوم بتوفير fallback بسيط للمتصفحات التي لا تدعم CSS Transforms. .no-csstransforms .cn-wrapper{ font-size:1em; height:5em; width:25.15em; bottom:0; margin-left: -12.5em; overflow: hidden; position: fixed; z-index: 10; left:50%; border:1px solid #ddd; } .no-csstransforms .cn-button{ display:none; } .no-csstransforms .cn-wrapper li{ position:static; float:left; font-size:1em; height:5em; width:5em; background-color: #eee; text-align:center; line-height:5em; } .no-csstransforms .cn-wrapper li a{ display:block; width:100%; height:100%; text-decoration:none; color:inherit; font-size:1.3em; border-right: 1px solid #ddd; } .no-csstransforms .cn-wrapper li a:last-child{ border:none; } .no-csstransforms .cn-wrapper li a:hover, .no-csstransforms .cn-wrapper li a:active, .no-csstransforms .cn-wrapper li a:focus{ background-color: white; } .no-csstransforms .cn-wrapper li.active a { background-color: #6F325C; color: #fff; } وبالطبع فنحن نريد أن تكون القائمة متجاوبة مع جميع الأجهزة وتتقلص للشاشات الصغيرة: @media screen and (max-width:480px){ .csstransforms .cn-wrapper{ font-size:.68em; } .cn-button{ font-size:1em; } .csstransforms .cn-wrapper li { font-size:1.52em; } } @media screen and (max-width:320px){ .no-csstransforms .cn-wrapper{ width:15.15px; margin-left: -7.5em; } .no-csstransforms .cn-wrapper li{ height:3em; width:3em; } } هذا كان كل شيء يخص المثال الأول. دعونا الآن ننتقل لتنسيق المثال الثاني. في المثال الثاني سوف تكون القائمة مختلفة قليلًا عن القائمة في المثال الأول، ولكن كل المنطق والحسابات التي يخص القائمة الأولى سينطبق على هذه القائمة مع وجود ثلاثة اختلافات. لن نقوم بالرجوع وتوضيح كل شيء مرة أخرى وإنما سوف نكتفي بالتحدث عن الاختلافات الثلاثة فقط. دعونا نأخذ نفس المثال الموجود في الأعلى ونقوم بتغيير خاصية CSS واحدة فقط ونرى ما التغيير الذي سوف تصنعه على شكل عناصر القائمة. سوف نقوم بتطبيق تدرج لوني دائري على عناصر a مع لون خلفية شفاف، وسوف تظهر النتيجة كالتالي: سوف نقوم الآن بإضافة بعض المسافة بين عناصر القائمة وذلك بتغيير درجة الدوران لكل عنصر. سوف نقوم كذلك بإزالة لون الخلفية لعناصر القائمة وللحاوي وللحدود وسوف نقوم بتقليص قيمة الخاصية padding-top لعناصر a حتى نجعل الأيقونات متوسطة بشكل مثالي داخل العناصر. النتيجة النهائية ستكون كما في الصورة التالية: يمكنك أن ترى بأنّ القائمة بدأت تبدو بمظهر مختلف، وبقي شيء واحد مهم يجب أن نقوم به. ففي حالة بقاء التنسيقات كما هي عليه في الوقت الحالي فإنّ المناطق القابلة للنقر الخاصة بعناصر a ستكون أكبر مما نريده، فما نريده هو أن يكون الجزء الملون من القائمة هو فقط القابل للنقر. الصورة التالية توضح الجزء الزائد القابل للنقر والذي لا نريده: عندما تقوم بوضع مؤشر الفأرة فوق المنطقة الحمراء الموضحة في الصورة الموجودة في الأعلى فإنّه سيتم تفعيل حالة الـhover الخاصة بعناصر a وهو شيء طبيعي الحدوث ولكننا لا نريده لأننا نريد أن تظهر العناصر وكأنها هي فقط الجزء البنفسجي، وبالتالي سوف نحتاج إلى منع تفعيل أحداث الفأرة على الجزء الملون باللون الأحمر. لذلك ما سنقوم به هو استخدام pseudo-element (سوف تفي بالغرض لأننا لا نريد استعمال وسم فارغ) ليعمل كغطاء/غشاء يقوم بتغطية منطقة اللون الأحمر وبالتالي نمنع تفعيل أحداث الماوس على هذه المنطقة. إذًا سوف نقوم بتطبيق الخطوات الثلاثة التي ذكرناها مع تغيير بعض التنسيقات (كاللون والحجم) بالنسبة للقائمة ولعناصر القائمة ليظهر كل شيء كما في الصورة: .csstransforms .cn-wrapper { position: absolute; top: 100%; left: 50%; z-index: 10; margin-top: -13em; margin-left: -13.5em; width: 27em; height: 27em; border-radius: 50%; background: transparent; opacity: 0; transition: all .3s ease 0.3s; transform: scale(0.1); pointer-events: none; overflow: hidden; } /*cover to prevent extra space of anchors from being clickable*/ .csstransforms .cn-wrapper:after{ color: transparent; content:"."; display:block; font-size:2em; width:6.2em; height:6.2em; position: absolute; left: 50%; margin-left: -3.1em; top:50%; margin-top: -3.1em; border-radius: 50%; z-index:10; } .csstransforms .cn-wrapper li { position: absolute; top: 50%; left: 50%; overflow: hidden; margin-top: -1.3em; margin-left: -10em; width: 10em; height: 10em; font-size: 1.5em; transition: all .3s ease; transform: rotate(76deg) skew(60deg); transform-origin: 100% 100%; pointer-events: none; } .csstransforms .cn-wrapper li a { position: absolute; position: fixed; /* fix the "displacement" bug in webkit browsers when using tab key */ right: -7.25em; bottom: -7.25em; display: block; width: 14.5em; height: 14.5em; border-radius: 50%; background: #429a67; background: radial-gradient(transparent 35%, #429a67 35%); color: #fff; text-align: center; text-decoration: none; font-size: 1.2em; line-height: 2; transition: all .3s ease; transform: skew(-60deg) rotate(-75deg) scale(1); pointer-events: auto; } .csstransforms .cn-wrapper li a span { position: relative; top: 1.8em; display: block; font-size: .5em; font-weight: 700; text-transform: uppercase; } .csstransforms .cn-wrapper li a:hover, .csstransforms .cn-wrapper li a:active, .csstransforms .cn-wrapper li a:focus { background: radial-gradient(transparent 35%, #449e6a 35%); } نريد للعناصر في المثال الثاني أن تظهر بتأثير شبيه لحركة المروحة عندما نقوم بفتح القائمة (يمكنك الذهاب إلى المثال الثاني لترى ما الذي أقصده). وللحصول على هذا التأثير فإننا قمنا بموضعة العناصر بنفس المكان وبنفس التمييل والتدوير بقيمة (rotate(76deg) skew(60deg. يمكننا باستعمال تأخيرات التنقل (transition delays) بأن نسمح للعناصر بأن تتمدد/تتفرق عن بعضها بعد أن يظهر الحاوي ويتمدد. وعندما نقوم بغلق القائمة فسوف ننتظر حتى تعود عناصر القائمة قبل أن نقوم بتقليص الحاوي وإخفائه. عند الضغط على زر الفتح فسوف نقوم بإبعاد عناصر القائمة عن بعضها وذلك عن طريق تدوير كل عنصر إلى مكانه النهائي في الدائرة. .csstransforms .opened-nav { border-radius: 50%; opacity: 1; transition: all .3s ease; transform: scale(1); pointer-events: auto; } .csstransforms .opened-nav li { transition: all .3s ease .3s; } .csstransforms .opened-nav li:first-child { transform: rotate(-20deg) skew(60deg); } .csstransforms .opened-nav li:nth-child(2) { transform: rotate(12deg) skew(60deg); } .csstransforms .opened-nav li:nth-child(3) { transform: rotate(44deg) skew(60deg); } .csstransforms .opened-nav li:nth-child(4) { transform: rotate(76deg) skew(60deg); } .csstransforms .opened-nav li:nth-child(5) { transform: rotate(108deg) skew(60deg); } .csstransforms .opened-nav li:nth-child(6) { transform: rotate(140deg) skew(60deg); } .csstransforms .opened-nav li:nth-child(7) { transform: rotate(172deg) skew(60deg); } وبالطبع سوف نوفر fallback بسيط للمتصفحات غير الداعمة: .no-csstransforms .cn-wrapper{ margin:10em auto; overflow:hidden; text-align:center; padding:1em; } .no-csstransforms .cn-wrapper ul{ display:inline-block; } .no-csstransforms li{ font-size:1em; width:5em; height:5em; float:left; line-height:5em; text-align:center; background-color: #fff; } .no-csstransforms li a{ display:block; height:100%; width:100%; text-decoration: none; color: inherit; } .no-csstransforms .cn-wrapper li a:hover, .no-csstransforms .cn-wrapper li a:active, .no-csstransforms .cn-wrapper li a:focus{ background-color: #f8f8f8; } .no-csstransforms .cn-wrapper li.active a { background-color: #6F325C; color: #fff; } .no-csstransforms .cn-button{ display:none; } وكذلك سوف نجعل القائمة متجاوبة وذلك بتقليصها للشاشات الصغيرة: @media only screen and (max-width: 620px) { .no-csstransforms li{ width:4em; height:4em; line-height:4em; } } @media only screen and (max-width: 500px) { .no-ccstransforms .cn-wrapper{ padding:.5em; } .no-csstransforms .cn-wrapper li{ font-size:.9em; width:4em; height:4em; line-height:4em; } } @media only screen and (max-width: 480px) { .csstransforms .cn-wrapper{ font-size: .68em; } .cn-button{ font-size:1em; } } @media only screen and (max-width:420px){ .no-csstransforms .cn-wrapper li{ width:100%; height:3em; line-height:3em; } } هذا كان كل ما يخص تنسيقات CSS. دعونا الآن نرى ما الذي سوف نحتاج لعمله باستخدام الجافاسكربت. بعض الجافاسكربت لن نستخدم أي إطار عمل للجافاسكربت هنا ولكننا سوف نستخدم Classie.js لإضافة وإزالة الفئات (classes). وأما بالنسبة للمتصفحات التي لا تدعم addEventListener و removeEventListener فإننا سوف نستخدم EventListener polyfill. سوف نقوم بإضافة مدير أحداث (event handler) إلى الزر بحيث يتم فتح/اغلاق القائمة عند النقر عليه أو استخدام زر tab في لوحة المفاتيح (أي عند حدوث focus على العنصر). نريد أيضًا فيالمثال الأول أن يتم اغلاق القائمة عند النقر على أي مكان خارج القائمة. لنبدأ أولًا بالجافاسكربت الخاص بالمثال الأول: (function(){ var button = document.getElementById('cn-button'), wrapper = document.getElementById('cn-wrapper'), overlay = document.getElementById('cn-overlay'); //open and close menu when the button is clicked var open = false; button.addEventListener('click', handler, false); button.addEventListener('focus', handler, false); wrapper.addEventListener('click', cnhandle, false); function cnhandle(e){ e.stopPropagation(); } function handler(e){ if (!e) var e = window.event; e.stopPropagation(); //so that it doesn't trigger click event on document if(!open){ openNav(); } else{ closeNav(); } } function openNav(){ open = true; button.innerHTML = "-"; classie.add(overlay, 'on-overlay'); classie.add(wrapper, 'opened-nav'); } function closeNav(){ open = false; button.innerHTML = "+"; classie.remove(overlay, 'on-overlay'); classie.remove(wrapper, 'opened-nav'); } document.addEventListener('click', closeNav); })(); الجافاسكربت الخاص ب المثال الثاني مشابه للأولى نوعًا ما ولكن مع وجود بعض الاختلافات: (function(){ var button = document.getElementById('cn-button'), wrapper = document.getElementById('cn-wrapper'); //open and close menu when the button is clicked var open = false; button.addEventListener('click', handler, false); button.addEventListener('focus', handler, false); function handler(){ if(!open){ this.innerHTML = "Close"; classie.add(wrapper, 'opened-nav'); } else{ this.innerHTML = "Menu"; classie.remove(wrapper, 'opened-nav'); } open = !open; } function closeWrapper(){ classie.remove(wrapper, 'opened-nav'); } })(); خاتمة هذا كان كل شيء فيما يتعلق بهذا الدرس، أتمنى أن تكون قد استفدت منه وتعلمت شيئًا جديدًا. ترجمة -وبتصرّف- للمقال Building a Circular Navigation with CSS Transforms HTML/CSS لصاحبته Sara Soueidan.
    1 نقطة
  4. هل ترغب في تعلّم برمجة إضافات ووردبريس؟ هذه السّلسلة أُعدّت خصّيصًا لك. سنتعرف في هذه السّلسلة على كل ما تحتاجه لتطوير أوّل إضافة لك على ووردبريس، وفق النّهج التّالي: 1. مُقدّمة إلى برمجة إضافات Wordpress: ستتعرّف في هذا المقال على ماهية إضافات ووردبريس، الأسباب التي تدفعك إلى إنشاء إضافة، وكيف تبني أوّل إضافية بسيطة. 2. برمجة إضافات ووردبريس: الخُطّافات (Hooks) : في هذا المقال ستتعرّف على مفهوم الخّطافات في ووردبريس والتي تعد من أهم الخصائص التي يوفرها ووردبريس والتي جعلته مرنًا وقابل للتمدد بصورة قل أن تجد لها نظيرًا في برمجيات الويب. 3. استقبال وحفظ خيارات (وبيانات) الإضافة: سنتعرف في هذا الدّرس على أنسب الطّرق لحفظ الخيارات العامةللإضافة، وكيفيّة إضافة صفحة إعدادات Settings Page في لوحة التحكم تمكن المستخدم من إدخال وتعديل الخيارات الخاصة بالإضافة 4. نظرة شاملة على قاعدة بيانات ووردبريس: في هذا المقال ستتعرّف على جداول قاعدة بيانات ووردبريس ودور كل جدول بالإضافة لأهم محتوياته وارتباطه بالجداول الأخرى، بالإضافة إلى ذكر بعض الطرق التي يوفرها ووردبريس للتعامل مع البيانات. الآن وبعد أن كوّنت صورة واضحة على كيفية برمجة إضافة ووردبريس، نختم السّلسلة بمثال عملي: 5. مثال عملي لبرمجة إضافة ووردبريس- الجزء الأوّل 6. مثال عملي لبرمجة إضافة ووردبريس- الجزء الثّاني
    1 نقطة
  5. يوفر تهجير قواعد البيانات Database migration في Laravel آليات لإنشاء الجداول Tablesوالتعديل عليها بغض النظر نظام إدارة قواعد البيانات المستخدم. يعني هذا أنك لن تضطر للاهتمام بالاختلافات بين نظم إدارة قواعد البيانات في صياغة أوامر SQL. يمكّن التهجير أيضا من التراجع والعودة إلى ما كانت عليه قاعدة البيانات قبل آخر التعديلات. هذا الدرس جزء من سلسلة تعلم Laravel والتي تنتهج مبدأ "أفضل وسيلة للتعلم هي الممارسة"، حيث ستكون ممارستنا عبارة عن إنشاء تطبيق ويب للتسوق مع ميزة سلة المشتريات. يتكون فهرس السلسلة من التالي: مدخل إلى Laravel 5.تثبيت Laravel وإعداده على كلّ من Windows وUbuntu.أساسيات بناء تطبيق باستخدام Laravel.إنشاء روابط محسنة لمحركات البحث (SEO) في إطار عمل Laravel.نظام Blade للقوالب.تهجير قواعد البيانات في Laravel. (هذا الدرس)استخدام Eloquent ORM لإدخال البيانات في قاعدة البيانات، تحديثها أو حذفها.إنشاء سلة مشتريات في Laravel.الاستيثاق في Laravel.إنشاء واجهة لبرمجة التطبيقات API في Laravel.إنشاء مدوّنة باستخدام Laravel.استخدام AngularJS واجهةً أمامية Front end لتطبيق Laravel.الدوّال المساعدة المخصّصة في Laravel.استخدام مكتبة Faker في تطبيق Laravel لتوليد بيانات وهمية قصدَ الاختبار. يمكن النظر إلى تهجير قواعد البيانات كما لو كان نظام إدارة نسخ خاص بقواعد البيانات، إذ يتيح لفريق العمل سهولة تغيير مخطّط Schema البيانات وتشاركه. نكمل في هذا الدرس اعتمادا على ما أنشأناه في الدروس السابقة من السلسلة. يغطي الدرس المواضيع التالية: متطلبات التهجير.أمر Artisan لتهجير قواعد البيانات.بنية التهجير.إنشاء جدول بآلية التهجير.استخدام آلية التهجير للتراجع Rollback عن التعديلات.بذر قواعد البيانات Database seeding.بنية قاعدة البيانات الخاصة بمشروع Larashop.ملفات التهجير لقاعدة بيانات Larashop.متطلبات التهجيريجب أولا إنشاء قاعدة بيانات في نظام إدارة قواعد البيانات المستخدم (MySQL في حالتنا) وإعداد معطيات الاتصال بها في Laravel ولدى أداة سطر الأوامر Artisan. إنشاء قاعدة بياناتنفذ الأمر التالي في سطر أوامر MySQL أو استخدم التطبيق المفضّل لديك (PHPMyAdmin مثلا) لإنشاء قاعدة بيانات larashop: CREATE DATABASE `larashop`;إعداد Laravel للاتصال بقاعدة البياناتأعددنا Laravel في الدرس الأول من هذه السلسلة للاتصال بقاعدة بيانات باسم larashop. في ما يلي تذكير بخطوات الإعداد. افتح الملف config/database.php واعثر على الأسطُر التالية: 'mysql' => [ 'driver' => 'mysql', 'host' => env('DB_HOST', 'localhost'), 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), 'charset' => 'utf8', 'collation' => 'utf8_unicode_ci', 'prefix' => '', 'strict' => false, ],حدّث القيم التالية لتوافق إعدادات MySQL لديك: 'database' => env('DB_DATABASE', 'larashop'), 'username' => env('DB_USERNAME', 'root'), 'password' => env('DB_PASSWORD', 'melody'), إعداد معطيات اتصال Artisan بقاعدة البياناتيواجه الكثير من المطورين رسالة الخطأ التالية عند العمل على تهجير قواعد البيانات باستخدام أداة Artisan: Access denied for user 'homestead'@' localhost' (using password: YES)ستظهر الرسالة أعلاه حتى ولو كانت معطيات الاتصال في الملف configuration/database.php صحيحة. يعود السبب في ذلك إلى أن Artisan يستخدم المعطيات الموجودة في الملف env.. الحل هو إذن تحرير الملف env. الواقع في مجلد التطبيق، ستجد ما يلي: APP_ENV=local APP_DEBUG=true APP_KEY=aqk5XHULL8TZ8t6pXE43o7MBSFchfgy2 DB_HOST=localhost DB_DATABASE=homestead DB_USERNAME=homestead DB_PASSWORD=secret CACHE_DRIVER=file SESSION_DRIVER=file QUEUE_DRIVER=sync MAIL_DRIVER=smtp MAIL_HOST=mailtrap.io MAIL_PORT=2525 MAIL_USERNAME=null MAIL_PASSWORD=null MAIL_ENCRYPTION=null حدث المتغيرات التالية: DB_HOST=localhost DB_DATABASE=homestead DB_USERNAME=homestead DB_PASSWORD=secretلتصبح: DB_HOST=localhost DB_DATABASE=larashop DB_USERNAME=root DB_PASSWORD=melody احرص على موافقة اسم قاعدة البيانات، اسم المستخدم وكلمة مروره للمعطيات لديك. احفظ التعديلات. تهجير قواعد البيانات بأداة Artisanينشئ أمر artisan ملفا على المسار database/migrations لكل عملية تهجير. يمكن تغيير المسار الخاص بحفظ ملفات التهجير إن أردت ولكننا هنا سنكتفي بالمسار المبدئي. ننفذ الأمر التالي لإنشاء أول ملف تهجير: php artisan make:migration create_drinks_tableتظهر رسالة باسم ملف التهجير الجديد. اخترنا اسم create_drinks_table للدلالة على أن التهجير ينشئ جدولا باسم drinks في قاعدة البيانات. نتيجة الأمر هي إنشاء ملف للتهجير بنفس الاسم الذي أعطيناه مع إضافة ختم زمني قبله، مثلا: 2015_12_21_215845_create_drinks_table.phpبنية ملف التهجيرندرس الآن محتوى ملف التهجير الذي أنشأناه للتو. افتح الملف التالي لرؤية محتواه (انتبه إلى أن اسم الملف يبدأ بختم زمني للحظة إنشائه): database/migrations/2015_12_21_215845_create_drinks_table.phpنجد ما يلي: <?php use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateDrinksTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { // } /** * Reverse the migrations. * * @return void */ public function down() { // } }يعرف ملف التهجير صنفا جديدا باسم CreateDrinksTable يمدد الصنف Migration: CreateDrinksTable extends Migrationداخل الصنف CreateDrinksTable توجد دالة باسم up. تنفّذ تعليمات الدالة up عند تشغيل التهجير. توجد أيضا دالة باسم down في الصنف CreateDrinksTable. تنفذ تعليمات الدالة down عند التراجع عن تغييراتِ تهجير.ملف تهجير لإنشاء جدول بقاعدة البياناتليمكن إنشاء جدول في قاعدة البيانات فجيب تعريف حقوله في ملف التهجير. نعيد فتح ملف التهجير السابق ونعدله ليصبح محتواه التالي : <?php use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateDrinksTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('drinks', function (Blueprint $table) { $table->increments('id'); $table->string('name',75)->unique(); $table->text('comments')->nullable(); $table->integer('rating'); $table->date('juice_date'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('drinks'); } }في الدالة up: نستدعي الدالة create المعرَّفة في الصنف Schema ونمرر لها معطيين، الأول اسم الجدول الذي نريد إنشاءه drinks، والمعطى الثاني دالة غير محدّدة الاسم تعرّف حقول الجدول. نستخدم كائنا من صنف Blueprint لتعريف الجدول.نعرف أول حقل من الجدول وهو الحقل id. تعرف الدالة increments التابعة للصنف Blueprint عددا طبيعيا (عدد صحيح إشارته موجبة) يزداد تلقائيّا مع كل إدخال للبيانات في الجدول.الحقل الثاني هو حقل الاسم name، الذي نعرفه بالدالة string. تنشئ الدالة stringحقلا من سلسلة محارف مع تحديد طول السلسلة (75 في المثال). نعلّم الحقل name بالدالة unique \لجعله وحيدا وهو ما يعني أنه لا يمكن لتسجيلتين في الجدول أن تحويا نفس القيمة بالنسبة لهذا الحقل.الحقل الثالث comments نصي، وتستخدم الدالة text لتعريفه. نتيح إمكانية ألا يحوي الحقل بيانات باستخدام الدالة nullable.الحقل الرابع rating للتقيمات. نستخدم الدالة integer للإشارة إلى أنه عدد صحيح.ثم نضيف حقلا لتخزين تاريخ المشروب juice_date ونستخدم الدالة date لهذا الغرض.تُستخدم الدالة timestamps لإضافة حقلين هما created_at وupdated_at في الجدول تلقائيا. الحقلان عبارة عن ختم زمني ل، على التوالي، تاريخ إضافة التسجيلة إلى قاعدة البيانات وتاريخ آخر تحديث عليها.في الدالة down نحذف الجدول drinks من قاعدة البيانات في حالة وجوده.ننفذ بعد حفظ ملف التهجير الأمر التالي: php artisan migrateستظهر مخرجات في سطر الأوامر على النحو التالي: Migration table created successfully. Migrated: 2014_10_12_000000_create_users_table Migrated: 2014_10_12_100000_create_password_resets_table Migrated: 2015_12_21_215845_create_drinks_tableإن نظرت في جداول قاعدة البيانات الآن فستجد التالي: ستلاحظ وجود أربعة جداول من بينها جدول drinks. الجداول الأخرى أنشأها Laravel لأن ملفات تهجيرها تأتي مبدئيا مع Laravel. التراجع عن التعديلاتيوفر التهجير إمكانية التراجع عن تعديلاته والعودة إلى حالة قاعدة البيانات قبل تنفيذه. أنشأنا في الفقرة السابقة جداول في قاعدة البيانات، ننفذ الأمر التالي للتراجع عن ذلك: php artisan migrate:rollbackتظهر الرسائل التالية: Rolled back: 2015_12_21_215845_create_drinks_table Rolled back: 2014_10_12_100000_create_password_resets_table Rolled back: 2014_10_12_000000_create_users_tableإن أعدت التحقق في MySQL سترى أن الجدول drinks لم يعد موجودا. نعيد إنشاء الجدول بتنفيذ التهجير مرة أخرى: php artisan migrateتمكن ملاحظة أن تنفيذ التهجير يكون بالتسلسل الزمني التصاعدي لتاريخ إنشاء ملفات التهجير (من الأقدم إلى الأحدث)، في ما يكون التراجع بتنفيذ ملفات التهجير حسب التسلسل الزمني التنازلي (من الأحدث إلى الأقدم). إدارة جداول البيانات في Laravel باستخدام التهجيرسنرى في هذه الفقرة كيفية استخدام التهجير للقيام بأشغال شائعة على جداول قواعد البيانات. إدراج بياناتسنرى الآن كيفية استخدام التهجير لإدراج بيانات في جدول أثناء إنشائه. ننشئ جدولا بالموظفين employees وندرج فيه 33 تسجيلة بالاعتماد على مكتبة Faker (سنخصص درسا لتفصيل استخدام Faker). نفذ الأمر التالي لإنشاء ملف تهجير لجدول employees: php artisan make:migration employeesنفتح الملف المنشأ للتو ونضيف الشفرة التالية: <?php use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class Employees extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('employees', function (Blueprint $table) { $table->increments('id'); $table->string('name'); $table->string('email')->unique(); $table->string('contact_number'); $table->timestamps(); }); $faker = Faker\Factory::create(); $limit = 33; for ($i = 0; $i < $limit; $i++) { DB::table('employees')->insert([ //, 'name' => $faker->name, 'email' => $faker->unique()->email, 'contact_number' => $faker->phoneNumber, ]); } } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('employees'); } } ننشئ كائنا من صنف Faker بالتعليمة $faker = Faker\Factory::create();نحدد عدد التسجيلات التي نود إدراجها: limit$. نستخدم حلقة for التكرارية لإضافة التسجيلات إلى الجدول. تولد التعليمة faker->name$ اسما وهميّا، faker->unique()->email$ اسم بريد وحيد وfaker->phoneNumber$ رقم هاتف وهميا.الأمر التالي ينفذ التهجير: php artisan migrateتظهر الرسالة التالية دلالة على تهجير الجدول employees: Migrated: 2015_12_21_225233_employees إن بحثت الآن عن محتوى الجدول employees، مثلا بتنفيذ الاستعلام التالي في سطر أوامر MySQL: SELECT * FROM employees;ستحصُل على أسماء الموظفين، عناوينهم البريدية وأرقام هواتفهم. نتراجع عن إنشاء الجدول employees بتنفيذ الأمر: php artisan migrate:rollbackتظهر رسالة دلالة على التراجع عن إنشاء الجدول. نفتح ملف التهجير للتعديل عليه ثم نضع الشفرة الخاصة بإدراج بيانات وهمية بين علامتي تعليق، هكذا: /* $faker = Faker\Factory::create(); $limit = 33; for ($i = 0; $i < $limit; $i++) { DB::table('employees')->insert([ //, 'name' => $faker->name, 'email' => $faker->unique()->email, 'contact_number' => $faker->phoneNumber, ]); } */احفظ ملف التهجير ثم نفذ الأمر: php artisan migrateسيُنشأ جدول employees من جديد ولكن هذه المرة دون إدراج تسجيلات في الجدول. إضافة عمود إلى جدول أو حذفه منهنفرض أننا نود إضافة عمود جديد gender لتخزين جنس الموظّف، مباشرة بعد العمود contact_number. ننفذ الأمر التالي لإنشاء ملف تهجير باسم add_gender_to_employees مع تحديد الجدول الذي نريد العمل عليه وهو employees: php artisan make:migration add_gender_to_employees --table=employeesتشير التعليمة table=employees-- إلى أننا نريد العمل على الجدول employees الموجود في قاعدة البيانات. افتح ملف التهجير المنشأ بعد الأمر السابق، وعدله ليصبح على النحو التالي: <?php use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class AddGenderToEmployees extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::table('employees', function (Blueprint $table) { $table->string('gender')->after('contact_number'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('employees', function (Blueprint $table) { $table->dropColumn('gender'); }); } } في الدالة up أضفنا حقلا جديدا من نوع string (سلسلة محارف) وحددنا مكانه بأنه بعد العمود contact_number.في الدالة down نحذف الحقل gender.الآن عند تنفيذ أمر التهجير php artisan migrate ستلاحظ إضافة عمود جديد باسم gender بعد عمود contact_number. تغيير نوع عموديحتاج تغيير نوع العمود لتثبيت حزمة Doctrine Database Abstract Layer, DBAL. تُستخدم هذه الحزمة لتهجيرات التعديل على الجداول Alter table. سنستخدم أداة إدارة الاعتماديات Composer لتثبيت الحزمة. افتح ملف composer.json الذي يوجد في مجلد التطبيق. ابحث عن مقطع require: "require": { "php": ">=5.5.9", "laravel/framework": "5.2.*" },توجد في هذا المقطع حزم المكتبات التي يحتاجها تطبيقنا. حتى الآن توجد حزمتان فقط هما php وlaravel. يشير الجزء الأول (قبل النقطتين) إلى اسم الحزمة، في ما يشير الثاني لإصدارها. نضيف حزمة dbal` إلى هذه الاعتماديات، وذلك على النجو التالي: "require": { "php": ">=5.5.9", "laravel/framework": "5.2.*", "doctrine/dbal": "v2.4.2" }, لاحظ الفاصلة اللاتينية التي أضفناها بعد حزمة Laravel. نفذ الأمر التالي لتحديث المشروع: composer updateعند إنشاء العمود gender لم نحدد طول الحقل، أي أنه سيأخذ الطول المبدئي للحقول من نوع string وهو255 محرفا. ننشئ ملف تهجير جديدا لتعديل طول الحقل ليصبح 5 كحد أقصى. نعدل الملف على النحو التالي: php artisan make:migration modify_gender_in_employees --table=employeesقبول فراغ الحقول في الجدوليفترض Laravel عند إنشاء الحقول أنها لا تقبل فراغ القيمة، أي أنه يجب ذكر قيمة للحقل عند إدراج تسجيلات في الجدول. يمكننا تغيير هذا الإعداد المبدئي وجعل قيمة حقل ما اختيارية. سنأخذ الحقل gender للتمثيل به. ننشئ ملفا للتهجير: php artisan make:migration make_gender_null_in_employees --table=employeesثم نعدله على النحو التالي: <?php use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class MakeGenderNullInEmployees extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::table('employees', function (Blueprint $table) { $table->string('gender', 5)->nullable()->change(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('employees', function (Blueprint $table) { $table->string('gender', 5)->change(); }); } } تجعل الدالة nullable الحقل gender يقبل قيما فارغة.php artisan migrate إضافة مفتاح خارجي Foreign keyنصنف موظفينا حسب القسم الذي يعملون فيه. ننشئ جدولا للأقسام depts ثم نضيف مفتاحا خاريجا في جدول الموظفين employees. الأمر أدناه ينشئ ملف تهجير لجدول الأقسام: php artisan make:migration deptsعدل ملف التهجير: <?php use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class Depts extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('depts', function (Blueprint $table) { $table->increments('id'); $table->string('name'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('depts'); } }ثم ننفذ أمر التهجير لإنشاء الجدول: php artisan migrateيُشترط لتصح علاقة عبر مفتاح خارجي بين جدولين أن يكون المفتاح الخارجي والمفتاح الرئيس Primary key متطابقين في النوع. استخدمنا في تعريف المفتاح الرئيس idضمن الجدول depts دالة increments التي تعطي النوع عددا طبيعيا من عشرة أرقام ;(unsigned integer INT(10 وهو ما يعني أننا سنعطي نفس النوع للمفتاح الخارجي الذي سننشئه في الجدول employees. الفرق أن المفتاح الخارجي لا يزداد تلقائيا لذا سنستخدم الدالة unsignedInteger التي لها نفس مفعول increments من حيث نوع الحقل وطوله، مع فرق أنها لا تضيف الازدياد التلقائي. ملحوظة: حتى تمكن إضافة مفتاح خارجي في الجدول employees يجب أن يكون الجدول فارغا (بدون تسجيلات). لهذا السبب علقنا في فقرة ماضية الشفرة الخاصة بـFaker. نفذ الأمر التالي لإنشاء ملف تهجير لإضافة حقل المفتاح الخارجي dept_id إلى الجدول employees: php artisan make:migration add_dept_id_in_employees --table=employeesثم نعدل ملف التهجير: <?php use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class AddDeptIdInEmployees extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::table('employees', function (Blueprint $table) { $table-> unsignedInteger ('dept_id')->after('gender'); $table->foreign('dept_id') ->references('id')->on('depts') ->onDelete('cascade'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('employees', function (Blueprint $table) { $table->dropColumn('dept_id'); }); } }ثم ننفذ التهجير: php artisan migrate بذر قواعد البياناتيشير مصطلح البذر Seeding إلى عملية إضافة بيانات وهمية لأغراض الاختبار في قواعد البيانات. نطبق هذا الإجراء على جدول drinks الذي أنشأناه في أول الدرس. نفذ الأمر التالي لإنشاء ملف للبذر: php artisan make:seeder DrinksTableSeederينشئ الأمر ملفا باسم DrinksTableSeeder.php على المسار database/seeds. افتح الملف: <?php use Illuminate\Database\Seeder; class DrinksTableSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { // } } يمدد الصنف DrinksTableSeeder الصنف Seeder ويعرّف الدالة run التي تُنفّذ عند تشغيل أمر البذر في Artisan. عدل الملف ليصبح محتواه التالي: <?php use Illuminate\Database\Seeder; class DrinksTableSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { DB::table('drinks')->insert([ 'name' => 'Orange Juice', 'comments' => 'Rich in C vitamin', 'rating' => 9, 'juice_date' => '2015-12-20', ]); } أضفنا في الدالة run أمر إدراج في جدول البيانات drinks ومررنا مصفوفة توافق عناصرها حقول الجدول مع تحديد قيم عناصر المصفوفة. ننفذ الأمر أمر البذر لإضافة التسجيلة أعلاه إلى الجدول: php artisan db:seed --class=DrinksTableSeederنمرر لأمر البذر php artisan db::seed اسم الملف المراد تنفيذه. الآن عند التحقق نجد في جدول قاعدة البيانات التسجيلة التالية: قاعدة البيانات الخاصة بمشروع Larashopتعرفنا في الفقرات الماضية على أساسيات التهجير في Laravel. سنجعل هذه المعرفة موضع التطبيق لإنشاء قاعدة بيانات لمشروع Larashop. ستشنرك جميع الجداول في الحقول التالية التي أنشأناها لأغراض الفحص والتدقيق. التسلسلالحقلنوع البياناتالوصف1created_at Timestamp ختم زمني لتاريخ إدراج التسجيلة2updated_at Timestamp ختم زمني لتاريخ تحديث التسجيلة3created_at_ip (Varchar(45 عنوان IP المستخدم لإدراج التسجيلة4updated_at_ip (Varchar(45 عنوان IP المستخدم لتحديث التسجيلةجدول منشورات المدونةالتسلسلالحقلنوع البياناتالوصف1id INT مفتاح رئيس (AUTOINCREMENT) عدد طبيعي يزداد تلقائيا2url (Varchar(255 رابط الصفحة3title (Varchar(140 عنوان الصفحة4description (Varchar(170 وصف يظهر في محركات البحث5content Text محتوى الصفحة أو المنشور 6conblogtent (Tinyint(1 يحدد ما إذا كان المنشور صفحةجدول التصنيفاتالتسلسلالحقلنوع البياناتالوصف1id INT مفتاح رئيس (AUTOINCREMENT) عدد طبيعي يزداد تلقائيا2name (Varchar(255 اسم التصنيفجدول العلامات التجاريةالتسلسلالحقلنوع البياناتالوصف1id INT مفتاح رئيس (AUTOINCREMENT) عدد طبيعي يزداد تلقائيا2name (Varchar(255 اسم العلامة التجاريةجدول المنتجاتلكل منتج تصنيف وعلامة تجارية وحيدين. التسلسلالحقلنوع البياناتالوصف1id INT مفتاح رئيس (AUTOINCREMENT) عدد طبيعي يزداد تلقائيا2name (Varchar(255 اسم المنتج3title (Varchar(140 عنوان المنتج4description (Varchar(500 عنوان المنتج5price int ثمن المنتج6category_id int معرف تصنيف المنتج7brand_id int معرف العلامة التجارية للمنتجملفات التهجير لجداول قاعدة بيانات المشروعسننشئ في هذه الفقرة ملفات تهجير لجداول البيانات المذكورة أعلاه؛ سنضيف أيضا بعض البيانات الوهمية إلى الجداول باستخدام آلية البذر التي تعرفنا عليها سابقا. توليد ملفات التهجيرافتح سطر الأوامر ونفذ الأوامر التالية لتوليد ملفات الجداول: جدول منشورات المدونة: php artisan make:migration create_posts_table جدول تصنيفات المنتجات php artisan make:migration create_categories_tableجدول العلامات التجارية للمنتجات php artisan make:migration create_brands_tableجدول المنتجات php artisan make:migration create_products_tableتحرير ملفات التهجيرننتقل لتحرير كل ملف من ملفات التهجير. جدول منشورات المدونة <?php use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreatePostsTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('posts', function (Blueprint $table) { $table->increments('id'); $table->string('url', 255)->unique(); $table->string('title', 140); $table->string('description', 170); $table->text('content'); $table->boolean('blog'); $table->timestamps(); $table->string('created_at_ip'); $table->string('updated_at_ip'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('posts'); } } جدول التصنيفات <?php use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateCategoriesTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('categories', function (Blueprint $table) { $table->increments('id'); $table->string('name', 255)->unique(); $table->timestamps(); $table->string('created_at_ip'); $table->string('updated_at_ip'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('categories'); } } جدول العلامات التجارية <?php use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateBrandsTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('brands', function (Blueprint $table) { $table->increments('id'); $table->string('name', 255)->unique(); $table->timestamps(); $table->string('created_at_ip'); $table->string('updated_at_ip'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('brands'); } } جدول المنتجات <?php use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateProductsTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('products', function (Blueprint $table) { $table->increments('id'); $table->string('name', 255)->unique(); $table->string('title', 140); $table->string('description', 500); $table->integer('price'); $table->unsignedInteger('category_id'); $table->unsignedInteger('brand_id'); $table->timestamps(); $table->string('created_at_ip'); $table->string('updated_at_ip'); // مفتاح خارجي على جدول التصنيفات $table->foreign('category_id') ->references('id')->on('categories') ->onDelete('cascade'); // مفتاح خارجي على جدول العلامات التجارية $table->foreign('brand_id') ->references('id')->on('brands') ->onDelete('cascade'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('products'); } }بذر قاعدة بيانات المشروعندرج بيانات وهمية في جداول قاعدة البيانات قصدَ الاختبار. أنشئ ملفات البذر بتنفيذ الأوامر أدناه على التوالي: php artisan make:seeder CategoriesTableSeeder php artisan make:seeder BrandsTableSeeder php artisan make:seeder ProductsTableSeederيحوي جدول المنتجات مفتاحين خارجيين لجدولي التصنيف والعلامة التجارية. لذا يجب البدء بهما (لا يصح إدراج مفتاح خارجي لتسجيلة غير موجودة في الجدول الذي مثل المفتاح الخارجي مرجعا إليه). بذر جدول التصنيفات <?php use Illuminate\Database\Seeder; class CategoriesTableSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { DB::table('categories')->insert(['name' => 'MENS']); DB::table('categories')->insert(['name' => 'WOMENS']); DB::table('categories')->insert(['name' => 'KIDS']); DB::table('categories')->insert(['name' => 'FASHION']); DB::table('categories')->insert(['name' => 'CLOTHING']); } } بذر جدول العلامات التجارية<?php use Illuminate\Database\Seeder; class BrandsTableSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { DB::table('brands')->insert(['name' => 'ACNE']); DB::table('brands')->insert(['name' => 'RONHILL']); DB::table('brands')->insert(['name' => 'ALBIRO']); DB::table('brands')->insert(['name' => 'ODDMOLLY']); } } بذر جدول المنتجات<?php use Illuminate\Database\Seeder; class ProductsTableSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { DB::table('products')->insert(['name' => 'Mini skirt black edition', 'title' => 'Mini skirt black edition','description' => 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna','price' => 35,'category_id' => 1,'brand_id' => 1,]); DB::table('products')->insert(['name' => 'T-shirt blue edition', 'title' => 'T-shirt blue edition','description' => 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna','price' => 64,'category_id' => 2,'brand_id' => 3,]); DB::table('products')->insert(['name' => 'Sleeveless Colorblock Scuba', 'title' => 'Sleeveless Colorblock Scuba','description' => 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna','price' => 13,'category_id' => 3,'brand_id' => 2,]); } } ثم ننفذ أوامر البذر لكل جدول. خاتمةتعرفنا في هذا الدرس على تهجير قواعد البيانات وبذرها في Laravel. كما أننا حددنا هيكلة قاعدة بيانات المشروع الذي نعمل عليه. في الدروس القادمة سنتعرف على إطار عمل Eloquent الذي سنعتمد عليه للتخاطب مع قاعدة البيانات وعرض محتوياتها عند الاقتضاء. ترجمة -وبتصرّف- لمقال Laravel 5 Migrations لصاحبه Rodrick Kazembe.
    1 نقطة
  6. يمكنك فعل ذلك بطرق متعددة في لغة بايثون، لكن في أغلب الأحيان لا يجب عليك استخدام التعابير النمطية من أجل فعل ذلك، فمثلا يمكنك استخدام دالة splitext من وحدة os.path ومن ثم تحصل على العنصر الأول فقط، وستتعامل هذه الطريقة أيضا مع أسماء الملفات المختلفة مثل .bashrc طريقة استعمال الدالة كالتالي: os.path.splitext(filename)[0] وإذا رغبت بفعل ذلك بالتعابير النمطية وكانت جميع الملفات تنتهي بـ .jpg فيمكنك كتابة سطر مشابه لهذا: s = re.sub(r'\.jpg$', '', s)
    1 نقطة
  7. خدمة العملاء الجيدة لا تعني مساعدة العميل الذي يطلب المساعدة فحسب. ذهبتُ يومًا مع أصدقائي لتناول العشاء عندما كنت مقيمًا في سان فرانسيسكو، كان للمطعم الذي قصدناه شعبية عالية؛ ما جعله مزدحمًا حينها. بدأت العثرات تظهر خلال دقيقتين من جلوسنا -خلافًا لتوقعاتنا العالية-، عندما قدّم النادل المشروبات التي طلبناها لطاولة أخرى، لكن الأمور لم تتوقف عند هذا الحدّ إذ سرعان ما ازدادت سوءا، فقد أحضر النادل طبقًا مغايرًا لما طلبه أحدنا، وطبقًا محترقًا لآخر، في حين نسي أن يجلب وجبة لصديق ثالث، وأغفل أحد طلباتنا من المشروبات، وفي الختام؛ كان علينا أن نسأل المحاسب شطب طبقين لم نطلبهما لكنهما ذُكرا في الفاتورة. لم نحدث ضجّة حول ذلك فقد كان عشاء مسليًا؛ وكانت الأخطاء تتزايد بشكل كوميديّ لدرجة جعلتنا نحتاج لضبط ضحكاتنا المتكررة. ورغم ذلك، كتبت صديقة كانت معنا في العشاء رأيها وتجربة زيارتها للمطعم في اليوم التالي؛ كما اعتادت أن تفعل مع كل مطعم تزوره عبر موقع yelp، في الحقيقة لم تكن مراجعة مليئة بالمديح؛ لكن إدارة المطعم كانت مصغية، ففي نفس اليوم تلقت الصديقة ردًا على مراجعتها من مدير المطعم يطلب منها مهاتفته لشرح ما حصل، عندما اتصلت به هاتفيًا اعتذر بشكل بالغ؛ وقدم دعوة بوجبة مجانية لنا جميعًا كنوع من التعويض. هذه القصة مثال جيدة على استعادة خدمة العميل (customer service recovery)؛ لكنني لا أشاركها من هذا المنطلق، وإنما لأوضح أن إدارة هذا المطعم -وآلاف الشركات الأخرى- يفهمون بشكل عميق الحقيقة التالية: الكثير من العملاء لا يخبرونك دائمًا بما يواجههم من مشكلات مع منتجك؛ عوضًا عن ذلك يختارون خدمات أخرى ويشاركون تجارب استخدامهم السيئة مع الآخرين. وتوضح الدراسات هذه النقطة بشكل أفضل؛ إذ تبعًا لإحصاء أجراه مركز لي للموارد العالمية: لقاء كل عميل يشكو لك مشكلته؛ ثمة 26 عميلًا آخرين لا يخبرونك بشيء. ولكنهم في كثير من الأحيان يشاركون الناس شكاويهم، ومن خلال إيجادك هذه الشكاوى ومعالجة أسبابها ستجني الكثير؛ ليس فقط على صعيد بناء علاقة أفضل مع عملائك؛ وإنما بإبداء حرصك على فعل كل ما من شأنه أن يُصحّح مسار عملك. لا ينحصر عملك بحلّ المشكلات التي تأتي إليك، وإنما يمتدّ لحلّ تلك التي لا يخبرك العميل بها. كيف تحل مشكلات عميلك؟ اخرج من صندوق بريدك الواردثمة ثلاث طرق تمكّنك من جعل خدمة العملاء الصادرة outbound support أسهل لك ولفريقك: راقب حساباتك على الشبكات الاجتماعيةحسنًا؛ أعلم أن هذه النصيحة متأخرة ثمانية سنوات لآتي وأقول "اشترك بمواقع التواصل الاجتماعي"، لكن هذا مهمّ؛ فالشبكات الاجتماعية هي المكان الأول الذي سيقصده عملاؤك للحصول على الدعم الفني. من المريع رؤية كمّ حسابات تويتر التجارية والمهملة من قبل أصحابها، فقد اتضح من خلال الدراسات أن هذا الخطأ يكبّد الشركات خسائر لا يستهان بها؛ على سبيل المثال تشير دراسة غارتنر المنشورة عام 2012 أن فشل الدعم عبر قنوات التواصل الاجتماعية قد يزيد نسبة تخلي المستخدم عن الخدمة بمقدار 15%. وبرأيي فإنه من الأفضل عدم وجود حساب لك على تويتر نهائيًا، من أن تبدو بمظهر من يتجاهل رسائل زبائنه. فائدة: البحث في تويتر لا تفترض أن يعرف زبائنك اسم معرفك على تويتر؛ في الحقيقة فقد أظهرت دراسة أن 3% فقط من الإشارات -mentions- للعلامات التجارية تكون باستخدام اسم المعرف؛ عوضًا عن ذلك يُستخدم عادة اسم الشركة أو المنتج. ابحث عن اسم شركتك أو منتجك على تويتر بشكل منتظم -ستساعدك بعض الأدوات على ذلك، فمثلًا تعطيك HootSuite تحديثات أوتوماتيكية لنتائج بحثك على الشبكات الاجتماعية، لترى ما يقوله الناس عنك. اعرف مكان تواجد عملائك على الشبكةعلى خلاف المطاعم؛ قد لا يتحدث زبائنك عنك على موقع yelp، لذا سيتوجب عليك البحث أكثر قليلًا؛ لكن إن كنت تهتم بتنمية العملاء -كما يفترض بك- فإنك غالبًا ستفهمهم بما فيه الكفاية لمعرفة مكان طرح نقاشاتهم على الشبكات الاجتماعية. إذا كان عملاؤك من مطوري الويب؛ ربما سيشيرون إليك على مواقع من قبيل Stack Exchange أو Stack Overflow، أما إن كانوا يهتمون بالشركات الناشئة المختصة بالخدمات البرمجية SaaS startups، سيتجهون إلى Hacker News على الفور، وبغض النظر عن الخدمة التي تقدمها واهتمامات زبائنك، فغالبًا ما ستجد بعضهم في أحد المنتديات الفرعية لموقع reddit. ستمكّنك متابعة المنصات الاجتماعية الخاصة بعملائك والمشاركة فيها من الانتباه لأيّة إشارة تذكر منتجك، ناهيك عن تحسين علاقتك بزبائنك وفهمك المتزايد لهم. استخدم أدوات الرصدهناك أدوات مخصصة للبحث في الويب وتنبيهك لدى ظهور أية إشارة لمنتجك أو علامتك التجارية في مكان ما على الإنترنت، وهي تتنوع ما بين أدوات البحث المجانية مثل: Social Mention. وأخرى ذات الخصائص الكاملة والمدفوعة من قبيل: Moz’s Fresh Web Explorer. تمكّنك هذه الأدوات من الاطلاع الدائم على ما يُكتب عن منتجك على الإنترنت؛ أين يشار إليه ومتى، ما يجعلها من الأدوات المهمّة للتسويق؛ وبذات الأهمية لخدمة دعم العملاء أيضًا. كيف تستجيب لشكاوى العملاء؟حسنًا، أنت الآن تقود الأمور بالاتجاه الصحيح وتصغي لعملائك في المكان المناسب، ما الذي يتوجب عليك فعله عند قراءتك لشكوى كتبها أحدهم عن منتجك؟ ببساطة: حلّ المشكلة. تواصل معهم إما عبر القناة التي نشروا الشكوى فيها، أو بشكل شخصي باستخدام البريد الإلكتروني، وافعل ما بوسعك لإصلاح الأمور. قبل حوالي عام من الآن، نشر مدوّن مراجعته عن عدد من تطبيقات المساعدة المكتبية بما فيها Groove، وأشار لبعض الخصائص التي أعجبته بـ Groove ولخصائص أخرى لم تعجبه أيضًا، في النهاية، ختم تدوينته بما يلي: لم تكن هذه الخاتمة رائعة بالنسبة لنا، لكن عوضًا عن الدفاع أو المطالبة بإزالة الجزء السلبي من المراجعة -وهي واحدة من أسوأ الأخطاء التي يمكن أن ترتكبها في خدمات الدعم الصادرة- فقد تواصلنا مع المدون لجعل الأمور أفضل، بعد عدة أسابيع نشر التحديث التالي: ببساطة، وعن طريق الاستماع ومعالجة مشكلة لم يخبرنا أحد عنها، استطعنا تحويل عميل سابق إلى عميل مخلص على المدى الطويل. اخرج من صندوق بريدك الواردمقابل كل 26 عميل غير راض، هناك عميل واحد فقط يختار إرسال شكوى بريدية لك وإخبارك بمشكلته، والأمر ببساطة أنه لا يمكنك تجاهل أولئك الذين اختاروا مشاركة شكاويهم مع الآخرين، لذا اخرج من صندوق بريدك، ابحث عنهم واستمع لهم، وافعل كل ما بوسعك ليكونوا سعداء، فخدمة العملاء المميزة لا تتوقف عند حدود بريدك ألإلكتروني. تُرجم بتصرف عن مقال Your Customers Aren’t Telling You Everything لصاحبه Len Markidan.
    1 نقطة
×
×
  • أضف...