لوحة المتصدرين
المحتوى الأكثر حصولًا على سمعة جيدة
المحتوى الأعلى تقييمًا في 08/20/21 في كل الموقع
-
2 نقاط
-
لعل السؤال "من أين أبدأ" واحد من أكثر الأسئلة شيوعا و اﻷكثر طرحا في المجال و رغم أن السؤال يطرح وحده بالنسبة لكل جديد على المجال إلا أني أظن أنه لا يجب سؤالها ابدا لأن كل لغة لها تخصص معين ولها طريقة كتابة وسياق خاص بها و لربما لها طريقة خاصة بالتعلم ! و لذلك قبل طرح هذا السؤال يجب أولا : اختيار مجال معين من مثل : تطوير مواقع الويب , تطوير تطبيقات الأندرويد , تطوير الألعاب و غيرها من المجالات . البحث عن لغات هذا المجال الأكثر شيوعا و استعمالا وافهم وظيفة كل لغة تخص هذا المجال . يفضل تعلم مبادئ البرمجة و التفكير المنطقي قبل التوغل في أي مجال . أشياء من مثل هاته يمكن إكتسابها و تعلمها عن طريق تعلم الخوارزميات و بنية البيانات . يمكنك الان تعلم لغات هذا المجال عن طريق دورات أو معاهد تكوينية . يمكنك إلقاء نظرة على دورات الأكاديمية هنا . التطبيق العملي و الاختبار و التجربة و الكثير منه . يساعدك كل من المراحل السابقة على تحديد هدفك و توضيحه أكثر و أكثر , و هو ما ستحتاج إليه على طول مسيرتك في المجال . كما أن هذا سيولد لديك الكثير من الأسئلة التي ستوسع نظرتك للمجال و تطور من ثروتك النظرية مما سيدفعك أسرع و أسرع للتطبيق العملي و الإنتاج .2 نقاط
-
var id = $(this).(val); أعتقد أن الخطأ هنا، التصحيح: var id = $(this).val(); إن لم تعمل أجزاء HTML المحدثة، عليك تعديل الاستعلام ليعيد بيانات، ثم تبنى جزء select بشكل ديناميكي بجافاسكربت: PHP ستعيد مصفوفة أي فقط بيانات.. $result = mysqli_query($con,$sql); while( $row = mysqli_fetch_array($result) ){ $user_id = $row['id']; $user_name = $row['name']; $users_arr[] = array("id" => $user_id, "name" => $user_name); } في استقبال النتيجة، نبني العناصر فا جافاسكربت: $.post({ url: 'getUsers_ids.php', data: {depart:deptid}, dataType: 'json', success:function(response){ var len = response.length; $("#sel_user").empty(); for( var i = 0; i<len; i++){ var id = response[i]['id']; var name = response[i]['name']; $("#sel_user").append("<option value='"+id+"'>"+name+"</option>"); } } });2 نقاط
-
سنوجّه اهتمامنا في هذا القسم إلى التعامل مع الواجهة الخلفية وآلية تطوير التطبيق بإضافة وظائف جديدة تنفذ هذه المرة من قبل الخادم. سنبني تلك الوظائف باستخدام NodeJS وهي بيئة تشغيل JavaScript مبنية على محرك JavaScript من تصميم Google يدعى Chrome V8. كُتبَت المادة العلمية للمنهاج باستخدام Node.js 10.18.0، وتأكد من أن النسخة المثبتة على جهازك هي على الأقل مطابقة للإصدار السابق، ويمكنك استخدام الأمر node -v للتحقق من الإصدار المثبت لديك. أشرنا سابقًا في مقال أساسيات جافاسكربت اللازمة للعمل مع React أن المتصفحات لا تدعم كامل الميزات الأحدث للغة JavaScript مباشرةً، لذلك من الضروري نقل الشيفرة التي تعمل على المتصفح إلى إصدار أقدم باستخدام babel مثلًا. لكن الأمر سيختلف تمامًا مع JavaScript التي تُنفّذ في الواجهة الخلفية، ذلك أن الإصدار الأحدث من Node.js سيدعم الغالبية العظمى من الميزات الجديدة للغة، فلا حاجة عندها للنقل. سنضيف شيفرة تتعامل مع تطبيق الملاحظات الذي تعرفنا عليه سابقًا في مقالات سابقة من هذه السلسلة لكن تنفيذها سيكون في الواجهة الخلفية. لكن علينا أولًا تعلم الأساسيات من خلال كتابة التطبيق التقليدي "Hello world". ملاحظة: لن تكون جميع التطبيقات والتمارين في هذا القسم تطبيقات React، ولن نستخدم create-react-app في تهيئة المشاريع التي تضم التطبيقات. لقد تعرفنا سابقًا في مقال إحضار البيانات من الخادم في تطبيقات React على مدير الحزم npm وهو أداة لإدارة حزم JavaScript تعود أصلًا إلى بيئة Node.js. انتقل إلى مجلد مناسب وأنشئ قالبًا لتطبيقنا مستخدمًا الأمر npm init. أجب عن الأسئلة التي تولدها الأداة، وستكون النتيجة إنشاء الملف package.json ضمن جذر المشروع يضم معلومات عنه. { "name": "backend", "version": "0.0.1", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "Matti Luukkainen", "license": "MIT" } من هذه المعلومات بالطبع -ستكون قد أدخلتها عند إجابتك عن الأسئلة- اسم المشروع ونقطة انطلاق التطبيق والتي هي الملف index.js في تطبيقنا. لنجري بعض التعديلات على محتوى الكائن scripts في الملف: { // ... "scripts": { "start": "node index.js", "test": "echo \"Error: no test specified\" && exit 1" }, // ... } لننشئ الإصدار الأول لتطبيقنا بإضافة ملف باسم index.js إلى المجلد الجذري الذي أنشأنا فيه المشروع. اكتب الأمر التالي في الملف: console.log('hello world') يمكن تشغيل التطبيق من node بكتابة التالي في سطر الأوامر: node index.js كما يمكن تشغيله كسكريبت (npm script): npm start ستعمل سكريبت npm لأننا عرفناها ضمن الكائن script في ملف package.json: { // ... "scripts": { "start": "node index.js", "test": "echo \"Error: no test specified\" && exit 1" }, // ... } سيُنفِّذ npm المشروع عند استدعاء الملف index.js من سطر الأوامر. يعتبر تشغيل التطبيق كسكريبت npm أمرًا اختياريًا. يعرّف ملف package.json افتراضيًا سكريبت npm أخرى تدعى npm test. وطالما أن المشروع لا يضم حتى الآن مكتبة للاختبارات، سينفذ npm الأمر التالي: echo "Error: no test specified" && exit 1 خادم ويب بسيط لنحوّل تطبيقنا إلى خادم ويب: const http = require('http') const app = http.createServer((req, res) => { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end('Hello World') }) const PORT = 3001 app.listen(PORT) console.log(`Server running on port ${PORT}`) ستطبع الطرفية الرسالة التالية بمجرد تشغيل التطبيق: Server running on port 3001 سنشغل تطبيقنا المتواضع بطلب عنوان الموقع http://localhost:3001 من المتصفح: سيعمل الخادم كما سبق بغض النظر عن بقية أقسام العنوان، حتى أن كتابة عنوان الموقع بالشكل http://localhost:3001/foo/bar يعطي النتيجة نفسها. ملاحظة: إن كان المنفذ 3001 محجوزًا من قبل تطبيق آخر، سينتج عن تشغيل الخادم رسالة الخطأ التالية: ➜ hello npm start > hello@1.0.0 start /Users/mluukkai/opetus/_2019fullstack-code/part3/hello > node index.js Server running on port 3001 events.js:167 throw er; // Unhandled 'error' event ^ Error: listen EADDRINUSE :::3001 at Server.setupListenHandle [as _listen2] (net.js:1330:14) at listenInCluster (net.js:1378:12) في هذه الحالة ستكون أمام خياران: إما أن تغلق التطبيق الذي يَشغُل المنفذ 3001، أو اختيار منفذ آخر. لنتأمل الآن السطر الأول من شيفرة تطبيق الخادم: const http = require('http') يُدرج التطبيق وحدة خادم الويب web server المدمجة ضمن Node. لقد تعلمنا إدراج الوحدات في شيفرة الواجهة الأمامية لكن بعبارة مختلفة قليلًا: import http from 'http' تُستعمل حاليًا وحدات ES6 ضمن شيفرة الواجهة الأمامية. وتذكّر أنّ تعريف الوحدات يكون باستعمال التعليمة export واستخدامها ضمن الشيفرة باستعمال التعليمة import. تَستعمل Node.js ما يسمى CommonJS (وحدات JavaScript المشتركة). ذلك أن بيئتها تطلبت وجود الوحدات قبل أن تدعمها JavaScript بوقت طويل. بدأت Node.js بالدعم التجريبي لوحدات ES6 مؤخّرًا فقط. لن نجد فرقًا تقريبًا بين وحدات ES6 ووحدات CommonJS، على الأقل ضمن حدود منهاجنا. ستبدو القطعة التالية من الشيفرة على النحو التالي: const app = http.createServer((request, response) => { response.writeHead(200, { 'Content-Type': 'text/plain' }) response.end('Hello World') }) تستخدم الشيفرة التابع createServer الموجود ضمن الوحدة http لإنشاء خادم ويب جديد. تعرّف الشيفرة بعد ذلك معالج حدث ضمن الخادم، يُستدعى كلما ورد طلب HTTP إلى العنوان http://localhost:3001. يستجيب الخادم برمز الحالة (200) معيدًا ترويسة "نوع المحتوى" على أنها text/plain (نص أو فارغ)، وكذلك محتوى صفحة الويب التي ستُعرض، وهذا المحتوى هو العبارة "Hello World". تهيئ أسطر الشيفرة الأخيرة خادم http الذي أُسند إلى المتغيّر App لينصت إلى طلبات HTTP القادمة إلى المنفذ 3001: const PORT = 3001 app.listen(PORT) console.log(`Server running on port ${PORT}`) إنّ الغاية الأساسية من استخدام خادم الواجهة الخلفية في منهاجنا، هو تقديم بيانات خام بصيغة JSON إلى الواجهة الأمامية. ولهذا السبب سنعدّل الخادم (تطبيق الخادم) ليعيد قائمة من الملاحظات المكتوبة مسبقًا بصيغة JSON: const http = require('http') let notes = [ { id: 1, content: "HTML is easy", date: "2019-05-30T17:30:31.098Z", important: true }, { id: 2, content: "Browser can execute only Javascript", date: "2019-05-30T18:39:34.091Z", important: false }, { id: 3, content: "GET and POST are the most important methods of HTTP protocol", date: "2019-05-30T19:20:14.298Z", important: true } ] const app = http.createServer((request, response) => { response.writeHead(200, { 'Content-Type': 'application/json' }) response.end(JSON.stringify(notes)) }) const PORT = 3001 app.listen(PORT) console.log(`Server running on port ${PORT}`) لنعد تشغيل الخادم ولنحدث المتصفح. ملاحظة: يمكنك إغلاق الخادم بالضغط على ctrl+c من طرفية سطر أوامر Node.js. تُعلِم القيمة (application/JSON) الموجودة في ترويسة "نوع المحتوى" متلقي البيانات أنها بصيغة JSON.تُحوَّل المصفوفة notes إلى JSON باستخدام التابع ()JSON.stringify. ستظهر المعلومات على المتصفح تمامًا كما ظهرت في مقال إحضار البيانات من الخادم في تطبيقات React عندما استخدمنا خادم JSON. مكتبة Express يمكن كما رأينا كتابة شيفرة الخادم مباشرة باستخدام الوحدة http المدمجة ضمن Node.js.لكن الأمر سيغدو مربكًا عندما يزداد حجم التطبيق. طوّرت العديد من المكتبات لتسهّل تطوير تطبيقات الواجهة الخلفية باستخدام Node، وذلك بتقديم واجهة أكثر ملائمة للعمل بالموازنة مع وحدة http المدمجة. تعتبر المكتبة express حتى الآن الأكثر شعبية لتحقيق المطلوب. لنضع express موضع التنفيذ بتعريفها كملف اعتمادية dependency وذلك بتنفيذ الأمر: npm install express --save يُضاف ملف الاعتمادية أيضًا إلى الملف package.json: { // ... "dependencies": { "express": "^4.17.1" } } تُثبت الشيفرة المصدرية لملف الاعتمادية ضمن المجلد node_modules الموجود في المجلد الجذري للمشروع. يمكنك إيجاد عدد كبير من ملفات الاعتمادية بالإضافة إلى express ضمن هذا المجلد. في الواقع سيضم المجلد السابق ملفات اعتمادية express وملفات اعتمادية متعلقة بملفات اعتمادية express وهكذا. ندعو هذا الترتيب بملفات الاعتمادية الانتقالية transitive dependencies للمشروع. ثُبِّتت في مشروعنا المكتبة express 4.17.1. لكن ما الذي تعنيه إشارة (^) أمام رقم الإصدار في ملف package.json؟ "express": "^4.17.1" يستخدم npm ما يسمى بآلية الإصدار الدلالية semantic versioning، وتعني الدلالة (^) أنه إذا حُدِّثت ملفات اعتمادية المشروع فإن إصدار express سيبقى 4.17.1. يمكن أن يتغير رقم الدفعة ضمن الإصدار (الرقم الأخير) أو رقم الإصدار الثانوي (الرقم الأوسط) لكن رقم الإصدار الرئيسي (الرقم الأول) يجب أن يبقى كما هو. نستخدم الأمر التالي لتحديث ملفات اعتمادية المشروع: npm update يمكننا تثبيت أحدث ملفات اعتمادية معرفة في ملف package.json إذا أردنا أن نعمل على مشروعنا في حاسوب آخر باستخدام الأمر: npm install وبشكل عام عند تحديث ملف اعتمادية، يدل عدم تغير رقم الإصدار الرئيسي أن الإصدار الأحدث سيبقى متوافقًا مع الإصدار الأقدم دون الحاجة لتغييرات في الشيفرة. بينما تغير رقم الإصدار الرئيسي سيدل أن الإصدار الأحدث قد يحوي تغييرات قد تمنع التطبيق من العمل. فقد لا يعمل تطبيقنا إن كان إصدار المكتبة express فيه 4.17.7 مثلًا وتم تحديثها إلى الإصدار 5.0.0 (الذي قد نراه مستقبلًا)، بينما سيعمل مع الإصدار 4.99.1. استخدام express في تطوير صفحات الويب لنعد إلى تطبيقنا ونجري بعض التعديلات عليه: const express = require('express') const app = express() let notes = [ ... ] app.get('/', (req, res) => { res.send('<h1>Hello World!</h1>') }) app.get('/api/notes', (req, res) => { res.json(notes) }) const PORT = 3001 app.listen(PORT, () => { console.log(`Server running on port ${PORT}`) }) سنعيد تشغيل التطبيق حتى نحصل على النسخة الجديدة منه. طبعًا لن نلاحظ تغييرًا كليًا فلقد أدرجنا express على شكل دالة ستنشئ تطبيق express يُخزّن ضمن المتغيّر App: const express = require('express') const app = express() وبعدها عرّفنا مسارين للتطبيق، يعرّف الأول معالج حدث يتعامل مع طلبات HTTP-GET الموجهة إلى المجلد الجذري للتطبيق (' / '): app.get('/', (request, response) => { response.send('<h1>Hello World!</h1>') }) حيث تقبل دالة المعالج معاملين الأول request ويحوي كل المعلومات عن طلب HTTP، ويستخدم الثاني response لتحديد آلية الاستجابة للطلب. يستجيب الخادم إلى الطلب في شيفرتنا باستخدام التابع send العائد للكائن response. حيث يرسل الخادم عند الاستجابة العبارة النصية <h1>Hello World!</h1>التي مُرّرت كمعامل للتابع send. وطالما أن القيمة المعادة نصية ستَسند express القيمة text/html إلى ترويسة "نوع المحتوى" ويعاد رمز الحالة 200. يمكننا التحقق من ذلك من خلال النافذة Network في طرفية تطوير المتصفح: بالنسبة للمسار الثاني فإنه يعرّف معالج حدث يتعامل مع طلبات HTTP-GET الموجهة إلى موقع وجود الملاحظات، نهاية المسار notes: app.get('/api/notes', (request, response) => { response.json(notes) }) يستجيب الخادم إلى الطلب باستخدام التابع json العائد للكائن response. حيث تُرسل مصفوفة الملاحظات التي مُرّرت للتابع بصيغة JSON النصية. وكذلك ستتكفل express بإسناد القيمة application/json إلى ترويسة "نوع المحتوى". سنلقي تاليًا نظرة سريعة على البيانات التي أرسلت بصيغة JSON. كان علينا سابقًا تحويل البيانات إلى نصوص مكتوبة بصيغة JSON باستخدام التابع JSON.stringify: response.end(JSON.stringify(notes)) لا حاجة لذلك عند استخدام express، فهي تقوم بذلك تلقائيًا. وتجدر الإشارة هنا أنه لا فائدة من كون JSON مجرد نص، بل يجب أن يكون كائن JavaScript مثل notes الذي أُسند إليه. ستشرح لك التجربة الموضحة في الشكل التالي هذه الفكرة: أُنجز الاختبار السابق باستخدام الوحدة التفاعلية node-repl. يمكنك تشغيل هذه الوحدة بكتابة node في سطر الأوامر. تفيدك هذه الوحدة بشكل خاص لتوضيح طريقة عمل الأوامر، وننصح بشدة أن تستخدمها أثناء كتابة الشيفرة. مكتبة nodemon رأينا سابقًا ضرورة إعادة تشغيل التطبيق عند تعديله حتى تظهر نتائج التعديلات. ونقوم بذلك عن طريق إغلاق التطبيق أولًا باستخدام ctrl+c ثم تشغيله من جديد. طبعًا فالأمر مربك بالموازنة مع طريقة عمل React التي تقوم بذلك تلقائيًا بمجرد تغيّر الشيفرة. إن حل هذه المشكلة يكمن في استخدام nodemon. سنثبت الآن nodemon كملف اعتمادية باستخدام الأمر: npm install --save-dev nodemon سيتغير أيضًا محتوى الملف package.json: { //... "dependencies": { "express": "^4.17.1", }, "devDependencies": { "nodemon": "^2.0.2" } } إن أخطأت في كتابة الأمر وظهر ملف اعتمادية nodemon تحت مسمى ملفات الاعتمادية "dependencies" بدلًا من ملفات اعتمادية التطوير"devDependencies"، أصلح الأمر يدويًا في ملف package.json. إن الإشارة لملف اعتمادية على أنه ملف اعتمادية تطوير هو للدلالة على الحاجة له أثناء تطوير التطبيق فقط، ليعيد على سبيل المثال تشغيل التطبيق كما تفعل nodemon. ولن تحتاجها لاحقًا عندما تشغل التطبيق على خادم الاستثمار الفعلي مثل Heroku. لتشغيل التطبيق مع nodemon اكتب الأمر التالي: node_modules/.bin/nodemon index.js سيسبب الآن أي تغيير في الشيفرة إعادة تشغيل الخادم تلقائيًا. وطبعًا لا فائدة من إعادة تشغيل الخادم إن لم نحدث المتصفح الذي يعرض الصفحة، لذلك علينا القيام بذلك يدويًا. فلا نمتلك حاليًا وسيلة لإعادة التحميل الدائم (hot reload) كما في React. يبدو الأمر السابق طويلًا، فلنعرف إذًا سكريبت npm خاصة بتشغيل التطبيق ضمن الملف package.json: { // .. "scripts": { "start": "node index.js", "dev": "nodemon index.js", "test": "echo \"Error: no test specified\" && exit 1" }, // .. } لا حاجة في هذه السكريبت لتحديد المسار التالي node_modules/.bin/nodemon للمكتبة nodemon، لأن npm يعرف أين سيبحث عنها. لنشغل الخادم بوضعية التطوير كما يلي: npm run dev على خلاف مخطوطتي start و test يجب إضافة التعليمة run إلى الأمر. العمل مع واجهة التطبيقات REST لنجعل تطبيقنا قادرًا على تأمين واجهة http متوافقة مع REST كما فعلنا مع خادم JSON. قدم روي فيلدينغ مفهوم نقل حالة العرض (REpresentational State Transfer) واختصارًا REST، عام 2000 في أطروحته للدكتوراه وهي عبارة عن أسلوب تصميمي لبناء تطبيقات ويب بمقاسات قابلة للتعديل. لن نغوص في تعريف REST، أو نهدر وقتنا في التفكير بالواجهات المتوافقة أو غير المتوافقة معها، بل سنعتمد مقاربة ضيقة تهتم فقط بكيفية فهم التوافق مع REST من منظور تطبيقات الويب، فمفهوم REST الأصلي ليس محدودًا بتطبيقات الويب. لقد أشرنا في القسم السابق أن REST تعتبر كل الأشياء الفردية -كالملاحظات في تطبيقنا- موردًا. ولكل مورد موقع محدد URL يمثل العنوان الفريد لهذا المورد. إن أحد الأعراف المتبعة في إنشاء عنوان فريد للمورد هو دمج نوع المورد مع المعرف الفريد له. فلو افترضنا أن الموقع الجذري للخدمة هو www.example.com/api، وأننا عرّفنا نوع المورد الذي يمثل الملاحظات على أنه note وأننا نحتاج إلى المورد note ذو المعرف 10، سيكون عنوان موقع المورد www.example.com/api/notes/10. وسيكون عنوان موقع مجموعة الملاحظات www.example.com/api/notes. يمكننا تنفيذ العديد من العمليات على الموارد، وتعرّف العملية التي نريد تنفيذها على مورد على أنها فعل HTTP: الموقع الفعل الوظيفة notes/10 GET إحضار مورد واحد notes GET إحضار كل موارد المجموعة notes POST إنشاء مورد جديد بناء على البيانات الموجودة في الطلب notes/10 DELETE حذف المورد المحدد notes/10 PUT استبدال كامل المورد المحدد بالبيانات الموجودة في الطلب notes/10 PATCH استبدال جزء من المورد المحدد بالبيانات الموجودة في الطلب table { width: 100%; } thead { vertical-align: middle; text-align: center;} td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; } وهكذا نحدد الخطوط العامة لما تعنيه REST كواجهة نموذجية، أي أنها تعرّف أسلوبُا ثابتُا ومنسقًا للواجهات، تجعل الأنظمة المختلفة قادرة على العمل المشترك. إنّ المفهوم الذي اعتمدناه حول REST يصنف كمستوًى ثانٍ من التوافق وفق نموذج توافق ريتشاردسون. مع هذا فنحن إذ عّرفنا واجهة REST، لم نعرفها لتتوافق تمامًا مع المعايير التي قدمها روي فيلدينغ في أطروحته، وهذا حال الغالبية العظمى من الواجهات التي تدعي توافقها الكامل مع REST. يشار إلى نموذجنا الذي يقدم واجهة أساسية (إنشاء-قراءة-تحديث-حذف) (CRUD: Create-Read-Update-Delete) في العديد من المراجع (Richardson, Ruby: RESTful Web Services) على أنه تصميم موجه للعمل مع الموارد resource oriented architecture بدلًا من كونه متوافقًا مع REST. إحضار مورد واحد لنوسع تطبيقنا بحيث يقدم واجهة REST تتعامل مع الملاحظات بشكل فردي. لكن علينا أولًا إنشاء مسار لإحضار المورد. سنعتمد نموذج (نوع/معرف) في الوصول إلى موقع المورد (notes/10 مثلًا). يمكن تعريف معاملات المسار في express كما يلي: app.get('/api/notes/:id', (request, response) => { const id = request.params.id const note = notes.find(note => note.id === id) response.json(note) }) سيتعامل المسار (...,'app.get('/api/notes/:id مع طلبات HTTP-GET التي تأتي بالصيغة api/notes/SOMETHING حيث يشير SOMETHING إلى نص افتراضي. يمكن الوصول إلى المعامل id للمسار من خلال الكائن request: const id = request.params.id نستخدم التابع find الخاص بالمصفوفات للبحث عن الملاحظة من خلال معرفها الفريد id والذي يجب أن يتطابق مع قيمة المعامل، ثم تعاد الملاحظة إلى مرسل الطلب. لكن لو جربنا الشيفرة المكتوبة واستخدمنا المتصفح للوصول إلى العنوان http://localhost:3001/api/notes/1، فلن يعمل التطبيق كما هو متوقع، وستظهر صفحة فارغة. لن يفاجئني هذا كمطور اعتاد على المشاكل، لذا فقد حان وقت التنقيح. لنزرع الأمر console.log كما اعتدنا: app.get('/api/notes/:id', (request, response) => { const id = request.params.id console.log(id) const note = notes.find(note => note.id === id) console.log(note) response.json(note) }) بمجرد انتقال المتصفح إلى العنوان السابق ستُظهر الطرفية الرسالة التالية: لقد مُرِّر المعامل id إلى التطبيق، لكن التابع find لم يجد ما يتطابق معه. وللتعمق في تقصي مصدر الخطأ زرعنا الأمر console.log ضمن الدالة التي تُمرَّر كمعامل للتابع find. وكان علينا لتنفيذ ذلك إعادة كتابة الدالة السهمية بشكلها الموسع واستخدام عبارة return في نهايتها: app.get('/api/notes/:id', (request, response) => { const id = request.params.id const note = notes.find(note => { console.log(note.id, typeof note.id, id, typeof id, note.id === id) return note.id === id }) console.log(note) response.json(note) }) سيطبع لنا الأمر console.log (معرف الملاحظة، نوعه، المتغير id، نوعه، هل هناك تطابق). وعندما نتوجه مجددًا نحو عنوان الملاحظة عبر المتصفح، ستُطبع عبارة مختلفة على الطرفية مع كل استدعاء للدالة: 1 'number' '1' 'string' false 2 'number' '1' 'string' false 3 'number' '1' 'string' false يبدو أن سبب المشكلة قد توضّح الآن. إن المتغيّر id يضم قيمة نصية هي "1"، بينما يحمل معرف الملاحظة قيم صحيحة. حيث يعتبر عامل المساواة الثلاثي === في JavaScript وبشكل افتراضي أن القيم من أنواع مختلفة غير متساوية. لنصحح الخطأ بتحويل قيمة المتغير id إلى عدد: app.get('/api/notes/:id', (request, response) => { const id = Number(request.params.id) const note = notes.find(note => note.id === id) response.json(note) }) لقد تم الأمر! لاتزال هناك مشكلة أخرى في التطبيق. فلو بحثنا عن ملاحظة معّرفها غير موجود أصلًا، سيجيب الخادم كالتالي: يعيد الخادم رمز الحالة 200، وهذا يعني أن الاستجابة قد تمت بنجاح. وطبعًا لا توجد هناك بيانات لإعادتها طالما أن قيمة ترويسة "طول المحتوى" هي 0. يمكن التحقق من ذلك أيضًا عبر المتصفح. إن السبب الكامن خلف هذا السلوك، هو أن المتغير note سيأخذ القيمة undefined إن لم نحصل على تطابق. وبالتالي لابد من التعامل مع هذه المشكلة على الخادم الذي يجب أن يعيد رمز الحالة not found 404 بدلًا من 200. لنعدل الشيفرة كالتالي: app.get('/api/notes/:id', (request, response) => { const id = Number(request.params.id) const note = notes.find(note => note.id === id) if (note) { response.json(note) } else { response.status(404).end() } }) عندما لا تكون هناك بيانات لإعادتها نستخدم التابع status لإعداد حالة التطبيق، والتابع end للاستجابة على الطلب دون إرسال أية بيانات. تظهر لك العبارة الشرطية if أن كل كائنات JavaScript محققة (تعيد القيمة المنطقية "صحيح" في عمليات الموازنة)، بينما يعتبر الكائن undefiend خاطئ. سيعمل التطبيق الآن، وسيرسل رمز الحالة الصحيح إن لم يعثر على الملاحظة المطلوبة. لن يعرض التطبيق شيئًا على الصفحة كغيره من التطبيقات عندما تحاول الوصول إلى مورد غير موجود. وهذا في الواقع أمر طبيعي، فلا حاجة لعرض أي شيء على المتصفح طالما أن REST واجهة معدة للاستخدام برمجيًا، وسيكون رمز الحالة الذي يعيده التطبيق هو كل ما نحتاجه. حذف الموارد لنضف مسارًا لحذف مورد محدد. يتم ذلك من خلال الطلب HTTP-DELETE إلى موقع المورد: app.delete('/api/notes/:id', (request, response) => { const id = Number(request.params.id) notes = notes.filter(note => note.id !== id) response.status(204).end() }) إن نجحت عملية الحذف، أي أن المورد وُجد وحُذف، سيجيب التطبيق على الطلب برمز الحالة no content 204 دون إعادة أية بيانات. لا يوجد رمز حالة محدد لإعادته عند محاولة حذف مورد غير موجود، لذلك ولتبسيط الأمر سنعيد الرمز 204 في الحالتين. اختبار التطبيقات باستخدام Postman كيف نتأكد أن عملية حذف المورد قد تمت فعلًا؟ من السهل إجراء طلب HTTP-GET من خلال المتصفح والتأكد. لكن كتابة شيفرة اختبار ليست الطريقة الأنسب دائمًا. ستجد العديد من الأدوات الجاهزة لتسهيل الاختبارات على الواجهة الخلفية. فمنها على سبيل المثال curl وهو برنامج سطر أوامر أشرنا إليه في القسم السابق، لكننا سنستخدم هنا Postman للقيام بهذه المهمة. إذًا لنثبت Postman: من السهل جدًا استخدام Postman في حالتنا هذه، إذ يكفي أن تختار موقعا وطلب HTTP مناسب (DELETE في حالتنا). يستجيب خادم الواجهة الخلفية بشكل صحيح كما يبدو. فلو أرسلنا إلى الموقع http://localhost:3001/api/notes طلب HTTP-GET سنجد أن الملاحظة التي معرّفها 2 غير موجودة ضمن قائمة الملاحظات، فقد نجحت عملية الحذف. ستعود قائمة الملاحظات إلى وضعها الأصلي عند إعادة تشغيل التطبيق، لأن الملاحظات قد حُفِظت في ذاكرة الجهاز فقط. عميل REST في بيئة التطوير Visual Studio Code إن كنت تستخدم Visual Studio Code في تطوير التطبيقات يمكنك تثبيت الإضافة REST client واستعمالها بدلًا من Postman حيث ننشئ مجلدًا يدعى requests عند جذر التطبيق، ثم نحفظ كل طلبات REST client في ملف ينتهي باللاحقة rest. ونضعه في المجلد السابق. لننشئ الآن الملف getallnotes.rest ونعرّف فيه كل طلبات HTTP التي تحضر الملاحظات: بالنقر على النص Send Requests، سينفذ REST client طلبات HTTP وستظهر استجابة الخادم في نافذة المحرر. استقبال البيانات سنوسع التطبيق بحيث نغدو قادرين على إضافة ملاحظة جديدة إلى الخادم. تنفذ العملية باستخدام طلب HTTP-POST إلى العنوان http://localhost:3001/api/notes، حيث تُرسل المعلومات عن الملاحظة الجديدة بصيغة JSON ضمن جسم الطلب. وحتى نصل إلى معلومات الملاحظة بسهولة سنحتاج إلى مفسّر JSON الذي تقدمه express ويستخدم عبر تنفيذ الأمر (()app.use(express.json. لنستخدم مفسّر JSON ولنضف معالج حدث أوّلي للتعامل مع طلبات HTTP-POST: const express = require('express') const app = express() app.use(express.json()) //... app.post('/api/notes', (request, response) => { const note = request.body console.log(note) response.json(note) }) يمكن لدالة معالج الحدث الوصول إلى البيانات من خلال الخاصية body للكائن request. لكن من غير استخدام مفسر JSON ستكون هذه الخاصية غير معرّفة undefined. فوظيفة المفسر استخلاص بيانات JSON من الطلب وتحويلها إلى كائن JavaScript يرتبط مع الخاصية body للكائن requestوذلك قبل أن تُستدعى الدالة التي تتعامل مع مسار الطلب. حتى هذه اللحظة لا يفعل التطبيق شيئًا سوى استقبال البيانات وطباعتها على الطرفية ثم إعادة إرسالها كاستجابة للطلبات. قبل أن نضيف بقية الشيفرة إلى التطبيق، لنتأكد باستخدام Postman أن الخادم قد استقبل البيانات بالفعل. لكن يجب الانتباه إلى تعريف البيانات التي أرسلناها ضمن جسم الطلب بالإضافة إلى موقع المورد: يطبع البرنامج البيانات التي أرسلناها ضمن الطلب على الطرفية: ملاحظة: ابق الطرفية التي تُشغّل التطبيق مرئية بالنسبة لك طيلة فترة العمل مع الواجهة الخلفية. ستساعدنا Nodemon على إعادة تشغيل التطبيق عند حدوث أية تغييرات في الشيفرة. إذا ما دققت في ما تعرضه الطرفية، ستلاحظ مباشرة وجود أخطاء في التطبيق: من المفيد جدًا التحقق باستمرار من الطرفية لتتأكد من أن كل شيء يسير كما هو متوقع ضمن الواجهة الخلفية وفي مختلف الحالات، كما فعلنا عندما أرسلنا البيانات باستخدام الطلب HTTP-POST. ومن الطبيعي استخدام الأمر console.log في مرحلة التطوير لمراقبة الوضع. من الأسباب المحتملة لظهور الأخطاء هو الضبط الخاطئ لترويسة "نوع المحتوى" في الطلبات. فقد ترى الخطأ التالي مع Postman إذا لم تعرّف نوع جسم الطلب بشكل صحيح: حيث عُرّف نوع المحتوى على أنه text/plain (نص أو فارغ): ويبدو أن الخادم مهيأ لاستقبال مشروع فارغ فقط: لن يتمكن الخادم من تفسير البيانات بشكل صحيح دون وجود القيمة الصحيحة في الترويسة. ولن يخمن حتى صيغة البيانات نظرًا للكمية الهائلة من أنواع المحتوى التي قد تكونها البيانات. إن كنت قد ثبتت VS REST client ستجد أن الطلب HTTP-POST سيرسل بمساعدة REST client كما يلي: حيث أنشأنا الملف create_note.rest وصغنا الطلب كما يوصي توثيق البرنامج. يتمتع REST client بميزة على Postman بأن التعامل يدويًا مع الطلبات متاح في المجلد الجذري للمشروع، ويمكن توزيعها إلى أعضاء فريق التطوير. بينما وإن كان Postman سيسمح بحفظ الطلبات، لكن ستغدو الأمور فوضوية وخاصة إذا استُخدم في تطوير عدة مشاريع غير مترابطة. ملاحظات جانبية مهمة قد تحتاج عند تنقيح التطبيقات إلى معرفة أنواع الترويسات التي ترافق طلبات HTTP. يعتبر استخدام التابع get العائد للكائن request، إحدى الطرق التي تسمح بذلك. حيث يستخدم التابع للحصول على قيمة ترويسة محددة، كما يمتلك الكائن request الخاصية headers التي تحوي كل الترويسات الخاصة بطلب محدد. قد تحدث الأخطاء عند استخدام VS REST client لو أضفت سطرًا فارغًا بين السطر الأعلى والسطر الذي يعرّف ترويسات HTTP. حيث يفسر البرنامج ذلك بأن كل الترويسات قد تُركَت فارغة، وبالتالي لن يعلم خادم الواجهة الخلفية أن البيانات التي استقبلها بصيغة JSON. يمكنك أن ترصد ترويسة "نوع المحتوى" المفقودة إذا ما طبعت كل ترويسات الطلب على الطرفية باستخدام الأمر (console.log(request.headers. لنعد الآن إلى تطبيقنا، طالما تأكدنا أن الخادم قد استقبل البيانات الواردة إليه بالشكل الصحيح، وقد حان الوقت لإتمام معالجة الطلب: app.post('/api/notes', (request, response) => { const maxId = notes.length > 0 ? Math.max(...notes.map(n => n.id)) : 0 const note = request.body note.id = maxId + 1 notes = notes.concat(note) response.json(note) }) نحتاج إلى id فريد للملاحظة الجديدة وللقيام بذلك علينا أن نجد أكبر قيمة id تحملها قائمة الملاحظات ونسند قيمتها للمتغير maxId. هكذا سيكون id الملاحظة الجديدة هو maxId+1. في واقع الأمر، حتى لو استخدمنا هذا الأسلوب حاليًّا، فلا ننصح به، وسنتعلم أسلوبًا أفضل قريبًا. الشيء الآخر أن تطبيقنا بشكله الحالي يعاني مشكلة مفادها أن طلب HTTP-POST يمكن أن يُستخدَم لإضافة كائن له خصائص غير محددة. لذلك سنحسن التطبيق بأن لا نسمح لقيمة الخاصية content أن تكون فارغة، وسنعطي الخاصيتين important وdate قيمًا افتراضية، وسنهمل بقية الخصائص: const generateId = () => { const maxId = notes.length > 0 ? Math.max(...notes.map(n => n.id)) : 0 return maxId + 1 } app.post('/api/notes', (request, response) => { const body = request.body if (!body.content) { return response.status(400).json({ error: 'content missing' }) } const note = { content: body.content, important: body.important || false, date: new Date(), id: generateId(), } notes = notes.concat(note) response.json(note) }) وضعنا الشيفرة التي تولد المعرف id للملاحظة الجديدة ضمن الدالة generateId. فعندما يستقبل الخادم بيانات قيمة الخاصية content لها غير موجودة، سيجيب على الطلب برمز الحالة 400 bad request: if (!body.content) { return response.status(400).json({ error: 'content missing' }) } لاحظ أن استخدام return حيوي جدًا لسير العملية، فلولاها ستُنفّذ الشيفرة تباعًا وستُحفظ الملاحظة ذات الصياغة الخاطئة. وبالطبع ستحفظ الملاحظة التي تحمل فيها الخاصية content قيمة، بما تحويه من بيانات. ونذكّر أيضًا أننا تحدثنا عن ضرورة توليد زمن إنشاء الملاحظة من قبل الخادم وليس جهاز الواجهة الأمامية، لذلك ولّده الخادم. إن لم تحمل الخاصية important قيمة فسنعطيها القيمة الإفتراضية false. لاحظ الطريقة الغريبة التي استخدمناها: important: body.important || false, وتفسر الشيفرة السابقة كالتالي: إن حملت الخاصية important للبيانات المستقبلة قيمة ستعتمد العبارة السابقة هذه القيمة وإلا ستعطيها القيمة false. أمَّا عندما تكون قيمة الخاصية important هي false عندها ستعيد العبارة التالية body.important || false القيمة false بناء على قيمة الطرف الأيمن من العبارة. ستجد شيفرة التطبيق بشكله الحالي في المستودع part3-1 على موقع GitHub. يحوي المسار الرئيسي للمستودع كما ستلاحظ شيفرات للنسخ التي سنطورها لاحقًا، لكن النسخة الحالية في المسار part3-1. إن نسخت المشروع، فتأكد من تنفيذ الأمر npm install قبل أن تشغل التطبيق باستخدام إحدى التعليمتين npm start أو npm run dev. ملاحظة أخرى قبل الشروع بحل التمارين، سيبدو الشكل الحالي لدالة توليد المعرفات IDs كالتالي: const generateId = () => { const maxId = notes.length > 0 ? Math.max(...notes.map(n => n.id)) : 0 return maxId + 1 } يحتوي جسم الدالة سطرًا يبدو غريبًا نوعًا ما: Math.max(...notes.map(n => n.id)) ينشئ التابع (notes.map(n=>n.id مصفوفة جديدة تضم كل قيم المعرفات id للملاحظات. ثم يعيد التابع Math.max أعلى قيمة من القيم التي مُرّرت إليه. وطالما أن نتيجة تطبيق الأمر (notes.map(n=>n.ids ستكون مصفوفة، فمن الممكن تمريرها مباشرة كمعامل للتابع Math.max. وتذكّر أنه يمكن فصل المصفوفة إلى عناصرها باستخدام عامل النشر (…). التمارين 3.1 - 3.6 ملاحظة: من المفضل أن تنفذ تمارين هذا القسم ضمن مستودع خاص على git، وأن تضع الشيفرة المصدرية في جذر المستودع، وإلا ستواجه المشاكل عندما تصل للتمرين 3.10. ملاحظة: لا ننفذ حاليًا مشروعًا للواجهة الأمامية باستخدام React، ولم ننشئ المشروع باستخدام create-react-app. لذلك هيئ المشروع باستعمال الأمر npm init كما شرحنا في هذا الفصل. توصية مهمة: ابق نظرك دائمًا على الطرفية التي تُشغِّل تطبيقك عندما تطور التطبيقات للواجهة الخلفية. 3.1 دليل الهاتف للواجهة الخلفية: الخطوة 1 اكتب تطبيقًا باستخدام Node يعيد قائمة من مدخلات دليل هاتف مكتوبة مسبقًا وموجودة في الموقع http://localhost:3001/api/persons: لاحظ أنه ليس للمحرف '/' في المسار api/persons أي معنى خاص بل هو محرف كباقي المحارف في النص. يجب أن يُشّغل التطبيق باستخدام الأمر npm start. كما ينبغي على التطبيق أن يعمل كنتيجة لتنفيذ الأمر npm run dev وبالتالي سيكون قادرًا على إعادة تشغيل الخادم عند حفظ التغيرات التي قد تحدث في ملف الشيفرة المصدرية. 3.2 دليل الهاتف للواجهة الخلفية: الخطوة 2 أنشئ صفحة ويب عنوانها http://localhost:3001/info بحيث تبدو مشابهةً للصفحة التالية: ستظهر الصفحة الوقت الذي استقبل فيه الخادم الطلب، وعدد مدخلات دليل الهاتف في لحظة معالجة الطلب. 3.3 دليل الهاتف للواجهة الخلفية: الخطوة 3 أضف إمكانية إظهار معلومات مُدخل واحد من مُدخلات دليل الهاتف. يجب أن يكون موقع الحصول على بيانات الشخص الذي معرفه 5 هو http://localhost:3001/api/persons/5. إن لم يكن المُدخَل ذو المعرف المحدد موجودًا، على الخادم أن يستجيب معيدًا رمز الحالة المناسب. 3.4 دليل الهاتف للواجهة الخلفية: الخطوة 4 أضف إمكانية حذف مدخل واحد من مُدخلات دليل الهاتف مستعملًا الطلب HTTP-DELETE إلى عنوان المدخل الذي سيُحذَف ثم اختبر نجاح العملية مستخدمًا Postman أو Visual Studio Code REST client. 3.5 دليل الهاتف للواجهة الخلفية: الخطوة 5 أضف إمكانية إضافة مدخلات إلى دليل الهاتف مستعملًا الطلب HTTP-POST إلى عنوان مجموعة المدخلات http://localhost:3001/api/persons. استعمل الدالة Math.random لتوليد رقم المعرف id للمُدخل الجديد. واحرص أن يكون مجال توليد الأرقام العشوائية كبيرًا ليقلّ احتمال تكرار المعرف نفسه لمدخلين. 3.6 دليل الهاتف للواجهة الخلفية: الخطوة 6 أضف معالج أخطاء للتعامل مع ما قد يحدث عند إنشاء مدخل جديد. حيث لا يُسمح بنجاح العملية إذا كان: اسم الشخص أو رقمه غير موجودين الاسم موجود مسبقًا في الدليل. استجب للحالتين السابقتين برمز حالة مناسب، وأعد كذلك معلومات توضح سبب الخطأ كالتالي: { error: 'name must be unique' } فكرة عن أنواع طلبات HTTP يتحدث معيار HTTP عن خاصيتين متعلقتين بأنواع الطلبات هما الأمان safety والخمول idempotence. فيجب أن يكون طلب HTTP-GET آمنًا: يعني الأمان أن تنفيذ الطلب لن يسبب تأثيرات جانبية على الخادم. ونعني بالتأثيرات الجانبية أن حالة قاعدة البيانات لن تتغير كنتيجة للطلب وأن الاستجابة ستكون على شكل بيانات موجودة مسبقًا على الخادم. لا يمكن أن نضمن أمان الطلب GET وما ذكر مجرد توصية في معيار HTTP. لكن بالتزام مبادئ التوافق مع REST في واجهة تطبيقنا، ستكون طريقة استخدام GET آمنة دائمًا. كما عرّف معيار HTTP نمطًا للطلبات هو HEAD وعلى هذا الأخير أن يكون آمنًا أيضًا. يعمل HEAD تمامًا عمل GET، إلا أنه لا يعيد سوى رمز الحالة وترويسات الاستجابة. لن يعيد الخادم جسمًا للاستجابة عندما تستخدم HEAD. وعلى كل طلبات HTTP أن تكون خاملة ماعدا POST: ويعني هذا أن التأثيرات الجانبية التي يمكن أن يسببها طلب، يجب أن تبقى كما هي، بغض النظر عن عدد المرات التي يرسل فيها الطلب إلى الخادم. فلو أرسلنا الطلب HTTP-PUT إلى العنوان api/notes/10/ حاملًا البيانات {content:"no sideeffects!", important:true} فإن الاستجابة ستكون نفسها بغض النظر عن عدد المرات التي يُرسَل فيها هذا الطلب. وكما أن الأمان في GET لا يتحقق دومًا، كذلك الخمول. فكلاهما مجرد توصيات في معيارHTTP لا يمكن ضمانها معتمدين على نوع الطلب فقط. لكن بالتزام مبادئ التوافق مع REST في واجهة تطبيقنا، سنستخدم GET، HEAD PUT، DELELTE بطريقة تحقق خاصية الخمول. أما الطلب POST فلا يعتبر آمنًا ولا خاملًا. فلو أرسلنا 5 طلبات HTTP-POST إلى الموقع /api/notes، بحيث يضم جسم الطلب البيانات {content: "many same", important: true}، ستكون النتيجة 5 ملاحظات جديدة لها نفس المحتوى. البرمجيات الوسيطة Middleware يصنف مفسّر JSON الذي تقدمه express بأنه أداة وسطية. فالبرمجيات الوسيطة (Middleware) هي دوال تستخدم لمعالجة كائنات الطلبات والاستجابات. فمفسر JSON الذي تعرفنا عليه سابقًا في هذا الفصل سيأخذ بيانات خام من جسم كائن الطلب، ثم يحولها إلى كائن JavaScript ويسندها إلى الكائن request كقيمة للخاصية body. يمكن في الواقع استخدام عدة أدوات وسطية في نفس الوقت. ستنفذ البرمجيات الوسيطة بالتتالي وفق ترتيب استخدامها من قبل express. لنضف أدوات وسطية خاصة بنا لطباعة معلومات حول كل طلب أرسل إلى الخادم. الأداة الوسطية التي سنستعملها دالة تقبل ثلاث معاملات: const requestLogger = (request, response, next) => { console.log('Method:', request.method) console.log('Path: ', request.path) console.log('Body: ', request.body) console.log('---') next() } سنجد في نهاية الدالة، دالة أخرى هي ()next مررت كمعامل لدالة الأداة الوسطية وتستدعى في نهايتها. تنقل الدالة next التحكم إلى الأداة الوسطية التالية. تستخدم الأداة الوسطية كالتالي: app.use(requestLogger) تستدعى دوال البرمجيات الوسيطة بالتتالي وفق ترتيب استخدامها من قبل express عن طريق التابع use. وانتبه إلى أن مفسر JSON سيستخدم قبل الأداة الوسطية requestLogger، وإلا فلن يهيأ جسم الكائن request عندما تُنفّذ الدالة requestLogger. يجب أيضًا تعريف دوال البرمجيات الوسيطة قبل المسارات إن أردنا تنفيذها قبل أن يُستدعى معالج الحدث الخاص بالمسار. لكن قد تكون هناك حالات معاكسة نعرّف فيها الأداة الوسطية بعد تعريف المسار، وخاصة إن أردنا لدالة الأداة الوسطية أن تُنفّذ إن لم تُعرّف أية مسارات لمعالجة طلب HTTP. لنعرف الأداة الوسطية التالية في شيفرتنا بعد تعريف المسار، لالتقاط الطلبات الموجهة إلى مسارات غير موجودة. ستعيد دالة الأداة الوسطية في هذه الحالة رسالة خطأ بصيغة JSON: const unknownEndpoint = (request, response) => { response.status(404).send({ error: 'unknown endpoint' }) } app.use(unknownEndpoint) ستجد شيفرة التطبيق بشكله الحالي في المستودع part3-2 على موقع GitHub. التمارين 3.7 - 3.8 3.7 دليل هاتف للواجهة الخلفية: الخطوة 7 أضف الأداة الوسطية morgan إلى تطبيقك للمراقبة. اضبط الأداة لطباعة الرسائل على الطرفية وفقًا لنمط التهيئة tiny. لا يقدم لك توثيق Morgan كل ما تريده، وعليك قضاء بعض الوقت لتتعلم تهيئة الأداة بشكل صحيح. وهذا بالطبع حال التوثيقات جميعها، فلا بد من فك رموز هذه التوثيقات. ثبت Morgan كأي مكتبة أخرى باستخدام الأمر npm install. واستخدمها تمامًا كما نستخدم أية أداة وسطية بتنفيذ الأمر app.use. 3.8 دليل هاتف للواجهة الخلفية: الخطوة 8 * هيئ Morgan حتى تعرض لك أيضًا بيانات الطلبات HTTP-POST: قد يكون طباعة بيانات الطلبات خطرًا طالما أنها تحتوي على معلومات حساسة قد تخرق بعض قوانين الخصوصية لبعض البلدان مثل الاتحاد الأوروبي، أو قد تخرق معيار الأعمال. لا تهتم لهذه الناحية في تطبيقنا، لكن عندما تنطلق في حياتك المهنية، تجنب طباعة معلومات حساسة. يحمل التمرين بعض التحديات، علمًا أن الحل لا يتطلب كتابة الكثير من الشيفرة. يمكنك إنجاز التمرين بطرائق عدة، منها استخدام التقنيات التالية: إنشاء مفاتيح جديدة (new token) JSON.stringify ترجمة -وبتصرف- للفصل Node.js, Express من سلسلة Deep Dive Into Modern Web Development1 نقطة
-
عندما نضيف حقل قاعدة بيانات في جانغو Django، نكتب بشكل عام: models.CharField(max_length=255, null=True, blank=True) لكن لم أفهم ما الفرق بين الحالات التالية: كتابة null=True فقطك، أو كتابة blank=True فقط، أو كتابة null=True و blank=True معًا نحن نستعمل نفس الشيء من حقول مثل ForeignKey و ManyToManyField و DateTimeField، ما هي فوائد وعيوب كل طريقة من الطرق الثلاث السابقة؟ وهل يوجد فرق من الأساس؟1 نقطة
-
لدي بيانات على هيئة dataframe ، وأود أن أقوم بتبديل قيم بعض العناصر فيها بقيم أخرى، فقط عناصر محددة وليست كل البيانات، كيف يمكنني فعل هذا؟1 نقطة
-
لدي بعض البيانات على هيئة dataframe ، وأود ترتيبها بشكل تصاعدي وتنازلي مرة اخري ،كيف يمكن ترتيبها أبجديا بالأسماء؟1 نقطة
-
شكر الله لك استاذنا الفاضل نعم معك حق ، و قمت بالتعديل فعلا الا انها لم تعمل يتغيير السكربت بالشكل التالي $(document).on('change', '.color', function() { var id = $(this).val(); //get the current value's option $.ajax({ type:'POST', url: "functions/getSize.php", data:{'id':id}, success:function(data){ $(".size").html(data); } }); }); مجرد غيرت طريقة اخبار البرنامج بماذا يفعل عند تغيير قائمة color من document.getElementById("color").addEventListener("change", function () { إلى $(document).on('change', '.color', function() { و ضبطت لكن لا اعلم لماذا لم تضبط دالة addEventListener ككل خالص شكري لك و احملني فالتطبيق الذي اعمل عليه ضخم و خبرتي ليست مثلكم و انا اتعلم منكم1 نقطة
-
أنا أقوم بإنشاء تطبيق ويب مع جانغو Django. الأسباب التي جعلتني اخترت جانغو Django هي أنه مجاني ومفتوح المصدر وأني سأستخدم لغة Python حيث أنها أسهل وأسرع في الكتابة عن بقية اللغات، لكن لدي بعض الأسئلة: ما هي أكبر المواقع التي تم بناؤه بإستخدام جانغو Django؟ هل يمكن لـ جانغو Django أن يتحمل عدد زوار 100,000 يوميًا مع تصفح ساعتين لكل زائر (في المتوسط)؟ وهل يمكن عمل موقع مثل Stack Overflow بإستخدام جانغو Django؟1 نقطة
-
من قضلكم لدي مشكل هو عند تسجيل دخول الزبون يتم توجيهه الى داش بورد لاكن الموقع عم يدخل لداش بورد ثم يقوم باعادة ارداعه لصفحة الرئيسية للموقع على اساس لاتوجد جلسة هدا هو الكود ممكن حد يقولي فين الخطأ <?php session_start(); error_reporting(0); require_once('data_config.php'); include('../connect.php'); if (!isset($_SESSION["userName"])) { ?> <script type="text/javascript"> window.location = "../index.php"; </script> <?php } else { $userName = $_SESSION["userName"]; $sql = "select * from users where userName ='$userName' "; $result = $conn->query($sql); while ($row = $result->fetch_assoc()) { $logintype = $row['logintype']; $name = $row['name']; $userid = $row['id']; $shopname= $row['shopname']; if ($logintype != 1) { ?> <script type="text/javascript"> window.location = "../index.php"; </script> <?php } } } $sql = "select * from ordermag where ordercase = '0' and user_id= $userid"; $result = $conn->query($sql); $count = $result->num_rows; ?> <?php if (isset($_POST['Submitpubg'])) { if (empty($_POST['amount']) || empty($_POST['playerid']) || empty($_POST['confirm_playerid']) || empty($_POST['playername'])) { $error = "الرجاء ملأ جميع الخانات المطلوبة"; ?> <script type="text/javascript"> alert('الرجاء ملأ جميع الخانات المطلوبة'); </script> <?php } else { $name = $_POST['name']; $playerid = $_POST['playerid']; $playername = $_POST['playername']; $confirm_playerid = @htmlentities(strip_tags($_POST['confirm_playerid'])); $amount = $_POST['amount']; $date = date("d-m-Y h:ia"); if ($playerid != $confirm_playerid) { $error = "رقم id غير متطابقين"; ?> <script type="text/javascript"> alert('رقم id غير متطابقين'); </script> <?php } if (empty($error)) { $sql = "insert into ordermag (typecharge,amount,playerid,playername,ordercase,user_id,creat_at,updat_at,shopname) values ('pubg','$amount','$playerid','$playername','0','$userid','$date','$date','$shopname')"; $result = $conn->query($sql); if ($result) { mysqli_query($conn, $query); ?> <script type="text/javascript"> alert('تم الاضافة بنجاح'); </script> <?php $subject = mysqli_real_escape_string($conn, "طلب شحن بقيمة ($amount) نوع الشحن (pubg) "); $comment = mysqli_real_escape_string($conn, "اسم اللاعب ($playername) "); $user = mysqli_real_escape_string($conn, $userfullname); $date = date("d-m-Y h:ia"); $query = " INSERT INTO comments(comment_subject, comment_text,comment_user,comment_time,comment_status, sorting,user_id,user_type ) VALUES ('$subject', '$comment','$user','$date','0','1','$userid','$user_type') "; mysqli_query($conn, $query); echo ' <script type="text/javascript"> alert("تم الاضافة"); </script>'; } else { echo mysqli_error($conn); ?> <script type="text/javascript"> alert("عفوا ، error"); </script> <?php } } } } ?> <!DOCTYPE html><html dir="rtl" style="height: auto; min-height: 100%;"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>واجهه المحل</title> <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport"> <script src="js/all.js"></script> <link rel="stylesheet" href="css/bootstrap.min.css"> <link rel="stylesheet" href="css/ionicons.min.css"> <link rel="stylesheet" href="css/skin-blue.min.css"> <link rel="stylesheet" href="css/font-awesome-rtl.min.css"> <link rel="stylesheet" href="css/AdminLTE-rtl.min.css"> <link href="https://fonts.googleapis.com/css?family=Cairo:400,700" rel="stylesheet"> <link rel="stylesheet" href="css/bootstrap-rtl.min.css"> <link rel="stylesheet" href="css/rtl.css"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script> <style> span.info-box-icon { padding: 20px; } div.dropdown-menu { height: 350px; overflow: auto; width: 350px; padding-right: 20px; } .dropdown-menu { font-size: 18px; font-weight: 400 } a.nav-link { background-color: rgb(60, 141, 188);; } @media screen and (max-width: 849px) { .dropdown{ font-size: 25px; background-color: #f8f9fa; } } .dropdown{ font-size: 25px; } .navbar-badge{ font-size: 12px; background-color: red; color: white } .dropdown-header{ color: #16181b; text-decoration: none; background-color: #f8f9fa; } .dropdown-footer{ color: #16181b; background-color: #f8f9fa; text-decoration: none; overflow: visible; position: sticky; font-size: medium; bottom:0; } .div-only-mobile { display: none; padding-top: 10px; font-size: 20px; } @media screen and (max-width: 849px) { .div-only-mobile { display: block; } } body, h1, h2, h3, h4, h5, h6 { font-family: 'Cairo', sans-serif !important; } </style> </head> <body class="skin-blue sidebar-mini" style="height: auto; min-height: 100%;"> <div class="wrapper" style="height: auto; min-height: 100%;"> <header class="main-header"> <a href="" class="logo"> <span class="logo-mini"><b>A</b>LT</span> <span class="logo-lg" style="font-family: 'Cairo', sans-serif !important;">لوحة التحكم</span> </a> <nav class="navbar navbar-static-top text-center"> <ul class="nav navbar-nav navbar-left"> <!-- Notifications Dropdown Menu --> <li class="nav-item "> <a class="nav-link" href="profile.php"> <i class="far fa-user"></i> مرحبا بك <?php echo $shopname;?> </a> </li> </ul> <a class="div-only-mobile" href="../logout.php"><span style="font-family: 'Cairo', sans-serif !important;color: white">خروج</span></a> <ul class="nav navbar-nav navbar-center"> <!-- Notifications Dropdown Menu --> <li class="nav-item dropdown"> <a class="nav-link toggle" data-toggle="dropdown" href="#"> <i class="far fa-bell"></i> <span class="badge badge-danger navbar-badge count"></span> </a> <div class="dropdown-menu dropdown-menu-lg dropdown-menu-right"> </div> </li> </ul> </nav> </header> <aside class="main-sidebar"> <section class="sidebar"> <div class="user-panel"> <!-- sidebar menu: : style can be found in sidebar.less --> <ul class="sidebar-menu tree" data-widget="tree"> <li class="header">لوحة الانتقالات</li> <li><a href="index.php"><i class="fa fa-home"></i><span> الرئيسيه</span></a></li> <li><a href="orders/wating.php"><i class="fa fa-check-square"></i><span> الطلبات قيد الانتظار</span></a></li> <li><a href="orders/return.php"><i class="fa fa-list-alt"></i><span> الطلبات التى تحتاج الى تعديل</span></a></li> <li><a href="orders/finshed.php"><i class="fa fa-cogs"></i><span> الطلبات المنتهية</span></a></li> <li><a href="cards/"><i class="fa fa-credit-card"></i><span> البطاقات</span></a></li> <li><a href="cards/create.php"><i class="fa fa-shopping-cart"></i><span> طلب بطاقات</span></a></li> <li><a href="deposit.php"><i class="far fa-arrow-alt-circle-down"></i><span> ايداع الأموال</span></a></li> <li><a href="cards/create.php"><i class="fas fa-arrow-circle-up"></i><span> سحب الأموال</span></a></li> <li><a href="profile.php"><i class="fa fa-users"></i><span> بياناتى</span></a></li> <li><a href="#"><i class="fa fa-life-ring"></i><span> الشكاوي والتقارير</span></a></li> <li><a href="../logout.php"><i class="fa fa-lock"></i><span> خروج</span></a></li> </ul> </section> </aside> <div class="content-wrapper" style="min-height: 300px;"> <section class="content-header"> <h1>لوحة القيادة </h1> <ol class="breadcrumb"> <li class="active"><i class="fa fa-home"></i> لوحة القيادة </li> </ol> </section> <section class="content"> <div class="alert alert-warning alert-dismissible"> <h3><i class="far fa-bell"></i>تذكير بالإشتراك !</h3> <a href="https://www.martship.com/settings/subscribtion">إشترك</a> في واحدة من عروضنا لتتمتع بمزايا الشحن !. </div> <div class="row"> <div class="col-sm-6 col-lg-4"> <div class="info-box"> <span class="info-box-icon bg-aqua"><i class="fas fa-money-bill-wave"></i></span> <div class="info-box-content"> <span class="info-box-text">المجموع</span> <span class="h2"><strong>30 دج</strong></span> </div> </div> </div> <div class="col-sm-6 col-lg-4"> <div class="info-box"> <span class="info-box-icon bg-green"><i class="fa fa-arrow-circle-down"></i></span> <div class="info-box-content"> <span class="info-box-text"> مبلغ اخر إيداع</span> <span class="info-box-number">30 دج</span> </div> </div> </div> <div class="col-sm-6 col-lg-4"> <div class="info-box"> <span class="info-box-icon bg-yellow"><i class="fa fa-percent"></i></span> <div class="info-box-content"> <span class="info-box-text">نوع الاشتراك</span> <span class="info-box-number">عـــادي</span> </div> </div> </div> <div class="col-sm-6 col-lg-4"> <div class="info-box"> <span class="info-box-icon bg-red"><i class="far fa-clock"></i></span> <div class="info-box-content"> <span class="info-box-text">طلب الشحن قيد الانتظار</span> <span class="info-box-number" style="color:red;"><?php echo $count; ?></span> </div> </div> </div> <div class="col-sm-6 col-lg-4"> <div class="info-box"> <?php $sql = "select * from ordermag where ordercase = '1' and user_id= $userid"; $result = $conn->query($sql); $count = $result->num_rows; ?> <span class="info-box-icon bg-blue"><i class="fas fa-globe"></i></span> <div class="info-box-content"> <span class="info-box-text">طلبات الشحن المكتملة</span> <span class="info-box-number"><?php echo $count ?></span> </div> </div> </div> <div class="col-sm-6 col-lg-4"> <div class="info-box"> <span class="info-box-icon bg-purple"><i class="fas fa-exclamation-circle"></i></span> <div class="info-box-content"> <span class="info-box-text">طلبات الشحن المرفوضة</span> <span class="info-box-number" style="color:red;">2</span> </div> </div> </div> </div> <!--mooooooooodle--> <div id="freefiremodal" class="modal fade bs-example-modal-lg" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel" aria-hidden="true"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" style="margin-top: -10px;color: red;" data-dismiss="modal" aria-hidden="true">×</button> <h4 class="modal-title" id="myModalLabel" style="text-align: center">طــلب شـــحن FREE FIRE</h4> </div> <form action="" method="post" class="new-card-form" style="margin: 10px; padding:5px "> <div class="form-group"> <div class="col-md-12"> <label for="playerid" style="font-weight: bold;color: #3e5a94;margin-top: 10px ;">رقم id<span class="required" style="color:red">*</span></label> <input style="border: 1px solid #39b6b9" type="text" required name="playerid" class="form-control input-md" placeholder="أدخل رقم id" value="<?= @$_POST['playerid'] ?>"> </div> </div> <div class="form-group"> <div class="col-md-12"> <label for="confirm_playerid" style="font-weight: bold;color: #3e5a94;margin-top: 10px;">أعد رقم id<span class="required" style="color:red">*</span></label> <input style="border: 1px solid #39b6b9" type="text" required name="confirm_playerid" class="form-control input-md" placeholder="أعد ادخال رقم id" value="<?= @$_POST['confirm_playerid'] ?>"> </div> </div> <div class="form-group"> <div class="col-md-12"> <label for="playername" style="font-weight: bold;color: #3e5a94;margin-top: 10px ;">(للتأكيد) اسمك داخل اللعبة<span class="required" style="color:red">*</span></label> <input style="border: 1px solid #39b6b9" type="text" required name="playername" class="form-control input-md" placeholder="أدخل اسمك داخل اللعبة" value="<?= @$_POST['playername'] ?>"> </div> </div> <div class="form-group"> <div class="col-md-12"> <label for="playername" style="font-weight: bold;color: #3e5a94;margin-top: 10px ;">اختر قيمة الشحن<span class="required" style="color:red">*</span></label> <select class="form-control" style="height: 40px" id="amount_offers" name="amount" required> <option style="font-weight: bold;font-size: 18px">اختر قيمة الشحن</option> <option style="font-weight: bold;font-size: 18px">1$ (100 + 10 Bounus) </option> <option style="font-weight: bold;font-size: 18px">2$ (210 + 21 Bounus) </option> <option style="font-weight: bold;font-size: 18px">5$ (530 + 53 Bounus) </option> <option style="font-weight: bold;font-size: 18px">10$ (1080 + 108 Bounus)</option> <option style="font-weight: bold;font-size: 18px">20$ (2200 + 220 Bounus)</option> </select> </div> </div> <div class="row mt-lg"> <div class="col-md-12 center" style="margin: 18px 13px;"> <input type="submit" name="Submitfree" style="margin-left: 15px;" value="ارسال الطلب" class="btn btn-success pull-left mb-xl"> <input type="reset" class="btn btn-danger pull-left mb-xl mx-2" value="مسح "> <!--<a href="order_status.php" class="btn btn-primary pull-left mb-xl mx-2" >تتبع طلبي</a>--> </div> </div> </form> </div> </div> </div> <!--MOOOOOOOOOOOOOOOODLE end (free fir )--> <!--mooooooooodle PUBG--> <div id="pubgmodal" class="modal fade bs-example-modal-lg" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel" aria-hidden="true"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" style="margin-top: -10px;color: red;" data-dismiss="modal" aria-hidden="true">×</button> <h4 class="modal-title" id="myModalLabel" style="text-align: center">طــلب شـــحن PUBG(Global)</h4> </div> <form action="" method="post" class="new-card-form" style="margin: 10px; padding:5px "> <div class="form-group"> <div class="col-md-12"> <label for="playerid" style="font-weight: bold;color: #3e5a94;margin-top: 10px ;">رقم id<span class="required" style="color:red">*</span></label> <input style="border: 1px solid #39b6b9" type="text" required name="playerid" class="form-control input-md" placeholder="أدخل رقم id" value="<?= @$_POST['playerid'] ?>"> </div> </div> <div class="form-group"> <div class="col-md-12"> <label for="confirm_playerid" style="font-weight: bold;color: #3e5a94;margin-top: 10px;">أعد رقم id<span class="required" style="color:red">*</span></label> <input style="border: 1px solid #39b6b9" type="text" required name="confirm_playerid" class="form-control input-md" placeholder="أعد ادخال رقم id" value="<?= @$_POST['confirm_playerid'] ?>"> </div> </div> <div class="form-group"> <div class="col-md-12"> <label for="playername" style="font-weight: bold;color: #3e5a94;margin-top: 10px ;">(للتأكيد) اسمك داخل اللعبة<span class="required" style="color:red">*</span></label> <input style="border: 1px solid #39b6b9" type="text" required name="playername" class="form-control input-md" placeholder="أدخل اسمك داخل اللعبة" value="<?= @$_POST['playername'] ?>"> </div> </div> <div class="form-group"> <div class="col-md-12"> <label for="playername" style="font-weight: bold;color: #3e5a94;margin-top: 10px ;">اختر قيمة الشحن<span class="required" style="color:red">*</span></label> <select class="form-control" style="height: 40px" id="amount_offers" name="amount" required> <option style="font-weight: bold;font-size: 18px">اختر قيمة الشحن</option> <option style="font-weight: bold;font-size: 18px">60UC </option> <option style="font-weight: bold;font-size: 18px">300+25UC </option> <option style="font-weight: bold;font-size: 18px">600+60UC </option> <option style="font-weight: bold;font-size: 18px">1500+300UC </option> </select> </div> </div> <div class="row mt-lg"> <div class="col-md-12 center" style="margin: 18px 13px;"> <input type="submit" name="Submitpubg" style="margin-left: 15px;" value="ارسال الطلب" class="btn btn-success pull-left mb-xl"> <input type="reset" class="btn btn-danger pull-left mb-xl mx-2" value="مسح "> <!--<a href="order_status.php" class="btn btn-primary pull-left mb-xl mx-2" >تتبع طلبي</a>--> </div> </div> </form> </div> </div> </div> <!--MOOOOOOOOOOOOOOOODLE end (pubg)--> <div class="row"> <div class="col-md-12"> <div class="row"> <div class="col-md-12 text-center"> <div class="box box-info"> <div class="box-header with-border text-center"> <h3 class="box-title">إختصارات</h3> </div> <div class="box-body"> <a class="btn btn-app" data-toggle="modal" data-target="#freefiremodal"> <img class="img" style="display: block;width: 100px;height: 140px;padding: 29px 10px;margin: 0 auto 10px;border-bottom: 1px solid #ececec;text-align: center;" src="https://cdngarenanow-a.akamaihd.net/gop/app/0000/100/067/icon.png" alt="شحن Free Fire">FREE FIRE</a> <a class="btn btn-app" data-toggle="modal" data-target="#pubgmodal"> <img class="img" style="display: block;width: 100px;height: 140px;padding: 29px 10px;margin: 0 auto 10px;border-bottom: 1px solid #ececec;text-align: center;" src="https://gsmrida.com/img/pubgg.png" alt="شحن Free Fire">PUBG</a> <a class="btn btn-app" data-toggle="modal" data-target="#accountmodal"> <img class="img" style="display: block;width: 100px;height: 140px;padding: 29px 10px;margin: 0 auto 10px;border-bottom: 1px solid #ececec;text-align: center;" src="img/account.png" alt="شحن Free Fire">الشحن بالحساب</a> <a class="btn btn-app" data-toggle="modal" data-target="#indiamodal"> <img class="img" style="display: block;width: 100px;height: 140px;padding: 29px 10px;margin: 0 auto 10px;border-bottom: 1px solid #ececec;text-align: center;" src="img/india.png" alt="شحن Free Fire">الشحن الهندي</a> <a class="btn btn-app" data-toggle="modal" data-target="#netflexmodal"> <img class="img" style="display: block;width: 100px;height: 140px;padding: 29px 10px;margin: 0 auto 10px;border-bottom: 1px solid #ececec;text-align: center;" src="img/netflix.png" alt="شحن Free Fire">NETFLIX</a> <a class="btn btn-app" data-toggle="modal" data-target="#fortnitexmodal"> <img class="img" style="display: block;width: 100px;height: 140px;padding: 29px 10px;margin: 0 auto 10px;border-bottom: 1px solid #ececec;text-align: center;" src="img/fortnite.png" alt="شحن Free Fire">FORTNITE</a> <a class="btn btn-app" data-toggle="modal" data-target="#fortnitexmodal"> <img class="img" style="display: block;width: 100px;height: 140px;padding: 29px 10px;margin: 0 auto 10px;border-bottom: 1px solid #ececec;text-align: center;" src="img/callofduty.png" alt="شحن Free Fire">CALL OF DUTY</a> </div> </div> </div> </div> <div class="box box-info"> <div class="box-header with-border"> <h3 class="box-title">آخر المعاملات</h3> </div> <div class="box-footer clearfix"> <a class="btn btn-sm btn-default btn-flat pull-right" href="https://www.martship.com/transactions">كل التعاملات </a> </div> </div> </div> </div> </section><!-- end of content --> </div><!-- end of content wrapper --> <?php include "inc/footer.php"; ?> <script> $(document).ready(function(){ function load_unseen_notification(view = '') { $.ajax({ url:"fetchh.php", method:"POST", data:{view:view}, dataType:"json", success:function(data) { $('.dropdown-menu').html(data.notification); if(data.unseen_notification > 0) { $('.count').html(data.unseen_notification); bong.play(); } } }); } load_unseen_notification(); var bong = document.createElement('audio'); bong.setAttribute('src', '../pristine-609.mp3'); setInterval(function(){ load_unseen_notification(); }, 5000); $(document).on('click', '.toggle', function(){ $('.count').html(''); load_unseen_notification('yes'); }); //Then put this in your code where you want to play the sound }); </script> <script> $(function() { $(document).on('input change', '#game', function() { var offers; console.log($(this).val()) if ($(this).val() == "pubg") { offers = ` <?php foreach ($offers['pubg'] as $key => $offer) { $price = ($key != 0) ? ' (' . $prices_da_baridimob['pubg'][$offer] . ' DA)' : ''; $price_paysera = ($key != 0) ? ' - (' . $prices_paysera['pubg'][$offer] . ' EUR)' : ''; ?> <option value="<?= $key ?>" style="font-weight: bold;font-size: 18px"><?= $offer . $price .$price_paysera?></option> <?php } ?>`; } else { offers = `<?php foreach ($offers['freefire'] as $key => $offer) { $price = ($key != 0) ? ' (' . $prices_da_baridimob['freefire'][$offer] . ' DA)' : ''; $price_paysera = ($key != 0) ? ' - (' . $prices_paysera['freefire'][$offer] . ' EUR)' : ''; ?> <option value="<?= $key ?>" style="font-weight: bold;font-size: 18px"><?= $offer . $price .$price_paysera ?></option> <?php } ?>`; } $('#amount_offers').html(offers); }) }) </script> <?php if (isset($_POST['Submitfree'])) { if (empty($_POST['amount']) || empty($_POST['playerid']) || empty($_POST['confirm_playerid']) || empty($_POST['playername'])) { $error = "الرجاء ملأ جميع الخانات المطلوبة"; } else { $playerid= @htmlentities(strip_tags($_POST['playerid'])); $confirm_playerid = @htmlentities(strip_tags($_POST['confirm_playerid'])); $playername = @htmlentities(strip_tags($_POST['playername'])); $amount = @htmlentities(strip_tags($_POST['amount'])); if ($playerid != $confirm_playerid) { $error = "رقم id غير متطابقين"; } if (empty($error)) { $created_at = date('Y-m-d H:i:s', time()); $updated_at = date('Y-m-d H:i:s', time()); $sql = "insert into ordermag (typecharge,amount,playerid,playername,ordercase,user_id,creat_at,shopname) values ('freefire','$amount','$playerid','$playername','0','$userid','$created_at','$shopname')"; $insert = mysqli_query($conn, $sql); if ($insert) { ?> <script type="text/javascript"> alert('تم الاضافة بنجاح'); </script> <?php $orderid = mysqli_insert_id($conn); $subject = mysqli_real_escape_string($conn, "طلب شحن بقيمة ($amount) بدون تسجيل "); $comment = mysqli_real_escape_string($conn, "رقم الطلب #$orderid "); $user = mysqli_real_escape_string($conn, $playername); $date = date("d-m-Y h:ia"); }else{ echo mysqli_errno($conn); } } } } ?> <?php if (isset($_POST['Submitfree'])) { if (empty($_POST['amount']) || empty($_POST['playerid']) || empty($_POST['confirm_playerid']) || empty($_POST['playername'])) { $error = "الرجاء ملأ جميع الخانات المطلوبة"; ?> <script type="text/javascript"> alert('الرجاء ملأ جميع الخانات المطلوبة'); </script> <?php } else { $name = $_POST['name']; $playerid = $_POST['playerid']; $playername = $_POST['playername']; $confirm_playerid = @htmlentities(strip_tags($_POST['confirm_playerid'])); $amount = $_POST['amount']; $date = date("d-m-Y h:ia"); if ($playerid != $confirm_playerid) { $error = "رقم id غير متطابقين"; ?> <script type="text/javascript"> alert('رقم id غير متطابقين'); </script> <?php } if (empty($error)) { $sql = "insert into ordermag (typecharge,amount,playerid,playername,ordercase,user_id,creat_at,updat_at,shopname) values ('freefire','$amount','$playerid','$playername','0','$userid','$date','$date','$shopname')"; $result = $conn->query($sql); if ($result) { ?> <script type="text/javascript"> alert('تم الاضافة بنجاح'); </script> <?php $subject = mysqli_real_escape_string($conn, "طلب شحن بقيمة ($amount) نوع الشحن ({$games[$chargtype]}) "); $comment = mysqli_real_escape_string($conn, "اسم اللاعب ($playername) "); $user = mysqli_real_escape_string($conn, $userfullname); $date = date("d-m-Y h:ia"); $query = " INSERT INTO comments(comment_subject, comment_text,comment_user,comment_time,comment_status, sorting,user_id,user_type ) VALUES ('$subject', '$comment','$user','$date','0','1','$userid','$user_type') "; mysqli_query($conn, $query); echo ' <script type="text/javascript"> alert("تم الاضافة"); </script>'; } else { echo mysqli_error($conn); ?> <script type="text/javascript"> alert("عفوا ، error"); </script> <?php } } } } ?> <script> $(document).ready(function() { function load_unseen_notification(view = '') { $.ajax({ url: "fetchh.php", method: "POST", data: { view: view }, dataType: "json", success: function(data) { $('.dropdown-menu').html(data.notification); if (data.unseen_notification > 0) { $('.count').html(data.unseen_notification); bong.play(); } } }); } load_unseen_notification(); var bong = document.createElement('audio'); bong.setAttribute('src', '../pristine-609.mp3'); setInterval(function() { load_unseen_notification(); }, 5000); $(document).on('click', '.toggle', function() { $('.count').html(''); load_unseen_notification('yes'); }); //Then put this in your code where you want to play the sound }); </script> </div> هدا صفحة الدخول <?php error_reporting(0); session_start(); include "inc/header.php" ;?> <title>شحن اللعبة | دخول </title> <!--header--> <div class="projects-2 section " style="background: #fff;"> <div class="container"> <div class="row "> <div class="col-md-12"> <h1 style="color: #064a6e">تسجيل الدخول <span> </span></h1> </div> </div> <div class="row"> <div class="container"> <?php error_reporting(0); include('connect.php'); ?> <div class="row"> <div class="col-md-12"> <div class="featured-boxes"> <div class="row"> <div class="col-sm-6"> <div class="featured-box featured-box-primary align-left mt-xlg clear" id="login-form" style=""> <div class="box-content bg_Lightgrey"> <h4 class="heading-primary text-uppercase mb-md">لديك حساب حالي ؟</h4> <form action="" id="frmSignIn" method="post"> <div class="alert alert-danger" id="error" style="display: none"> <strong>خطأ!</strong> بيانات الدخول غير صحيحة </div> <div class="alert alert-danger" id="erroractive" style="display: none"> <strong>خطأ!</strong> هذا الحساب بانتظار التفعيل من قبل الادارة ، سيتم التفعيل فى اقرب وقت شكرا </div> <div class="alert alert-danger" id="errorstop" style="display: none"> <strong>خطأ!</strong> هذا الحساب موقوف </div> <div class="alert alert-success" id="success" style="display: none"> <strong>تهانينا!</strong> نجاح العملية يرجى الانتظار جارى التوجيه... </div> <div class="form-group"> <div class="col-md-12"> <label for="LoginEmail">اسم المستخدم<span class="required">*</span></label> <input type="text" name="userName" value="<?php if (isset($_COOKIE["member_login"])) { echo $_COOKIE["member_login"]; } ?>" class="form-control input-md" required="required" placeholder="أدخل اسم المستخدم"> </div> </div> <div class="form-group"> <div class="col-md-12"> <label for="Password">كلمة المرور <span class="required">*</span></label> <input type="password" value="<?php if (isset($_COOKIE["member_login"])) { echo $_COOKIE["member_login"]; } ?>" name="password" class="form-control input-md" required="required" placeholder="أدخل كلمة المرور"> </div> </div> <div class="form-group"> <div class="col-md-12"> <span><input type="checkbox" <?php if (isset($_COOKIE["member_login"])) { ?> checked <?php } ?> name="remember">تذكرنى </span><br> <a href="forgetpass.php">نسيت كلمة السر؟</a> </div> </div> <div class="row mt-lg"> <div class="col-md-12"> <input type="submit" name="login" value="تسجيل الدخول" class="btn btn-primary pull-left mb-xl"> </div> </div> </form> </div> </div> </div> </div> </div> </div> </div> </div> </div> <?php include "inc/footer.php"; ?> <?php error_reporting(0); if (isset($_POST["login"])) { $userName = htmlspecialchars($_POST['userName']); $password = htmlspecialchars($_POST['password']); $countc = 0; $resc = mysqli_query($conn, "select * from users where BINARY userName='$userName' && BINARY password= '$password' && active = '0' LIMIT 1"); $countc = mysqli_num_rows($resc); if ($countc > 0) { ?> <script type="text/javascript"> document.getElementById("erroractive").style.display = "block"; </script> <?php } else { $counts = 0; $ress = mysqli_query($conn, "select * from users where BINARY userName='$userName' && BINARY password= '$password' && active = '2' LIMIT 1"); $counts = mysqli_num_rows($ress); if ($counts > 0) { ?> <script type="text/javascript"> document.getElementById("errorstop").style.display = "block"; </script> <?php } else { $count = 0; $res = mysqli_query($conn, "select * from users where BINARY userName='$userName' && BINARY password= '$password' && active = '1' LIMIT 1"); $count = mysqli_num_rows($res); if ($count == 0) { ?> <script type="text/javascript"> document.getElementById("error").style.display = "block"; </script> <?php } else { $sql = "select * from users where BINARY userName='$userName' && BINARY password= '$password' && active = '1' "; $result = $conn->query($sql); while ($row = $result->fetch_assoc()) { $logintype = $row['logintype']; if ($logintype == 1) { ?> <script type="text/javascript"> document.getElementById("success").style.display = "block"; </script> <?php $_SESSION["userName"] = $userName; ?> <script type="text/javascript"> window.location = "Supermarket"; </script> <?php } elseif ($logintype == 2) { ?> <script type="text/javascript"> document.getElementById("success").style.display = "block"; </script> <?php $_SESSION["userName"] = $userName; ?> <script type="text/javascript"> window.location = "client"; </script> <?php } } } } } } ?> والمشكلة لايوجه الى داش بورد <script type="text/javascript"> window.location = "Supermarket"; </script>1 نقطة
-
كيف اقوم بربط جدولين داخل قاعدة البيانات بإستخدام php يعني لدي جدول user و جدول notification كيف اربطهم1 نقطة
-
يمكنك فعل ذلك من خلال استخدام inner join بهذه الطريقة $con = mysqli_connect("localhost", "root", "123123", "database"); mysqli_query($con, "SELECT n.*, u.* FROM notification n inner join user u on n.user_id=u.id"); فلاحظ أننا قمنا بوضع مفتاح يعبر عن كل جدول فحرف n يعبر عن جدول notification و حرف u يعبر عن جدول user ثم قمنا بجلب كل من الحقول من كلا الجدولين باستخدام * ثم قمنا ربط الجدولين باستخدام inner join كما في المثال السابق . ايضا يمكنك ربط أكثر من جدول بهذه الطريقة من خلال استخدام inner join كذلك mysqli_query($con, "SELECT n.*, u.*, c.title FROM notification n inner join user u on n.user_id=u.id inner join chat c on c.user_id=u.id"); هل يمكنك إرفاق الكود الخاص بك لنساعدك في ربط الجداول ؟1 نقطة
-
1 نقطة
-
أحاول تثبيت تنسرفلو باستخدام pip، لكن يظهر لي هذا الخطأ، مالحل؟ $ pip install tensorflow --user Collecting tensorflow Could not find a version that satisfies the requirement tensorflow (from versions: ) No matching distribution found for tensorflow1 نقطة
-
بالتأكيد حجم المنافسة على الكلمات يُعد عاملًا مهمًا، حيث أن الكلمات التي لديها منافسة كبيرة سيكون من الصعب جدًا على مدونة جديدة أن تنافس عليها. الأفضل المدونات الحديثة نسبيًا أن تستهدف كلمات عليها منافسة منخفضة وفي نفس الوقت تحصل هذه الكلمات على عدد لا بأس به من الزيارات، وعندما يزداد عدد زوار المدونة ومصداقية المدونة في محركات البحث، فيمكن أن تستهدف كلمات عليها منافسة أعلى، وهكذا. أيضاً أنصحك أن تستهدف الكلمات المفتاحية الطويلة نسبياً والتي تكون محددة للغاية، حيث أن هذه الكلمات يكون عليها عدد زيارات صغير أو متوسط، ولكن المنافسة عليها ليست كبيرة، وبالتالي يمكن أن تنافس على هذه الكلمات المدونات الجديدة بسهولة. بالطبع لن يستمر الوضع على ما هو عليه إلى الأبد، فكما أشرت سابقًا بمجرد أن يزداد عدد الزوار ويزداد ال authority الخاص بالمدونة، سيمكنك أن تنافس على كلمات أصعب مع الوقت.1 نقطة
-
السلام عليكم أخي هناك دورات أكاديمية مجانية كثيرة في اليوتيوب يمكنك الإعتماد عليها في تطوير مهاراتك وهناك أيضا موقع إدراك فهو مجاني ويعطيك شهادة بعد إكمال الدورة إذا تريد تطوير مهارتك يجب عليك التطبيق كثيرا والقيام بالتغذية البصرية وحاول أن تخرج بستايل خاص بك تحياتي1 نقطة
-
في CSS يتم تمثيل كل عنصر من عناصر مستند HTML عند إنشائه في المستعرض بأربعة مستطيلات متداخلة تشمل نموذج الصندوق و هي من الداخل و الخارج: محتوى العنصر "Content": و يمثل كل ما هو موجود بين وسمي بداية و نهاية العنصر. الإزاحة الداخلية "padding": و تمثل المسافة الفاصلة بين محتوى العنصر و بين حدوده. الحدود "border": و يمثّل الإطار الخارجي للعنصر. الإزاحة الخارجيّة "margin": و تمثّل المسافة الفاصلة بين حدود العنصر و العناصر الأخرى في المستند.1 نقطة