القائمة الرئيسية

الصفحات

التحكم في سكربت بايثون من خلال المتصفح بواجهة html

التحكم في سكربت بايثون من خلال المتصفح وارسال واستقبال البيانات بين javascript و python

ارسال post عبر ajax الي python


في هذا الموضوع سوف نتعرف علي كيفية ارسال واستقبال البيانات بين جافا سكربت و بايثون وانشاء واجهة رسومية بدون مكتبات Tkinter او qt كما اننا لن نستخدم Flask او Django.

 في البداية سوف نشرح الاكواد الاساسية لارسال واستقبال البيانات بين python ajax php حتي تضح الامور.

بعد ذالك سوف ننشيء تصميم بسيط لصفحة html وربطة مع نظام تسجيل الطلاب الذي قمنا بشرحة في الدرس السابق.

ملاحظة يجب استضافة الملفات علي ويب سيرفر سواء محلي او عالمي يمكنك تحميل appserv او xamp لويندوز.


ارسال واستقبال البيانات بين python , javascript

كما نعلم فان javascript لا يمكنها التحكم في سطر الاوامر او الـ command line لذالك فانها لا تستطيع تشغيل سكربت بايثون مباشرتا.

وهذا لانها لغة client side ولكنها لغة تفاعلية لا تحتاج لاعادة تحديث الصفحة لارسال او استقبال البيانات لذالك سوف نستعين بلغة تكون وسيط بين javascript و python ولها القدرة علي التحكم في الـ shell وهي لغة php.

الكود التالي هو حلقة الوصل بين بايثون وجافا سكربت نرسل البيانات من javascript الي ملف php ثم الي python.

متغير $text لتخزين البيانات الواردة من عملية ارسال post في javascript.

في متغير $cmd نقوم باستخدام دالة escapeshellcmd لفلترة الامر قبل تنفيذة لمنع استغلالة لاغراض خبيثة.

لا تنسي تعديل مسار بايثون لمسار بايثون علي حاسوبك.

بعد ذالك نقوم بحفظ البيانات الواردة من تنفيذ سكربت بايثون عبر shell_exec في متغير $result.

ثم نقوم بطباعتة حتي تستقبلة ajax.

php:
<?php
    $text = $_POST["msdCalls"];
    $cmd = escapeshellcmd("C:\\Users\\username\\AppData\\Local\\Programs\\Python\\Python38-32\\python.exe stud.py $text");
    $result = shell_exec($cmd);
    echo $result;
?>

الكود التالي هو مثال لارسال post عبر ajax واستقبال الرد.

ومن خلالة يمكننا ارسال النصوص عبر باراميتر data يمكن ارسال اكتر من متغير او نصوص بتنسيق json.

ملاحظة لتنفيذ الكود التالي لابد من استدعاء jquery وسوف نوضحها في السطور القادمة.

jquery:
  $.ajax({
       method:"POST",
       url:"center.php",
       dataType: 'html',
       data:{msdCalls:msdCalls},
       success:function(data){
         reply = JSON.parse(data);
}
});

الكود التالي سوف يكون في سكربت بايثون لاستقبال براميتر متغير $text من سطر الاوامر والتعديل عليه ثم الطباعة لنحصل علي النتيجة في متغير $result في ملف php.

python:
data = sys.argv[1]
print('welcome:' + data)

الاكواد السابقة هي كل ما تحتاجة لارسال البيانات من ajax الي بايثون والعكس.

تصميم وربط نظام تسجيل الطلاب بواجة مستخدم html

في درس سابق قمنا بشرح برمجة نظام تسجيل الطلاب باستخدام لغة بايثون و json.

لن نعيد شرح كتابتة مرة اخري ولاكن سوف نشرح الاكواد الجديدة لادارة السكربت.


داخل مجلد www الموجود علي سيرفر apache نقوم بانشاء الملفات الثلاث التي سوف نكتبها وهي:

  1. index.htm
  2. center.php
  3. stud.py

تصميم صفحة الواجهة الرئيسية index.htm

في هذا الجزء سوف نقوم بتصميم الحقول والازرار ودوال javascript اذا واجهتك اي صعوبة او ترغب في توضيح اي شيء لا تتردد بوضع تعليق بالاسفل.

سوف نحتاج الي بعض عناصر html لاضافة طالب جديد و البحث والتعديل وعرض كل الطلاب :

  • لاضافة طالب جديد نريد 3 عناصر ادخال وزر ارسال.
  • للبحث عن الطلاب نحتاج حقل ادخال وزر ارسال.
  • ولعرض الطلاب من نتيجة البحث او الكل نحتاج جدول.
  • وسوف نضيف عنصر لعرض رسائل الخطا.
قمنا بتصميم نموذج بسيط يمكنك تطويرة كما تشاء وهو كالاتي:
      <div id="add-form">
        <label for="fname">First name:</label>
        <input type="text" id="fname" name="fname">
        <label for="lname">Last name:</label>
        <input type="text" id="lname" name="lname">
        <label for="score">score:</label>
        <input type="text" id="score" name="score">
        <input type="submit" id="add" name="add" value="add new">
      </div>
      <hr/>
      <div id="search-form">
        <span>search: <input type="text" id="search-text" name="sfname"> <input type="submit" id="search" name="seach" value="search"></span>
      </div>
      <hr/>
      <h3>users:</h3>
      <table>
        <thead>
          <td class="id">ID</td>
          <td>firstname</td>
          <td>lastname</td>
          <td>score</td>
        </thead>
        <tbody id="users">
         
        </tbody>
      </table>

<p id="msgs"></p>

<textarea id="textarea"></textarea>

كما تلاحظ جميع العناصر المهمه لديها id لنتتبعها من javascript لكل عنصر قيمة id مختلفة.

id عناصر اضافة طالب:
  • fname حقل ادخال الاسم الاول.
  • lname حقل ادخال الاسم الاخير.
  • score حقل ادخال النقاط.
  • add زر الاضافة
id عناصر البحث عن طالب:
  • search-text حقل ادخال نص البحث.
  • search زر بدا البحث.
id عنصر عرض الطلاب:
  • users جسم الجدول.
id عنصر عرض رسائل الخطا:
  • msgs برجراف لعرض الرسائل به.
عنصر textarea لعرض الرد كامل لتتبع الاخطاء اثناء البرمجة يمكنك ازالتة.

عزيزي المبرمج لاحظ هنا لم نقم باضافة زر الحذف والتعديل وحقول التعديل لاننا سوف نضيفها لاحقا بجانب كل طالب اثناء عرضة عبر javascript.

بعد الانتهاء من اضافة كل عناصر html علينا الان استدعاء jquery لارسال استعلام post.

داخل جسم الصفحة <body></body> نقوم باستدعاء jquery كالتالي :
<script src="jquery-3.6.0.min.js"></script>

ولكن قم بتحميل ملف jquery-3.6.0.min.js واضافتة في نفس مجلد السكربت www او قم باستدعائة عبر cdn :

<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>


شرح دالة الارسال والاستقبال في javascript:

بعد استدعاء jquery نكتب javascript الخاصة بنا داخل سكربت جديد <script></script>.

شرح المطلوب:

نحتاج لارسال رسالة تحتوي علي كل البيانات و نوع العملية حتي نستطيع توجية الطلب بشكل مناسب داخل كود بايثون.

ونحتاج لاستقبال رسالة الرد ومعرفة نوع العملية وتوجيهها داخل javascript.

لذالك علينا انشاء كائن object وتعبئتة بالبيانات المطلوبة ونوع العملية ثم تحويلة الي نص json وارسالة ومن ثم استقبال كائن بايثون الذي تم تحويلة الي نص وتحويلة الي كائن javscript.
مثال علي شكل الكائن:
{"callType":"add","callData":"data"}

علي هذا الاساس قمنا بانشاء الدوال التالية:
  • sender لارسال واستقبال البيانات عبر ajax وتحويلها من نص json الي كائن javascript والعكس.
  • receiver لتحديد نوع الرد وتنفيذ التعليمات المطلوبة.

دالة الارسال والاستقبال sender:

function sender(msdCalls){
  msdCalls = JSON.stringify(msdCalls).replaceAll('"','[*]').replaceAll(' ','_');
 
  $.ajax({
       method:"POST",
       url:"center.php",
       dataType: 'text',
       data:{msdCalls:msdCalls},
       success:function(data){
         reply = JSON.parse(data);
         receiver(reply)
       }
     });
}

في السطر الاول من الدالة قمنا بتحويل كائن javascript الي نص عبر JSON.stringify(msdCalls) ثم استبدال علامة التنصيص ( " ) برمز اخر replaceAll('"','[*]') لمنع حدوث اخطاء وسوف نعيد استبدالة في بايثون.

ثم نستبدل المسافة الفارغة الي علامة ( _ ) لمنع حدوث اخطاء في الـ shell اذا كان اسم الطالب اسم مركب مثل "عبد الله".

بعد ذالك عبر الـ ajax نقوم بارسال بيانات متغير msdCalls.
  • method:"POST" - نوع الارسال
  • url:"center.php" - رابط صفحة php
  • dataType: 'text' - نوع البيانات نص
  • data:{msdCalls:msdCalls} - بيانات الارسال
وعند نجاح الارسال واستقبال الرد success نقوم بتحويل نص json الي كائن javascript وارسالة الي دالة receiver(reply) لتحديد نوع البيانات واتخاذ اللازم من الاجرائات.

دالة تحديد نوع البيانات وادارة الواجهة receiver:

function receiver(reply){
  delbutton = '<input type="submit" class="delete" data-id="-id-" data-name="-name-" data-query="-query-" value="delete">';
  editbutton = '<input type="submit" class="edit" data-id="-id-" name="edit" value="edit">';
  if(reply['callType'] == 'viewall'){
    if(reply['students'].length > 0){
      studs = reply['students'];
      allu = '';
      for(var i=0; i<studs.length;i++){
        allu += '<tr class="user-col"><td class="id">'+i+'</td><td>'+studs[i]['first_name']+'</td><td>'+studs[i]['last_name']+'</td><td>'+studs[i]['score']+'</td><td>'+editbutton.replace('-id-', i)+'</td><td>'+delbutton.replace('-id-', i)+'</td></tr>';
      }
      $('#users').html(allu);
    }
  }
  else if(reply['callType'] == 'useradded') {
    stud = reply['stud'];
    u = '<tr class="user-col"><td class="id">'+reply['id']+'</td><td>'+stud['first_name']+'</td><td>'+stud['last_name']+'</td><td>'+stud['score']+'</td><td>'+editbutton.replace('-id-', reply['id'])+'</td><td>'+delbutton.replace('-id-', reply['id'])+'</td></tr>';
    $('#users').html($('#users').html() + u);
  }
  else if(reply['callType'] == 'update') {
    stud = reply['stud'];
    u = '<td class="id">'+reply['id']+'</td><td>'+stud['first_name']+'</td><td>'+stud['last_name']+'</td><td>'+stud['score']+'</td><td>'+editbutton.replace('-id-', reply['id'])+'</td><td>'+delbutton.replace('-id-', reply['id'])+'</td>';
    $('#update').html(u);
    $('#update').removeAttr('id');
  }
  else if(reply['callType'] == 'search'){
    $('#users').html('');
    studs = reply['students'];
   
    if(reply['students'].length != 0){
      allu = '';
      for(var i=0; i<studs.length;i++){
        std = studs[i];
        allu += '<tr class="user-col"><td class="id">'+std['id']+'</td><td>'+std['first_name']+'</td><td>'+std['last_name']+'</td><td>'+std['score']+'</td><td>'+editbutton.replace('-id-', std['id'])+'</td><td>'+delbutton.replace('-id-', std['id']).replace('-name-','search').replace('-query-',$('#search-text').val())+'</td></tr>';
      }
      $('#users').html(allu);
    }
  }
  else if(reply['callType'] == 'error') {
    $('#msgs').html(reply['msg']);
  }

  $('#textarea').html(JSON.stringify(reply));
       
}

في هذه الدالة سوف نتعرف علي نوع البيانات من عنصر reply['callType'] كالتالي:

  • viewall - الكائن يحتوي علي بيانات كل الطلاب.
  • useradded - الكائن يحتوي علي بيانات الطالب الذي تمت اضافتة.
  • update - الكائن يحتوي علي بيانات الطالب بعد التعديل.
  • search - الكائن يحتوي علي بيانات البحث عن طالب او اكثر.
  • error - الكائن يحتوي علي رسالة خطا من بايثون لتتبع مشاكل سكربت بايثون.

وفي حالة الاضافة او الحذف سوف يكون نوع البيانات viewall او search في حالة ان كنت تحذف مستخدم من نتيجة البحث او من عرض الكل.

والان لنبدا في شرح الدالة وما تحتوية من دوال شرطية:
في بداية الدالة قمنا بانشاء متغير delbutton و editbutton لاضافة زر الحذف والتعديل لصفوف الطلاب حتي لا نكرر كتابة كل هذا مراراً وتكراراً.
delbutton = '<input type="submit" class="delete" data-id="-id-" data-name="-name-" data-query="-query-" value="delete">';
  editbutton = '<input type="submit" class="edit" data-id="-id-" value="edit">';
في زر الحذف اضفنا عدة صفات attributes للعنصر وهي:
  1. class="delete" : لتتبع الزر عبر الكلاس ولم نستخدم هنا الـ id لانه سيكون هناك اكثر من زر يحمل نفس الصفة delete.
  2. data-id="-id-" : سوف نستخدم تلك الصفة لتخزين رقم الطالب باستبدال -id- الي رقم الطالب اثناء العرض.
  3. data-name="-name-" : هذه الصفة سوف نستخدمها للتعرف علي مصدر الحذف اذا كان من نتيجة بحث او من الكل.
  4. data-query="-query-" : اذا كانت نتيجة بحث سوف نحتفظ بنص البحث هنا لتحديث البحث عند الحذف.

وفي زر التعديل لن نحتاج سوي صفة واحدة وهي data-id="-id-" للاحتفاظ برقم الطالب.

سوف نقوم بتعديل بعض هذه الصفات داخل الدوال الشرطية تابع.

الشروط وادارة البيانات:

الشرط الاول عرض الكل viewall.

 مثال علي هيكل viewall object:

reply = {"callType":"viewall","students":[{"first_name":"خميس","last_name":"محمد","score":222},{"first_name":"اسامه","last_name":"رزه","score":50}]}

 في هيكل الكائن السابق نوع البيانات callType يساوي viewall و عنصر students يحتوي علي مصفوفة array[] وبداخل المصفوفة قاموس dictionary لكل طالب يحتوي كل قاموس علي الاسم الاول first_name والاسم الاخير last_name والنقاط score.

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

تقوم بالقرائة من البداية اذا كان قاموس {} فيجب استخدام المفتاح وفي الهيكل السابق هناك مفتاحان لهذا القاموس وهما:

  1. callType - نص (string)
  2. students - مصفوفة (array)
لاستخراج احدي تلك البيانات من الكائن تكون كالتالي
  1. reply['callType']
  2. reply['students']

في الحالة الاولي callType تحتوي علي نص اما في الحالة الثانية students تحتوي علي مصفوفة [].

المصفوفة array لها طول ( length ) ويتم الوصول لكل عنصر فيها باستخدام رقم الفهرس (index) ويجب ان يكون من النوع الرقمي integer علي عكس القاموس يجب ان يكون المفتاح قيمة نصية.

وعلي هذا الاساس يمكننا الوصول لعناصر المصفوفة كالتالي:

  • reply['students'][0]
  • reply['students'][1]
فهرس 0 هو الطالب الاول و فهرس 1 الطالب الثاني لان المصفوفة تبدا العد من صفر.
بعد ان وصلنا لعناصر مصفوفة students يتبين لنا ان كل عنصر فيها عبارة عن قاموس{}.
وكما ذكرنا سابقاً نستدعي البيانات بالمفتاح النصي :
  • الطالب الاول
  • reply['students'][0]['first_name']
  • reply['students'][0]['last_name']
  • reply['students'][0]['score']
  • الطالب الثاني
  • reply['students'][1]['first_name']
  • reply['students'][1]['last_name']
  • reply['students'][1]['score']
وبهذا الشكل وصلنا لبيانات كل طالب داخل هيكل البيانات viewall ويمكننا الان عبر حلقة تكرار بسيطة استخراج كل بيانات الطلاب:
for(var INDEX = 0; INDEX < reply['students'].length; INDEX++){
    reply['students'][INDEX]['first_name']
}

بعد ان تعرفنا علي كيفية استخراج بيانات الطلاب من كائن javascript نكتب الشرط الاول كالتالي: 

if(reply['callType'] == 'viewall'){
    if(reply['students'].length > 0){
      studs = reply['students'];
      allu = '';
      for(var i=0; i<studs.length;i++){
        allu += '<tr class="user-col"><td class="id">'+i+'</td><td>'+studs[i]['first_name']+'</td><td>'+studs[i]['last_name']+'</td><td>'+studs[i]['score']+'</td><td>'+editbutton.replace('-id-', i)+'</td><td>'+delbutton.replace('-id-', i)+'</td></tr>';
      }
      $('#users').html(allu);
    }
  } 


  1. اذا كان نوع البيانات callType في كائن reply يساوي عرض الكل viewall.
  2. يتم التحقق من ان طول مصفوفة students اكبر من صفر :  len(students) > 0.
  3. يتم اختصار reply['students'] في متغير studs.
  4. وانشاء متغير نصي allu لجمع صفوف الطلاب من حلقة التكرار for.
  5. حلقة التكرار تبدا من 0 وتتوقف اذا تساوي متغير ( i ) مع طول المصفوفة studs قبل تنفيذ ما بداخلها.
  6.  مع كل تكرار يتم دمج بيانات الطالب مع عناصر html tr & td وجمعهم مع قيمة متغير allu.
  7. ثم اضافة allu لجسم الجدول #users.

لاحظ اثناء اضافة زر الحذف والتعديل استخدمنا دالة الاستبدال delbutton.replace('-id-', i) لاستبدال (-id-) الي رقم الطالب عبر متغير (i).

الشرط الثاني تمت اضافة الطالب useradded.

مثال علي شكل useradded object:

reply = {"callType":"useradded","stud":{"first_name":"ahmed","last_name":"mohamed","score":100},"id":1} 

 في الكائن السابق callType يساوي useradded , ومفتاح stud يساوي قاموس {} لطالب واحد ومفتاح اخير باسم id رقم الطالب.

هنا لدينا بيانات طالب واحد لذالك لن نستخدم حلقة تكرار فقط استخراج الاسم الاول والاخير والنقاط كالتالي:

else if(reply['callType'] == 'useradded') {
    stud = reply['stud'];
    u = '<tr class="user-col"><td class="id">'+reply['id']+'</td><td>'+stud['first_name']+'</td><td>'+stud['last_name']+'</td><td>'+stud['score']+'</td><td>'+editbutton.replace('-id-', reply['id'])+'</td><td>'+delbutton.replace('-id-', reply['id'])+'</td></tr>';
    $('#users').html($('#users').html() + u);
  }


  1. او اذا كان نوع البيانات callType يساوي useradded.
  2. نختصر reply['stud'] في متغير stud.
  3. نضيف بيانات الطالب في صف وخلايا html tr td.
  4. ثم نقوم بجمعها مع الصفوف الموجودة بالفعل في جسم الجدول #users.


الشرط الثالث بحث search.
مثال علي شكل search object:
reply = {"callType":"search","students":[{"first_name":"ahmed","last_name":"mohamed","score":"100","id":"1"}]}

هيكل الكائن السابق مشابة لكائن viewall من حيث انه قاموس وبداخلة عنصر الطلاب علي شكل مصفوفة بها قواميس الطلاب ولكن مع عنصر اضافي وهو id وهو رقم الطالب.

سوف نستخدم حلقة التكرار لاستخراج بيانات كل طالب مع تعديل صفات زر الحذف :

delbutton = '<input type="submit" class="delete" data-id="-id-" data-name="-name-" data-query="-query-" value="delete">';

 سوف نستبدل -id- الي رقم الطالب و استبدال -name- بكلمة search واستبدال -query- بنص البحث الموجود في حقل البحث.

بمعني اننا سوف نستخدم قيمة data-name لنحدد اين تم ضغط هذا الزر من نتيجة بحث ام من كل السجلات, وسوف نستخدم قيمة data-query لنحتفظ بنص البحث لتحديث النتائج بالترتيب الجديد للـ id.

delbutton.replace('-id-', std['id']).replace('-name-','search').replace('-query-',$('#search-text').val())

وهذا الشكل النهائي للشرط: 

else if(reply['callType'] == 'search'){
    $('#users').html('');
    studs = reply['students'];
    
    if(studs.length > 0){
      allu = '';
      for(var i=0; i<studs.length;i++){
        std = studs[i];
        allu += '<tr class="user-col"><td class="id">'+std['id']+'</td><td>'+std['first_name']+'</td><td>'+std['last_name']+'</td><td>'+std['score']+'</td><td>'+editbutton.replace('-id-', std['id'])+'</td><td>'+delbutton.replace('-id-', std['id']).replace('-name-','search').replace('-query-',$('#search-text').val())+'</td></tr>';
      }
      $('#users').html(allu);
    }
  }
  1. اذا كان نوع البيانات callType في كائن reply يساوي بحث search.
  2. نحذف صفوف الجدول #users.
  3. يتم اختصار reply['students'] في متغير studs.
  4. يتم التحقق من ان طول مصفوفة studs اكبر من صفر :  len(students) > 0.
  5. وانشاء متغير نصي allu لجمع صفوف الطلاب من حلقة التكرار for.
  6. حلقة التكرار تبدا من 0 وتتوقف اذا تساوي متغير ( i ) مع طول المصفوفة studs قبل تنفيذ ما بداخلها.
  7.  مع كل تكرار يتم دمج بيانات الطالب مع عناصر html tr & td وجمعهم مع قيمة متغير allu.
  8. ثم اضافة allu لجسم الجدول #users.

الشرط الرابع الاخطاء error:
هذا الشرط لتتبع اخطاء ملف بايثون اثناء التنفيذ لان بدونة لن يمكننا معرفة الاخطاء التي حدثت اثناء تشغيل ملف بايثون.
else if(reply['callType'] == 'error') {
    $('#msgs').html(reply['msg']);
  }
  1. اذا كان نوع البيانات callType يساوي error.
  2. يتم عرض الرسالة في عنصر #msgs.
وفي نهاية الدالة قمنا بعرض كامل الرد في عنصر textarea لسهولة التعديل علي الكائن اثناء العمل علي السكربت.
$('#textarea').html(JSON.stringify(reply));
الي هنا تم الانتهاء من شرح دالة تحديد نوع الرد receiver(reply).

الاحداث والنقرات:
في هذا الجزء سوف نتتبع الاحداث والنقرات مثل الضغط علي الازرار وعند تحميل الصفحة.
$(document).on('click', '#add', function(){
  var msdCalls = {
                "callType":"add",
                "callData":{
                            "firstName":$('#fname').val(),
                            "lastName":$('#lname').val(),
                            "score":parseInt($('#score').val())
                          }
                  }
  sender(msdCalls);
})

$(document).on('click', '.delete', function(){
  var query = "";
  if($(this).data('name') == 'search'){
    query = $(this).data('query');
  }
  var msdCalls = {
                    "callType":"delete",
                    "query":query,
                    "callData":{"id":$(this).attr("data-id")}
                  }
  sender(msdCalls);
})

var trbackup = '';
$(document).on('click', '.edit', function(){
  trbackup = $(this).closest('tr').html();
  var tr = $(this).closest('tr');
  var id = $(tr.find('td')[0]).text();
  var fname = $(tr.find('td')[1]).text();
  var lname = $(tr.find('td')[2]).text();
  var score = $(tr.find('td')[3]).text();
  tr.attr("id", "update");
  $(tr).html('<td class="id">'+id+'</td><td><input type="text" id="edit-fname" value="'+fname+'"></td><td><input type="text" id="edit-lname" value="'+lname+'"></td><td><input type="text" id="edit-score" value="'+score+'"></td><td><input type="submit" id="save" data-id="'+id+'" value="save"></td><td><input type="submit" id="cancel-e" value="cancel"></td>');

})

$(document).on('click', '#cancel-e', function(){
  var tr = $(this).closest('tr');
  tr.removeAttr('id');
  $(tr).html(trbackup);
})

$(document).on('click', '#save', function(){
  var msdCalls = {
                "callType":"update",
                "callData":{
                            "id":$(this).attr("data-id"),
                            "firstName":$('#edit-fname').val(),
                            "lastName":$('#edit-lname').val(),
                            "score":parseInt($('#edit-score').val())
                          }
                  }
  sender(msdCalls);
})

$(document).on('click', '#search', function(){
  var msdCalls = {
                "callType":"search",
                "callData":$('#search-text').val()
                }
  sender(msdCalls);
})

window.onload = function() {
  var msdCalls = {
                "callType":"viewall"
                  }
  sender(msdCalls);

};

الحدث الاول عند الضغط علي زر اضافة طالب add.
سوف نتتبع الضغط علي هذا الزر عبر قيمة id ويرمز لها بـ ( # ) لاحظ ان هذه الصفة لا يمكن ان يتشارك في قيمتها عنصرين معاً عكس صفة class.
  1. يتم انشاء كائن مع تحديد نوع البيانات callType يساوي add.
  2. وعبر jquery نقوم بجلب بيانات الطالب من حقول الادخال #fname و #lname و #score.
  3. ثم نفعل دالة الارسال sender مع متغير msdCalls.
$(document).on('click', '#add', function(){
  var msdCalls = {
                "callType":"add",
                "callData":{
                            "firstName":$('#fname').val(),
                            "lastName":$('#lname').val(),
                            "score":parseInt($('#score').val())
                          }
                  }
  sender(msdCalls);
})
الحدث الثاني ( حذف ) delete:

في هذا الحدث سوف نتتبع الضغط علي زر الحذف بواسطة قيمة class ويرمز لها بعلامة النقطة ( . ) مع تحديد اذا كان تم الضغط علية من نتيجة بحث او من كل السجلات من خلال قيمة data-name اذا كانت تحتوي علي كلمة search او لا.

تفعيل الحدث عند الضغط علي اي زر يحمل قيمة class=delete:
$(document).on('click', '.delete', function(){
  var query = "";
  if($(this).data('name') == 'search'){
    query = $(this).data('query');
  }
  var msdCalls = {
                    "callType":"delete",
                    "query":query,
                    "callData":{"id":$(this).attr("data-id")}
                  }
  sender(msdCalls);
})

لاحظ في هذا الحدث قمنا باستخراج قيمة data-name و data-query عبر كلمة this لاستخراج القيم من هذا الزر تحديدا دون بقية اخوتة في الكلاس delete.

  1. في هذا الحدث اذا كانت قيمة الـ data-name تساوي كلمة search فهذا يعني انه تم الضغط علي الزر من نتائج البحث وسوف يتم اضافة قيمة الصفة data-query الي الكائن msdCalls.
  2. اما اذا كانت قيمة data-name فارغة فهذا يعني انه تم الضغط علي الزر من نتائج الكل viewall وفي تلك الحالة سوف نبقي عنصر query في الكائن فارغ.
  3. وتحديد نوع البيانات delete.
  4. وفي عنصر callData سوف نضع قيمة data-id الموجودة في زر الحذف وهي رقم الطالب في ملف json.
  5. ثم ارسال الـ object عبر sender.

الحدث الثالث التعديل edit:

في هذا الحدث سوف نتتبع الضغط علي اي زر يحمل قيمة class=edit ثم استبدال الصف الموجود به هذا الزر تحديدا بصف اخر به 3 حقول ادخال input مع اضافة قيم بيانات الطالب المراد تعديل بياناته مع زر الغاء و زر حفظ.

var trbackup = '';
$(document).on('click', '.edit', function(){
  trbackup = $(this).closest('tr').html();
  var tr = $(this).closest('tr');
  var id = $(tr.find('td')[0]).text();
  var fname = $(tr.find('td')[1]).text();
  var lname = $(tr.find('td')[2]).text();
  var score = $(tr.find('td')[3]).text();
  tr.attr("id", "update");
  $(tr).html('<td class="id">'+id+'</td><td><input type="text" id="edit-fname" value="'+fname+'"></td><td><input type="text" id="edit-lname" value="'+lname+'"></td><td><input type="text" id="edit-score" value="'+score+'"></td><td><input type="submit" id="save" data-id="'+id+'" value="save"></td><td><input type="submit" id="cancel-e" value="cancel"></td>');

})

في السطر الاول خارج الحدث صرحنا عن متغير نصي باسم trbackup لاسترجاع بيانات الطالب كما كانت في حالة الضغط علي زر الغاء cancel-e.

داخل الحدث وعبر jquery استخرجنا كود html من الاب لهذا العنصر $(this).closest('tr').html() وهي بمعني اختر اقرب عنصر tr لهذا العنصر.

علي السطر الذي يلية قمنا باختصار مسار العنصر في متغير لسهولة الاستخدام var tr = $(this).closest('tr');.

ثم بعد ذالك قمنا بنسخ محتوي الخلايا لهاذا الطالب لاضافتها لحقول الادخال للتعديل عليها.

var id = $(tr.find('td')[رقم الطالب]).text();
var fname = $(tr.find('td')[الاسم الاول]).text();
var lname = $(tr.find('td')[الاسم الاخير]).text();
var score = $(tr.find('td')[النقاط]).text();

ثم بعد ذالك قمنا باضافة id باسم update لصف الجدول هذا للتتبع واستبدال محتواه ببيانات الطالب المحدثة به عند عودتها tr.attr("id", "update");.

ثم بعد ذالك اضفنا للصف 3 عناصر ادخال input بقيمة id التالية:
  1. edit-fname
  2. edit-lname
  3. edit-score
ثم اضفنا زر حفظ و زر الغاء بقيمة id التالية:
  1. save
  2. cancel-e
لاحظ قمنا باضافة رقم الطالب لقيمة data-id في زر الحفظ لارسالة مع object تحديث بيانات الطالب.

حدث زر الغاء التعديل:
عند الضغط علي زر الالغاء #cancel-e الذي تم انشائة في الحدث الثالث edit.
$(document).on('click', '#cancel-e', function(){
  var tr = $(this).closest('tr');
  tr.removeAttr('id');
  $(tr).html(trbackup);
})
في هذا الحدث نحذف id من صف الجدول هذا ونرجع قيمته للقيمة السابقة التي احتفظنا بها في متغير trbackup السابق.

حدث حفظ البيانات الجديدة للطالب:
عند الضغط علي زر الحفظ #save الذي تم انشائة في الحدث الثالث edit.
$(document).on('click', '#save', function(){
  var msdCalls = {
                "callType":"update",
                "callData":{
                            "id":$(this).attr("data-id"),
                            "firstName":$('#edit-fname').val(),
                            "lastName":$('#edit-lname').val(),
                            "score":parseInt($('#edit-score').val())
                          }
                  }
  sender(msdCalls);
})
هذا الحدث يشبة حدث اضافة طالب مع اختلاف نوع البيانات update ورقم الطالب id والحقول التي ناخذ منها البيانات $('#edit-fname').val().

الحدث الرابع البحث search:
عند الضغط علي زر البحث يتم اضافة قيمة حقل البحث '#search-text' الي object البحث مع تحديد نوع البيانات "callType":"search" ثم الارسال عبر sender.
$(document).on('click', '#search', function(){
  var msdCalls = {
                "callType":"search",
                "callData":$('#search-text').val()
                }
  sender(msdCalls);
})
الحدث الخامس عرض الكل viewall:
هذا الحدث سوف يفعل عند تحميل او اعادة تحميل الصفحة وسوف يرسل طلب لجلب بيانات كل الطلاب كلما فتحنا او حدثنا الصفحة.
window.onload = function() {
  var msdCalls = {
                "callType":"viewall"
                  }
  sender(msdCalls);
};

عند الانتهاء من تحميل الصفحة window.onload انشىء object بنوع بيانات "callType":"viewall" وارسال الـobject الي دالة الارسال sender.

الي هنا انتهينا من كتابة ملف index.htm

وهذا هو كود الملف بالكامل:

ملف index.htm كامل:

index.htm
<html>
<head>
  <meta http-equiv="Content-Type" content="application/xhtml+xml; charset=UTF-8" />
  <?xml version="1.0" encoding="UTF-8"?>
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <title>python anywhere!</title>
  <style>
    h3, h4 {
      height: 3px;
      width: 100%;
    }
    p, textarea {
      width: 100%;
    }
    table {
      width: 100%;
    }
    td {
      width: 30%;
    }
    .id {
      width:10%;
    }
  </style>
</head>
<body>
      <h1>python student mangament</h1>
      <h3>add new user :</h3>
      <div id="add-form">
        <label for="fname">First name:</label>
        <input type="text" id="fname" name="fname">
        <label for="lname">Last name:</label>
        <input type="text" id="lname" name="lname">
        <label for="score">score:</label>
        <input type="text" id="score" name="score">
        <input type="submit" id="add" name="add" value="add new">
      </div>
      <hr/>
      <div id="search-form">
        <span>search: <input type="text" id="search-text" name="sfname"> <input type="submit" id="search" name="seach" value="search"></span>
      </div>
      <hr/>
      <h3>users:</h3>
      <table>
        <thead>
          <td class="id">ID</td>
          <td>firstname</td>
          <td>lastname</td>
          <td>score</td>
        </thead>
        <tbody id="users">
         
        </tbody>
      </table>

<p id="msgs"></p>

<textarea id="textarea"></textarea>

<!-- <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>    
-->
<script src="jquery-3.6.0.min.js"></script>    


<script>

function sender(msdCalls){
  msdCalls = JSON.stringify(msdCalls).replaceAll('"','[*]').replaceAll(' ','_');
 
  $.ajax({
       method:"POST",
       url:"center.php",
       dataType: 'text',
       data:{msdCalls:msdCalls},
       success:function(data){
         reply = JSON.parse(data);
         receiver(reply)
       }
     });
}



function receiver(reply){
  delbutton = '<input type="submit" class="delete" data-id="-id-" data-name="-name-" data-query="-query-" value="delete">';
  editbutton = '<input type="submit" class="edit" data-id="-id-" name="edit" value="edit">';
  if(reply['callType'] == 'viewall'){
    if(reply['students'].length > 0){
      studs = reply['students'];
      allu = '';
      for(var i=0; i<studs.length;i++){
        allu += '<tr class="user-col"><td class="id">'+i+'</td><td>'+studs[i]['first_name']+'</td><td>'+studs[i]['last_name']+'</td><td>'+studs[i]['score']+'</td><td>'+editbutton.replace('-id-', i)+'</td><td>'+delbutton.replace('-id-', i)+'</td></tr>';
      }
      $('#users').html(allu);
    }
  }
  else if(reply['callType'] == 'useradded') {
    stud = reply['stud'];
    u = '<tr class="user-col"><td class="id">'+reply['id']+'</td><td>'+stud['first_name']+'</td><td>'+stud['last_name']+'</td><td>'+stud['score']+'</td><td>'+editbutton.replace('-id-', reply['id'])+'</td><td>'+delbutton.replace('-id-', reply['id'])+'</td></tr>';
    $('#users').html($('#users').html() + u);
  }
  else if(reply['callType'] == 'update') {
    stud = reply['stud'];
    u = '<td class="id">'+reply['id']+'</td><td>'+stud['first_name']+'</td><td>'+stud['last_name']+'</td><td>'+stud['score']+'</td><td>'+editbutton.replace('-id-', reply['id'])+'</td><td>'+delbutton.replace('-id-', reply['id'])+'</td>';
    $('#update').html(u);
    $('#update').removeAttr('id');
  }
  else if(reply['callType'] == 'search'){
    $('#users').html('');
    studs = reply['students'];
   
    if(reply['students'].length > 0){
      allu = '';
      for(var i=0; i<studs.length;i++){
        std = studs[i];
        allu += '<tr class="user-col"><td class="id">'+std['id']+'</td><td>'+std['first_name']+'</td><td>'+std['last_name']+'</td><td>'+std['score']+'</td><td>'+editbutton.replace('-id-', std['id'])+'</td><td>'+delbutton.replace('-id-', std['id']).replace('-name-','search').replace('-query-',$('#search-text').val())+'</td></tr>';
      }
      $('#users').html(allu);
    }
  }
  else if(reply['callType'] == 'error') {
    $('#msgs').html(reply['msg']);
  }

  $('#textarea').html(JSON.stringify(reply));
       
}



$(document).on('click', '#add', function(){
  var msdCalls = {
                "callType":"add",
                "callData":{
                            "firstName":$('#fname').val(),
                            "lastName":$('#lname').val(),
                            "score":parseInt($('#score').val())
                          }
                  }
  sender(msdCalls);
})

$(document).on('click', '.delete', function(){
  var query = "";
  if($(this).data('name') == 'search'){
    query = $(this).data('query');
  }
  var msdCalls = {
                    "callType":"delete",
                    "query":query,
                    "callData":{"id":$(this).attr("data-id")}
                  }
  sender(msdCalls);
})

var trbackup = '';
$(document).on('click', '.edit', function(){
  trbackup = $(this).closest('tr').html();
  var tr = $(this).closest('tr');
  var id = $(tr.find('td')[0]).text();
  var fname = $(tr.find('td')[1]).text();
  var lname = $(tr.find('td')[2]).text();
  var score = $(tr.find('td')[3]).text();
  tr.attr("id", "update");
  $(tr).html('<td class="id">'+id+'</td><td><input type="text" id="edit-fname" value="'+fname+'"></td><td><input type="text" id="edit-lname" value="'+lname+'"></td><td><input type="text" id="edit-score" value="'+score+'"></td><td><input type="submit" id="save" data-id="'+id+'" value="save"></td><td><input type="submit" id="cancel-e" value="cancel"></td>');

})

$(document).on('click', '#cancel-e', function(){
  var tr = $(this).closest('tr');
  tr.removeAttr('id');
  $(tr).html(trbackup);
})

$(document).on('click', '#save', function(){
  var msdCalls = {
                "callType":"update",
                "callData":{
                            "id":$(this).attr("data-id"),
                            "firstName":$('#edit-fname').val(),
                            "lastName":$('#edit-lname').val(),
                            "score":parseInt($('#edit-score').val())
                          }
                  }
  sender(msdCalls);
})

$(document).on('click', '#search', function(){
  var msdCalls = {
                "callType":"search",
                "callData":$('#search-text').val()
                }
  sender(msdCalls);
})

window.onload = function() {
  var msdCalls = {
                "callType":"viewall"
                  }
  sender(msdCalls);

};
</script>
</body>
</html>


كتابة ملف بايثون stud.py كامل

stud.py
#!/usr/bin/python
# -*- coding: utf-8 -*-

import json
import sys
import os



def errmsg(e):
    exc_type, exc_obj, exc_tb = sys.exc_info()
    fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
    err = 'error: %s : file: %s : line: %s : type: %s' % (e, fname, exc_tb.tb_lineno, exc_type)
    err = ''.join( c for c in str(err) if  c not in "'<>\"")
    print({"callType":"error","msg":err})


try:
    dfile = './stud.json'
    if not os.path.exists(dfile):
        newFile = '{"students" : []}'
        jloads = json.loads(newFile)

        with open(dfile, 'w') as f:
            json.dump(jloads, f, ensure_ascii=False, indent=4)
    studFile = json.loads(open(dfile, 'r', encoding='utf-8').read())
    studF = json.loads(open(dfile, 'r').read())

except Exception as e:
    errmsg(e)

def savedata():
    try:
        jdumps = json.dumps(studF)
        jloads = json.loads(jdumps)
        with open(dfile, 'w') as f:
            json.dump(studF, f, ensure_ascii=False, indent=4)
    except Exception as e:
        errmsg(e)
   
def ajax(arg):
    try:
        arg = arg.replace('[*]','"').replace('_',' ')
       
        data = json.loads(arg)
       
        if data["callType"] == 'add':
            studObj = {
            "first_name": data["callData"]["firstName"],
            "last_name": data["callData"]["lastName"],
            "score": data["callData"]["score"]
            }

            studF["students"].append(studObj)
            savedata()
            studFs = json.loads(open(dfile, 'r', encoding='utf-8').read())
            #print(data["callData"], end="")
            id = len(studFs["students"])-1
            print(json.dumps({"callType":"useradded","stud":studFs["students"][id], "id":id}))
       
        elif data["callType"] == 'delete':
            for i in range(len(studF["students"])):
                std = studF["students"][i]
                if int(data["callData"]["id"]) == i:
                    del studF["students"][i]
                    savedata()
                    break
            if data["query"] == '':
                studFs = json.loads(open(dfile, 'r', encoding='utf-8').read())
                print(json.dumps({"callType":"viewall","students":studFs["students"]}))
            else:
                research = '{"callType":"search","callData":"%s"}' % data["query"]
                ajax(research)
           
        elif data["callType"] == 'update':
            ud = data["callData"]
            for i in range(len(studF["students"])):
                std = studF["students"][i]
                if int(ud["id"]) == i:
                    studF["students"][i] = { "first_name": ud['firstName'], "last_name": ud['lastName'], "score": int(ud["score"]) }
                    savedata()
                    studFs = json.loads(open(dfile, 'r', encoding='utf-8').read())
                    break
            id = int(ud["id"])
            print(json.dumps({"callType":"update","stud":studFs["students"][id], "id":id}))
       
        elif data["callType"] == 'search':
            fn = data["callData"]
            multistud = []
            for i in range(len(studF["students"])):
                objstud = studF["students"][i]
                std = studF["students"][i]
                if fn != '':
                    if fn in std["first_name"] +' '+ std["last_name"]:
                        multistud.append('{"first_name":"%s", "last_name":"%s","score":"%s", "id":"%s"}' % (objstud["first_name"], objstud["last_name"], objstud["score"], i))

            multistud = "[" + ','.join(multistud) + "]"
            print('{"callType":"search","students":%s}' % multistud)

        elif data["callType"] == 'viewall':
            try:
                print(json.dumps({"callType":"viewall","students":studFile["students"]}))
            except Exception as e:
                errmsg(e)
    except Exception as e:
        errmsg(e)


ajax(sys.argv[1])


قمنا بشرح سكربت stud.py في المقال السابق يمكنك الرجوع اليه لمزيد من التفاصيل ويمكنك طلب المساعدة او طلب توضيح اي شىء عبر التعليقات بالاسفل.

تحميل ملفات السكربت جاهزة:


شكراً للمتابعة ولا تنسي مشاركة المقال لتعم الفائدة.





أنت الان في اول موضوع

تعليقات

التنقل السريع