لوحة المتصدرين
المحتوى الأكثر حصولًا على سمعة جيدة
المحتوى الأعلى تقييمًا في 05/24/23 في كل الموقع
-
2 نقاط
-
السلام عليكم أنا مبتدأ في مجال البرمجة و أشتغل على إنشاء تطبيق جوال ANDROID APP من خلال برنامج FLUTTER و لغة DART وصلت لمرحلة إنشاء صفحة إقتناء المشتريات items ووضعها في سلة الشراء PANIER(BASKET). للأسف واجهتني مشكلة تتمثل كالتالي : E/flutter ( 4867): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: type 'int' is not a subtype of type 'String?' in type cast E/flutter ( 4867): #0 SharedPreferences.getString (package:shared_preferences/shared_preferences.dart:112:58) E/flutter ( 4867): #1 Paniercontroller.add (package:dadou/controler/paniercontroller.dart:27:37) E/flutter ( 4867): #2 Itemsdetails.build.<anonymous closure> (package:dadou/view/screen/itemsdetails.dart:26:43) كل ما أريد إضافة قطعة (buy items) أي عندما أضغط على + / - على سبيل المثال لزيادة أو إنقاص القطع يظهر هذا الخطأ . ممكن شخص لديه خبرة أن يساعدني في حل هذا المشكل . مع الشكر بارك الله فيكم ! BACKEND : PHP MYSQL مع العلم أني قمت بإختبار BACKEND و عمل بشكل عادي تجدون في المرفقات جميع الأكواد . على حسب ما توصلت إليه أظن أن الخطأ متأتي عند الإشتغال على users_id شكرا لتفاعل جميع الأطراف . في إنتظار ردكم ! itemsdetailscontroller.dart logincontroller.dart paniercontroller.dart crud.dart itemsdetails.dart items.dart1 نقطة
-
السلام عليكم لقد قمت ب host لمشروعي in github pages لكن لسبب ما يخبرني المتصفح انه enable js to run this app *------------------------------------------- from scratch: اتبعت فيديو عن كيفية host a react app باستعمال npm install gh-pages --save-dev و اضفت بال package.json : "homepage": "http://boumlik000.github.io/portfilioBM", "homepage": "http://boumlik000.github.io/portfilioBM", "deploy":"gh-pages -d build", "build": "react-scripts build", ومن تم اعمل npm run deploy لكن للاسف يظهر لي هدا اللخطا ملفات المشروعportfilio.rar1 نقطة
-
تم شرح كيفية رفع مشروع React على github pages في النقاشات التالية: قم بقرائتها وإتباع الخطوات وإذا واجهتك مشكلة ابحث عن الحل أولاً لتستفيد ثم ضع استفسارك هنا.1 نقطة
-
1- لنبدأ بحل كل رسالة تحذير وخطأ على حدى، ورسالة التحذير الأولى هي: wrning: You are importing createRoot from "react-dom" which is not supported. You should instead import it from "react-dom/client". p وهي تعني أنك تستخدم createRoot من react-dom وهو غير مدعوم في الإصدار الحالي من React وهو الإصدار 18و بدلاً من ذلك، يجب عليك استيراده من react-dom/client. 2- والتحذير الثاني لديك هو: warning: ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://reactjs.org/link/switch-to-createroot p ويعني أن استخدام ReactDOM.render لم يعد مدعومًا في React 18 ويجب استخدام createRoot بدلاً من ذلك، ويوضح أنه حتى تقوم بالتبديل إلى الواجهة البرمجية الجديدة، سيتصرف تطبيقك كما لو كان يعمل بناءً على React 17. حيث في React 18، تم إدخال تغييرات في طريقة تجهيز وتقديم التطبيق، وبدلاً من استخدام ReactDOM.render لتقديم التطبيق في نقطة الدخول الرئيسية، يجب استخدام createRoot لإنشاء نقطة جذرية وتقديم التطبيق من خلالها. 3- وأيضًا لديك مشكلة أخرى وهي تستخدم الكود التالي في ملفي App.jsx وindex.jsx: App.jsx createRoot(document.getElementById('root')).render(<App />); في ملف index.jsx ReactDOM.render( <BrowserRouter> <App /> </BrowserRouter>, document.getElementById('root') ); وهو يقوم بإنشاء نقطة جذرية باستخدام createRoot من react-dom ويقوم بتقديم مكون التطبيق <App /> من خلالها. أي يجب وضعه في مكان واحد فقط وهو index.jsx فهو نقطة الدخول لتطبيقك في webpack. 4- المشكلة الأخيرة هي أنك تقوم بتضمين المكون App داخل أكثر من Router حيث أنك في الملف App.jsx تضعه داخل Router والتي هي BrowserRouter وفي index.jsx تضعه داخل BrowserRouter أخرى. وذلك غير مسموح بالطبع وسيسبب لديك مشكلة في التطبيق. 5- لماذا قمت بإنشاء ملفي App.jsx وindex.jsx فكل ما تحتاجه هو index.jsx كملف رئيسي للمشروع. 6- كمعلومة جانبية أنت لست بحاجة إلى استيراد React في بداية أي مكون في إصدار React 18. وبناءًا على ما سبق عليك تعديل الكود في ملف index.jsx كما يلي: // react-dom/client الاستيراد من import { createRoot } from 'react-dom/client'; import './App.css'; import './index.css'; import reportWebVitals from './reportWebVitals'; import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; import Home from './pages/home'; import 'bootstrap/dist/css/bootstrap.css'; import 'bootstrap/dist/js/bootstrap.min.js'; import '@fortawesome/fontawesome-free/css/all.css'; createRoot(document.getElementById('root')).render( // استخدم createRoot بدلاً من ReactDOM.render <Router> <Routes> <Route path='/' element={<Home />} /> </Routes> </Router> ); reportWebVitals(); أي قم بحذف ملف App.jsx واستخدم الكود السابق فقط في index.jsx1 نقطة
-
أنا أعمل على مشروع بـ MERN stack وقمت بإنشاء دالة في ملف تسجيل الدخول ولا أريدها أن تبدأ عملها حتى تنتهي دالة أخرى موجودة في ملف آخر من عملها وهو استدعاء بيانات محددة من قاعدة البيانات بغض النظر عن الدالة ... لنفترض أنها ()console.log فهل يمكن ذلك؟ وكيف؟ بالمناسبة أنا أعرف طريقة استخدام async و await ولكن المشكلة أعرف طريقة استخدامها لدالتين في نفس الملف ولكن ملفين مختلفين للأسف لا أعرف1 نقطة
-
- الفرق بين الParameters والArguments هو أن الParameters هي المتغيرات التي تحدد في تعريف الFunction، بينما الArguments هي القيم التي تمرر إلى الFunction عند استدعائها. مثلا، في الFunction التالية: function add(x, y) { return x + y; } x و y هما Parameters، وعندما نستدعي الFunction بهذه الطريقة: add(3, 5); 3 و 5 هما Arguments. - الفرق بين الDeclaration والInitialization هو أن الDeclaration هو إعلان اسم ونوع المتغير أو الFunction دون تحديد قيمته، بينما الInitialization هو تعيين قيمة مبدئية للمتغير أو الFunction. مثلا، في هذا المثال: var x; // declaration x = 10; // initialization function square(n) { // declaration and initialization return n * n; } - الExecution Context هو بيئة تشغيل للكود في لغة برمجة معينة. يحدد ما هي المتغيرات والFunctions والObjects المتاحة للكود في ذلك الوقت. عادة، يوجد Execution Context عام للبرنامج ككل، وExecution Contexts محلية لكل Function تستدعى. مثلا، في هذا المثال: var x = 10; // global variable function add(y) { // global function var z = 5; // local variable return x + y + z; } console.log(add(7)); // 22 console.log(z); // error: z is not defined الExecution Context العام يحتوي على المتغير x والFunction add، بينما Execution Context للFunction add يحتوي على المتغير y (الذي يأخذ قيمة Argument) والمتغير z (الذي يعرف داخل الFunction). عندما نستدعي add(7)، يتم إنشاء Execution Context جديد للFunction add ويتم تشغيل كودها داخله. عندما نحاول طباعة قيمة z خارج الFunction، نحصل على خطأ لأن z غير معرف في Execution Context العام. - الLexical Environment هو مفهوم مرتبط بالExecution Context، وهو يشير إلى كيفية تحديد مجال (Scope) المتغيرات والFunctions في لغة برمجة معينة. يعتمد الLexical Environment على كيفية كتابة الكود (Lexical Structure)، وليس على كيفية تشغيله (Dynamic Structure). مثلا، في هذا المثال: var x = 10; // global variable function foo() { // global function var y = 20; // local variable in foo function bar() { // local function in foo var z = 30; // local variable in bar console.log(x + y + z); // 60 } bar(); // call bar } foo(); // call foo الLexical Environment يحدد أن المتغير x متاح للFunctions foo و bar، وأن المتغير y متاح فقط للFunction foo و bar (ولكن ليس للبرنامج الرئيسي)، وأن المتغير z متاح فقط للFunction bar (ولكن ليس للFunction foo أو للبرنامج الرئيسي). هذه التراتبية في المجالات تسمى Lexical Scoping أو Static Scoping.1 نقطة
-
1 نقطة
-
يتم استخدام خاصية before في CSS لعرض محتوى معين قبل العنصر الذي تتم معالجته. ومع ذلك، فإنه لا يمكن استخدام خاصية before مع عنصر input كما تم الشرح في التعليقات السابقة علاوة على ذلك، فإن استخدام الخاصية before مع عنصر input من نوع search قد يؤدي إلى تغيير مظهر العنصر بشكل غير مرغوب فيه، وذلك بسبب كيفية تنسيق المتصفحات لهذا النوع من العنصر. بدلاً من ذلك، يمكن استخدام الصورة كخلفية لعنصر input والتحكم في مظهرها باستخدام CSS. ومن الممكن أيضافة الصورة باستخدام خاصية background-image في CSS، ويمكن تعديل مظهر الصورة باستخدام خصائص مثل background-position و background-size وغيرها. على سبيل المثال، يمكن استخدام الكود التالي لإضافة ايقونة البحث كخلفية لعنصر input من نوع search: input[type="search"] { background-image: url("path/to/search-icon.png"); background-position: right center; background-repeat: no-repeat; padding-right: 20px; /* تعديل هذه القيمة حسب حجم الصورة */ } يتم وضع الصورة كخلفية للعنصر input باستخدام خاصية background-image. كما يتم تحديد موضع الصورة باستخدام خاصية background-position، وتحديد عدم تكرار الصورة باستخدام خاصية background-repeat. وأخيرًا، يتم إضافة بعض التباع باستخدام خصائص الحشو (padding) لضمان عدم تغطية النص بالصورة. ومثال على ذلك يمكنك الإطلاع على ال pen التالية https://codepen.io/tippingpointdev/pen/WNZbWGe1 نقطة
-
في CSS، تعتبر ":before" عنصر زائف وتستخدم لإضافة محتوى قبل عنصر محدد، بينما ":after" تستخدم لإضافة محتوى بعد عنصر محدد، وغالبًا تستخدم هاتين الخاصيتين لإضافة عناصر مرئية إلى عناصر HTML الموجودة. لكن في حالة عنصر الإدخال "input"، يعتبر استخدام ":before" غير صحيح ولا يعمل على نحو صحيح.، ذلك لأنه عنصر الإدخال "input" يعتبر عنصراً فارغاً لا يحتوي على محتوى داخلي. مما يعني أنه لا يمكنك استخدام ":before" لإضافة محتوى قبل عنصر "input" نفسه. وإذا كنت ترغب في إنشاء رمز البحث داخل عنصر الإدخال "input"، تستطيع استخدام العديد من الطرق لتحقيق ذلك. واحدة من هذه الطرق هي وضع عنصر فرعي مستقل للعنصر "input" واستخدام الخاصية "position: absolute" لتحديد موقع الرمز بالنسبة لعنصر الإدخال، كتعيين أيقونة لرمز البحث كخلفية للعنصر الفرعي وتنسيقه بواسطة CSS ليتناسب مع المظهر المطلوب. وسأوضح لك الكود اللازم لإنشاء رمز البحث داخل عنصر الإدخال "input" باستخدام خاصية "position: absolute": HTML: <div class="search-container"> <input type="text" class="search-input"> <span class="search-icon"></span> </div> CSS: .search-container { position: relative; } .search-icon { position: absolute; top: 50%; right: 10px; transform: translateY(-50%); width: 20px; height: 20px; background-image: url('path/to/search-icon.png'); background-size: cover; } .search-input { padding-right: 30px; } حيث قمت بإنشاء عنصر <div> لتكون حاوية لعنصر الإدخال "input" وعنصر الرمز "span". ثم بتعيين الخاصية "position: relative" للعنصر الحاوي .search-container ليمكن تحديد موقع العناصر الفرعية بالنسبة له. وتحديد الخاصية "position: absolute" للعنصر الفرعي .search-icon ليمكن تحديد موقعه بالنسبة للحاوية. ثم استخدمت الخاصيات top, right, transform لتحديد موقع الرمز في الزاوية العلوية اليمنى ووسط العنصر، قمت أيضًا بتعيين خلفية للرمز باستخدام خاصية "background-image" وتحديد حجمها باستخدام "background-size". أخيرًا، تعديل حجم عنصر الإدخال "input" بإضافة الحشو على الجانب الأيمن ليسمح بوجود مساحة كافية لعرض الرمز داخل العنصر. ولا تنسى استبدال 'path/to/search-icon.png' بمسار الصورة الخاصة بأيقونة البحث، أو تستطيع استخدام أيقونة من Font Awesome. وإليك مثال إذا أردت استخدام Font Awesome: HTML: <!DOCTYPE html> <html> <head> <!-- استيراد Font Awesome --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw==" crossorigin="anonymous" referrerpolicy="no-referrer" /> </head> <body> <div class="search-container"> <input type="text" class="search-input"> <span class="search-icon"><i class="fas fa-search"></i></span> </div> </body> </html> قمت بتحديد الأيقونة المستخدمة لرمز البحث باستخدام العنصر <i> ومنحها الفئات المناسبة .fas و .fa-search، حيث .fas تعني استخدام الأيقونات الخطية (Solid) و.fa-search تعني استخدام أيقونة البحث. CSS: .search-container { position: relative; } .search-icon { position: absolute; top: 50%; right: 10px; transform: translateY(-50%); width: 20px; height: 20px; color: #999; /* لتغيير لون الأيقونة */ } .search-input { padding-right: 30px; }1 نقطة
-
أخي مازال يطبع auth فارغ في الضغطة لأولى على login وعندما أضغط مرة أخرى ينجح الأمر كما كان في السابق ولكن تأخرت الاستجابة قليلا بسبب await أنا أريد أن يتم طباعة بيانات الـ auth من الضغطة الأولى والتي هي عبارة عن بيانات المستخدم و الرمز المميز token1 نقطة
-
السبب في عدم صلاحية استخدام خاصية before مع عنصر input هو أن before تقوم بإضافة محتوى داخل العنصر التي يتم إضافتها له، وعنصر input لا يمكن أن يحتوي على عناصر أخرى. لذلك، لا يمكن تطبيق هذه الخاصية على عنصر input. إذا كنت تريد إضافة محتوى قبل أو بعد عنصر input، فقد تحتاج إلى استخدام عنصر آخر كحاوية له. قد يقيدك الاطلاع على المصادر التالية : العنصر input في موسوعة حسوب مقالة عن مربعات البحث :1 نقطة
-
تعتبر كل من بيئات العمل على الحاسوب المحلية (مثل Jupyter) والبيئات السحابية (مثل Google Colab) خيارات جيدة لتعلم الآلة وتحليل البيانات. ولكل منهما مزايا وعيوب: بيئات العمل على الحاسوب المحلية: مزايا: لديك السيطرة الكاملة على البيئة وإمكانية تثبيت أي برامج أو مكتبات تحتاجها. عادة ما تكون أسرع في التنفيذ لأن الحوسبة تتم محليًا. عيوب: تحتاج إلى موارد حاسوب قوية محلية لتشغيل التعلم الآلي. يمكن أن يكون الإعداد والصيانة معقدًا. البيئات السحابية مثل Google Colab: مزايا: لا تحتاج إلى موارد حاسوب قوية محلية. تستضيف Google البيئة وتوفر معالجة GPU. سهل الإعداد والاستخدام. لا تحتاج إلى تثبيت أي برامج. عيوب: ليس لديك السيطرة الكاملة على البيئة. محدودة بما توفره Google. عادة ما تكون أبطأ في التنفيذ بسبب الوقت اللازم لنقل البيانات إلى السحابة ومنها. لذا أنصح باستخدام كليهما واختيار الأنسب حسب المهمة. يمكنك استخدام بيئة محلية عند الحاجة إلى سرعة عالية أو مرونة كاملة، واستخدام Colab عند الحاجة إلى موارد GPU دون التزام بشراء حاسوب قوي. نعم، بشكل عام تعتبر بيئات العمل المحلية أكثر متعة في الاستخدام لأنها توفر لك حرية أكبر في التحكم وتخصيص البيئة. وبالنسبة لمعالجات M1 و M2 الجديدة في أجهزة ماك، فهي ممتازة لتعلم الآلة وتحليل البيانات. وذلك لأسباب عدة: تقدم M1 و M2 أداءً عاليًا مع كفاءة عالية في استهلاك الطاقة، مما يجعلها مثالية لأعمال الحوسبة الدقيقة مثل التعلم الآلي. تأتي M1 و M2 بوحدات معالجة الرسوميات (GPU) عالية الأداء مدمجة، والتي تفيد كثيرًا في تسريع التدريب على الشبكات العصبية. تدعم M1 و M2 معمارية ARM التي تميل إلى أن تكون أكثر كفاءة في الطاقة من معماريات x86 التقليدية. وهذا يفيد في تشغيل النماذج الحسابية المعقدة لفترة أطول. يوجد العديد من إطارات العمل الشهيرة في مجال التعلم الآلي مثل TensorFlow و PyTorch و Keras مدعومة جيدًا على المعالجات M1 و M2. لذلك، إذا كان بمقدورك شراء جهاز ماك الجديد بمعالج M1 أو M2، فسيكون خيارًا رائعًا لتعلم الآلة والحوسبة على الحاسوب الشخصي.1 نقطة
-
يمكن للغة جافاسكربت JavaScript إرسال طلبات شبكة إلى الخادم وتحميل معلومات جديدة عندما يتطلب الأمر ذلك، إذ يمكننا على سبيل المثال استخدام طلبات الشبكة في الحالات التالية: إرسال طلب. بتحميل معلومات مستخدم. الحصول على آخر التحديثات من الخادم. ويجري كل ذلك دون إعادة تحميل الصفحة. تنضوي طلبات الشبكة التي تنفذها لغة JavaScript تحت المظلة AJAX، وهي اختصار للعبارة Asynchronous JavaScript And XML، ورغم ذلك لا نحتاج إلى استخدام XML، فقد وضعت العبارة السابقة منذ فترة طويلة لذلك وجدت هذه الكلمة ضمنها، وقد تكون سمعت بهذه العبارة الآن أيضًا. هنالك طرق عديدة لإرسال طلبات عبر الشبكة والحصول على معلومات من الخادم، وسنبدأ بالطريقة الأحدث ()fetch، علمًا أنه لا تدعم المتصفحات القديمة هذه الدالة (ويمكن الاستعاضة عنها بشيفرة بديلة)، لكنها مدعومة جيدًا في المتصفحات الحديثة، وإليك صيغتها: let promise = fetch(url, [options]) حيث: url: عنوان المورد الذي ستصل إليه الدالة. options: المعاملات الاختيارية من توابع وترويسات وغيرها. تتحول الدالة إلى طلب GET بسيط لتنزيل محتوى العنوان url إن لم تكن هناك معاملات اختيارية options، ويبدأ المتصفح الطلب مباشرةً ويعيد وعدًا promise ستستخدمه الشيفرة التي تستدعي الطلب للحصول على النتيجة، وتكون الاستجابة عادةً عمليةً بمرحلتين: الأولى: يُحلَّل الوعد الذي تعيده fetch عبر كائن من الصنف Respo-nse حالما يستجيب الخادم بالترويسات المناسبة، ويمكن التحقق من نجاح الطلب أو عدم نجاحه، والتحقق أيضًا من الترويسات، لكن لن يصل جسم الطلب في هذه المرحلة، ويُرفَض الوعد إن لم تكن fetch قادرةً على إنجاز طلب HTTP لمشاكل في الشبكة مثلًا، أو لعدم وجود موقع على العنوان المُعطى، ولن تسبب حالات HTTP غير العادية مثل 404 أو 500 أخطاءً. يمكن معرفة حالة طلب من خصائص الاستجابة: status: رمز الحالة status code لطلب HTTP مثل الرمز 200. ok: قيمة منطقية "true" عندما يكون رمز الحالة بين 200 و299. إليك المثال التالي: let response = await fetch(url); if (response.ok) { // إن كان رمز الحالة بين 200-299 // الحصول على جسم الطلب let json = await response.json(); } else { alert("HTTP-Error: " + response.status); } الثانية: استخدام استدعاء إضافي للحصول على جسم الطلب، ويؤمن الكائن Response عدة توابع مبنية على الوعد للوصول إلى جسم الطلب وبتنسيقات مختلفة: ()response.text: لقراءة الاستجابة وإعادة نص. ()response.json: يفسِّر النص وفق تنسيق JSON. ()response.formData: يعيد الاستجابة على شكل كائن FormData سنشرحه في الفقرة التالية. ()response.blob: يعيد الاستجابة على شكل كائن البيانات الثنائية Blob. ()response.arrayBuffer: يعيد الاستجابة على شكل كائن ArrayBuffer وهو تمثيل منخفض المستوى للبيانات الثنائية. الكائن response.body وهو كائن من الصنف ReadableStream يسمح بقراءة جسم الطلب كتلةً كتلةً، وسنعرض مثالًا عن ذلك لاحقًا. لنحاول على سبيل المثال الحصول على كائن JSON من آخر نسخة معتمدة لموقع الدورة التعليمية هذه على GitHub: let url = 'https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits'; let response = await fetch(url); let commits = await response.json(); // Json قراءة الاستجابة على شكل شيفرة alert(commits[0].author.login); See the Pen JS-P3-Fetch-ex02 by Hsoub (@Hsoub) on CodePen. كما يمكن فعل ذلك من خلال الوعود الصرفة دون استخدام await: fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits') .then(response => response.json()) .then(commits => alert(commits[0].author.login)); See the Pen JS-P3-Fetch-ex03 by Hsoub (@Hsoub) on CodePen. استخدم ()await response.text للحصول على نص الطلب بدلًا من ()json.: let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits'); let text = await response.text(); // قراءة جسم الاستجابة على شكل نص alert(text.slice(0, 80) + '...'); See the Pen JS-P3-Fetch-ex04 by Hsoub (@Hsoub) on CodePen. لنستعرض مثالًا عن قراءة بيانات بالصيغة الثنائية، ونحضر صورةً ما ونظهرها: let response = await fetch('https://javascript.info/article/fetch/logo-fetch.svg'); let blob = await response.blob(); // Blob تنزيل على شكل // <img> إنشاء عنصر let img = document.createElement('img'); img.style = 'position:fixed;top:10px;left:10px;width:100px'; document.body.append(img); // إظهاره img.src = URL.createObjectURL(blob); setTimeout(() => { // إخفاءه بعد ثلاث ثوان img.remove(); URL.revokeObjectURL(img.src); }, 3000); See the Pen JS-P3-Fetch-ex05 by Hsoub (@Hsoub) on CodePen. let text = await response.text(); // انتهاء معالجة جسم الطلب let parsed = await response.json(); // سيخفق، فقد جرت المعالجة وانتهت ترويسات الاستجابة يمكن الحصول على ترويسات الاستجابة على شكل كائن ترويسات شبيه بالترابط Map من خلال الأمر response.headers، ولا يُعَد الكائن ترابطًا تمامًا، لكنه يمتلك توابع مماثلةً للحصول على ترويسات من خلال اسمها أو بالمرور عليها: let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits'); // الحصول على ترويسة واحدة alert(response.headers.get('Content-Type')); // application/json; charset=utf-8 // المرور على الترويسات كلها for (let [key, value] of response.headers) { alert(`${key} = ${value}`); } See the Pen JS-P3-Fetch-ex06 by Hsoub (@Hsoub) on CodePen. ترويسات الطلب يمكن استخدام خيار الترويسات headers لإعداد ترويسة الطلب في الدالة featch، إذ تمتلك كائنًا يضم الترويسات المُرسَلة كالتالي: let response = fetch(protectedUrl, { headers: { Authentication: 'secret' } }); لكن هناك قائمة من ترويسات HTTP المنوعة التي لا يمكن ضبطها: Accept-Charset وAccept-Encoding Access-Control-Request-Headers Access-Control-Request-Method Connection Content-Length Cookie وCookie2 Date DNT Expect Host Keep-Alive Origin Referer TE Trailer Transfer-Encoding Upgrade Via Proxy-* Sec-* تضمن هذه الترويسات ملاءمة طلبات HTTP وأمانها، لذلك يتحكم فيها المتصفح حصرًا. طلبات الكتابة POST لإرسال طلب POST أو طلب من أي نوع لا بدّ من استخدام خيارات fetch: method: نوع طلب HTTP مثل HTTP-POST. body: ويمثل جسم الطلب وقد يكون: نصًا: بتنسيق JSON مثلًا. كائن FormData لإرسال بيانات على شكل form/multipart. كائن Blob أو BufferSourceلإرسال بيانات ثنائية. URLSearchParams لإرسال البيانات بتشفير x-www-form-urlencoded، وهو نادر الاستخدام. يُستخدم تنسيق JSON غالبًا، حيث تُرسل الشيفرة التالية الكائن user وفق تنسيق JSON مثلًا: let user = { name: 'John', surname: 'Smith' }; let response = await fetch('https://javascript.info/article/fetch/post/user', { method: 'POST', headers: { 'Content-Type': 'application/json;charset=utf-8' }, body: JSON.stringify(user) }); let result = await response.json(); alert(result.message); لاحظ ضبط الترويسة Content-Type افتراضيًا على القيمتين text/plain;charset=UTF-8 إذا كان جسم الطلب على شكل نص، لكن طالما أننا سنرسل البيانات بصيغة JSON، فسنستخدم الخيار headers لإرسال الترويسة application/json بدلًا عن text/plain كونها تمثل المحتوى الصحيح للبيانات. إرسال صورة يمكن إرسال بيانات ثنائية عبر الدالة fetch باستخدام الكائنات Blob أو BufferSource، سنجد في المثال التالي معرّف لوحة رسم <canvas> التي يمكننا الرسم ضمنها بتحريك الفأرة، ومن ثم إرسال الصورة الناتجة إلى الخادم عند النقر على الزر "submit": <body style="margin:0"> <canvas id="canvasElem" width="100" height="80" style="border:1px solid"></canvas> <input type="button" value="Submit" onclick="submit()"> <script> canvasElem.onmousemove = function(e) { let ctx = canvasElem.getContext('2d'); ctx.lineTo(e.clientX, e.clientY); ctx.stroke(); }; async function submit() { let blob = await new Promise(resolve => canvasElem.toBlob(resolve, 'image/png')); let response = await fetch('/article/fetch/post/image', { method: 'POST', body: blob }); // يستجيب الخادم بتأكيد وصول البيانات وبحجم الصورة let result = await response.json(); alert(result.message); } </script> </body> ستظهر النتيجة كالتالي: See the Pen JS-P3-Fetch-ex07 by Hsoub (@Hsoub) on CodePen. لاحظ أننا لم نضبط هنا قيمة الترويسة Content-Type يدويًا، لأنّ الكائن Blob له نوع مضمَّن (هو image/png في حالتنا، كما ولّده التابع toBlob)، وسيمثّل هذا النوع قيمة الترويسة Content-Type في كائنات Blob. يمكن كتابة الدالة ()submit دون استخدام الصيغة async/await كالتالي: function submit() { canvasElem.toBlob(function(blob) { fetch('https://javascript.info/article/fetch/post/image', { method: 'POST', body: blob }) .then(response => response.json()) .then(result => alert(JSON.stringify(result, null, 2))) }, 'image/png'); } استخدام الكائن FormData لإرسال النماذج يمكننا الاستفادة من الكائن FormData لإرسال نماذج HTML مع ملفات أو بدونها، بالإضافة إلى حقول إضافية. وكما قد تخمِّن؛ يمثل هذا الكائن بيانات نماذج HTML، وإليك صيغة الدالة البانية له: let formData = new FormData([form]); سيتحكم الكائن FormData تلقائيًا بحقول العنصر form إذا استُخدم في مستند HTML، وما يميز الكائن FormData هو أنّ توابع إرسال الطلبات واستقبالها عبر الشبكة مثل Fetch ستقبله مثل جسم للطلب، إذ يُشفَّر ويُرسَل بترويسة قيمتها Content-Type: multipart/form-data، وتبدو العملية بالنسبة إلى الخادم مثل إرسال عادي لنموذج. إرسال نموذج بسيط لنرسل أولًا نموذجًا بسيطًا، وسيظهر في مثالنا هذا في نموذج من سطر واحد: <form id="formElem"> <input type="text" name="name" value="John"> <input type="text" name="surname" value="Smith"> <input type="submit"> </form> <script> formElem.onsubmit = async (e) => { e.preventDefault(); let response = await fetch('https://javascript.info/article/formdata/post/user', { method: 'POST', body: new FormData(formElem) }); let result = await response.json(); alert(result.message); }; </script> وستكون النتيجة كالتالي: See the Pen JS-P3-FormData-ex01 by Hsoub (@Hsoub) on CodePen. لا توجد شيفرة خاصة بالخادم في هذا المثال، لأنها خارج نطاق هذه الدورة التعليمية، حيث سيقبل الخادم الطلب HTTP-POST ويستجيب بالرسالة "User saved" أي "خُزّن المستخدم". توابع الكائن نستخدم عددًا من التوابع لتعديل الحقول في الكائن FormData: (formData.append(name, value: يُضيف حقلًا بالاسم name قيمته هي value. (formData.append(name, blob, fileName: يضيف حقلًا كما لو أنه العنصر <"input type="file>، حيث يحدد الوسيط الثالث للتابع fileName اسم الملف -وليس اسم الحقل- كما لو أنه اسم لملف في منظومة ملفات الجهاز. (formData.delete(name: يزيل حقلًا محددًا بالاسم name. (formData.get(name: يعطي قيمة الحقل المحدد بالاسمname. (formData.has(name: إذا وجد حقل بالاسم name فسيعيد القيمة true وإلا false. يمكن أن يحوي النموذج العديد من الحقول التي لها نفس الاسم، لذلك سينتج عن الاستدعاءات المختلفة لضم append الحقول حقولًا لها نفس الاسم. وسنجد التابع set الذي له صيغة append نفسها، لكنه يزيل جميع الحقول التي لها اسم محدد name، ثم يضيف الحقل الجديد، وبالتالي سنضمن وجود حقل وحيد بالاسم name، تشبه باقي التفاصيل التابع append. (formData.set(name, value (formData.set(name, blob, fileName. يمكن أيضًا إجراء تعداد على عناصر الكائن FormData باستخدام الحلقة for..of: let formData = new FormData(); formData.append('key1', 'value1'); formData.append('key2', 'value2'); // قائمة من الأزواج مفتاح/قيمة for(let [name, value] of formData) { alert(`${name} = ${value}`); // key1 = value1, then key2 = value2 } See the Pen JS-P3-FormData-ex02 by Hsoub (@Hsoub) on CodePen. إرسال نموذج مع ملف يُرسَل النموذج دائمًا بحيث تكون ترويسة المحتوى مثل التالي Content-Type: multipart/form-data، وتسمح هذه الطريقة في الترميز بإرسال الملفات، أي ستُرسَل الملفات التي يحددها العنصر <"input type="file> أيضًا بشكل مشابه للإرسال الاعتيادي للنماذج، إليك مثالًا عن ذلك: <form id="formElem"> <input type="text" name="firstName" value="John"> Picture: <input type="file" name="picture" accept="image/*"> <input type="submit"> </form> <script> formElem.onsubmit = async (e) => { e.preventDefault(); let response = await fetch('https://javascript.info/article/formdata/post/user-avatar', { method: 'POST', body: new FormData(formElem) }); let result = await response.json(); alert(result.message); }; </script> ستظهر النتيجة كالتالي: See the Pen JS-P3-FormData-ex03 by Hsoub (@Hsoub) on CodePen. إرسال ملف يحتوي على كائن بيانات ثنائية يمكن أن نرسل بيانات ثنائيةً مولّدةً تلقائيًا، مثل الصور على شكل كائن بيانات Blob، وبالتالي يمكن تمريره مباشرةً مثل المعامل body للدالة Fetch كما رأينا في الفقرة السابقة، ومن الأنسب عمليًا إرسال صورة لتكون جزءًا من نموذج له حقول وبيانات وصفية Metadata وليس بشكل منفصل، إذ يُلائم الخوادم عادةً استقبال نماذج مشفرة مكونة من أجزاء متعددة أكثر من بيانات ثنائية خام. يُرسل المثال التالي صورةً مرسومةً ضمن العنصر <canvas>، بالإضافة إلى بعض الحقول على شكل نموذج باستخدام الكائن FormData: <body style="margin:0"> <canvas id="canvasElem" width="100" height="80" style="border:1px solid"></canvas> <input type="button" value="Submit" onclick="submit()"> <script> canvasElem.onmousemove = function(e) { let ctx = canvasElem.getContext('2d'); ctx.lineTo(e.clientX, e.clientY); ctx.stroke(); }; async function submit() { let imageBlob = await new Promise(resolve => canvasElem.toBlob(resolve, 'image/png')); let formData = new FormData(); formData.append("firstName", "John"); formData.append("image", imageBlob, "image.png"); let response = await fetch('https://javascript.info/article/formdata/post/image-form', { method: 'POST', body: formData }); let result = await response.json(); alert(result.message); } </script> </body> وتظهر النتيجة كالتالي: See the Pen JS-P3-FormData-ex04 by Hsoub (@Hsoub) on CodePen. لاحظ كيف يُضاف الكائن Blob الذي يمثل الصورة: formData.append("image", imageBlob, "image.png"); وهذا الأسلوب مشابه لاستخدام العنصر ضمن نموذج، حيث يرسل الزائر الملف الذي يحمل الاسم "image.png" (الوسيط الثالث) والذي يحمل البيانات التي يحددها imageBlob (الوسيط الثاني) انطلاقًا من منظومة الملفات، ويقرأ الخادم بيانات النموذج وكذلك الملف كما لو أنها عملية إرسال نموذج اعتيادية. خلاصة يتكون طلب إحضار بيانات تقليدي من استدعاءين باستخدام الصيغة await: let response = await fetch(url, options); // يُنفَّذ مع ترويسة الاستجابة headers let result = await response.json(); // JSON قراءة جسم الطلب بتنسيق أو دون الصيغة await: fetch(url, options) .then(response => response.json()) .then(result => /* process result */) وتتمثل خصائص الاستجابة في الآتي: response.status: رمز حالة HTTP للاستجابة. response.ok: يأخذ القيمة "true" إذا كانت قيمة رمز الحالة بين 200-299. response.headers: تعيد كائنًا شبيهًا بالترابط Map يضم ترويسات HTTP. توابع الحصول على جسم الاستجابة: ()response.text: لقراءة الاستجابة وإعادة نص. ()response.json: يفسر النص وفق تنسيق JSON. ()response.formData: يعيد الاستجابة على شكل كائن FormData. ()response.blob: تعيد الاستجابة على شكل كائن بيانات ثنائية Blob. ()response.arrayBuffer: يعيد الاستجابة على شكل كائن ArrayBuffer وهو تمثيل منخفض المستوى للبيانات الثنائية. خيارات Fetch التي تعرفنا عليها حتى الآن: method: نوع طلب HTTP. headers: كائن يضم ترويسات الطلب، ويجب الانتباه إلى الترويسات التي يُمنع استخدامها. body: البيانات التي ستُرسل (جسم الطلب) على شكل string أو FormData أو BufferSource أو Blob أو UrlSearchParams. وسنتعرف على خيارات أخرى في الفصل التالي. تُستخدم الكائنات FormData للتحكم بنماذج وإرسالها باستخدام fetch أو أي دوال لإرسال الطلبات عبر الشبكة، ويمكن إنشاؤها بالأمر (new FormData(form انطلاقًا من نموذج HTML موجود، أو إنشاؤها دون نموذج ثم نضيف إليه الحقول باستخدام التوابع: (formData.append(name, value (formData.append(name, blob, fileName (formData.set(name, value (formData.set(name, blob, fileName لاحظ هاتين الميزتين: يزيل التابع set الحقول التي لها نفس الاسم، بينما لا يفعل ذلك التابع append، وهذا هو الاختلاف الوحيد بينهما. لا بدّ من استخدام صيغة تضم ثلاثة وسطاء لإرسال الملف، آخرها اسم الملف والذي يؤخذ عادةً من منظومة ملفات المستخدم من خلال العنصر <"input type="file>. من التوابع الأخرى: (formData.delete(name (formData.get(name (formData.has(name تمارين إحضار بيانات مستخدمين من GitHub أنشئ دالةً غير متزامنة "async" باسم (getUsers(names للحصول على مصفوفة من سجلات الدخول Logins على GitHub، وتحضر المستخدمين أيضًا، ثم تعيد مصفوفةً بأسماء المستخدمين على GitHub. سيكون العنوان الذي يحوي معلومات مستخدم معين له اسم مستخدم محدد USERNAME هو: api.github.com/users/USERNAME. ستجد مثالًا تجريبيًا وضعناه في نمط الحماية sandbox يوضح ذلك. تفاصيل مهمة: ينبغي أن يكون هناك طلب إحضار واحد لكل مستخدم. لا ينبغي أن ينتظر أي طلب انتهاء طلب آخر، لكي تصل البيانات بالسرعة الممكنة. في حال إخفاق أي طلب، أو عدم وجود مستخدم بالاسم المُعطى، فينبغي أن تعيد الدالة القيمة "null" في المصفوفة الناتجة. افتح التمرين في بيئة تجريبية الحل إليك حل التمرين: نفِّذ التعليمة التالية لإحضار مستخدم: fetch('https://api.github.com/users/USERNAME') استدع التابع ()json. لقراءة الكائن JS، إن كان رمز الحالة المرافق لاستجابة الخادم هو 200. في الحالة التي تخفق فيها تعليمة الإحضار fetch أو لم يكن رمز الحالة 200، أعد القيمة null في المصفوفة الناتجة. إليك الشيفرة: async function getUsers(names) { let jobs = []; for(let name of names) { let job = fetch(`https://api.github.com/users/${name}`).then( successResponse => { if (successResponse.status != 200) { return null; } else { return successResponse.json(); } }, failResponse => { return null; } ); jobs.push(job); } let results = await Promise.all(jobs); return results; } ملاحظة: يرتبط استدعاء التابع then. بمباشرة بالدالة fetch، وبالتالي لا تنتظر عمليات إحضار أخرى لتنتهي عندما تصلك الاستجابة على أحدها بل إبدأ بقراءة الاستجابة مستخدمًا ()json.. إن استخدمت الشيفرة التالية : await Promise.all(names.map(name => fetch(...))) ثم استدعيت ()json. لقراءة النتائج، فقد يكون عليك الانتظار لتنتهي جميع عمليات الإحضار. ستضمن قراءة نتيجة كل عملية إحضار بمفردها إن استخدمت مباشرة ()json. مع fetch. فما عرضناه كان مثالًا عن فائدة واجهة الوعود البرمجية منخفضة المستوى low-level Promise API حتى لو استخدمنا async/await. إليك الحل في بيئة تجريبية مع الاختبارات ترجمة -وبتصرف- للفصلين popups and window methods و FormData من سلسلة The Modern JavaScript Tutorial1 نقطة