1. المستوى: مُبتدأ/متوسّط
     
    يُمكن الاستفادة من مكتبة Multer في nodejs لرفع الصور والملفّات إلى السيرفر، فبأسطر برمجية بسيطة يُمكن رفع الملفات التي قام المُستخدم بتحديدها
    let app = require("express")() let multer = require("multer") let upload = multer({ dest: 'uploads/' }).any() app.get("/", (req,res)=>{ res.send("Teting...") }) app.post("/upload", (req, res)=>{ upload(req,res, (err)=>{ if (err) throw err }) res.send(req.files) }) app.listen(3000, ()=>{ console.log("Let's Upload....") }) الكود السابق يقوم باستلام الملفّات التي يُرسلها المستخدم إلى المسار /upload ليقوم برفعها ثم طباعة اسم الملف المُستلم. لكن وبعد تنفيذه قد تتفاجئ بأن عملية الرفع تجري بنجاح، إلا أن الاسم لا يظهر ويتم إرسال مصفوفة فارغة على الرغم من أن عملية الرفع تمّت دون مشاكل.
    السبب في ذلك هو طبيعة Nodejs، والتي سبق وأن تحدّثت عنها في مقال سابق بعنوان "تنفيذ الطلبات بتسلسل وترتيب في Nodejs باستخدام Async/Await". انصحك بقرائته لفهم آلية عمل إطار العمل بشكل كامل.
    ما يحدث على أرض الواقع هو أن النظام يبدأ بالرفع، ولأن تنفيذ المهام في nodejs يتم بشكل متوازي لتجنّب تأخير الرد على طلب المستخدم، سيقوم النظام فورًا بطباعة محتويات مصفوفة الملفّات req.files، التي ستكون فارغة بالفعل لأن النظام لم ينتهي بالأصل من عملية الرفع. والحل في هذه الحالة هو استخدام async/await ليُصبح الكود من الشكل:
    let app = require("express")() let multer = require("multer") let upload = multer({ dest: 'uploads/' }).any() app.get("/", (req,res)=>{ res.send("Testing Routes.....") }) let uploadPromise = (req, res)=>{ return new Promise((Resolve, Reject)=>{ upload(req,res, (err)=>{ if (err) Reject(err) Resolve() }) }) } app.post("/upload", async(req, res)=>{ try{ await uploadPromise(req, res) res.send(req.files) }catch(uploadErr){ throw uploadErr } upload(req,res, (err)=>{ if (err) throw err }) }) app.listen(3000, ()=>{ console.log("Let's Upload....") }) حاول تنفيذ الكود السابق وسترى أن اسم الملف سيظهر دون مشاكل لأن النظام قام أولًا برفع الملف ثم قام بطباعة الاسم، وهذا يعني أن المصفوفة لم تعد فارغة بشكل كامل. الفيديو التالي يشرح الفرق بشكل عملي:
    أما الراغبين بتحديد لاحقة الملف أو قبول نوع مُحدّد من الملفات فهم بحاجة لتعديل بعض جزئيات الكود، ولتجنّب تعقيد الأفكار ما بين Async/Await، سأقوم باستخدا مثال بسيط عن Multer.
    let app = require("express")() let multer = require("multer") let upload = multer({dest: "uploads"}) app.post("/", upload.single("photos"), (req,res)=>{ res.send("OK...") }) app.listen(3000, ()=>{ console.log("Ready to upload...") }) الكود السابق يشرح طريقة رفع ملف إلى مُجلّد uploads باستخدام multer، فعند طلب الرابط domain.com/ باستخدام POST فإن النظام سيأخذ الملف الموجود في الحقل photos وسيقوم برفعه. لكن ماذا لو أردنا تغيير الاسم أو اختيار اللاحقة؟ الأمر يتم من خلال ميثود موجودة داخل multer تُعرف بـ diskStorage ليُصبح الكود من الشكل
    let app = require("express")() let multer = require("multer") let storage = multer.diskStorage({ destination: function(req, file, cb){ cb(null, "uploads") }, filename: function(req, file, cb){ let ext = file.mimetype.split("/") ext = ext[ext.length-1] cb(null, file.originalname + "-"+Date.now()+"."+ext) } }) let upload = multer({storage:storage}) app.get("/", (req, res)=>{ console.log(Date.now()) }) app.post("/", upload.single("photos"), (req,res)=>{ res.send("OK...") }) app.listen(3000, ()=>{ console.log("Ready to upload...") }) قُمنا في الميثود بتعريف خاصيّتين الأولى هي destination الخاصّة بتحديد مكان الرفع، والثانية هي اسم الملف. الشرط الوحيد في الخصائص داخل multer هو استخدام دالة cb، وهي اختصار لـ Call Back لتمرير النتيجة التي نرغب بها.
    في مثالي في الأعلى قُمت بفحص نوع الملف باستخدام الـ mimeType الخاص به، فلو كان صورة سيظهر بالشكل image/jpeg أو image/png وهكذا. بعدها قمت بإضافة تلك اللاحقة للاسم الذي يُمكنك تسميته كيفما تشاء. أما بخصوص رفع نوع واحد من الملفات دونًا عن الغير فهذا مُمكن عبر تعديل السطر الخاص بتعريف upload ليُصبح من الشكل 
    let upload = multer({storage:storage, fileFilter: function(req, file, cb){ if (file.mimetype != "image/jpeg"){ return cb(null, false) } cb(null, true) }}) قُمنا بإضافة ما يُعرف بالـ fileFilter، وهي خاصيّة موجودة في multer قُمنا فيها بفحص نوع الملف مرّة أُخرى عبر mimeType وفي حالة عدم مُطابقة الشروط نقوم برفض الملف من خلال استخدام إعادة الـ callback فارغ مع false. بهذه الحالة سيقفز النظام عن الملف وسيتجاوزه.
    الفيديو التالي يشرح كل شيء
     
    في حالة وجود أي استفسار يُمكن تركه في التعليقات أو مُراسلتي عبر حسابي في تويتر @FerasAllaou
    مستوى المقال: مبتدئ
  2. بسم الله الرحمن الرحيم
    السلام عليكم ورحمة الله، كيف حال الجميع؟ عساكم بألف خير و عافية.
    سأحاول باذن الله البدأ في سلسلة جديدة حول تعلم أساسيات رياكت (React) و (RN = React Native) كما تعلمون هي مكتبة عرض (View) أطلقتها شركة الفيسبوك لبناء واجهاتك البرمجية (User Interfaces)  بطريقة سلسة و سهلة مبنية على نظام (Virtual DOM). و أول نسخة رسمية منها بتاريخ مارس 2013. و لما نقول أنها مكتبة عرض ليست اطار عمل (MVC) ركّز هنا جيّداً أخي القارئ؛ حيث تمثل هي حرف (V) فقط لا تقدم خدمة المتحكم (Controller) و لا (Model).
    بداية تعلمك React يصادفك شئ جديد عند كتابة أول مكوّن (Component's) حيث أنها تعود (returning) بصيغة تشبه صيغة HTML! ربما لم تلاحظها في مكتبات أو أطر عمل أخرى. تسمى بالـ JSX
    فما هي يا ترى JSX ؟ 
    JSX عبارة عن صيغة (syntax) أقول أُخترعت لمكتبة React التي تبدو مشابهة جداً لـ xHTML التي تمكنك من انشاء عناصر واجهتك البرمجية (Create Elements) بسهولة و يسر عن طريقها بدلاً من استعمال وضائف (functions) تستدعيها يدوياً لتنفذ مهمة معيّنة. ربما هذه نقطة مبهمة للبعض لكن ستفهمها بعد قليل باذن الله.
    من المهم أن تعلم أن جميع مكونات React المبنية بصيغة JSX تترجم من شفرة لشفرة (transpiler) الى تعليمات جافاسكربت حقيقية (Real JavaScript) باستخدام مكتبة تدعى بابل (Babel) تقوم بتحويل الرياكت الى شفرة ES5 القديمة لتشتغل على جميع المتصفحات (حتى القديمة منها).
    لنكتب مثالا بسيطاً بالـ JSX : 
    return <span> أهلا بعالم البرمجة! </span>; هل لاحظت سهولة كتابة جملة، و ارجاعها للظهور في المصتفح بكل سهولة مرفقة بكلمة (Return). تخيّل لو كتبناها بصيغة ES5 القديمة؟! 
    return React.createElement( 'span', {}, 'أهلا بعالم البرمجة!'); كم سطراً كتبناه لنعيد الجملة للظهور باستخدام جافاسكربت طبيعية! في هذا المثال استعملنا وظيفة (function) تسمى بـ React.createElement صيغتها كالتالي: 
    React.createElement( string|element, [propsObject], [children...]) ماذا لو أردنا كتابة عدة جمل متفرقة بداخل الشفرة البرمجية! ستكون هكذا بلا شك!
    React.createElement('div', {}, React.createElement('div', {}, 'text1'), React.createElement('div', {}, 'text2', ) ); لكن لسهولة JSX و هذا ما تميّزت به شركة الفيسبوك صراحة في مكتباتها البرمجية، الشفرة السابقة مثلاً باستخدام JSX : 
    function text1() { return <span>text1</span>; } function text2() { return <span>text2</span>; } هنا نستنتج مرة أخرى، أن JSX رائعة جدا و اختزال (shorthand) لكثرة الشفرات البرمجية التي تؤدي نفس الوظيفة. مما يكلفك وقتا اضافيا و كثرة المشاكل (bug's) عند تنقيح برمجياتك و تطبيقاتك؛ هذا كود شامل و أوسع، تلاحظ أنه يمكنك من تقسيم واجهاتك البرمجية عبر مكونات مختلفة و التحكم بها كلٌ على حِدة.
    من الثلاث خصائص المتوفرة في JSX هي : 
    العناصر المتداخلة (Nested elements): حيث يمكنك الجمع بين عدة عناصر (Multiple elements)  في حاوية واحدة (container element) كـ <div> مثل المثال الذي بالأسفل.
    السمات (Attributes): داخل كل عصنر div لك أن تضيف وظائف متعددة كـ (className، selected، style) ..الخ. 
    تعبيرات الجافاسكربت (JavaScript expressions): يمكنك اضافة تعابير (ليست شرطية statements)  داخل JSX كعمليات الحساب و المهم أن تكون ضمن قوسين {} .
    import React from 'react'; class App extends React.Component { render() { return ( <div> <p>Header</p> <p>Content</p> <p>Footer</p> </div> <div> <h1>{10+1}</h1> </div> <div style={{ height: 10 }}> Hello World! </div> ); } } export default App; في التعبيرات ذكرنا أنه لا يمكن استخدام التعابير الشرطية كـ if و else باستثناء التعبير الشرطي الثلاثي: { i === 1 ? 'true' : 'false' }  مثال : 
    import React from 'react'; class App extends React.Component { render() { const i = 1; return ( <div> <h1>{ i === 1 ? 'true' : 'false' }</h1> </div> ); } } export default App; من النصائح التي أقدمها و لا يجب عليك نسيانها: جميع أسماء المكونات (components) تكتب بحروف صغيرة (lowercase) التي تكون مدمجة بـعناصر HTML أو SVG كـ (div, ul, rect, etc.) و لا تنسى اغلاق العناصر بأوسمتها (Close Every Element) مثال مبسط: 
    // DO THIS: return <br/>; return <input type='password' .../>; return <li>text</li>; // NOT THIS: return <br>; return <input type='password' ...>; return <li>text; هذه أهم أساسيات JSX التي يجب على كل شخص معرفتها لمحبي تعلم ReactJS و RN عموماً. أتمنى تكون مفهومة للجميع. 
    و السلام عليكم.
    مستوى المقال: مبتدئ
  3. المستوى: مُبتدئ.
     
    تتميّز بيئة Nodejs بتنفيذ المهام دون إعاقة عمل التطبيق، بمعنى أن النظام يستلم طلبات المستخدم ويسعى لتنفيذها في نفس الوقت لتجنّب تأخير الرد أولًا، ولتجنّب زيادة الحمل على السيرفر الذي يحدث عندما لا تتوفّر الموارد الكافية لتلبية جميع الطلبات الواردة.
    أحيانًا، قد تحتاج لتنفيذ بعض المهام المُرتبطة في Nodejs، مثل رفع الصور أولًا إلى السيرفر ثم أخذ أسماء الملفات المرفوعة لتخزينها في قاعدة البيانات. في الوضع الطبيعي لن تفلح في الحصول على أسماء الملفات لو استخدمت التسلسل المنطقي الخاص ببيئة Nodejs، فالنظام سيقوم بتنفيذ جميع المهام معًا أملًا في الرد عليك في أسرع وقت. وكما نعلم، رفع الملفات قد يأخذ وقت أطول من اللازم، وهنا وخلال الرفع سيتم الانتقال لتنفيذ أمر الإضافة لقاعدة البيانات في ذات الوقت وستتفاجئ أن حقل أسماء الملفات فارغ، أو غير مُكتمل.
    هل من حل؟ بكل تأكيد هناك حل منطقي وبسيط جدًا يتمثّل في Async/Await المدعومة بشكل افتراضي في Nodejs، أي تنفيذ المهام بشكل متزامن. وقبل كتابة المثال دعونا نتّفق على شيء واحد يتمثّل في ضرورة استخدام Promises مع Async/Await فبدونها لن يعمل الكود بالشكل المطلوب.
    let app = require("express")() let myFunc = ()=>{ return new Promise((Resolve, Reject)=>{ setTimeout(()=>{ console.log("This is in between") Resolve() },3000) }) } app.get("/", (req, res)=>{ console.log("This is First") myFunc() console.log("This is Second") res.send("Ok") }) app.listen(3000, ()=>{ console.log("Running....`") })  باختصار يقوم الكود السابق باستقبال طلب المستخدم وطباعة ثلاث جمل، الأولى This is First والثانية This is in Between والثالثة This is Second. لكن لو قمت بتنفيذ الكود السابق ستتفاجئ بأن النتيجة ليست هكذا، حيث سيتم طبع الجملتين الأولى والثالثة، والثانية سيتم تنفيذها بعد 3 ثوان، وهذا لأنني أنا قمت بتأخيرها يدويًا لمحاولة شرح المُشكلة التي أتحدّث عنها، أي تنفيذ حدث دون انتهاء سابقه، الأمر الذي قد لا نرغب به أحيانًا.
    يُمكنك تخيّل الجملة الثانية هي رفع الصور والثالثة هي تخزين أسماء الملفات في قاعدة البيانات. وهنا يأتي دور الـ Async/Await ليُصبح الكود على الشكل
    let app = require("express")() let myFunc = ()=>{ return new Promise((Resolve, Reject)=>{ setTimeout(()=>{ console.log("This is in between") Resolve() },3000) }) } app.get("/", async(req, res)=>{ try{ console.log("This is First") await myFunc() console.log("This is Second") res.send("Ok") }catch(e){ throw e } }) app.listen(3000, ()=>{ console.log("Running....`") }) لو قمت الآن بتشغيل الكود ستجد أن التنفيذ يجري بترتيب منطقي، يتم طباعة الجملة الأولى ثم الانتظار حتى طباعة الثانية، بعدها يقوم النظام بطباعة الثالثة.
    لا تنس استخدام Promise مع الـ Async/Await، ولا تُفرط في استخدامها كثيرًا، فقط عند الحاجة. وكتنويه بسيط هناك بعض المكتبات مثل mongoose تقوم بإرجاع Promise لك عند إضافة سجل جديد لقاعدة البيانات، الأمر الذي يعني أنك تستطيع ببساطة وضعه ضمن Async/Await لتنفيذ أشياء فقط بعد الانتهاء من الإضافة. الفيديو يُظهر عمليًا ما الذي يحدث مع وبدون الـ Async/Await.
    أما ما يحدث في الخفاء فهو مشروح في الصور التالية لفهم معمارية NodeJS بشكل كامل. كما يُمكن قراءة الموضوع التالي: "ماهو الـSingle Thread Event Loop في الـNodeJS"


     
    إذا كانت هناك أية استفسارات أتمنى تركها في التعليقات أو التواصل معي في حسابي على تويتر @FerasAllaou
    مستوى المقال: مبتدئ
  4. بسم الله الرحمن الرحيم
    السلام عليكم ورحمة الله وبركاته
     من العايدين جميعًا وكل عام وانت بخير بمناسبة عيد الاضحى المبارك 
     
    ندخل مباشرة في الدرس
    ListView هو قائمة تقوم بإستخراج البيانات مباشرة من المصفوفة الى الواجهة
    اما HtmlView فهو لرسم اجزاء HTML في واجهة التطبيق ( لا تستطيع إستخدام JavaScript فيه بإختصار هو لادعمها )
     


     
     
    خصائص الملحق ListView
    items تحدد فيه المصفوفة المستهدف إستخارج البيانات منها itemTap يفوم بتشغيل دالة عند النقر على احد عناصر ملحق ListView itemLoading تشغيل دالة عند تحميل جميع العناصر  loadMoreItems يقوم بتشغيل دالة عند وصول منزلق الصفحة الى أخر عنصر بالأسفل في ListView
    خصائص الملحق HtmlView
    html تقوم بإلحاق كود الـHTML بداخلة
    مثال على الملحقات
    main-page.xml
    <Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="onNavigatingTo"> <ActionBar title="عالم البرمجة - مستعرض الـHTML و مستعرض المصفوفات"></ActionBar> <StackLayout> <ListView items="{{ List }}" itemTap="onItemTap"> <ListView.itemTemplate> <Label text="{{ name }}" textWrap="true" style="margin: 20px;" /> </ListView.itemTemplate> </ListView> <HtmlView html="<h1><font color='red'>عالم البرمجة</font> دورة nativescript</h1>"/> </StackLayout> </Page> main-page.js
    var Observable = require("data/observable").Observable; var page; // إنشاء الـ Observable var viewModel = new Observable(); // هذه الدالة سوف تعمل عند الإنتقال للصفحة function onNavigatingTo(args) { page = args.object; // إضافة مصفوفة للـ Observable viewModel.List = [ { name: "Salem"}, { name: "Saleh"}, { name: "Ahmed"}, { name: "Saeed"}, { name: "Eid"}, { name: "Mubarak"} ]; // ربط الـ Observable بالصفحة page.bindingContext = viewModel; } function onItemTap(args){ // يرجع لك الـindex للعنصر الذي تم النقر عليه // args.index alert(viewModel.List[args.index].name); } exports.onNavigatingTo = onNavigatingTo; // إستخراج الدالة لإستخدامها داخل main-page exports.onItemTap = onItemTap; الناتج

    من هنا 

    الملحق WebView
    WebView او مستعرض الويب كما يوحي اسمه هو ملحق لإستعراض صفحات الويب وشفرات الـHTML وهو يدعم الجافاسكربت
    وتستطيع استخدامة لإستعراض رابط معين او شفرات HTML & CSS & JavaScript
     
    خصائص الملحق
    src رابط الموقع او شفرة الـ HTML
    مثال على الملحق WebView
    <Page loaded="pageLoaded" class="page" xmlns="http://www.nativescript.org/tns.xsd"> <ActionBar title="مستعرض الويب" class="action-bar"></ActionBar> <GridLayout rows="*,*"> <WebView src="http://www.3alampro.com" row="0"></WebView> <WebView src="<html><head><title>عالم البرمجة</title></head><body style='background-color: gray;'> <script>alert('مرحبًا بالعالم')</script> </body></html>" row="1"></WebView> </GridLayout> </Page> الناتج

    من هنا

     
    مستوى المقال: مبتدئ
  5. بسم الله الرحمن الرحيم
    السلام عليكم ورحمة الله وبركاته
    ما هو موضح مسبقًا في الدرس العاشر 
    Image هو وسم لإستعراض الصور بداخلة ( يدعم استعمال صور من رابط خارجي )
    وActivityIndicator هو عبارة عن علامة Loading ( مؤشر انتظار )


     
    خصائص الملحق ActivityIndicator
    busy اظهار مؤشر التحميل يقبل قيمتين
    true | false busyChange تشغل دالة عند تغيير قيمة busy color لون المؤشر
    خصائص الملحق Image
    src مصدر الصورة
    اما برابط محلي مثل
    "~/3alampro/logo.png"
    او برابط من مجلد resources مثل
    "res://3alampro/logo"
     او رابط خارجي مثل
    "http://www.3alampro.com/logo.png" width لعرض الصورة height لطول الصورة stretch تحديد امتداد الصورة
    none | fill | aspectFill | aspectFit
    مثال على الملحقات
    ملف main-page.xml
    <Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="onNavigatingTo"> <Page.actionBar> <ActionBar title="عالم البرمجة - الصورة ومؤشر الإنتظار"></ActionBar> </Page.actionBar> <StackLayout style="padding: 20px"> <Image src="http://www.3alampro.com/uploads/monthly_2016_03/stamp.png.2809a48c070e3d284c9dc2d6d8b46b60.png" width="240" height="120" /> <ActivityIndicator busy="true" color="gray" /> </StackLayout> </Page> الناتج

    من هنا

     
    مستوى المقال: مبتدئ
  6. بسم الله الرحمن الرحيم
    السلام عليكم ورحمة الله وبركاته
    المنزلق Slider و شريط التقدم Progress


     
    الخصائص المشتركة بين الملحقين
    maxValue اعلى قيمة في المنزلق و شريط التقدم value للحصول على القيمة ( موقع شريط التقدم او المنزلق ) او تعينها valueChange تشغيل دالة عند تغيير قيمة المنزلق او شريط التقدم color لون المزلق و شريط التقدم backgroundColor لون خلفية شريط المنزلق ولون خلفية شريط التقدم  
    خصائص الملحق Slider
    minValue اقل قيمة في المنزلق
    مثال على الملحقات
    ملف main-page.xml
    <Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="onNavigatingTo"> <Page.actionBar> <ActionBar title="عالم البرمجة - مفتاح التبديل"></ActionBar> </Page.actionBar> <StackLayout style="padding: 20px"> <Progress maxValue="100" color="orange" backgroundColor="red" value="{{ Value }}" /> <Progress maxValue="100" value="{{ Value }}" /> <Slider minValue="0" color="green" backgroundColor="black" maxValue="100" value="{{ Value }}" /> <Slider minValue="50" maxValue="150" value="{{ Value }}" /> </StackLayout> </Page> ملف main-page.js
    var Observable = require("data/observable").Observable; var page; // إنشاء الـ Observable var viewModel = new Observable(); function onNavigatingTo(args) { page = args.object; // تعريف الـ Observable ـ Value واسناد له قيمة viewModel.Value = 0; // ربط الـ Observable بالصفحة page.bindingContext = viewModel; } exports.onNavigatingTo = onNavigatingTo; الناتج
     
    من هنا

    مستوى المقال: مبتدئ
  7. بسم الله رحمن الرحيم
    السلام عليكم ورحمة الله وبركاته
    الملحق SearchBar
    SearchBar او شريط البحث كما يوحي اسمه هو شريط مخصص للبحث

     
    خصائص الملحق
    text للحصول على النص أو تعيينه hint نص للتوضيح textChange تشغيل دالة عندما يتم ادخال نص submit تشغيل دالة عند النقر على زر بحث clear تشغيل دالة عند افراغ الحقل او الضغط على زر حذف النص
    مثال على الملحق SearchBar
    ملف main-page.xml
    <Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="onNavigatingTo"> <ActionBar title="عالم البرمجة - شريط البحث"></ActionBar> <StackLayout> <!-- وسم searchbar لشريط البحث --> <!-- ربط الدالو onClear و onSubmit بالصفحة لتعمل عند الحدث --> <SearchBar id="searchBar" hint="بحث" clear="onClear" submit="onSubmit" /> <!-- وسم label ليظهر ناتج results --> <Label text="{{ results }}" textWrap="true" /> </StackLayout> </Page> ملف main-page.js
    var Observable = require("data/observable").Observable; var page; // إنشاء الـ Observable var viewModel = new Observable(); function onNavigatingTo(args) { page = args.object; // تعريف الـ Observable ـ results واسناد له قيمة viewModel.results = "الناتج"; // ربط الـ Observable بالصفحة page.bindingContext = viewModel; } function Search(args){ // جلب الـ SearchBar من نقس حدث البحث والحذف var searchbar = args.object; // اسناد النص الذي بداخل الـ SearchBar بالـ results viewModel.set('results', searchbar.text); } // عمل export للدوال لإستخدامها داخل الصفحة exports.onSubmit = Search; exports.onClear = Search; exports.onNavigatingTo = onNavigatingTo; الناتج

    من هنا

     
    الملحق Switch
    الـ Switch او مفتاح التبديل هو مفتاح تبديل يرجع يقيمتين هما false و true ويستخدم عادتًا في اعدادات التطبيق الخاص بك


    خصائص الملحق Switch
    color لون الزر checked يحتوي قيمتين هما true و false 
    true عندما يكون المفتاح مفعل
    false عندما يكون المفتاح غير مفعل style لتعيين خصائص CSS مخصصة له وحدة id لتعيين تعريف للملحق isEnabled حالة تفعيل المفتاح 
    مثال على الملحق Switch
    ملف main-page.xml
    <Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="onNavigatingTo"> <ActionBar title="عالم البرمجة - مفتاح التبديل"></ActionBar> <StackLayout> <Switch checked="{{ checked }}" /> <Button isEnabled="{{ checked }}" text="الولوج الى عالم البرمجة" /> </StackLayout> </Page> ملف main-page.js
    var Observable = require("data/observable").Observable; var page; // إنشاء الـ Observable var viewModel = new Observable(); function onNavigatingTo(args) { page = args.object; // تعريف الـ Observable ـ checked واسناد له قيمة viewModel.checked = false; // ربط الـ Observable بالصفحة page.bindingContext = viewModel; } exports.onNavigatingTo = onNavigatingTo; الناتج

    من هنا

    مستوى المقال: مبتدئ
  8. السلام عليكم ورحمة الله وبركاته..
    قبل الدخول في هذا الموضوع و عرض الأمثلة، اود ان اوجه كلمة، من السهولة تعلم أي لغة برمجية تريد ولكن لن تستطيع برمجة تطبيق جيد الا من خلال فهم كيفية عمل هذه اللغة/فريمورك او الـEnvironment لذا، من المهم جدا أن تعرف كيفية عمل كل لغة تحت الغطاء، ماذا يدور في الخفاء وكيف تشتغل.

    في هذا الدرس سنتحدث عن اركتجر الـNodeJS ومفهوم Single Threaded Event Loop
     
    الكثير من تكنولوجيات التطبيقات تعمل على Multi-Thread Model مثل JSP, Spring MVC, ASP.NET ، ولكن مع الـNodeJS الامر مختلف فتطبيق النود هو عبارة عن تطبيق ++C يتم تحويله إلى لغة الآلة عن طريق محرك للجافاسكربت يدعى V8 حيث تعمل "بيئة" التطبيق على Single Thread 
    سأفترض ان مبرمجين الـ Functional Languages على علم مسبق بمودل Multithreaded ولكن ما هو الفرق بينه وبين مودل الـNodeJS Single Thread Event Loop ؟
    ملاحظة: سنقوم باختصار Single Thread Event Loop بـSTEL 
    بلاتفورم الـNodeJS
    كما اسلفنا النود يستخدم STEL Architecture بدلا من MultiThreaded اذا كيف يتم تلبية طلبات Requests المستخدمين المختلفة بدون استخدام أكثر من Thread ، وماهو الـEvent Loop Model ؟ 
    اولا: MultiThreaded Model
    كل تطبيق ويب غير مبرمج بالنود usually يستخدم هذا المودل، العميل يرسل طلب Request بعد ذلك السيرفر يقوم بعمليات معينة بناءا على طلب العميل، يجهز الجواب Response ويرسله إلى العميل.
    هذه المودل يستخدم بروتوكول الـHTTP و بما أن هذا البروتوكول  Stateless إذا كذلك Request Response Model يعد Stateless Model
    على كل حال "الزبدة" ان هذا المودل يستخدم اكثر من Thread لتلبية طلبات العملاء، و لفهم الامر اكثر، اِطلع على الدايقرام

     
    الويب سيرفر في حالة infinite loop ينتظر الطلبات القادمة. العميل يرسل طلب للسيرفر.
    الويب سيرفر داخليا يحدد العدد المتوفر من الـThreads لتلبية طلب العميل. الطلبات تصل الى الويب سيرفر. يستقبل الطلب ، و يعينه الى احد الـThreads المتاحة من بركة الـThreads. الثرد يواصل عملية تلبية طلب العميل، يحدد ما اذا كان هناك عمليات IO ام لا بناءا على طلب العميل، يتم التلبية وإعداد الجواب Response.  يُرسل الجواب العميل. يُفرغ الـThread ويتحول من busy الى await.
     
    السيرفر يدخل في عملية infinite loop ويكرر العملية المذكورة أعلاه لكل طلب ولكل عميل، هذا يعني أن في كل طلب بإستخدام هذا المودل ينشأ Thread واذا كان هنالك عمليات IO Blocking  ( عمليات التواصل مع قواعد البيانات مثلا) وعدد طلبات اكثر من الـThreads (وهذا صحيح اغلب الاحيان) اذا مبدئيا كل الـThreads سيكونون في حالة تأهيل الجواب، العملاء الآخرين في حالة انتظار داخل صف queue لحين وجود Thread متاح، مع التنبيه انه وقبل تعيين الثرد الى عميل اخر والتحول من حالة busy الى await يجب ان يتم تفريغه لأنه يأخذ حيز من resources السيرفر مثل الرام واذا اردت ان يكون تطبيقك جيد يجب عليه ترقية السيزفر الذي يشتغل عليه التطبيق بشكل مستمر و بمبالغ باهضة
     

     
    ثانيا: اركتجر Architecture الـ NodeJS و مفهوم الـSTEL
    بلاتفورم النود لا يتبع المودل أعلاه بل يعمل على مفهوم آخر أساسه Single Thread مستوحى من تقنية الـcallback في الجافاسكربت،  يجب ان يكون لديك فكرة ولو بسيطة عن تقنية او mechanism الـcallback لفهم ما سيتم شرحه الآن.
    اساس عمليه تلبية طلبات العملاء في النود هو Event Loop و بما ان Architecture النود يتبع هذه المكانيسم، اذا يستطيع تلبية طلبات العملاء الكثيرة بشكل بسيط وسريع

     
    العميل يرسل طلب الى الويب سيرفر ويب سيرفر النود داخليا يحدد عدد الـThreads المتوفره في السيرفر. ويب سيرفر النود يستقبل الطلب ويدخله في صف Queue يسمى بالـEvent Queue. يوجد مكون في النود يدعى Event loop يسمى بذلك لانه يستخدم indefinite loop ليستقبل الطلبات ويعمل على تلبيتها. indefinite loop هو بكل بساطه عن لوب ينتظر أمر ما ليحقق true وكذلك غير معلوم تكراره،  في حالتنا الكوندشن هو الطلب Request. الـEvent Loop يستخدم Thread واحد فقط وهو الـMain Thread، مهمته ان يفحص ما اذا كان هناك طلب من العميل ام لا،  اذا وجد الطلب ادخله في الـEvent Queue واذا لم يوجد،  بقي في حالة الـindefinitely. إذا وجد طلب في Event Queue ، يبدأ بعملية التلبية. إذا كان الطلب لا يوجد به عمليات IO،  يتم إعداد الجواب Response وإرساله للعميل. إذا كان طلب العميل يوجد به عمليات IO مثل التواصل مع قواعد البيانات، يعين هذا الجزء من الطلب إلى أحد الـThreads المتوفرة التي هي أساسا في C++ Thread Pool مكونة بإستخدام مكتبة libuv،  حيث يعمل الـThread على عملية التواصل مع قاعدة البيانات. يجهز Response ويرسله مره اخرى الى الـMain Thread الذي يتواجد فيه الـEvent loop و الذي بدوره يرسله الى العميل.  
    لوهلة قد تعتقد ان وجود التواصل بين الـEvent loop و الـThread هي عملية زائدة قد تطيل من بروسس تلبية طلب العميل،  بينما في الحقيقة هذا الـThread غير تابع للنظام إنما كما وضحت تابع الى Thread Pool مبرمجة بالـ++C بإستخدام مكتبة Libuv،  بالتالي لن يكون هناك حاجة لإنشاء Thread حقيقي لكل عميل حيث تعد هذه العملية مكلفة وتذكر دائما مقولة ان "الرام مكلفة".
    العدد الافتراضي للـC++ Threads هو 4 او 5، تستطيع زيادة هذا العدد من خلال كتابة هذا السطر في الكود (x) هو العدد المطلوب
    process.env.UV_THREADPOOL_SIZE = x؛  
    وإذا اردت كذلك استخدام الطريقة الاعتيادية الى الـMulti Threading تستطيع ذلك من خلال استخدام مكتبة Cluster (موجودة مسبقا مع تثبيت النود)
    اتمنى ان وضحت الفكرة وكيفية عمل النود، جميع الاسئلة مرحب بها هنا او على التويتر AbdullaScript
    مستوى المقال: متوسط
  9. بسم الله الرحمن الرحيم
    السلام عليكم ورحمة الله وبركاته
    شرح الملحقات الأربعة التالية
    Label, Button, TextView, TextField
     
    الخصائص المشتركة بينهم
    text للحصول على النص أو تعيينه. style لتعيين خصائص CSS مخصصة له وحدة id لتعيين تعريف للملحق textAlignment لمحاذات النص
    "initial" | "left" | "center" | "right" textDecoration الخط الذي تحت النص
    "none" | "underline" | "line-through" | "underline line-through" textTransform لتحول النص ( جميع الحروف صغيرة او كبيرة )
    "initial" | "none" | "capitalize" | "uppercase" | "lowercase" whiteSpace المسافات البيضاء بين الحروف
    "initial" | "normal" | "nowrap" | رقم لتحديد المسافة isEnabled حالة تفعيل للملحق ( الكل ما عدا Label )  
    خصائص الملحق Label
    textWrap السماح للنص بالألتفاف على السطور ( الصفوف )
    خصائص الملحق Button
    tap تشغيل دالة عند النقر على الزر
    خصائص الملحقان TextView و TextField المشتركة
    hint نص للتوضيح editable قابلية الحقل لتعديل النص الذي بداخلة
    true | false keyboardType نوع لوحة المفاتيح
    datetime | phone | number | url | email returnKeyType نوع زر الرجوع ( مثل "ارسال" , "رجوع" , "اذهب" )
    done | next | go | search | send returnPress تشغيل دالة عند الضغط على زر الرجوع في لوحة المفاتيح secure حماية الحقل ( حقل مشفر )
    true | false textChange تشغيل دالة عندما يتغير النص autocorrect التصحيح التلقائي للنص
    true | false
    مثال على الملحقات
    <Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="onNavigatingTo"> <StackLayout> <Button text="مرحبًا" tap="{{ hello }}"/> <TextView text="مرحبًا بالعالم" editable="false" /> <TextView hint="عالم البرمجة" editable="true" /> <TextView hint="عالم البرمجة" text="دورة nativescript" editable="true" /> <TextField text="السلام عليكم ورحمة الله وبركاته"></TextField> <TextField hint="ادخل رقم الجوال الخاص بك" keyboardType="phone"></TextField> <TextField hint="ادخل كلمة السر" secure="true"></TextField> <Label text="الدرس الحادي عشر 11"></Label> </StackLayout> </Page> الناتج

    من هنا

     
    مستوى المقال: مبتدئ
  10. بسم الله الرحمن الرحيم
    السلام عليكم ورحمة الله وبركاته
    ملحقات واجهة المستخدم او UI Component ( كلمة UI هي اختصار لـ user interface ( واجهة المستخم ) ) هي ملحقات اصلي للجهاز مثل الازرار ومستعراض النصوص والحقل النصي
    تساعد للأدخال والاخراج من والى المستخدم
     
    الملحقات في nativescript
    Button زر  
    Label مستعرض نص  
    TextField حقل نصي للأدخال  
    TextView حقل نصي متعدد الاسطر قابل للتمدد للأدخال  
    SearchBar شريط البحث  
    Switch محول ( مشابه للمفتاح الكهربائي ) للتفعيل او الغاء التفعيل ميزة مثلًا  
    Slider منزلق لتحديد بين عدة قيم  
    Progress شريط التقدم شريط يعرض ما بين قيمتين 0% و 100%  
    ActivityIndicator مؤشر انتظار  
    Image مستعرض صور  
    ListView مستعرض لمصفوفة او لعدة حقول  
    HtmlView مستعرض يعرض ناتج اكواد HTML  
    WebView متصفح لصفحات الانترنت  
    TabView مستعرض علامات تبويب  
    SegmentedBar شريط ازرار متقسمة  
    DatePicker لتحديد تاريخ معين من شريط التاريخ  
    TimePicker لتحديد وقت معين من شريط الوقت  
    ListPicker للتحديد نص من مصفوفة نصوص  
    Dialogs نوافذ منبثقة مثل التنبيهات  
    سوف اقوم بشرحها على دفعات ان شاء الله تقريبًا كل درس سوف يشمل من ملحقين الى 4 ملحقات كا حد اقصى
    مستوى المقال: مبتدئ
  11. مستوى الصعوبة: مبتدئ-متوسّط
     
    توفّر الشبكات الاجتماعية مكتبات برمجية تسمح الاستفادة من حسابات المُستخدمين في بقيّة التطبيقات، فعوضًا عن إنشاء حساب جديد وتعبئة كافّة البيانات بشكل يدوي، يُمكن للمستخدم اختيار تسجيل الدخول في حسابه في تويتر أو فيسبوك مثلًا لتتم العملية بسهولة تامّة.
    في NodeJS هناك مكتبة Passport الشهيرة التي تسمح بإنشاء نظام تسجيل دخول اعتمادًا على حسابات المستخدم في الشبكات الاجتماعية، أو حتى يُمكن إنشاء تسجيل دخول عبر البريد. لكن في هذا الدرس سأستعرض آلية إنشاء نظام تسجيل دخول باستخدام تويتر.
    قبل الخوض في آلية العمل سأشرح الفكرة النظرية. المستخدم أولًا يقوم في فيسبوك أو تويتر، أو حتى غوغل، بإنشاء تطبيق جديد من مركز المُطوّرين Dev Center ليحصل بذلك على رقم مُعرّف خاص بتطبيقه وعلى رمز سرّي يتم إدخالهما في مكتبة Passport لإتمام عملية تسجيل الدخول.

    الخطوة الأولى بكل تأكيد هي تثبيت المكتبات وإطار عمل ExpressJS كذلك لتسهيل إنشاء الواجهات البرمجية، والمطلوب تحميل التالي:
    npm install express passport passport-twitter body-parser cookie-parser express-session --save بعدها نقوم بإنشاء برنامجنا البسيط الذي يعرض زر لتسجيل الدخول عبر تويتر بالشكل التالي
    let express = require("express") let app = express() let PORT = process.env.PORT || 3000 let passport = require("passport") let cookieParser = require("cookie-parser") let session = require('express-session') let bodyParser = require("body-parser") app.use(cookieParser("3alamPro")) app.use(bodyParser.json()) app.use(bodyParser.urlencoded({ extended: false })) app.use(session({secret: "3alamPRO2017", saveUninitialized:true, resave:true})) app.use(passport.initialize()) app.use(passport.session()) require("./configs/passport")(passport) app.get("/", (req, res)=>{ res.send("<a href='/login/twitter'> تسجيل الدخول عبر تويتر </a>") }) app.get("/login/twitter", passport.authenticate('twitter')) app.get("/login/twitter/cb", passport.authenticate('twitter',{successRedirect:"/", failureRedirect:"/loginFail"})) app.listen(PORT, ()=>{ console.log(`Up & Running ${PORT}`) }) الكود السابق بسيط فهو استدعاء لبعض المكتبات الخاصّة بإدارة الجلسات والكوكيز مع تحديد مسارات تسجيل الدخول عبر تويتر، الأول login/twitter الذي سيتوجه المتصفّح له بعد الضغط على الزر. والثاني سيُعيدنا موقع تويتر إليها وهو ما يُعرف بالـ "كول باك" CallBack.
    الآن نحتاج لكتابة ملف passport.js ضمن مُجلّد congif مثلما هو واضح في الكود أعلاه، وهو الملف الذي سيحتوي على المنطق الخاص بعملية تسجيل الدخول
    let twitterStrategy = require("passport-twitter").Strategy module.exports = function(passport){ passport.serializeUser(function(user, done) { done(null, user); }); passport.deserializeUser(function(user, done) { done(null, user); }); passport.use(new twitterStrategy({ consumerKey:"هنا المُعرّف الخاص بالتطبيق تحصل عليه من تويتر", consumerSecret:"الرمز السرّي أيضًا من موقع تويتر", callbackURL: "/login/twitter/cb", // المسار الذي سيُعيدنا موقع تويتر إليه }, function(token, tokenSecret, profile, done){ console.log(profile) // هذه بيانات المستخدم من تويتر return done(null, profile) }))// end of Twitter }//end of exports في بداية الملف قُمنا باستدعاء مكتبة تسجيل الدخول في تويتر الخاصّة بمكتبة Passport. ولو رغبت بإنشاء تسجيل دخول عبر فيسبوك فأنت بحاجة لتثبيت واستدعاء مكتبة خاصّة على الشكل 
    let facebookStrategy = require("passport-facebook").Strategy بعدها نحتاج لاستخدام Middleware خاص بالمكتبة الجديدة، وفي مثالنا هو new twitterStrategy مع إدخال بيانات التطبيق، أما الـ "كول باك" فهو سيُعيد لنا أكثر من عنصر من بينها الـ Profile الذي سيحتوي على بيانات المستخدم بعد تحويله إلى تويتر وموافقته على منح صلاحيات للتطبيق. يُمكنك تسميته ما شئت، وفي هذا المكان وعوضًا عن console.log التي استخدمتها أنا بإمكانك مثلًا الاتصال مع قاعدة بيانات لإدخال بيانات المستخدم أو تحديثها إن كانت موجودة ولاتنس شيئين هامّين: الأول هو استخدام return done لأنها تُخبر برنامجنا أن كل شيء يعمل بسلاسة، والثاني هو تعريف passport.serialize وdeserialize لمرّة واحدة فقط لأنها مسؤولة عن إدارة الجلسات الخاصّة ببيانات المستخدم.

    أخيرًا، كيف يُمكن التأكيد أن المستخدم قام بتسجيل دخول ناجح وبياناته موجودة ضمن الجلسة من عدمها؟ الطريق بسيطة جدًا فقط اختبر القيمة التالية
    if (req.isAuthenticated()) return "ok" else return "غير مُسجّل للدخول" ماذا لو أردت استخدام فيسبوك مثلًا؟ المنطق بسيط جدًا. تقوم في تعريف المسارات بإضافة التالي
    app.get("/login/facebook/", passport.authenticate('facebook')) app.get("/login/facebook/cb", passport.authenticate("facebook", {successRedirect:"/", failureRedirect:"/loginFail"})) ومثلما اتفقنا في صفحة passport تقوم بتعريف "ميدل وير" جديد على الشكل
    passport.use(new facebookStrategy({ clientID:"معرف التطبيق", clientSecret:"الرمز السرّي", callbackURL:"/login/facebook/cb", }, function(token, refreshToken, profile, done){ console.log(profile._json) return done(null, profile._json) }))// end of FB  
    بانتظار الاستفسارات لو كانت موجودة.
    مستوى المقال: مبتدئ
  12. ماهي Firebase Cloud Functions؟
    هي خدمة قدمتها Google منذ بضعة أشهر ومازالت في المرحلة التجريبية BETA ,تساعدك Cloud Functions بتنفيذ أمور معينة عند حدوث أمر معين (عند الكتابة في قاعدة البيانات Firebase Realtime Database) . بعض الأمثلة:
    إرسال الإشعارات
    تعديل بعض النصوص على سبيل المثال (تغيير كلمة “Bad” الى “Not Good” بشكل أوتوماتيكي وبدون أي تدخل منك)
    توليد الصور المصغرة “Thumbnails” بمساعدة Firebase Storage
    والكثير من الأشياء الأخرى(ألقِ نظرة على الروابط في أسفل المقال)
    نأتي الى الأمر المهم وهو كيف سنبدأ  بتجهيز بيئة العمل و نبدأ بإرسال الإشعارات .
    الFirebase Cloud Functions مبنية على لغة Javascript وعلى Framework من نوع NodeJS
    ولهذا يجب علينا أن نقوم بتحميل NodeJS من هذا الرابط ,بعد تثبيته نقوم بفتح CMD او Terminal ونكتب الأمر التالي لتثبيت أدوات Firebase
     
    npm install -g firebase-tools عند نجاح التثبيت ستظهر بهذا الشكل


    ثم نكتب الأمر لتسجيل الدخول بحساب Google واختيار مشروع Firebase
    firebase login بعد ذلك ستُفتح صفحة ويب وتطلب منك تسجيل الدخول

     
    اختر Allow



     
    تمت عملية تسجيل الدخول


     

     
    نعود الى CMD ونقوم بإنشاء مجلد جديد وثم غير مسار CMD الى هذا المجلد (او إذا كنت على ويندوز فقم بفتح المجلد واضغط على زر Shift + زر الفأرة الأيمن واختر “Open Command Window Here” )
    ثم اكتب الأمر لبدء تجهيز مشروع Cloud Functions
     
    firebase init functions


    الآن سيتم عرض كافة مشاريع Firebase الموجودة في حسابك,قم باختيار المشروع الذي تريد



    ثم اختر Y

     
    تم تجهيز الملفات

     
    الآن اذا ذهبنا الى المجلد سنجد به بعض الملفات

     
    نتوجه الى مجلد functions وسنجد داخله ملف index.js هذا هو الملف الذي سنقوم بكتابة Cloud Functions بداخله
    قم بفتحه باستخدام أي محرر أكواد,سأستخدم VSCode يمكنك تحميله من هنا
    سنجد هذه الأكواد ,نقوم بمسحها

     
    سنقوم في مثالنا هذا إرسال إشعار للمستخدم عندما يقوم أحد بمتابعته(بنفس فكرة موقع تويتر) وسنعرض إسم الشخص الذي قام بمتابعته بالإضافة الى صورته في الإشعار
    ولنفترض أنه لديك مثل هذا الترتيب في قاعدة البيانات Realtime Database
    جدول users يحتوي على جميع المستخدمين ونسمي كل مستخدم بناء على UID الخاص بFirebase Auth .
    وداخل كل مستخدم نضع التالي:
    notificationTokens photo رابط صورة المستخدم userName اسم المستخدم
     
    سنقوم بإنشاء جدول نسميه followers والذي سيحتوي على الأشخاص الذين قاموا بمتابعة هذا المستخدم
     

     
    حان وقت العمل , فلنكتب بعض الأكواد
    ولكن قبل هذا سأذكرك بأن Firebase Cloud Fucntions تعمل على مبدأ تنفيذ أمر معين عند حدوث فعل(كتابة على Firebase Realtime Database مثلاً)
    //Cloud Functions Modules const functions = require('firebase-functions'); //Firebase Admin SDK Modules (it will send the Notifications to the user) const admin = require('firebase-admin'); //init Admin SDK admin.initializeApp(functions.config().firebase); السطر الثاني نقوم بتعريف او عمل import لمكتبة Firebase Cloud Functions
    السطر الرابع نقوم بتعريف مكتبة Firebase Admin وهي المسؤولة عن إرسال الإشعارات والكتابة في قاعدة البيانات ثم نقوم بتهيئة مكتبة Admin في السطر الأخير
    exports.sendNotificationOnNewFollow = functions.database.ref('/followers/{userId}/{followerId}/').onWrite(event => { }  
    بعد ذلك نقوم بتعريف Cloud Function عبر exports.sendNotificationOnNewFollow 
    (sendNotificationOnNewFollow هو اسم الFunction يمكنك تسميته كما تشاء) ونجعلها تستمع الى الأحداث في جدول followers
    مايكتب بين هذين القوسين{} عند تعريف Cloud Function يسمى Wildcard وببساطة يعني أننا نريد الإستماع الى الأحداث داخل جدول followers داخل userId داخل followerId ,كما أنها أيضاً تعيد لنا قيمة الحدث (كuserId و followerId)
    وقد يحتوي على أي قيمة 
    كما أنه يمكنك تسميتهم بأي إسم تريد
    الصورة التالية ستوضح لك الفكرة

     
    .onWrite أي أنه عندما يتم الكتابة وهي تعيد لنا حدث event والذي يحتوي على الأمور التى كتبت او تغيرت في Realtime Database
    const userId = event.params.userId; const followerId = event.params.followerId هنا قمنا بالحصول على userId و followerId عن طريق event.params
    يجب عليك أن تكتب نفس الأسماء التى في البارامترز

    سنقوم بكتابة هذا السطر
    if (!event.data.exists()) { return; } وهذا يعني أنه اذا كانت لاتتوفر بيانات(تم عمل متابعة ثم الغاؤها فوراً) عندها قم بعمل return ولا تنفذ أي شيئ آخر
    ثم نقوم بتعريف ميثود getDeviceTokensPromise مهمتها جلب الnotificationTokens الخاصة بالمستخدم الذي تمت متابعته(“Ahmad”)
    const getDeviceTokensPromise = admin.database().ref(`users/${userId}/notificationTokens/`).once('value'); لاحظ أنه تم استخدام userId الذي عرفناه سابقاً
    ثم بنفس الطريقة نعرف ميثود أخرى تقوم بجلب اسم المستخدم وصورته
    const getFollowerInfo = admin.database().ref(`users/${followerId}/`).once('value'); هذه المرة قمنا بجلب البيانات الخاصة بناءً على followerId وهو (“Sobhe”)
    الآن سنستدعي هذه الميثودز
    في Cloud Functions يجب علينا دائما أن نعود ب Promise وهو الذي سيقول ل Cloud Functions أنه قد انتهى من التنفيذ
    return Promise.all([getDeviceTokensPromise, getFollowerInfo]).then(results => { const tokensSnapshot = results[0]; const followerSnapshot = results[1]; } داخل Promise نعطيه الميثودز الذي أنشأناها ونضعها داخل مصفوفة Array.
    .then هذه تعني عندما ينتهي من تنفيذ الميثوذز وهي تعود لنا ب Snapshot أسميناها results 
    وبما أننا قد نفذنا أكثر من ميثود فقمنا بتعريف كل Snapshot على حدى وأعطيناها المكان من Array
    ثم سنتأكد من أنه يوجد tokens أم لا عبر السطر
    if (!tokensSnapshot.hasChildren()) { return console.log('There are no notification tokens to send to.'); } واذا لم يوجد سيقوم بطبع رسالة وسيتجاهل ماتبقى من الكود
    بعد ذلك نقوم بأخذ userName و photo من followerSnapshot ونقوم بطباعتهم في log
    const followerName = followerSnapshot.val().userName; const followerPhoto = followerSnapshot.val().photo; console.log('Follower Name is: ', followerName); console.log('Follower Photo is: ', followerPhoto); ثم نقوم بتعريف Payload وهو الإشعار  الذي سنستقبله في تطبيق Android او IOS او Web (سنشرح بشكل بسيط عن طريق الأندرويد)
    وهو يحتوي على:
    title عنوان الإشعار body مانريد كتابته داخل الإشعار(التفاصيل) imgUrl صورة المستخدم (“Sobhe”) // Notification details. const payload = { data: { title: 'you have a New Follower', body: `${followerName} has followed You.`, imgUrl: `${followerPhoto}` } }; ثم نقوم بأخذ notificationTokens من tokensSnapshot وبما أنه قد يحتوي على أكثر من عنصر قمنا بوضعهم في Array
    const tokens = Object.keys(tokensSnapshot.val()); أخيراً نقوم بإرسال الإشعار عبر messaging وهي تأخذ 2 بارامتر tokens و payload
    // Send notifications to all tokens. return admin.messaging().sendToDevice(tokens, payload).then(response => { }) وتعود لنا ب response 
    ثم نقوم بتعريف مصفوفة Array فارغة,سنملؤها لاحقا بNotification Tokens الغير صالحة لنقوم بحذفهم
    const tokensToRemove = []; وأخيراً نقوم بكتابة هذه الأكواد
    response.results.forEach((result, index) => { const error = result.error; if (error) { console.error('Failure sending notification to', tokens[index], error); // Cleanup the tokens who are not registered anymore. if (error.code === 'messaging/invalid-registration-token' || error.code === 'messaging/registration-token-not-registered') { tokensToRemove.push(tokensSnapshot.ref.child(tokens[index]).remove()); } } }); return Promise.all(tokensToRemove); إذا كان هنالك خطأ سنقوم بطباعته في Log ثم نعود ب Promise لحذف الTokens الغير صالحة في حال وجودهم
    ليصبح الكود الكامل كالتالي
    //Cloud Functions Modules const functions = require('firebase-functions'); //Firebase Admin SDK Modules (it will send the Notifications to the user) const admin = require('firebase-admin'); //init Admin SDK admin.initializeApp(functions.config().firebase); exports.sendNotificationOnNewFollow = functions.database.ref('/followers/{userId}/{followerId}/').onWrite(event => { const userId = event.params.userId; const followerId = event.params.followerId // If un-follow we exit the function. if (!event.data.exists()) { return; } // Get the list of device notification tokens. const getDeviceTokensPromise = admin.database().ref(`users/${userId}/notificationTokens/`).once('value'); // Get the follower Info. const getFollowerInfo = admin.database().ref(`users/${followerId}/`).once('value'); //Execute the Functions return Promise.all([getDeviceTokensPromise, getFollowerInfo]).then(results => { const tokensSnapshot = results[0]; const followerSnapshot = results[1]; // Check if there are any device tokens. if (!tokensSnapshot.hasChildren()) { return console.log('There are no notification tokens to send to.'); } const followerName = followerSnapshot.val().userName; const followerPhoto = followerSnapshot.val().photo; console.log('Follower Name is: ', followerName); console.log('Follower Photo is: ', followerPhoto); // Notification details. const payload = { data: { title: 'you have a New Follower', body: `${followerName} has followed You.`, imgUrl: `${followerPhoto}` } }; // Listing all tokens. const tokens = Object.keys(tokensSnapshot.val()); // Send notifications to all tokens. return admin.messaging().sendToDevice(tokens, payload).then(response => { // For each message check if there was an error. const tokensToRemove = []; response.results.forEach((result, index) => { const error = result.error; if (error) { console.error('Failure sending notification to', tokens[index], error); // Cleanup the tokens who are not registered anymore. if (error.code === 'messaging/invalid-registration-token' || error.code === 'messaging/registration-token-not-registered') { tokensToRemove.push(tokensSnapshot.ref.child(tokens[index]).remove()); } } }); return Promise.all(tokensToRemove); }); }); });  
    نقوم بحفظ الملف Ctrl + S ونعود مرة أخرى الى CMD ونتوجه الى مجلد المشروع
    ونكتب الأمر
    firebase deploy --only functions سيتم بدء رفع الملفات الى Firebase وقد تأخذ العملية بعض الوقت

     
    عند الإنتهاء نذهب الى Firebase Console الى Functions وستجد ظهور Cloud Function الذي أنشأتها
     

     
    سنتوجه الآن الى Android Studio بشكل سريع لترى كيف يتم تنفيذ الإشعار (يمكنك تحميل السورس كود وتعديله كما تشاء)
    نقوم بإنشاء كلاس جديد نسميه MyFCMService والذي سيقوم بتلقى الإشعارات من Cloud Functions ونجعله extends FirebaseMessaginService ولاننسَ أن نقوم بتشغيله من MainActivity
    ثم نقوم بعمل Override لميثود onMessageReceived والتى تعود لنا ب remoteMessage
    ثم نقوم بتعريف title و body و imgUrl ونلاحظ أنه يجب علينا كتابة نفس الإسم المكتوب في Payload الذي كتبناه في Cloud Functions

     
    ثم استدعينا ميثود سنقوم بإنشاءها وهي sendNotification وتأخذ المتغيرات الثلاثة ك بارامترز
    ثم نقوم بتعريف هذه الميثود,وهي ميثود بسيطة تقوم بأخذ البارامترز وعرضهم في Notification
    ونلاحظ أنه قد وضعنا صورة الشخص عبر setLargeIcon وقمنا باستدعاء ميثود getProfilePhotoAsBitmap
    والتي تأخذ imgUrl ك بارامتر
    private void sendNotification(String title, String messageBody, String imgUrl) { Intent intent = new Intent(this, MyFCMService.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* FRequest code */, intent, PendingIntent.FLAG_ONE_SHOT); Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) .setSmallIcon(android.R.drawable.star_on) .setLargeIcon(getProfilePhotoAsBitmap(imgUrl)) .setContentTitle(title) .setContentText(messageBody) .setAutoCancel(true) .setSound(defaultSoundUri) .setContentIntent(pendingIntent); NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.notify(0 /* ID of notification */, notificationBuilder.build()); } أخيراً نقوم بإنشاء الميثود getProfilePhotoAsBitmap وهي ميثود تقوم بأخذ الرابط وتقوم بتحميل الصورة وإرجاعها ك Bitmap لعرضها في Notification,ولهذا قمنا بالإستعانة بمكتبة Glide المختصة بعرض الصور
    private Bitmap getProfilePhotoAsBitmap(String url) { Bitmap bitmap = null; try { bitmap = Glide.with(this).load(url).asBitmap().into(168, 168).get(); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } return bitmap; } حان وقت التجربة  :]
    نقوم بتشغيل تطبيق الأندرويد ثم نجعل “Sobhe” يعمل Follow ل “Ahmad” (يمكنك ادخالهم بشكل يدوي من Firebase Console كما فعلت على سبيل التجربة!)

     
    رابط المشروع على Github
    ملاحظة:
    قد تم إرفاق ملف fcm-cloud-functions-export-Data Structure وهو ملف يحتوي على قاعدة البيانات البسيطة الذي أنشأناها,يمكنك استيرادها  من Realtime Database عبر خيار Import Backup (لا تنسَ عمل نسخ احتياطي لقاعدة البيانات لديك قبل تنفيذ عملية الاستيراد)
     
    بعض المصادر التي قد تهمك
    1,2,3
    مستوى المقال: مبتدئ

عالم البرمجة

عالم البرمجة مقالات برمجة و دورات مجانية لإحتراف البرمجة هدفنا تبسيط البرمجة ونشرها بيد الكل بشكل ممتع ومتطور ومحدث بإستمرار لمواكبة جديد تطورات البرمجة الحديثة و المتقدمة بدون مقابل