كيفية إضافة حقول مخصصة إلى النماذج

هذه مجموعة من إضافات التعليم التي توضح كيفية إضافة حقل مخصص إلى نماذج مختلفة في Discourse. وهي مُعدّة كأدوات تعليمية لمن يرغبون في تعلم كيفية بناء إضافات Discourse.

GitHub-Mark كيفية إضافة حقل مخصص إلى موضوع
GitHub-Mark كيفية إضافة حقل مخصص إلى فئة

لمن تُوجَّه هذه الإضافات

هذه الإضافات موجهة للأشخاص الراغبين في معرفة المزيد حول إنشاء إضافات Discourse. قبل البدء في العمل مع هذه الإضافات، يُفضَّل إكمال دليل المبتدئين لإنشاء إضافات Discourse.

يمكنك استخدام هذه الإضافات فقط لإضافة حقول مخصصة في نسخة Discourse الخاصة بك، لكنك ستحتاج مع ذلك إلى تعديل الكود قليلاً للقيام بذلك. لم تُصمَّم هذه الإضافات للاستخدام الفوري (plugin-and-play) على خادم حي.

كيف تعمل

إلى جانب احتوائها على كود يعمل، تحتوي كل إضافة على وصف خطوة بخطوة لما يفعله الكود في شكل تعليقات. على سبيل المثال:

## 
# type:        step
# number:      1
# title:       Register the field
# description: Where we tell discourse what kind of field we're adding.
#              You can register a string, integer, boolean or json field.
# references:  lib/plugins/instance.rb,
#              app/models/concerns/has_custom_fields.rb
##
register_topic_custom_field_type(FIELD_NAME, FIELD_TYPE.to_sym)

نأمل أن تكون الخطوات والتعليقات واضحة بذاتها. أما الإشارات references فهي موجودة لتوجيهك إلى المكان الذي يمكنك فيه الاطلاع على مزيد من المعلومات إذا رغبت في ذلك.

أخبرني إذا وجدتها مفيدة، أو إذا كان هناك شيء لا يعمل، أو إذا كانت الملاحظات غير واضحة :slight_smile:

28 إعجابًا

شكرًا لك على بناء هذا الإضافة لـنا،
لقد واجهت هذا الخطأ بعد تثبيت الإضافة:

عذرًا
واجهت البرمجيات التي تشغل منتدى المناقشة هذا مشكلة غير متوقعة. نعتذر عن الإزعاج.
تم تسجيل معلومات مفصلة حول الخطأ، وتم إنشاء إشعار تلقائي. سنقوم بالنظر في الأمر.
لا حاجة لاتخاذ أي إجراء إضافي. ومع ذلك، إذا استمرت حالة الخطأ، فيمكنك تقديم تفاصيل إضافية، بما في ذلك الخطوات اللازمة لتكرار الخطأ، من خلال نشر موضوع مناقشة في فئة ملاحظات الموقع.

مرحبًا، هل تقوم بتشغيل هذا في بيئة التطوير المحلية لديك؟ إذا كان الأمر كذلك، هل يمكنك إرسال سجلات التطوير عبر الرسائل الخاصة؟ إذا كانت بيئة التطوير الخاصة بك مُعدة بشكل صحيح، فستعمل هذه الإضافة.

إعجاب واحد (1)

قرأت، لكنني لم أستطع الفهم. هل يمكنك تخصيص الوقت لصنع فيديو تعليمي يوضح كيفية تثبيت هذا الإضافة، بدءًا من إعداد بيئة التطوير المحلية وصولاً إلى إضافة حقل مخصص خطوة بخطوة؟
من الصعب جدًا الوصول إلى هذا الإضافة.
إذا أمكن، هل يمكنك تحسين ميزة البحث لنوع الحقل الرقمي؟

يمكنني التبرع لهذا الإضافة، يرجى دعمي ومساعدتي!

هذا المورد الذي جمعه @angus أكثر فائدة مما أدركت في البداية.

فلا يقتصر الأمر على وجود كود إضافة حقل مخصص مع شرح واضح، بل إن الكثير منه يمكن دمجه مباشرة في أي إضافة، لأن الكود يستخدم في الغالب متغيرات مثل FIELD_NAME و FIELD_VALUE، والتي يمكنك تعريفها في ملف plugin/config/settings.yml (يجب أيضًا التأكد من أن هيكل ملفات الإضافة لديك مطابق لهيكل كود GitHub الذي قدمه @angus). كما أن الاطلاع على الكود ساعدني على فهم أفضل لبعض دوال ووظائف Discourse التي صادفتها من قبل، لكنني لم أفهمها حقًا حتى الآن.

حتى الآن، يعمل الكود بشكل ممتاز لإنشاء وحفظ حقول مخصصة للمواضيع. لكن هناك سؤالين يطرآن عليّ باستمرار:

  1. خطأ قائمة المواضيع: يبدو أنه يُظهر خطأ في حال محاولة تحميل قائمة فئات (أي قائمة مواضيع لفئة معينة) تحتوي على مواضيع تم إنشاؤها قبل إضافة الحقل المخصص. فيظهر صفحة الاستثناء، ويذكر هذا الخطأ: Attempting to access a non preloaded custom field, this is disallowed to prevent N+1 queries. ما هي الطريقة الموصى بها لحل هذه المشكلة؟

  2. هل توجد طريقة لتطبيق الحقل المخصص فقط على المواضيع في فئات معينة؟ فمثلًا، لنفترض أن لدي الفئة 1 والفئة 2 والفئة 3، وأريد فقط أن يظهر حقل الإدخال المخصص وأن يتم حفظ الحقل فقط إذا كان الموضوع جزءًا من الفئة 3. هل توجد طريقة للقيام بذلك؟

إعجاب واحد (1)

نحن (Pavilion) سنقوم بشيء مشابه في المستقبل، لكن حاليًا يتوفر فقط الكود والخطوات. إذا واجهت مشكلة محددة، أنشئ منشورًا في قناة Development ووصف المشكلة بتفصيل معقول.

لقد أضفت خطوة فرعية إلى موضوع إضافة حقول الموضوع المخصصة يوضح كيفية تحميل الحقول مسبقًا إذا كنت تستخدمها في قائمة الموضوعات.

ستحتاج إلى حقل مخصص للفئات لتحديد الفئات التي يجب أن يظهر فيها. لقد طبقت نفس المعاملة لحقول الفئات المخصصة في هذه الإضافة، مع توفير نفس التعليمات خطوة بخطوة:

دمج هاتين الإضافتين التعليميتين لن يقودك إلى هدفك بالكامل، لكن حاول المضي قدمًا من هناك.

إعجابَين (2)

هذا رائع، @angus. شكرًا جزيلاً لك.

فيديو دائمًا أمر لطيف—أنا مؤيد تمامًا لجعل الأمور بسيطة قدر الإمكان، لكنني لا أعتقد أنه ضروري للحصول على القيمة الأساسية من هذه الموارد التي جهزها @angus. هذه الموارد تمنحك الكود اللازم لتحقيق الهدف المحدد الذي تتناوله كل مورد (مثل إنشاء حقل مخصص للموضوع أو حقل مخصص للفئة). الفيديو على الأرجح سيكون مجرد @angus أو شخص آخر يشرح كيفية تنفيذ المورد، لكن هذا الأمر مباشر، ويمكننا بالتأكيد توضيحه هنا.

وللتوضيح، هذه الموارد ليست إضافات (plugins) تضيفها إلى موقعك ببساطة لتعمل فورًا وتخصّص المنتدى. بل إنها تمنحك بكفاءة الفهم اللازم لبرمجة حقولك المخصصة الخاصة بك ضمن إضافتك.


إليك كيف استخدمت هذه الموارد:

ستحتاج إلى إضافة اسم ونوع الحقل الذي تريده في config/settings. الكود في هذه الموارد يستخدم متغيرات مُعرَّفة هناك. لذا، في الواقع، لن تحتاج إلى تخصيص كبير للكود لجعله يعمل في إضافتك الخاصة بعد ذلك—المتغيرات في plugin.rb وفي أماكن أخرى تشير إلى config/settings، وعندها سيعمل كل شيء.

بعد تحديث config/settings، يمكنك ببساطة اتباع الكود وإضافته إلى إضافتك:

  • ابدأ بالكود الموجود في plugin.rb، وأضفه إلى ملف plugin.rb الخاص بإضافتك لإنشاء الحقل المخصص.

  • ثم انتقل إلى initializer (في assets/javascripts/discourse/[custom-field-initiliazer]) للحصول على الكود الذي سيقوم بتهيئة الحقل المخصص ويسمح بحفظه على الخادم.

  • ثم أنشئ النموذج في طبقة العرض (view layer) الذي سيكون المكان الذي يدخل فيه المستخدم (أو تطبيقك، إذا كان التطبيق يضيف الحقل تلقائيًا) قيمة الحقل المخصص، هنا (assets/discourse/connectors/[plugin-outlet-name]/[your special template].hbs).

  • لقد قام @angus بإعداد هذه العناصر بحيث يمكنك إضافة نماذج الحقول المخصصة في منفذ إضافي (plugin outlet) سيتم إدراجه في قالب Discourse. إعدادات هذا النموذج موجودة هنا (assets/javascripts/discourse/lib/[custom-field-name].js.es6)، لذا من المرجح أنك تريد تخصيصها أيضًا لجعل النموذج يعمل.

@angus، لا تتردد في تصحيح أي شيء قلته هنا.

بمجرد أن اكتسبت فهمًا جيدًا لإعداد الحقل المخصص باتباع الخطوات أعلاه، بدأت في تخصيص الأمور بشكل أكبر قليلًا (على سبيل المثال، الإبداع أكثر في طريقة عمل النموذج)، لكن هذا كان نقطة انطلاق مفيدة للغاية أنقذتني ساعات من العمل.

بعد الانتهاء من ذلك، كانت لدي بعض الأسئلة (كما سألته سابقًا)، لكن الحصول على ردود في Development يبدو الطريقة الأكثر فائدة للمضي قدمًا في الأمور من هناك.

3 إعجابات

وصف رائع! نعم، هكذا يُفترض استخدامها :+1:

إعجاب واحد (1)

تعديل: لقد نشرت في الأصل سؤالتي حول كيفية استرداد العناصر بناءً على حقل مخصص هنا، لكنني قررت أن السؤال يختلف بما يكفي ليبرر منشورًا خاصًا به. لذا قمت بنشره بشكل منفصل هنا.

إعجابَين (2)

أواجه سلوكًا غريبًا مع المُنشئ (Composer) بعد اتباع مثال حقول الموضوع المخصصة.

عند الضغط على زر “إنشاء موضوع” (على سبيل المثال، في صفحة عرض التصنيف—أو في أي مكان آخر في الموقع)، يفشل المُنشئ في الفتح، وأحصل على الخطأ التالي:

Uncaught Error: Assertion Failed: The key provided to set must be a string or number, you passed undefined
    at assert (index.js:172)
    at set (index.js:2802)
    at Class.set (observable.js:176)
    at composer.js:769
    at Array.forEach (<anonymous>)
    at Class.open (composer.js:768)
    at composer.js:898
    at invokeCallback (rsvp.js:493)
    at rsvp.js:558
    at rsvp.js:19

بدأت ظهور هذه الخطأ لأول مرة عندما حاولت إضافة زر جديد لـ “إنشاء موضوع” في صفحة جديدة، ولكن منذ ذلك الحين، حتى بعد إزالة هذا الزر الجديد وحتى بعد إزالة أي كود مرتبط به، لا يزال الخطأ قائمًا.

بشكل ما، أعتقد أن الكود التالي—من topic-custom-field-initializer—هو السبب في المشكلة:

api.serializeOnCreate(fieldName);
api.serializeToDraft(fieldName);
api.serializeToTopic(fieldName, `topic.${fieldName}`);

عند إزالة هذا الكود، تعمل أزرار إنشاء الموضوع مرة أخرى (تفتح المُنشئ بشكل صحيح). وعند إعادة الكود، يعود خطأ المُنشئ.

كان هذا الكود موجودًا في ملحقي سابقًا دون أي مشكلة. لكن الآن يتسبب في خطأ المُنشئ (حتى لو قمت بإزالة أي كود مرتبط بالمُنشئ أو بأزرار إنشاء الموضوع في ملحقي).

بالتأكيد، هذا الكود مهم—حيث يقوم بتسلسل الحقل المخصص. لكنه يبدو أنه يتعارض مع المُنشئ. هل لديكم أي أفكار حول كيفية إصلاح ذلك؟

تمكّنت من تحديد السبب: كنت أحاول إضافة حقلين مخصصين منفصلين إلى مُهيّئ الحقول المخصصة للمواضيع. ولأسباب غير واضحة، كان ذلك يتسبب في بعض التداخل. ربما توجد طريقة صحيحة لإضافة حقلين مخصصين إلى هذا الملف، لكن كودي، الذي كرّر نفس الكود للحقلين المخصصين المنفصلين، كان يسبب مشاكل. عاد كل شيء ليعمل بشكل صحيح بمجرد إزالة الحقل المخصص الثاني من ذلك الملف.

مع هذا الرمز الهيكلي، إذا أردت إضافة حقول متعددة، فهل يجب أن يكون كل حقل بمثابة إضافة مستقلة؟

أنا متحمس جدًا للعثور على هذا البرنامج التعليمي وأتساءل، ما مقدار التعديلات، إن وجدت، التي سأحتاج إلى إجرائها على هذه القوالب لكي تعمل مع حقول المستخدم المخصصة؟

لا، تحتاج فقط إلى إضافة رمز إضافي للحقل الإضافي. في معظم الحالات، يكون ذلك بمجرد تكرار الرمز الموجود، على سبيل المثال:

add_preloaded_topic_list_custom_field(FIELD_NAME_1)
add_preloaded_topic_list_custom_field(FIELD_NAME_2)

أول مكان للبحث عن حقول المستخدم المخصصة هو /admin/customize/user_fields والذي يمنحك واجهة مستخدم لإضافتها. إذا كنت ترغب في الحصول على تحكم أكثر دقة، فإن العملية تبدو مشابهة جدًا للموضوع والفئة، ولكنك لا تحتاج فعليًا إلى عناصر الواجهة الأمامية مع حقول المستخدم.

في الواقع، نحن (Pavilion) نفكر في إنشاء إضافة للحقول المخصصة (على غرار ACF لـ WordPress) والتي ستبدو في البداية مثل واجهة إدارة الحقول المخصصة في إضافة Custom Wizard.

في الواقع، يستخدم بعض الأشخاص بالفعل إضافة Custom Wizard كمدير للحقول المخصصة. تسرد جميع الحقول المخصصة في مثيلك (من أي مصدر) وتسمح لك بإضافة حقل من أي نوع إلى أي نموذج يدعمها.

إنها لا تضيف دعمًا للواجهة الأمامية، على سبيل المثال، مثل ذلك المعروض في إضافة تعليم Topic Custom Field (ولن يعمل ذلك في سياق إضافة Custom Wizard)، ولهذا السبب نفكر في فصل ذلك إلى إضافة منفصلة.

3 إعجابات

@angus، أعتقد أنني سأحب هذا.

خاصة إذا أضاف دعم الواجهة الأمامية.

أود أن تكون هناك طريقة سهلة للمسؤولين لإضافة حقول مخصصة لفئات مختلفة، والسماح للمستخدمين بملئها (على سبيل المثال، للمواضيع، المشاركات، ملفات تعريف المستخدمين)، وأن تكون هناك طريقة للواجهة الأمامية لعرضها.

الشيء الرئيسي الذي لا يمكنني الحصول عليه مع حقول المستخدم المخصصة الحالية هو مجموعة متنوعة من أنواع الحقول. حاليًا، يقتصر على 4 أعتقد، وأود أن أحصل على الخيارات المتاحة مع إضافة Custom Wizards.

من الناحية المثالية، أريد بناء دليل مستخدم متقدم جدًا قابل للبحث/التصفية/الفرز مع الكثير من الحقول المخصصة من أنواع عديدة، وسأجرب Custom Wizards لمعرفة ما إذا كانت ستعمل في الوقت الحالي وآمل أن تستثمروا في إضافة Custom Fields.

شكرًا!

@angus

أولاً، شكراً جزيلاً لك على هذه الإضافة.

هل لديك مثال عملي لهذه الإضافة (حقل مخصص إلى موضوع) لاستيعاب حقول مخصصة متعددة؟ تمكنت من إضافة حقل مخصص واحد بنجاح وإجراء بعض التعديلات دون مشكلة.

لقد حاولت تكرار الكود، وتعديله وإضافة إضافة إضافية، وما إلى ذلك..

هل لدى أي شخص مستودع كود أو مثال يرغب في مشاركته معي؟ أي مساعدة ستكون موضع تقدير كبير.

إعجاب واحد (1)

مرحباً @Joe_Stanton،

لقد فعلت ذلك عدة مرات، والطريقة التي فعلت بها ذلك كانت تخزين الحقول المخصصة في مصفوفة مع كائن له اسم ونوع للحقل المخصص.

على سبيل المثال:

fields = [
  { name: 'isClassifiedListing', type: 'boolean' },
  { name: 'listingStatus', type: 'string' },
  { name: "listingDetails", type: 'json' }
]

ثم أقوم بالتكرار عبر الحقول لتطبيق المنطق المذكور في هذا الموضوع. يمكنك رؤية مثال على استخدامه في إضافة أعمل عليها هنا. ومع ذلك، فإن الكود ذي الصلة أدناه.

مثال

من جانب الخادم:

  # تسجيل الحقول المخصصة
  fields.each do |field|
    # تسجيل الحقول
    register_topic_custom_field_type(field[:name], field[:type].to_sym)

    # طرق الحصول
    add_to_class(:topic, field[:name].to_sym) do
      if !custom_fields[field[:name]].nil?
        custom_fields[field[:name]]
      else
        nil
      end
    end

    # طرق التعيين
    add_to_class(:topic, "#{field[:name]}=") do |value|
      custom_fields[field[:name]] = value
    end

    # التحديث عند إنشاء الموضوع
    on(:topic_created) do |topic, opts, user|
      topic.send("#{field[:name]}=".to_sym, opts[field[:name].to_sym])
      topic.save!
    end

    # التحديث عند تعديل الموضوع
    PostRevisor.track_topic_field(field[:name].to_sym) do |tc, value|
      tc.record_change(field[:name], tc.topic.send(field[:name]), value)
      tc.topic.send("#{field[:name]}=".to_sym, value.present? ? value : nil)
    end

    # التسلسل إلى الموضوع
    add_to_serializer(:topic_view, field[:name].to_sym) do
      object.topic.send(field[:name])
    end

    # التحميل المسبق للحقول
    add_preloaded_topic_list_custom_field(field[:name])

    # التسلسل إلى قائمة الموضوعات
    add_to_serializer(:topic_list_item, field[:name].to_sym) do
      object.send(field[:name])
    end
  end

وبالمثل من جانب العميل لتسلسل الحقول:


 const CUSTOM_FIELDS = [
  { name: "isClassifiedListing", type: "boolean" },
  { name: "listingStatus", type: "string" },
  { name: "listingDetails", type: "json" },
];

  // تسلسل الحقول المخصصة:
  CUSTOM_FIELDS.forEach((field) => {
    api.serializeOnCreate(field.name);
    api.serializeToDraft(field.name);
    api.serializeToTopic(field.name, `topic.${field.name}`);
  });
إعجابَين (2)

شكراً @keegan!

أعتقد أنني قمت بإعداد ملف plugin.rb بشكل صحيح، وتبدو الحلقة صحيحة.

ومع ذلك، أواجه صعوبة في ملف topic-custom-field-initializer.js. إليك الكود الخاص بي لكلا الملفين. هل لديك أي اقتراحات لملف initializer.js؟ أعتقد أنني قريب جدًا هنا، عند إنشاء موضوع جديد، أحصل على حقل واحد من أصل ثلاثة، وهو حقل listingDetails، لكن لا يزال ينقصني isClassifiedListing و listingStatus.

enabled_site_setting :topic_custom_field_enabled
register_asset 'stylesheets/common.scss'

after_initialize do
  fields = [
  { name: 'isClassifiedListing', type: 'boolean' },
  { name: 'listingStatus', type: 'string' },
  { name: "listingDetails", type: 'json' }
]

 fields.each do |field|

  register_topic_custom_field_type(field[:name], field[:type].to_sym)

   add_to_class(:topic, field[:name].to_sym) do
      if !custom_fields[field[:name]].nil?
        custom_fields[field[:name]]
      else
        nil
      end
    end

   add_to_class(:topic, "#{field[:name]}=") do |value|
      custom_fields[field[:name]] = value
   end

   on(:topic_created) do |topic, opts, user|
      topic.send("#{field[:name]}=".to_sym, opts[field[:name].to_sym])
      topic.save!
   end

    PostRevisor.track_topic_field(field[:name].to_sym) do |tc, value|
      tc.record_change(field[:name], tc.topic.send(field[:name]), value)
      tc.topic.send("#{field[:name]}=".to_sym, value.present? ? value : nil)
    end

    add_to_serializer(:topic_view, field[:name].to_sym) do
      object.topic.send(field[:name])
    end

  add_preloaded_topic_list_custom_field(field[:name])

    # Serialize to the topic list
    add_to_serializer(:topic_list_item, field[:name].to_sym) do
      object.send(field[:name])
    end

end

end

Initializer.js

import { withPluginApi } from 'discourse/lib/plugin-api';
import discourseComputed from "discourse-common/utils/decorators";
import { alias } from '@ember/object/computed';
import { isDefined, fieldInputTypes } from '../lib/topic-custom-field';

export default {
  name: "topic-custom-field-intializer",
  initialize(container) {


    const CUSTOM_FIELDS = [
      { name: "isClassifiedListing", type: "boolean" },
      { name: "listingStatus", type: "string" },
      { name: "listingDetails", type: "json" },
    ];

    CUSTOM_FIELDS.forEach((field) => {

    withPluginApi('0.11.2', api => {

      api.registerConnectorClass('composer-fields', 'composer-topic-custom-field-container', {
        setupComponent(attrs, component) {
          const model = attrs.model;

          if (!isDefined(model[field.name]) && model.topic && model.topic[field.name]) {
            model.set(field.name, model.topic[field.name]);
          }

          let props = {
            fieldName: field.name,
            fieldValue: model.get(field.name)
          }
          component.setProperties(Object.assign(props, fieldInputTypes(field.type)));
        },

        actions: {
          onChangeField(fieldValue) {
            this.set(`model.${field.name}`, fieldValue);
          }
        }
      });

      api.registerConnectorClass('edit-topic', 'edit-topic-custom-field-container', {
        setupComponent(attrs, component) {
          const model = attrs.model;

          let props = {
            fieldName: field.name,
            fieldValue: model.get(field.name)
          }
          component.setProperties(Object.assign(props, fieldInputTypes(field.type)));
        },

        actions: {
          onChangeField(fieldValue) {
            this.set(`buffered.${field.name}`, fieldValue);
          }
        }
      });

      api.serializeOnCreate(field.name);
      api.serializeToDraft(field.name);
      api.serializeToTopic(field.name, `topic.${field.name}`);

      api.registerConnectorClass('topic-title', 'topic-title-custom-field-container', {
        setupComponent(attrs, component) {
          const model = attrs.model;
          const controller = container.lookup('controller:topic');

          component.setProperties({
            fieldName: field.name,
            fieldValue: model.get(field.name),
            showField: !controller.get('editingTopic') && isDefined(model.get(field.name))
          });

          controller.addObserver('editingTopic', () => {
            if (this._state === 'destroying') return;
            component.set('showField', !controller.get('editingTopic') && isDefined(model.get(field.name)));
          });

          model.addObserver(field.name, () => {
            if (this._state === 'destroying') return;
            component.set('fieldValue', model.get(field.name));
          });
        }
      });

      api.modifyClass('component:topic-list-item', {
        customFieldName: field.name,
        customFieldValue: alias(`topic.${field.name}`),

        @discourseComputed('customFieldValue')
        showCustomField: (value) => (isDefined(value))
      });

    });


    });
  }
}

إعجاب واحد (1)

لم أختبر ذلك، لكنني أعتقد أن السبب في رؤيتك لحقل واحد فقط من أصل ثلاثة هو أنه يمر عبر فئة موصل غير فريدة ويسجلها ويتجاوز السابقة.

بشكل عام، بالنسبة للجانب العميل، بدلاً من المرور عبر الحقول المخصصة وتحديد طرق واجهة برمجة التطبيقات (API). أقترح عليك تعريف مكونات لكل حقل على حدة، أو على الأقل إجراءات منفصلة حيث ستحتاج على الأرجح إلى منطق مختلف مرتبط بكل حقل؟

الجزء الوحيد الذي سأمر عبره وأعلنه هو هذا:

  api.serializeOnCreate(field.name);
      api.serializeToDraft(field.name);
      api.serializeToTopic(field.name, `topic.${field.name}`);

بالنسبة لبقية المكونات، من الأفضل على الأرجح إنشاء منطق منفصل لكل حالة.

3 إعجابات

@keegan لقد نجح الأمر! شكراً جزيلاً على كل الأفكار. لم أكن لأتمكن من فعل ذلك بدون مساعدتك.

4 إعجابات