لوحة المتصدرين
المحتوى الأكثر حصولًا على سمعة جيدة
المحتوى الأعلى تقييمًا في 02/17/23 في كل الموقع
-
FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':app:validateSigningDebug'. > java.util.concurrent.ExecutionException: com.android.builder.utils.SynchronizedFile$ActionExecutionException: java.io.IOException: com.android.ide.common.signing.KeytoolException: Failed to create keystore. * Try: > Run with --stacktrace option to get the stack trace. > Run with --info or --debug option to get more log output. > Run with --scan to get full insights. * Get more help at https://help.gradle.org BUILD FAILED in 12s Exception: Gradle task assembleDebug failed with exit code 11 نقطة
-
لدي نمذج لدخال رساله، لما اضغط على زر الاسال اكثر من مره يتم ارسال النموذج على حسب الضغطات على زر الارسال وبالتالي تكرار تكرار الرساله في قاعدة البيانات، فماهو الحل لمنع هذا الامر1 نقطة
-
هل استطيع التطبيق في دروس الخوارزميات بلغة JavaScript لاني لا احتاج تعلم بايثون علما ان كل معلوماتي في جافا سكربت هي من المسار الثاني فهل هذه المعلومات تكفي لتطبيق الخوارزميات وبنى المعطيات1 نقطة
-
مرحبا ايمن، بالطبع يمكنك فالخوارزميات وبنى المعطيات أوسع من أن تحصر في بايثون، ولكنك لن تحتاج تعلمه على نحو عميق اذ يتم في بادئ المسار شرح تهيئة بيئة عمل خاصة ببايثون فقط. اي انك لن تعرف منه الا القدر الذي ستحتاجه لاستيعاب مفاهيم المسار. ولك الخيار ان شئت تطبيق ذلك وفق الجافاسكربت. أظن أن الأمر سيكون متعبا ومشتتا اذ ستحتاج استيعاب شيفرات بايثون اولا ثم تحويلها الى شيفرات جافاسكربت، فأنت في الأخير ستجد نفسك تتعرض لها حتما.1 نقطة
-
الخطا ظهر عندما اضفت useEffect(() => { async function PromiseSign() { setTasks(await GetAllTasks()); } PromiseSign(); }, [ReducerValue, tasks]); لملف OutputList.tsx1 نقطة
-
مرحبًا هل يمكنك توضيح كيف لا تعمل؟ ما المشكلة التي واجهتك تحديدًا؟1 نقطة
-
1 نقطة
-
يوجد لديك مشكلة أنك تقوم بتغيير قيمة tasks عبر الدالة setTasks وفي نفس الوقت الخطاف useEffect يعتمد على tasks لذا يتم تغير قيمة الtasks فيتم نداء الخطاف فيقوم بتغيير قيمة الtasks مرة اخرى فيتم نداء الخطاف الى مالا نهاية, الحل ان تقوم بمسح الtasks من مصفوفة الاعتمادية في الخطاف فيكون شكل الخطاف كالتالي useEffect(() => { async function PromiseSign() { setTasks(await GetAllTasks()); } PromiseSign(); }, [ReducerValue]);1 نقطة
-
تأكدت من المسار عشرات المرات ساتوقف عن المحاولة واترك هذا الالمنت شكرا على المساعدة1 نقطة
-
1 نقطة
-
عند طرح السؤال حاول ذكر المزيد من التفاصيل عن مكان حدوث المشكلة في أي ملف أو صور عن رسائل الخطأ التي تظهر لك لأن ذلك سيساعدنا على حل مشكلتك بشكل أفضل. لم أجد حلقات تكرارية مستخدمة في مشروعك، ولكن أعتقد أن المشكلة خاصة بتكرار تضمين الملفات بشكل متداخل مع بعضها، لذلك حاول حذف التضمين التالي من ملف main.js الموجود ضمن المجلّد routes: const { db } = require("../models/Task"); فيجب عليك استخدام النماذج models بدلاً من db وقمت بتضمينها أيضاً ضمن الملف.1 نقطة
-
مرحبا تستطيع فعل ذلك بهذا الكود فقط بواسطة وسم video <video src="{your source}"></video> فقط قم بتديل your sorce بمسارك الخاص1 نقطة
-
نقوم باستخدام ال spread operator في ال useState حتى نقوم بتحديث القيم الموجودة في ال state وكما تم التوضيح في التعليق السابق بشكل جيد سأحاول توضيح أهميته من خلال المثال التالي إذا كان لديك مصفوفة من الكائنات أو الكائنات ذات الحقول والقيم ، وتريد إضافة المزيد من الحقول أو العناصر بحيث يمكنك القيام بذلك باستخدام التعليمات التالية. لنأخذ مثالاً ، لنفترض أن لدينا الحالة التالية بالأشياء const [values, setValues] = useState({ full_name: "", email: "", password: "", confirmPassword: "", type: "" }); ونريد فقط تحديث قيمة البريد الإلكتروني ، يمكننا القيام بذلك باستخدام spread operator: setValues({ ...values, email: 'new Value' }) لنفترض أنك تريد إضافة حقل أو كائن جديد في المصفوفة أو الحالة ، لتحقيق ذلك سنتبع الطريقة التالية setValues({ ...values, nickName: 'new Value' })1 نقطة
-
ربما يكون هذا الخطأ بسبب أنك تحتاج لتحديث إصدار ال Gradle لذلك قم بتثبيت الإصدار الأحدث من هنا وحسب الطريقة المتاحة أمامك يمكنك التثبيت أو يمكنك تنفيذ ذلك يدوياً من خلال قم بتنزيل أحدث إصدار لـ Gradle قم بفك ضغط مجلد Gradle الذي قمت بتحميله إلى C:\Gradle قم بعديل ال environment variable انقر بزر الماوس الأيمن على أيقونة الكمبيوتر ثم انقر فوق خصائص -> إعدادات النظام المتقدمة -> المتغيرات البيئية.ضمن متغيرات النظام ، حدد المسار ، ثم انقر فوق تحرير. قم بإضافة إدخال لـ C:\Gradle\gradle-8.0\bin . انقر فوق "موافق" للحفظ. قم بعد ذلك بالتأكد من التحديث عن طريق تنفيذ الأمر التالي gradle -v1 نقطة
-
سنبني في هذه المقالة تطبيق المهام To-Do application باستخدام جانغو Django وريآكت React. ريآكت هي مكتبة مبنية بلغة جافا سكربت، وتُستخدم لتطوير تطبيقات الصفحة الواحدة Single-page applications -أو اختصارًا SPA. تتمتع ريآكت بتوثيق قوي ومنظومة بيئية حيَّة؛ أما جانغو فهو إطار عمل ويب مبني بلغة بايثون طُوّر لتبسيط الممارسات الشائعة المتبعة لتطوير الويب، وهو إطار عمل موثوق ويتمتع هو الآخر بمنظومة بيئية حيَّة من المكتبات البرمجية المستقرة التي تدعم احتياجات التطوير الشهيرة. ستلعب ريآكت في التطبيق الذي سنطوّره دور الواجهة الأمامية frontend، أو ما يسمى "إطار عمل من جهة العميل"، إذ ستتولى مسؤولية التعامل مع واجهة المستخدم إلى جانب جلب ووضع قيم البيانات من خلال الطلبات المُرسلة إلى الواجهة الخلفية لجانغو، التي تمثّل واجهة برمجة تطبيقات API بُنِيَت باستخدام إطار عمل جانغو ريست Django REST -أو اختصارًا DRF. ستحصل في نهاية هذا المقال على تطبيق فعال ونظامي. ملاحظة: إذا أردت الحصول على الشيفرة المصدرية لهذا المقال، فستجدها هنا. تنبيه: الشيفرة الواردة هي للأغراض التعليمية وليست للاستخدام المهني للشركات. سيسمح التطبيق لمستخدميه بإنشاء قائمة بالمهام التي يودون إنجازها، وتُنجز كل مهمة بوضع علامة "مكتمل complete" عليها، وفي حال عدم إنجازها تُوضع عليها علامة "غير مكتمل incomplete". المتطلبات الأساسية سنحتاج لكي نتمكن من تنفيذ ما في هذه المقالة إلى ما يلي: تثبيت بايثون 3 وإعداد بيئة برمجية محلية له. تثبيت Node.js وإنشاء بيئة تطوير محلية. إضافةً إلى الاعتماديات التالية: Python v3.9.1 pip v20.2.4 Django v3.1.6 djangorestframework v3.12.2 django-cors-headers v3.7.0 Node v15.8.0 npm v7.5.4 React v17.0.1 axios v0.21.0 الخطوة الأولى - إعداد الواجهة الخلفية سننشئ الآن مجلد مشروع جديد ونثبّت جانغو وفق ما يلي: افتح نافذة طرفية جديدة terminal، ونفّذ الأمر التالي الذي سينشئ مجلد مشروع جديدًا: $ mkdir django-todo-react ثم انتقل إلى داخل المجلد الذي أنشأته: $ cd django-todo-react الآن ثبّت pipenv مستخدمًا الأمر التالي: $ pip install pipenv ملاحظة: قد تحتاج إلى استخدام pip3 بدلًا من pip وهذا يعتمد على نوع التثبيت الذي تستخدمه. فعّل الآن بيئة افتراضية جديدة: $ pipenv shell وثبّت جانغو باستخدام pipenv : $ pipenv install django ثم أنشئ مشروع جانغو جديد وسمِّه backend: $ django-admin startproject backend ثم انتقل إلى داخل ذلك المجلد: $ cd backend وافتتح تطبيقًا جديدًا وسمِّه todo: python manage.py startapp todo نفّذ عمليات التهجير migrations لكي تُطبق التغييرات التي أجريت للتو على مخطط قاعدة البيانات: python manage.py migrate وشغّل الخادم: python manage.py runserver اذهب إلى العنوان "http://localhost:8000" في متصفح الويب الذي تستخدمه، وستظهر لك الصفحة التالية: سيظهر لك في هذه النقطة نسخةً من تطبيق جانغو في وضعية التشغيل. يمكنك الآن بعد أن انتهيت إيقاف الخادم باستخدام المفتاحين التاليين: "CONTROL+C"، أو "CTRL+C". تسجيل تطبيق المهام todo يمكنك الآن بعد أن انتهيت من إعداد الواجهة الخلفية أن تبدأ في تسجيل تطبيق المهام todo، ونعني بذلك أن تذكره مع التطبيقات المثبّتة ليتمكن جانغو من التعرُّف عليه. افتح الملف "backend/settings.py" بمحرر الشيفرة الذي تستخدمه وأضف التطبيق "todo" إلى قائمة التطبيقات المثبّتة "INSTALLED_APPS": # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'todo', ] ثم احفظ التغييرات التي أجريتها على الملف. تعريف نموذج تطبيق المهام Todo لننشئ الآن نموذجًا نتمكن به من تعريف كيفية تخزين عناصر "Todo" في قاعدة البيانات. افتح الملف "todo/models.py" بمحرر الشيفرة وأضف السطور التالية: from django.db import models # Create your models here. class Todo(models.Model): title = models.CharField(max_length=120) description = models.TextField() completed = models.BooleanField(default=False) def _str_(self): return self.title تعرّف الشيفرة السابقة ثلاث خاصيّات ضمن النموذج Todo وهي: title description completed تدل الخاصية completed على حالة المهمة في لحظة ما، فهي إما مكتملة أو غير مكتملة. ستحتاج الآن إلى إنشاء ملف تهجير بما أنك أنشأت نموذج "Todo": $ python manage.py makemigrations todo ثم طبّق التغييرات على قاعدة البيانات: $ python manage.py migrate todo يمكنك التأكد من إمكانية تنفيذ عمليات الإنشاء والقراءة والتحديث والحذف Create Read Update Delete - أو اختصارًا CRUD- على نموذج "Todo" باستخدام واجهة الإدارة التي يوفرها جانغو لمستخدميه. ولفعل ذلك، افتح الملف "todo/admin.py" بمحرر الشيفرة، وأضف الأسطر التالية: from django.contrib import admin from .models import Todo class TodoAdmin(admin.ModelAdmin): list_display = ('title', 'description', 'completed') # Register your models here. admin.site.register(Todo, TodoAdmin) ثم احفظ التغييرات. ستحتاج إلى حساب مستخدم مميز superuser لكي تتمكن من الدخول إلى واجهة الإدارة، لذا نفذ الأمر التالي في نافذة الطرفية لديك: $ python manage.py createsuperuser سيُطلبُ منك إدخال اسم المستخدم والبريد الإلكتروني وكلمة المرور لحساب المستخدم المميز. ولأنك ستحتاجها دومًا للدخول إلى لوحة تحكم الإدارة، ننصحك بأن تختار تفاصيل يسهل عليك تذكرها. شغّل الخادم مرةً أخرى: $ python manage.py runserver والآن انتقل إلى العنوان "http://localhost:8000/admin" في متصفح الويب الذي تستخدمه، وسجِّل الدخول مستعملًا اسم المستخدم وكلمة المرور اللذين أنشأتهما للتو. يمكنك إنشاء وتحرير وحذف عناصر "Todo" باستخدام هذه الواجهة: بعد إجراء تجاربك على هذه الواجهة، يمكنك إيقاف الخادم باستخدام المفتاحين "CONTROL+C" أو "CTRL+C". الخطوة الثانية - إعداد واجهة برمجة التطبيقات API سننشئ هنا واجهة برمجة تطبيقات مستعينين بإطار عمل جانغو ريست، وسنثبّت لتحقيق ذلك كلًّا من djangorestframework و django-cors-headers مستخدمين pipenv: pipenv install djangorestframework django-cors-headers يجب أن تضيف اسمي التطبيقين "rest_framework" و "corsheaders" إلى قائمة التطبيقات المثبّتة ضمن ملف الإعدادات. لذلك، افتح ملف الإعدادات "backend/settings.py" بمحرر الشيفرة، وحدّث قسمي التطبيقات المثبّتة "INSTALLED_APPS"، والبرمجيات الوسيطة "MIDDLEWARE" كما يلي: # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'corsheaders', 'rest_framework', 'todo', ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'corsheaders.middleware.CorsMiddleware', ] ثم ألحق أسطر الشيفرة التالية بنهاية الملف "backend/settings.py": CORS_ORIGIN_WHITELIST = [ 'http://localhost:3000' ] تُعد مكتبة "django-cors-headers" مكتبة بايثون تمنع من حدوث الأخطاء التي تنتج عادةً عن استخدام بنود سياسة تعدد الموارد CORS، وهذا ما جعلك تضيف "localhost:3000" ضمن القائمة البيضاء "CORS_ORIGIN_WHITELIST"، لأنك تريد أن تمكِّن الواجهة الأمامية للتطبيق -التي تستخدم ذلك المنفذ- من التفاعل مع واجهة برمجة التطبيقات. إنشاء المسلسلات serializers ستحتاج للمسلسِلات لتحويل نسخ النموذج إلى محتوى من النوع JSON، وذلك لكي تتمكن الواجهة الأمامية من العمل مع البيانات التي تستلمها. أنشئ الملف "todo/serializers.py" باستخدام محرر الشيفرة، ثم افتحه وأضف إليه أسطر الشيفرة التالية: from rest_framework import serializers from .models import Todo class TodoSerializer(serializers.ModelSerializer): class Meta: model = Todo fields = ('id', 'title', 'description', 'completed') تحدد الشيفرة السابقة النموذج الذي سيجري العمل عليه إلى جانب الحقول التي ستُحوّل إلى محتوى من النوع JSON. إنشاء العرض ستحتاج إلى إنشاء الصنف TodoView في الملف "todo/views.py". افتح الملف "todo/views.py" بمحرر الشيفرة الذي تستخدمه وأضف أسطر الشيفرة التالية: from django.shortcuts import render from rest_framework import viewsets from .serializers import TodoSerializer from .models import Todo # Create your views here. class TodoView(viewsets.ModelViewSet): serializer_class = TodoSerializer queryset = Todo.objects.all() يوفِّر الصنف الأساسي viewsets تنفيذًا لعمليات CRUD افتراضيًا. تحدد الشيفرة السابقة صنف المسلسِل serializer_class و مجموعة الاستعلام queryset. افتح الملف "backend/urls.py" بمحرر الشيفرة وبدّل محتوياته بالأسطر التالية: from django.contrib import admin from django.urls import path, include from rest_framework import routers from todo import views router = routers.DefaultRouter() router.register(r'todos', views.TodoView, 'todo') urlpatterns = [ path('admin/', admin.site.urls), path('api/', include(router.urls)), ] تحدد الشيفرة السابقة مسار محدّد الموارد الموحد URL لواجهة برمجة التطبيقات API، وبهذه الخطوة نكون قد انتهينا من بناء واجهة برمجة التطبيقات. أصبح بإمكانك الآن إنجاز عمليات CRUD على نموذج المهام "Todo"، إذ يتيح لك صنف الموجِّه router class إجراء الاستعلامات التالية: /todos/: يعيد قائمةً بكافة عناصر Todo، ويمكن تنفيذ عمليتي الإنشاء CREATE والقراءة READ هنا. todos/id/ - يعيد عنصر Todo واحدًا اعتمادًا على المفتاح الأساسي id، ويمكن تنفيذ عمليتي التحديث UPDATE والحذف DELETE هنا. دعنا الآن نعيد تشغيل الخادم: $ python manage.py runserver اذهب إلى العنوان "http://localhost:8000/api/todos" في متصفح الويب الذي تستخدمه. يمكنك إنشاء عنصر مهام Todo جديد بهذه الواجهة باستخدام العملية CREATE كما يلي: إذا كانت عناصر قائمة المهام Todo قد أنشئت بنجاح، فستحصل على استجابة ناجحة. يمكنك أيضًا تنفيذ عمليتي الحذف DELETE والتحديث UPDATE على بعض عناصر Todo بناءً على المفاتيح الأساسية id التي تزودها. استخدم بنية العنوان "/api/todos/{id}" وحدد المفتاح الأساسي "id" الذي تريد تطبيق هذه العمليات عليه. أضف "1" إلى نهاية عنوان محدد الموارد الموحد URL لمعرفة عنصر Todo الذي يحمل مفتاحه الأساسي القيمة "1"، ثم انتقل إلى "http://localhost:8000/api/todos/1" في متصفح الويب الذي تستخدمه. تبين الشاشة التالية النتيجة التي حصلنا عليها: إذا ظهرت لك هذه الشاشة فذلك يعني أنك انتهيت من بناء الواجهة الخلفية للتطبيق. الخطوة الثالثة - إعداد الواجهة الأمامية بعد أن انتهيت من إعداد الواجهة الخلفية، حان وقت تجهيز الواجهة الأمامية وتمكينها من التواصل مع الواجهة الخلفية عبر الواجهة التي أنشأتها. افتح نافذة طرفية واذهب إلى مجلد المشروع "django-todo-react". سنعتمد في إعداد الواجهة الأمامية على التطبيق Create React الذي تتنوع طرق استخدامه، ومن أشهرها استخدام مشغّل npx لتشغيل الحزمة وإنشاء المشروع: $ npx create-react-app frontend وللاطلاع أكثر على هذه الطريقة يرجى مراجعة بدء العمل مع مكتبة ريآكت. يمكنك الآن بعد أن أنشأت المشروع الانتقال إلى المجلد الذي أنشأته للتو "frontend": $ cd frontend الآن، شغّل التطبيق: $ npm start سيفتح متصفحك الرابط "http://localhost:3000" وستظهر لك الشاشة الابتدائية لتطبيق "Create React". ثم ثبّت بوتستراب bootstrap وريآكتستراب reactstrap لتصبح أدوات واجهة المستخدم في حوزتك: $ npm install bootstrap@4.6.0 reactstrap@8.9.0 --legacy-peer-deps ملاحظة: ربما تتلقى رسائل خطأ مثل "unable to resolve dependency tree" بحسب إصدارات كل من ريآكت وبوتستراب وريآكتستراب. أصبحت النسخة الأحدث للملف "popper.js" في لحظة كتابة هذه المقالة مهملة وتتعارض مع الإصدار 17 وما بعده من ريآكت، وهذه مشكلة معروفة، ويمكن استخدام الخيار legacy-peer-deps-- عند التثبيت. افتح الملف "index.js" في محرر الشيفرة الذي تستخدمه وأضف bootstrap.min.css: import React from 'react'; import ReactDOM from 'react-dom'; import 'bootstrap/dist/css/bootstrap.css'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') ); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals(); إذا واجهت أي صعوبة أثناء هذه الخطوة، فننصحك بالاستعانة بموقع التوثيق الرسمي لإضافة بوتستراب. افتح الملف "App.js" بمحرر الشيفرة وأضف أسطر الشيفرة التالية: import React, { Component } from "react"; const todoItems = [ { id: 1, title: "Go to Market", description: "Buy ingredients to prepare dinner", completed: true, }, { id: 2, title: "Study", description: "Read Algebra and History textbook for the upcoming test", completed: false, }, { id: 3, title: "Sammy's books", description: "Go to library to return Sammy's books", completed: true, }, { id: 4, title: "Article", description: "Write article on how to use Django with React", completed: false, }, ]; class App extends Component { constructor(props) { super(props); this.state = { viewCompleted: false, todoList: todoItems, }; } displayCompleted = (status) => { if (status) { return this.setState({ viewCompleted: true }); } return this.setState({ viewCompleted: false }); }; renderTabList = () => { return ( <div className="nav nav-tabs"> <span className={this.state.viewCompleted ? "nav-link active" : "nav-link"} onClick={() => this.displayCompleted(true)} > Complete </span> <span className={this.state.viewCompleted ? "nav-link" : "nav-link active"} onClick={() => this.displayCompleted(false)} > Incomplete </span> </div> ); }; renderItems = () => { const { viewCompleted } = this.state; const newItems = this.state.todoList.filter( (item) => item.completed == viewCompleted ); return newItems.map((item) => ( <li key={item.id} className="list-group-item d-flex justify-content-between align-items-center" > <span className={`todo-title mr-2 ${ this.state.viewCompleted ? "completed-todo" : "" }`} title={item.description} > {item.title} </span> <span> <button className="btn btn-secondary mr-2" > Edit </button> <button className="btn btn-danger" > Delete </button> </span> </li> )); }; render() { return ( <main className="container"> <h1 className="text-white text-uppercase text-center my-4">Todo app</h1> <div className="row"> <div className="col-md-6 col-sm-10 mx-auto p-0"> <div className="card p-3"> <div className="mb-4"> <button className="btn btn-primary" > Add task </button> </div> {this.renderTabList()} <ul className="list-group list-group-flush border-top-0"> {this.renderItems()} </ul> </div> </div> </div> </main> ); } } export default App; تحتوي هذه الشيفرة على بعض المتوفرة مسبقًا hardcoded لأربعة عناصر، وستبقى هذه القيم إلى حين إحضار العناصر من الواجهة الخلفية. تعرض الدالة ()renderTabList امتدادين spans يظهِر كل منهما عند الضغط عليه مجموعةً من المهام، إذ يظهر الضغط على الامتداد Completed المهام المكتملة، ويظهر الامتداد Incomplete المهام غير المكتملة في المقابل. احفظ الآن التغييرات التي أجريتها وتأمل النتيجة في متصفحك: هناك أفعال معينة داخل تطبيق المهام، مثل إضافة المهام وتحريرها، ولكي تتمكن من التعامل معها ستحتاج إلى إنشاء مكوّن شرطي modal component. أولًا، أنشئ مجلد المكوّنات "components" داخل المجلد "src": $ mkdir src/components ثم أنشئ الملف "Modal.js" وافتحه بمحرر الشيفرة وأضف إليه الأسطر التالية: import React, { Component } from "react"; import { Button, Modal, ModalHeader, ModalBody, ModalFooter, Form, FormGroup, Input, Label, } from "reactstrap"; export default class CustomModal extends Component { constructor(props) { super(props); this.state = { activeItem: this.props.activeItem, }; } handleChange = (e) => { let { name, value } = e.target; if (e.target.type === "checkbox") { value = e.target.checked; } const activeItem = { ...this.state.activeItem, [name]: value }; this.setState({ activeItem }); }; render() { const { toggle, onSave } = this.props; return ( <Modal isOpen={true} toggle={toggle}> <ModalHeader toggle={toggle}>Todo Item</ModalHeader> <ModalBody> <Form> <FormGroup> <Label for="todo-title">Title</Label> <Input type="text" id="todo-title" name="title" value={this.state.activeItem.title} onChange={this.handleChange} placeholder="Enter Todo Title" /> </FormGroup> <FormGroup> <Label for="todo-description">Description</Label> <Input type="text" id="todo-description" name="description" value={this.state.activeItem.description} onChange={this.handleChange} placeholder="Enter Todo description" /> </FormGroup> <FormGroup check> <Label check> <Input type="checkbox" name="completed" checked={this.state.activeItem.completed} onChange={this.handleChange} /> Completed </Label> </FormGroup> </Form> </ModalBody> <ModalFooter> <Button color="success" onClick={() => onSave(this.state.activeItem)} > Save </Button> </ModalFooter> </Modal> ); } } تنشئ الشيفرة السابقة الصنف CustomModal وهو يتداخل مع المكون Modal المشتق من المكتبة reactstrap. وقد عرَّفت هذه الشيفرة أيضًا ثلاثة حقول ضمن نموذج التعبئة form هي: title description completed هذه الحقول هي نفسها التي عرفناها مثل خصائص في نموذج قائمة المهام "Todo" عند إعداد الواجهة الخلفية. يستقبل CustomModal القيم activeItem و toggle و onSave مثل خاصيّات، بحيث يمثِّل: activeItem عنصر قائمة المهام الذي سيُحرّر. toggle الدالة التي ستُستخدم للتحكم في حالة المكون Modal، أي فتحه أو غلقه. onSave دالةً تُستدعى لحفظ القيم التي حرِّرت لعنصر قائمة المهام. الآن، سنستورد المكون CustomModal إلى داخل الملف "App.js". عد إلى الملف "src/App.js" وافتحه بمحرر الشيفرة واستبدل محتوياته بأسطر الشيفرة التالية: import React, { Component } from "react"; import Modal from "./components/Modal"; const todoItems = [ { id: 1, title: "Go to Market", description: "Buy ingredients to prepare dinner", completed: true, }, { id: 2, title: "Study", description: "Read Algebra and History textbook for the upcoming test", completed: false, }, { id: 3, title: "Sammy's books", description: "Go to library to return Sammy's books", completed: true, }, { id: 4, title: "Article", description: "Write article on how to use Django with React", completed: false, }, ]; class App extends Component { constructor(props) { super(props); this.state = { viewCompleted: false, todoList: todoItems, modal: false, activeItem: { title: "", description: "", completed: false, }, }; } toggle = () => { this.setState({ modal: !this.state.modal }); }; handleSubmit = (item) => { this.toggle(); alert("save" + JSON.stringify(item)); }; handleDelete = (item) => { alert("delete" + JSON.stringify(item)); }; createItem = () => { const item = { title: "", description: "", completed: false }; this.setState({ activeItem: item, modal: !this.state.modal }); }; editItem = (item) => { this.setState({ activeItem: item, modal: !this.state.modal }); }; displayCompleted = (status) => { if (status) { return this.setState({ viewCompleted: true }); } return this.setState({ viewCompleted: false }); }; renderTabList = () => { return ( <div className="nav nav-tabs"> <span className={this.state.viewCompleted ? "nav-link active" : "nav-link"} onClick={() => this.displayCompleted(true)} > Complete </span> <span className={this.state.viewCompleted ? "nav-link" : "nav-link active"} onClick={() => this.displayCompleted(false)} > Incomplete </span> </div> ); }; renderItems = () => { const { viewCompleted } = this.state; const newItems = this.state.todoList.filter( (item) => item.completed === viewCompleted ); return newItems.map((item) => ( <li key={item.id} className="list-group-item d-flex justify-content-between align-items-center" > <span className={`todo-title mr-2 ${ this.state.viewCompleted ? "completed-todo" : "" }`} title={item.description} > {item.title} </span> <span> <button className="btn btn-secondary mr-2" onClick={() => this.editItem(item)} > Edit </button> <button className="btn btn-danger" onClick={() => this.handleDelete(item)} > Delete </button> </span> </li> )); }; render() { return ( <main className="container"> <h1 className="text-white text-uppercase text-center my-4">Todo app</h1> <div className="row"> <div className="col-md-6 col-sm-10 mx-auto p-0"> <div className="card p-3"> <div className="mb-4"> <button className="btn btn-primary" onClick={this.createItem} > Add task </button> </div> {this.renderTabList()} <ul className="list-group list-group-flush border-top-0"> {this.renderItems()} </ul> </div> </div> </div> {this.state.modal ? ( <Modal activeItem={this.state.activeItem} toggle={this.toggle} onSave={this.handleSubmit} /> ) : null} </main> ); } } export default App; احفظ التغييرات وتأمل ما حصل للتطبيق في متصفحك: إذا حاولت تحرير وحفظ عنصر من عناصر قائمة المهام Todo، سيظهر لك تنبيه يُظهِرُ الكائن المرتبط بعنصر المهام Todo. سيؤدي النقر على حفظ Save أو حذف Delete إلى تنفيذ الفعل المقابل على هذا العنصر. ملاحظة: قد تواجه أخطاءً في واجهة الطرفية وذلك بحسب نسختي ريآكت وريآكتستراب اللتين تستخدمهما، وأثناء عمل هذه المراجعة، كان الخطآن التاليان من بين الأخطاء الشائعة في ريآكتستراب: Warning: Legacy context API has been detected within a strict-mode tree Warning: findDOMNode is deprecated in StrictMode ستعدل الآن التطبيق ليتمكن من التفاعل مع واجهة برمجة التطبيقات لجانغو التي بنيتها في القسم السابق. أعد فتح نافذة الطرفية الأولى وتأكد من أن الخادم يعمل، وإذا لم يكن يعمل، استخدم الأمر التالي: $ python manage.py runserver ملاحظة: إذا كنت قد أغلقت نافذة الطرفية هذه، تذكر أنك ستحتاج للذهاب إلى المجلد "backend" واستخدام صدفة Pipenv الافتراضية. لإنشاء طلبات توجه إلى نقاط النهاية لواجهة برمجة التطبيقات في خادم الواجهة الخلفية، سنثبّت واحدةً من مكتبات جافا سكريبت المهمة وهي مكتبة "axios". في نافذة الطرفية الثانية، تأكد أنك موجود داخل المجلد "frontend"، ثم ثبّت مكتبة axios: $ npm install axios@0.21.1 ثم افتح الملف "frontend/package.json" بمحرر الشيفرة وأضف خادمًا وسيطًا proxy كما يلي: [...] "name": "frontend", "version": "0.1.0", "private": true, "proxy": "http://localhost:8000", "dependencies": { "axios": "^0.18.0", "bootstrap": "^4.1.3", "react": "^16.5.2", "react-dom": "^16.5.2", "react-scripts": "2.0.5", "reactstrap": "^6.5.0" }, [...] تتركز مهمة الخادم الوسيط في المساعدة في إنشاء نفق tunneling لطلبات واجهة برمجة التطبيقات إلى شبكة أخرى وإرسالها إلى العنوان "http://localhost:8000"، ليستلمها تطبيق جانغو هناك ويعالجها، وبدون هذا الخادم الوسيط، ستضطر إلى تحديد المسارات الكاملة كما يلي: axios.get("http://localhost:8000/api/todos/") أما مع استخدام الخادم الوسيط، فستحتاج للتزويد بمسارات نسبية فقط: axios.get("/api/todos/") ملاحظة: ربما تحتاج إلى إعادة تشغيل خادم التطوير لكي يسجل الخادم الوسيط في التطبيق. عد إلى الملف "frontend/src/App.js" وافتحه بمحرر الشيفرة، إذ ستحذف في هذه الخطوة عناصر قائمة المهام todoItems ذات القيم الموفرة hardcoded وتستخدم البيانات من الطلبات الموجهة إلى خادم الواجهة الخلفية مستخدمًا handleSubmit و handleDelete. افتح الملف App.js واستبدل محتوياته بالنسخة النهائية التالية: import React, { Component } from "react"; import Modal from "./components/Modal"; import axios from "axios"; class App extends Component { constructor(props) { super(props); this.state = { viewCompleted: false, todoList: [], modal: false, activeItem: { title: "", description: "", completed: false, }, }; } componentDidMount() { this.refreshList(); } refreshList = () => { axios .get("/api/todos/") .then((res) => this.setState({ todoList: res.data })) .catch((err) => console.log(err)); }; toggle = () => { this.setState({ modal: !this.state.modal }); }; handleSubmit = (item) => { this.toggle(); if (item.id) { axios .put(`/api/todos/${item.id}/`, item) .then((res) => this.refreshList()); return; } axios .post("/api/todos/", item) .then((res) => this.refreshList()); }; handleDelete = (item) => { axios .delete(`/api/todos/${item.id}/`) .then((res) => this.refreshList()); }; createItem = () => { const item = { title: "", description: "", completed: false }; this.setState({ activeItem: item, modal: !this.state.modal }); }; editItem = (item) => { this.setState({ activeItem: item, modal: !this.state.modal }); }; displayCompleted = (status) => { if (status) { return this.setState({ viewCompleted: true }); } return this.setState({ viewCompleted: false }); }; renderTabList = () => { return ( <div className="nav nav-tabs"> <span onClick={() => this.displayCompleted(true)} className={this.state.viewCompleted ? "nav-link active" : "nav-link"} > Complete </span> <span onClick={() => this.displayCompleted(false)} className={this.state.viewCompleted ? "nav-link" : "nav-link active"} > Incomplete </span> </div> ); }; renderItems = () => { const { viewCompleted } = this.state; const newItems = this.state.todoList.filter( (item) => item.completed === viewCompleted ); return newItems.map((item) => ( <li key={item.id} className="list-group-item d-flex justify-content-between align-items-center" > <span className={`todo-title mr-2 ${ this.state.viewCompleted ? "completed-todo" : "" }`} title={item.description} > {item.title} </span> <span> <button className="btn btn-secondary mr-2" onClick={() => this.editItem(item)} > Edit </button> <button className="btn btn-danger" onClick={() => this.handleDelete(item)} > Delete </button> </span> </li> )); }; render() { return ( <main className="container"> <h1 className="text-white text-uppercase text-center my-4">Todo app</h1> <div className="row"> <div className="col-md-6 col-sm-10 mx-auto p-0"> <div className="card p-3"> <div className="mb-4"> <button className="btn btn-primary" onClick={this.createItem} > Add task </button> </div> {this.renderTabList()} <ul className="list-group list-group-flush border-top-0"> {this.renderItems()} </ul> </div> </div> </div> {this.state.modal ? ( <Modal activeItem={this.state.activeItem} toggle={this.toggle} onSave={this.handleSubmit} /> ) : null} </main> ); } } export default App; الدالة ()refreshList معدّةٌ ليُعاد استخدامها المرة تلو المرة، وتُستدعى بعد كل مرة يكتمل فيها طلب واجهة برمجة تطبيقات، ومهمتها تحديث قائمة المهام لتعرِض دومًا أحدث حالة لها؛ أما الدالة ()handleSubmit فهي مسؤولة عن عمليتي الإنشاء والتحديث، فإذا لم يمتلك العنصر الممرّر مثل معامل parameter مفتاحًا أساسيًّا، فالأرجح عندها أنه لم يُنشأ، لذا يتولى مهمة إنشائه. تأكد الآن من أن خادم الواجهة الخلفية لديك يعمل في أول نافذة طرفية استخدمتها. $ python manage.py runserver ملاحظة: إذا أغلقت نافذة الطرفية هذه، فستحتاج للذهاب إلى المجلد "backend" واستخدام صدفة Pipenv الافتراضية. وتأكد في نافذة الطرفية الثانية أنك في مجلد الواجهة الأمامية "frontend" وشغّل تطبيق الواجهة الأمامية: $ npm start الآن عندما تذهب في متصفحك إلى العنوان "http://localhost:3000"، سيسمح لك التطبيق بإنشاء المهام وقراءتها وتعديلها وحذفها. وبهذا تكتمل الواجهة الأمامية والواجهة الخلفية لتطبيق المهام. خاتمة بنينا في هذه المقالة تطبيق مهام باستخدام جانغو وريآكت، وقد حققنا هذا بالاستعانة بالمكتبات: djangorestframework و django-cors-headers و axios و bootstrap و reactstrap. إذا أردت تعلم المزيد عن جانغو ننصحك بمراجعة صفحة مواضيع جانغو في أكاديمية حسوب. وإذا أردت تعلم المزيد عن ريآكت ننصحك بزيارة دلبل ريآكت في موسوعة حسوب. ترجمة -وبتصرف- للمقالة How To Build a To-Do application Using Django and React لصاحبها Jordan Irabor. اقرأ أيضًا إنشاء تطبيق قائمة مهام باستخدام React إنشاء تطبيق جانغو وتوصيله بقاعدة بيانات البدء مع إطار العمل جانغو لإنشاء تطبيق ويب1 نقطة