1. ماهي 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
    مستوى المقال: مبتدئ
  2. يسعى المُبرمج دائمًا لاختبار تطبيقاته بصورة مُكثّفة وبأكثر من حالة قبل إطلاقها بشكل رسمي. إلا أن هذا لا يعني أن المشاكل لن تحدث، وهنا يأتي دور فريق العمل الذي يحتاج لمراقبة كل شيء عن كثب لتقديم تجربة استخدام مُميّزة، وإلا سيُغادر المستخدم لو وجد الخطأ يتكرّر على فترة زمنية طويلة.
    في إطار عمل ExpressJS هناك أكثر من طريقة لمراقبة الطلبات والأخطاء الحاصلة في التطبيق، منها مكتبة Morgan التي تقوم بكتابة كل طلب وارد في الكونسول Console،  وبالتالي يُمكن معرفة الطلبات الناجحة وتلك التي عادت بـ 404، دلالة على أن المستخدم طلب صفحة غير موجودة. في هذه الحالة يُمكن للقائمين على التطبيق تحليل الطلب ومعرفة فيما إذا كان هناك خطأ بالفعل للتخلّص منه.
    استخدام المكتبة بسيط جدًا يبدأ بتثبيتها
    npm install morgan --save بعدها وفي الملف الرئيسي للتطبيق يتم استخدامها بالطريقة الآتية
    let morgan = require("morgan") app.use(morgan('combined')) وبعد إتمام تلك الخطوات يُمكن مراقبة الكونسول Console وستظهر الطلبات الواردة بهذا الشكل 

    ويُمكن بالمناسبة تغيير combined إلى dev أو tiny لتتغير البيانات التي يتم تدوينها في الكونسول.
    ماذا عن الأخطاء الأُخرى الحاصلة نتيجة لـ Throw؟ هل سيقضي المُبرمج وقته مُراقبًا ملفات الـ Log أو الكونسول طوال الوقت؟ بكل تأكيد لا لأن أداة AirBrake تأتي لمعالجة هذه المشكلة.
    تقوم هذه الأداة برصد الأخطاء ومن ثم إرسال تنبيه للمستخدم عبر بريده بشكل فوري، أو عبر حسابات فريق العمل في تطبيق سلاك مثلًا، وبالتالي يضمن المُبرمج أن يطّلع أولًا بأول على المشاكل الحاصلة.
    طريقة الاستخدام سهلة أيضًا ومُباشرة، تبدأ بالتوجه لموقع الأداة AirBrake.io ثم تسجيل حساب مجاني وإنشاء تطبيق جديد للحصول على مفتاح خاص. ثم يتم تثبيت مكتبة الأداة بالشكل التالي:
    npm install airbrake-js --save في الملف الرئيسي للتطبيق نقوم بالآتي
    let airBrake = require('airbrake-js') let ErrorHandler = require('./node_modules/airbrake-js/dist/instrumentation/express') // إنشاء كائن جديد let airBrakeClient = new airBrake({ projectId: 00000, //يتم استبدال الأصفار بالمعرف الخاص الذي ستحصل عليه بعد التسجيل في الموقع projectKey: 'MYKEY' // يتم استبدال المفتاح هذا بالمفتاح الذي يقوم الموقع بتوليده لك }) أخيرًا وقبل الـ Listen يتم إضافة السطر الآتي
    app.use(ErrorHandler(airBrakeClient)); في هذه الحالة وعند حدوث أي خطأ سيتم التبليغ عبر لوحة تحكّم الموقع أولًا كما هو موضّح في الصورة. مع إمكانية ربط الحساب مع حسابات أُخرى مثلما ذكرت سابقًا لتسهيل التنبيه على الأخطاء.
    مستوى المقال: مبتدئ
  3. بسم الله الرحمن الرحيم
    السلام عليكم ورحمة الله وبركاته
    التخطيط المرن او flex layout كما يوحي اسمه فهو تخطيط يعطيك مرونة اكثر في تخطيط الواجهه
    فهو يستخدم نفس الـ flex في CSS لتخطيط الواجهه
     
    خصائص التخطيط
     
    flexDirection لتحديد اتجاه المرونة للتخطيط يقبل القيم التالية
    row ( افتراضيًا ) تخطيط صفوف row-reverse تخطيط صفوف معكوس ( من اخر عنصر الى اول عنصر ) column تخطيط اعمدة column-reverse تخطيط اعمدة معكوس ( من اخر عنصر الى اول عنصر ) flexWrap التفاف العناصر على السطور
    nowrap (افتراضيًا) جميع العناصر تكون في سطر واحد wrap التفاف العناصر على عدة اسطر ( مماثل لتخطيط WrapLayout ) عندما يمتلى السطر ولا توجد مساحة للعنصر التالي فاينزل العنصر تلقائيًا من الاعلى الى الاسفل wrap-reverse مماثل لـ Wrap , الاختلاف : عندما يمتلئ السطر ولاتجود مساحة للعنصر التالي فايرتفع العنصر تلقائيًا للأعلى من الاسفل الى الأعلى
    justifyContent للتحكم بمحاذات العناصر داخل التخطيط
    flex-start (افتراضيًا) في بداية التخطيط flex-end في نهاية التخطيط center في منتصف التخطيط space-between اول عنصر يكون في بداية التخطيط واخر عنصر يكون في نهاية التخطيط وباقي العناصر يتم تقسم المساحة عليها بالتساوي space-around يتم توزيع المساحة بالتساوي على جميع العناصر مع مساحة على الأطراف ( المسافة التي بينهم غير متساوية ) space-evenly يتم توزيع المساحة بحيث يتباعد كل عنصرين (مع المساحة على الأطراف) بالتساوي ( المسافة الفاصلة بينهم متساوية )
    alignItems محاذات العناصر على المحور
    flex-start ببداية المحور flex-end بنهاية المحور center بمنتصف المحور baseline محاذات العناصر بالنصوص ( للأسف لا تستطيع إستخدامها في nativescript ) stretch ( افتراضيًا ) كامل ارتفاع التخطيط
    alignContent لمحاذات الاسطر داخل التخطيط
    flex-start جميع الاسطر تكون في بداية التخطيط flex-end جميع الأسطر تكون في نهاية التخطيط center جميع الأسطر تكون في منتصف التخطيط space-between اول سطر يكون في بداية التخطيط واخر سطر يكون في منتصف التخطيط وباقي المسافات الفارغة في التخطيط بين الأسطر يتم تقسيمها بالتساوي space-around يتم تقسيم المسافة الفارغة في التخطيط بالتساوي على جميع الاسطر والحواف stretch ( افتراضيًا ) الأسطر تمتد لتاخذ كامل المساحة الفارغة  
    خصائص عناصر التخطيط

    order اولوية ترتيب العنصر في التخطيط  ( افتراضيًا يتم توزيع العناصر بترتيبها في التخطيط )

    flexGrow  ( افتراضيًا هو 1 ) يعرف التخطيط بان العنصر ينموا
    flexShrink  ( افتراضيًا هو 1 ) يعرف التخطيط بان العنصر يتقلص

    alignSelf ( افتراضيًا هي تتبع align-items المعرف في التخطيط ) تقوم بإضافة محاذاه للعنصر نفسه في التخطيط ( للتنويه استخدامها محدود مع نظام IOS )
    flexWrapBefore تقبل true و false ( افتراضيًا هي false ) تقوم بالسماح للعنصر بالألتفاف داخل التخطيط
     
    مثال على التخطيط
    <Page xmlns="http://schemas.nativescript.org/tns.xsd"> <FlexboxLayout flexDirection="column"> <!-- Reverse the natural flow of items --> <FlexboxLayout height="22" flexDirection="row-reverse"> <!-- Use even flexGrow to achieve uniform grid --> <Label text="1" backgroundColor="#EEEEEE" flexGrow="1" /> <Label text="2" backgroundColor="#DDDDDD" flexGrow="1" /> <Label text="3" backgroundColor="#CCCCCC" flexGrow="1" /> <Label text="4" backgroundColor="#BBBBBB" flexGrow="1" /> <Label text="5" backgroundColor="#AAAAAA" flexGrow="1" /> <Label text="6" backgroundColor="#999999" flexGrow="1" /> </FlexboxLayout> <!-- Set flexGrow to accomodate into any extra space --> <ScrollView flexGrow="1"> <!-- Wrap excessive items on new lines --> <FlexboxLayout flexWrap="wrap" alignContent="flex-start"> <Label textWrap="true" text="أبجد هوز حطي كلمن سعفص قرشت ثخذ ضظغ." /> <!-- Use flexWrapBefore to control explicit line wrapping --> <Label text="Gihub issue labels:" flexWrapBefore="true" fontSize="11" /> <Label text="S: High" flexWrapBefore="true" borderRadius="5" backgroundColor="red" margin="2" /> <Label text="T: Feature" borderRadius="5" backgroundColor="green" margin="2"/> <Label text="3 - In Progress" borderRadius="5" backgroundColor="gray" margin="2"/> <Label text="S: Ullamcorper" borderRadius="5" backgroundColor="red" margin="2" /> <Label text="T: Vulputate" borderRadius="5" backgroundColor="green" margin="2"/> <Label text="2 - Egestas magna" borderRadius="5" backgroundColor="gray" margin="2"/> <Label text="S: High" borderRadius="5" backgroundColor="red" margin="2" /> <Label text="T: Phasellus" borderRadius="5" backgroundColor="green" margin="2"/> <Label text="عالم البرمجة" borderRadius="5" backgroundColor="gray" margin="2" order="2"/> <Label flexWrapBefore="true" textWrap="true" text="أبجد هوز حطي كلمن سعفص قرشت ثخذ ضظغ أبجد هوز حطي كلمن سعفص قرشت ثخذ ضظغ أبجد هوز حطي كلمن سعفص قرشت ثخذ ضظغ أبجد هوز حطي كلمن سعفص قرشت ثخذ ضظغ." id="description" /> </FlexboxLayout> </ScrollView> <FlexboxLayout> <Button text="&lt;" width="50" backgroundColor="gray" /> <!-- Set flexGrow to 1 accomodate into any extra space --> <Button text="إعادة النشاط" flexGrow="1" backgroundColor="darkgray" /> <Button text="&gt;" width="50" backgroundColor="gray" /> </FlexboxLayout> </FlexboxLayout> </Page>
    مصدر A Complete Guide to Flexbox
    مستوى المقال: مبتدئ
  4. بسم الله الرحمن الرحيم
    السلام عليكم ورحمة الله وبركاته
    هذا التخطيط يقوم بترتيب العناصر حسب المساحة المتوفرة ويبدًا صف جديد عندما تكون المساحة غير كافية للعنصر
     
    خصائص التخطيط
    orientation التوجية وتقبل قيمتين هما horizontally أفقيا و vertically رأسيا افتراضيًا هو horizontally وitemWidth يستخدم لتحديد عرض العنصر في التخطيط وitemHeight يستخدم لتحديد ارتفاع العنصر في التخطيط
    مثال على التخطيط
    <Page xmlns="http://schemas.nativescript.org/tns.xsd"> <WrapLayout orientation="horizontal" width="210" height="210" style.backgroundColor="lightgray"> <Button text="تخطيط" width="70" height="70" backgroundColor="red"/> <Button text="الدوران" width="70" height="70" backgroundColor="green"/> <Button text="عالم" width="70" height="70" backgroundColor="blue"/> <Button text="البرمجة" width="70" height="70" backgroundColor="yellow"/> </WrapLayout> </Page>
    بإستخدام خاصية orientation راسي vertical
    <Page xmlns="http://schemas.nativescript.org/tns.xsd"> <WrapLayout orientation="vertical" width="210" height="210" style.backgroundColor="lightgray"> <Button text="تخطيط" width="70" height="70" backgroundColor="red"/> <Button text="الدوران" width="70" height="70" backgroundColor="green"/> <Button text="عالم" width="70" height="70" backgroundColor="blue"/> <Button text="البرمجة" width="70" height="70" backgroundColor="yellow"/> </WrapLayout> </Page>
    بإستخدام itemWidth و itemHeight

    <Page xmlns="http://schemas.nativescript.org/tns.xsd"> <WrapLayout orientation="vertical" itemWidth="50" itemHeight="50" width="210" height="210" style.backgroundColor="lightgray"> <Button text="تخطيط" width="70" height="70" backgroundColor="red"/> <Button text="الدوران" width="70" height="70" backgroundColor="green"/> <Button text="عالم" width="70" height="70" backgroundColor="blue"/> <Button text="البرمجة" width="70" height="70" backgroundColor="yellow"/> </WrapLayout> </Page>
    مستوى المقال: مبتدئ
  5. بسم الله الرحمن الرحيم
    السلام عليكم ورحمة الله وبركاته
    هذا التخطيط يرتب عناصرة أفقيا ( horizontally ) أو رأسيا ( vertically ) . يتم تعيين الاتجاه مع خاصية التوجه ( orientation ) .
     
     الخصائص الخاصة بالتخطيط هي واحده
    orientation التوجية وتقبل قيمتين هما horizontally أفقيا و vertically رأسيا افتراضيًا هو horizontally مثال على التخطيط
    <Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="onNavigatingTo"> <StackLayout> <StackLayout> <Button text="مرحبًا في دورة nativescript" /> <Button text="من" /> <Button text="عالم البرمجة" /> </StackLayout> <StackLayout orientation="horizontal"> <Button text="مرحبًا في دورة nativescript" /> <Button text="من" /> <Button text="عالم البرمجة" /> </StackLayout> </StackLayout> </Page>
    مستوى المقال: مبتدئ
  6. بسم الله الرحمن الرحيم
    السلام عليكم ورحمة الله وبركاته
    الـ GridLayout او التخطيط الشبكي 
    يحدد هذا التخطيط منطقة تخطيط مستطيلة تتكون من أعمدة ( columns ) وصفوف ( rows )
    من ثم تقوم بتحديد لكل عنصر صف ( row ) وعمود ( col )
     
    يتطلب خاصيتين ليعمل هما 
    rows ( يقبل نص للتخطيط ) لتحديد تخطيط الصف
    و columns ( يقبل نص للتخطيط ) لتحديد تخطيط الأعمدة
     
    ويكتسب عناصرة ( اطفاله ) اربع خصائص هم
    row ( يقبل رقم ) لتحديد العمود الخاص بالعنصر يبدا من 0
    وcol ( يقبل رقم ) لتحديد الصف الخاص بالعنصر يبدا من 0
    وrowSpan ( يقبل رقم ) لتحديد امتداد العنصر في الصفوف ( لصف او صفين او ثلاثة الخ... ) يبدا من 1
    وcolSpan ( يقبل رقم ) لتحديد امتداد العنصر في الأعمدة يبدا من 1
     
    ماهو نص التخطيط وكيف اكتبة؟
    نص التخطيط ( تسمية للتوضيح سوف استخدمها لكامل الدورة ) هو نص تستطيع من خلالة تحديد المساحة المخصصة لكل عمود او صف وعددها
    وتفصل بين كل عمود او صف بعلامة فاصلة " , " 
     
    عناصر تحديد المسافةلنص التخطيط
    قيمة مطلقة حجم ثابت من وحدة البكسل ( pixel ).
    نجمة [*] ياخذ مساحة كبيرة من المتاحة له
    ومن خصائصة عند جعل جميع الأعمدة منه ( نجوم [*] ) يقوم بتقسم المساحة بالتساوي على الأعمدة.
    auto (تلقائي) يقوم باخذ المساحة التي يحتاجها العنصر.
     
    افتراضيًا عند عدم تحديد الصفوف و الأعمدة لتخطيط الشبكة يكون التخطيط عمود واحد نجمة وصف واحد نجمة.
     
    امثلةعلى نص التخطيط
    <GridLayout columns="50, *, auto"> يقوم بإنشاء GridLayout بثلاث اعندة العمود الاول مساحتة 50 بيكسل ( Pixel ) ثابت
    والعمود الثاني ياخذ المساحة المتبقية 
    والعمود الثالث ياخذ المساحة التي تحتاجها عناصر العمود
    ونفس الامر على الأعمدة بنفس المنوال
     
    مثال على التخطيط
    <Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="onNavigatingTo" class="page"> <GridLayout rows="auto" columns="175,*,auto" rows="*,*"> <Button row="0" rowSpan="2" col="0" text="عالم البرمجة" /> <Label row="0" col="2" text="تخطيط الشبكة" textWrap="true" /> <Label row="0" col="1" text="WhatsApp" /> <Button row="1" col="1" text="install" /> </GridLayout> </Page>
    مستوى المقال: مبتدئ
  7. بسم الله  الرحمن الرحيم
    السلام عليكم ورحمة الله وبركاته
    التخطيط الرصيفي او الـ DockLayout هو تخطيط يقوم برصف جميع العناصر على حدود التخطيط وياخذ ماتبقى من المسافة ويضع العناصر الاخيرة فيها
    مع تحديد الأتجاه لكل عنصر بالخاصة dock
    left يسار
    right يمين
    top اعلى
    bottom اسفل
     
    الخاصية stretchLastChild تقبل القيمة true او false وتختص بإمتداد العنصر الأخير ام لا ( بشكل افتراضي هي true )
     
    مثال على التخطيط
    <Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="onNavigatingTo"> <DockLayout stretchLastChild="true"> <Button dock="left" text="يسار" backgroundColor="red" /> <Button dock="top" text="اعلى" backgroundColor="green" /> <Button dock="right" text="يمين" backgroundColor="yellow" /> <Button dock="bottom" text="يسار" backgroundColor="blue" /> <Button text="المتبقي" backgroundColor="orange" /> <Button text="عالم البرمجة" backgroundColor="orange" /> </DockLayout> </Page>  

     
    بدون امتداد العنصر الأخير
    <Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="onNavigatingTo"> <DockLayout stretchLastChild="false"> <Button dock="left" text="يسار" backgroundColor="red" /> <Button dock="top" text="اعلى" backgroundColor="green" /> <Button dock="right" text="يمين" backgroundColor="yellow" /> <Button dock="bottom" text="يسار" backgroundColor="blue" /> <Button text="المتبقي" backgroundColor="orange" /> <Button text="عالم البرمجة" backgroundColor="orange" /> </DockLayout> </Page>
     
    مستوى المقال: مبتدئ
  8. بسم الله  الرحمن الرحيم
    السلام عليكم ورحمة الله وبركاته
    التخطيط العائم او absolute layout في الـ nativescript هو تخطيط يتطلب منك تحديد لكل عنصر من عناصرة قيمة top ( الاعلى ) وقيمة left ( اليسار )
    لتحديد مكان إبتعاد العنصر في التخطيط
     
    مثال على التخطيط
    <Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="onNavigatingTo"> <AbsoluteLayout> <Button text="دورة nativescript" top="10" left="10" backgroundColor="red" width="200" /> <Button text="في" top="40" left="40" backgroundColor="yellow" width="200" /> <Button text="عالم البرمجة" top="70" left="70" backgroundColor="blue" width="200" /> </AbsoluteLayout> </Page>  

     
     
    مستوى المقال: مبتدئ
  9. بسم الله الرحمن الرحيم
    السلام عليكم ورحمة الله وبركاته
    الـ layouts او نظام التخطيط في nativescript ينقسم الى 6 انظمة تخطيط مشابه لـ display في CSS
    مهمتها تخطيط واجهة التطبيق مثل وضع النص على اليمين والزر على اليسار ويكون تحته مثلًا صورة الخ...
     
    الـ layouts 
    AbsoluteLayout
    انتقل للشرح
    في هذا التخطيط تحتاج لتحديد لكل عنصر قيمة top وقيمة left DockLayout
    انتقل للشرح
    هذا التخطيط يرتب عناصرة على حوافه الخارجية ويسمح للعنصر الأخير بتولي المساحة المتبقية في المنتصف GridLayout
    انتقل للشرح
    يحدد هذا التخطيط منطقة تخطيط مستطيلة تتكون من أعمدة ( columns ) وصفوف ( rows )
    من ثم تقوم بتحديد لكل عنصر الصف ( row ) والعمود ( col )
    StackLayout
    انتقل للشرح

    هذا التخطيط يرتب عناصرة أفقيا ( horizontally ) أو رأسيا ( vertically ) . يتم تعيين الاتجاه مع خاصية التوجه ( orientation ) . WrapLayout
    انتقل للشرح
    هذا التخطيط يقوم بترتيب العناصر حسب المساحة المتوفرة ويبدًا صف جديد عندما تكون المساحة غير كافية للعنصر
     

    FlexboxLayout
    انتقل للشرح
    هذا التخطيط هو تنفيذ غير مطابقة لتخطيط Flexible Box Layout في CSS
     
    تم شرح جميع الـ layouts في الدروس بالروابط على اليمين
    مستوى المقال: مبتدئ
  10. ماهو الcallback hell،  جحيم الكولباك؟
    لا يوجد شيء مميز في الجافا سكربت يسمى بجحيم الكولباك ما هو الا تسميه لأمر ما قد يقع فيه المطور وهو طريقة كتابة كود حيث يحتوي على عدة دوال Asynchronous مترابطة مع بعضها، واحدة داخل الاخرى،  مثال:
     
    getData1(function(x){ getData2(x, function(y){ getData3(y, function(z){ ... }); }); });  
    والسبب في ذلك يعود لأن ناتج "results" كل دالة يعتمد على ما سبقتها.
     
    ملاحظة: كلمة asynchronous تعني أمر يحدث لاحقا << لا تركز على هالشي مو موضوعنا.
     
    جحيم الكولباك أمر ليس بجيد للكود، خصوصا في الحالات المعقدة التي تكون فيها دوال كثيرة مربوطه مع بعضها البعض وهنا قد يؤخر وقت انتهاء من التنفيذ و كمطور انت لا تريد ان تقع في هذه المشكلة, لحل مشكلة جحيم الكولباك، يستخدم المطورون إحدى مكتبات البرومس promises "الوعد، الوعود"، فالوعد دالة إما أن يتم العمل به fulfilled مع اظهار نتيجة الوعد value او رده rejected مع إظهار السبب error، وقد يكون الكود يحتوي على أكثر من وعد، واحد تلو الآخر.
     

     
    شخصيا لا اعتمد على الوعود في حل مشكلة جحيم الكولباك، عند التعامل مع قواعد البيانات MySQL رغم أنها الطريقة المثلى ولكن تتطلب الكثير مع الكود "وجهة نظر" و قد تواجهك مشاكل في أداء performance التطبيق بعد ذلك, استخدم طريقة أكثر بدائية و فعالة "تفي بالغرض" ، مبدأ الفكرة و طريقة عملها تتشابه كثيرا مع مبدأ عمل promises ولكن بكود أقل، سأرفق الطريقة مع المثال بالأسفل.
     
    للتعرف أكثر على جحيم الكولباك، سأعرض لكم مثال على التعامل مع قاعدة البيانات MySQL بالـ NodeJS وعمل اكثر من query وناتج كل query يعتمد على كل سبقتها.
    var express = require("express"); var app = express(); var mysql = require('mysql'); var conn = mysql.createConnection({ host: 'localhost', user: 'me', password: 'secret', database: 'my_db' }); conn.connect(); // GET method route app.get('/user/posts', function (req, res) { /* http://www.example.com/user/posts?userId=26 */ var userId = req.query.userId; conn.query('SELECT username FROM users WHERE ID = ?', [userId], function (error, results, fields) { if (error) throw error; if (results.length) { var username = results[0].username; conn.query('SELECT * FROM posts WHERE post_by = ?', [username], function (error, posts, fields) { if (error) throw error; if (posts.length) { conn.query('SELECT * FROM users_addresses WHERE username = ?', [username], function (error, address, fields) { if (error) throw error; if (statistics.length) { res.json(200, { posts: posts, address: address }); } else { res.json(200, { error: 'no statistics found' }); } }); } else { res.json(200, { error: 'no posts found' }); } }); } else { res.json(200, { error: 'no user found by ID 26' }); } }); });  
    للتخلص على مثل هذه المشاكل في حالة التعامل مع MySQL / MariaDB يكون بإسناد results كل عملية query في دالة مثلا
    var express = require("express"); var app = express(); var mysql = require('mysql'); var conn = mysql.createConnection({ host: 'localhost', user: 'me', password: 'secret', database: 'my_db' }); conn.connect(); // GET method route app.get('/user/posts', function (req, res) { /* http://www.example.com/user/posts?userId=26 */ var userId = req.query.userId; conn.query('SELECT username FROM users WHERE ID = ?', [userId], function (error, results, fields) { if (error) throw error; if (results.length) { setUsername(results[0].username); } else { res.json(200, { error: 'no user found by ID 26' }); } }); function setUsername(user) { var username = user; conn.query('SELECT * FROM posts WHERE post_by = ?', [username], function (error, posts, fields) { if (error) throw error; if (posts.length) { setPosts(posts, username); } else { res.json(200, { error: 'no posts found' }); } }); } function setPosts(po, user) { var posts = po; // po is the result of the prev query and it is an object it could be length of 1 or more doesn't matter var username = user; conn.query('SELECT * FROM users_addresses WHERE username = ?', [username], function (error, address, fields) { if (error) throw error; if (statistics.length) { res.json(200, { username: username, posts: posts, address: address }); } else { res.json(200, { error: 'no statistics found' }); } }); } });  
    أو بطريقة اخرى وهي عمل execution لكل query بطريقة parallel او series  بإستخدام مكتبة async
    /* Make Sure To Install async npm install async --save */ var async = require(“async”); // Parallel Method async.parallel([ wait5SecondsAndReturn1, wait5SecondsAndReturn2 ], function (err, results){ //after 5 seconds, results will be [1, 2] }); function wait5SecondsAndReturn1(callback) { setTimeout(function(){ callback(null, 1); }, 5000); } function wait5SecondsAndReturn2(callback) { setTimeout(function(){ callback(null, 2); }, 5000); } //Series Method async.series([ wait5SecondsAndReturn1, wait5SecondsAndReturn2 ], function (err, results){ //after 10 seconds, results will be [1, 2] }); function wait5SecondsAndReturn1(callback) { setTimeout(function(){ callback(null, 1); }, 5000); } function wait5SecondsAndReturn2(callback) { setTimeout(function(){ callback(null, 2); }, 5000); } // Another one async.series([ function(callback) { setTimeout(function() { console.log(“Task 1”); callback(null, 1); }, 300); }, function(callback) { setTimeout(function() { console.log(“Task 2”); callback(null, 2); }, 200); }, function(callback) { setTimeout(function() { console.log(“Task 3“); callback(null, 3); }, 100); } ], function(error, results) { console.log(results); });  
    وبذلك تستطيع التخلص من جحيم الكولباك بدون promises التي يلقى الكثير من المبرمجين المبتدأئين صعوبة في التعامل مع هذا النوع من الدوال.
     
    ملاحظات حول المثال السابق:
    الأخذ بالاعتبار جميع الشروط من ان هذا المستخدم user لدية احصائية مسجله و بوستات, ان لم يكن تستطيع التخلص من الشرط if(posts.length) لا يهم , بالتالي سيتم تمرير اوبجكت بدون قيم. بعض المبرمجين قد لا يتفق مع العملية, احترم ذلك ولكنها تفي بالغرض مع ES2015 و مايليه .. اسناد اوبجكت الى متغير يعطيه نفس قيمه الاوبجكت بشكل مباشر.
    مستوى المقال: مبتدئ
  11. لا يخفى على الجميع تفوق لغة الجافاسكربت في الاونه الاخيره " منذ مدة في الحقيقة " بعد ظهور كمية كبيرة وضخمه جدا من المكتبات و أُطُر العمل libraries/frameworks، هذا التضخم و النمو السريع في لغة الجافاسكربت سببت ارباك لكثير من المبرمجين الذين أصبحوا لا يعرفون من اين البداية لدراسة هذه الفريمووركس واين النهايه خصوصا مع وجود تشابه مابينها بسبب المنافسة، على سبيل المثال:
    AngularJS VueJS ReactJS  
    حيث يعد الإثنين أطر عمل Frontend، ومع التطور ظهر أطر عمل مساعدة لاطار عمل اساسي (ان صح التعبير بالعربي) "بطتنا بطت بطن بطتكم" مثل فريمورك ExpressJS المساعد لل Runtime Environment NodeJS حيث يعمل الإثنين في السيرفر وليس جهة المستخدم، وامثلة كثيرة اخرى لا يسعنا التطرق لهم ( يخرب بيتهم كل يوم شي جديد يطلع ويصدم اكثر من الي قبله ).

    في هذا الدرس سنقوم بعرض احد المكتبات المبرمجة بلغة الجافاسكربت "PressureJS" لعمل / لتطبيق فكرة الـ3D Touch الموجودة في اجهزة ابل الجديدة، والـ3D Touch هو عبارة عن الضغط بقوة اكثر من المعتاده على بعض الخيارات لإظهار خيارات اكثر او عمل، تفعيل event (حدث) معين.

    اولا: قم بتحميل المكتبه من خلال الضغط هنا


    او بإستخدام npm or bower.
    npm
    npm install pressure --save bower
    bower install pressure --save
    ثانيا: طرق استخدام المكتبة
    Example 1:
    <script src="/js/pressure.js"></script> <script src="/js/jquery.pressure.js"></script> Pressure.set('#id-name', { change: function(force){ this.innerHTML = force; } });

    Example 2:
    var Pressure = require('pressure'); Pressure.set('#id-name', { change: function(force){ this.innerHTML = force; } });

    كما هو موضح، التجربة ستكون على عنصر في الصفحة بدلالة الـId وهو id-name، تحتوي الدالة اعلاه على حدث event بإسم change تحمل متغير يسمى force، هذا الحدث change يستخدم لمعرفة التغير في قوة الضغط force على العنصر id-name ويكون المتغير force رقم مابين الصفر الى 1 كحد اقصى.

    ملاحظة: في المثال الذي سأطرحة حاليا، كلمة this ستنعني العنصر نفسه مثلا this.style.width
    Pressure.set('#element', { start: function(event){ }, end: function(){ // this is called on force end }, startDeepPress: function(event){ // this is called on "force click" / "deep press", aka once the force is greater than 0.5 }, endDeepPress: function(){ // this is called when the "force click" / "deep press" end }, change: function(force, event){ // this is called every time there is a change in pressure // force will always be a value from 0 to 1 on mobile and desktop }, unsupported: function(){ // NOTE: this is only called if the polyfill option is disabled! // this is called once there is a touch on the element and the device or browser does not support Force or 3D touch } });

    او عند استخدامك الـjQuery
    // Select Element By Id $('#element').pressure({ // events goes here }); // Select Element By Class $('.element').pressure({ // events goes here });
    لنستعرض الدالة اعلاة كل جزء على حدى:
    - يوجد 6 انواع من الأحداث events التي تعتمد عليها / تقدمها هذه المكتبه في كلاس Pressure وهم:
     حدث start: لمعرفة بدأ الضغط حدث end: لمعرفة انتهاء الضغط. حدث startDeepPress: اذا كانت قوة الضغط force اعلى من 0.5 وهي اعلى من نص القيمة العليا 1. حدث endDeepPress: عند الانتهاء من الضغط على العنصر وقيمة الضغط دخلت ضمن قيمة startDeepPress اي اعلى من 0.5. حدث change: وياخذ متغير force كما شرحناه بالاعلى، هذا الحدث سيستدعى دائما عند تغير قيمه الضغط مابين 0 و 1. حدث unsupported: اذا كان المتصفح او الجهاز لايدعم ال3D Touch.
    - الخيارات options، بالاضافة الى الاحداث والمتغيرات السابقة هناك ايضا متغير خارج دالة الset وهو عبارة عن اوبجكت اختياري يحتوي على عدة عناصر منها polyfill اما ان يكون true او false "يستخدم اذا كان المتصفح او الجهاز لايدعم ال3D Touch ،يكون true بشكل افتراضي.
    Pressure.set('#example', { change: function(force, event){ this.innerHTML = force; }, unsupported: function(){ alert("Oh no, this device does not support pressure."); } }, {polyfill: false});
    - المتغير الثاني في الoptions هو polyfillSpeedUp عدد ms ويستخدم لتحديد سرعة الوصول من 0 الى 1، كل 1000 تساوي ثانية واحدة.
    Pressure.set('#example', { change: function(force, event){ this.innerHTML = force; } }, {polyfillSpeedUp: 5000}); // takes 5 seconds to go from a force value of 0 to 1 // only on devices that do not support pressure  
    - المتغير polyfillSpeedDown عكس SpeedUp تماما, الوقت بالميلي سكند ms لرجوع قيمة قوة الضغط من 1 إلى 0.
    Pressure.set('#polyfill-speed-down', { change: function(force, event){ this.innerHTML = force; } }, {polyfillSpeedDown: 2000}); // takes 2 seconds to go from a force value of 1 to 0 // only on devices that do not support pressure
    - المتغير only اما ان يكون mouse, touch or pointer وهذا يعني عند الضغط على العنصر المراد تطبيق الـ3D Touch عليه اما ان يكون من خلال الفأرة فقط، الضغط بالاصبع فقط او بالبوينتر فقط.
    // فقط بالفأرة Pressure.set('#example',{ change: function(force, event){ console.log(force); }, }, {only: 'mouse'}); // فقط عند الضغط بالاصبع, اجهزة ابل Pressure.set('#example',{ change: function(force, event){ console.log(force); }, }, {only: 'touch'}); // فقط بالبوينتر ديفايسس مثل اجهزة تابلت الواكوم Pressure.set('#example',{ change: function(force, event){ console.log(force); }, }, {only: 'pointer'});
    - متغير preventSelect يعرفه الكثير بـpreventDefault. اما ان يكون true او false , قيمة المتغير تكون true افتراضيا وتستخدم لمنع العمل الافتراضي للعناصر.
    Pressure.set('#example',{ change: function(force, event){ console.log(force); }, }, {preventSelect: false});
    الجزء الأخير من PressureJS هو دالة لعمل Configuration عام تتشارك فيه جميع العناصر يحتوي على مجموعة من الخيارات التي تم شرحها في قسم Options مثلا.
    // These are the default configs set by Pressure Pressure.config({ polyfill: true, polyfillSpeedUp: 1000, polyfillSpeedDown: 0, preventSelect: true, only: null });  
    ولكن, تستطيع عمل override لهذه الاعدادات من خلال عمل دالة Pressure.set({...}); I وإسنادها إلى مجموعة عناصر او عنصر واحد بإستخدام الـId او الـClass.
    انتهى .. من لديه اسئلة يتفضل هنا او على حسابي في تويتر @AbdullaScript
    مستوى المقال: مبتدئ
  12. بسم الله الرحمن الرحيم
    السلام عليكم ورحمة الله وبركاته
    ماهو ejs ؟ هو لغة قوالب صمم ليعمل من جهة الخادم تم بناءة من قبل Matthew Eernisse
    يتيح لك إستخدام لغة javascript داخل ملفات html بشكل شبيه جدًا بالـ PHP
     
    استخدامة مع إطار Express
    اول يجب ان تقوم بإضافة ejs لمشروعك بكتابة الأمر التالي
    npm install ejs من ثم تضيف الشفرة التالية لتطبيق express الخاص بك
    app.set('view engine', 'ejs'); من ثم إضافة الملفات الى مجلد views ويجب ان ينتهي الملف بالأمتداد ejs
    ولعمل رسم للملف بإستخدام الشفرة أستخدم
    .render(اسم الملف نص, المدخلات عنصر)
    مثال
    app.get('/', function(req, res) { // مع إستبدال index بإسم الملف res.render('index', {name:"عالم البرمجة"}); }); مثال لمشروع Express
    ملفات المشروع
    . ├── app.js ├── package.json ├── package-lock.json ├── node_modules ├── public │   └── images │   ├── normal.png │   └── transparent.png └── views ├── footer.ejs ├── header.ejs └── index.ejs  
    ملف app.js
    var express = require('express'); var app = express(); app.set('view engine', 'ejs'); app.use(express.static("public")); app.get('/', function(req, res){ var images = [ 'transparent.png','normal.png' ]; res.render('index', { imgs: images, name:"عالم البرمجة" }); }); app.listen(80,function(){ console.log('[info] متصل على المنفذ 80'); });  
    ملف view/header.ejs
    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title><%= title %></title> </head> <body> <p>مرحبًا بالعالم</p> <ul> <li><%= name %></li> </ul>  
    ملف view/index.ejs
    <%- include('header', {title: "مثال عالم البرمجة"}) %> <ul> <% for(var i=0; i< 5; i++) {%> <li><%= i + 1 %></li> <% } %> </ul> <h1><%= name %></h1> <% imgs.forEach( (img) => { %> <img src="images/<%= img %>"/><br/> <% }); %> <%- include('footer') %>  
    ملف footer.ejs
    <h5>&copy; عالم البرمجة</h5> </body> </html>  
    بعد تشغيل التطبيق
    الناتج

     
    الأوسمة المخصصة لـ Ejs والدوال
    <% %> يقوم بتنفيذ شفرة الجافاسكربت التي بداخلة <%= %> يطبع النص الذي بداخلة ( يقوم بعمل html entities encode للأوسمة html ) <%- %> يقوم بطباعة النص الذي بداخلة كا HTML <%% يطبع '<%' بالنص الذي بداخلة متخطي عملية المعالجة %%> يطبع '%>' بالنص الذي بداخلة متخطي عملية المعالجة <%_ يقوم بحذف جميع المسافات الفارغة التي قبلة مع تنفيذ شفرة الجافاسكربت التي بداخلة _%> يقوم بحذف جميع المسافات الفارغة التي بعدة مع تنفيذ شفرة الجافاسكربت التي بداخلة  
    include(اسم الملف نص, المدخلات عنصر) يقوم بجلب ملف ejs بعد عملية المعالجة له باقي الدوال تجدها في ViewHelpers  
     
    صورة الغلاف منقولة من coligo.io
    مستوى المقال: مبتدئ

عالم البرمجة

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