طريقة بسيطة لإنشاء مكونات قالب قابلة لإعادة الاستخدام في Django.
عليك أولاً تسجيل المكون الخاص بك
from django_web_components import component
@ component . register ( "card" )
class Card ( component . Component ):
template_name = "components/card.html"قالب المكون:
# components/card.html
{% load components %}
< div class =" card " >
< div class =" card-header " >
{% render_slot slots.header %}
</ div >
< div class =" card-body " >
< h5 class =" card-title " >
{% render_slot slots.title %}
</ h5 >
{% render_slot slots.inner_block %}
</ div >
</ div >يمكنك الآن تقديم هذا المكون بـ:
{% load components %}
{% card %}
{% slot header %} Featured {% endslot %}
{% slot title %} Card title {% endslot %}
< p > Some quick example text to build on the card title and make up the bulk of the card's content. </ p >
< a href =" # " class =" btn btn-primary " > Go somewhere </ a >
{% endcard %}مما سيؤدي إلى تقديم HTML التالي:
< div class =" card " >
< div class =" card-header " >
Featured
</ div >
< div class =" card-body " >
< h5 class =" card-title " >
Card title
</ h5 >
< p > Some quick example text to build on the card title and make up the bulk of the card's content. </ p >
< a href =" # " class =" btn btn-primary " > Go somewhere </ a >
</ div >
</ div > pip install django-web-components
ثم أضف django_web_components إلى INSTALLED_APPS .
INSTALLED_APPS = [
...,
"django_web_components" ,
] لتجنب الاضطرار إلى استخدام {% load components %} في كل قالب ، يمكنك إضافة العلامات إلى قائمة builtins داخل الإعدادات الخاصة بك.
TEMPLATES = [
{
...,
"OPTIONS" : {
"context_processors" : [
...
],
"builtins" : [
"django_web_components.templatetags.components" ,
],
},
},
]تدعم المكتبة Python 3.8+ و Django 3.2+.
| نسخة بيثون | إصدار Django |
|---|---|
3.12 | 5.0 ، 4.2 |
3.11 | 5.0 ، 4.2 ، 4.1 |
3.10 | 5.0 ، 4.2 ، 4.1 ، 4.0 ، 3.2 |
3.9 | 4.2 ، 4.1 ، 4.0 ، 3.2 |
3.8 | 4.2 ، 4.1 ، 4.0 ، 3.2 |
هناك طريقتان لكتابة المكونات: المكونات القائمة على الفصل والمكونات القائمة على الوظائف.
from django_web_components import component
@ component . register ( "alert" )
class Alert ( component . Component ):
# You may also override the get_template_name() method instead
template_name = "components/alert.html"
# Extra context data will be passed to the template context
def get_context_data ( self , ** kwargs ) -> dict :
return {
"dismissible" : False ,
} سيتم تقديم المكون عن طريق استدعاء طريقة render(context) ، والتي ستعمل افتراضيًا على تحميل ملف القالب وتقديمه.
بالنسبة للمكونات الصغيرة ، قد تشعر بالتعقيد لإدارة كل من فئة المكون وقالب المكون. لهذا السبب ، يمكنك تحديد القالب مباشرة من طريقة render :
from django_web_components import component
from django_web_components . template import CachedTemplate
@ component . register ( "alert" )
class Alert ( component . Component ):
def render ( self , context ) -> str :
return CachedTemplate (
"""
<div class="alert alert-primary" role="alert">
{% render_slot slots.inner_block %}
</div>
""" ,
name = "alert" ,
). render ( context ) يمكن أيضًا تعريف المكون كدالة واحدة تقبل context وإرجاع سلسلة:
from django_web_components import component
from django_web_components . template import CachedTemplate
@ component . register
def alert ( context ):
return CachedTemplate (
"""
<div class="alert alert-primary" role="alert">
{% render_slot slots.inner_block %}
</div>
""" ,
name = "alert" ,
). render ( context )ستستخدم الأمثلة الواردة في هذا الدليل في الغالب المكونات القائمة على الوظائف ، حيث أنه من الأسهل أن تجسد مثل رمز المكون والقالب في نفس المكان ، لكنك حر في اختيار أي طريقة تفضلها.
تستخدم المكتبة قوالب Django العادية ، والتي تتيح لك إما تحميل القوالب من الملفات ، أو إنشاء كائنات قالب مباشرة باستخدام سلاسل القالب. يتم دعم كلتا الطريقتين ، وكلاهما لهما مزايا وعيوب:
فيما يتعلق بالتخزين المؤقت ، توفر المكتبة CachedTemplate ، والتي ستقوم بتخزين كائن Template وإعادة استخدامها طالما أنك تقدم name لها ، والذي سيتم استخدامه كمفتاح ذاكرة التخزين المؤقت:
from django_web_components import component
from django_web_components . template import CachedTemplate
@ component . register
def alert ( context ):
return CachedTemplate (
"""
<div class="alert alert-primary" role="alert">
{% render_slot slots.inner_block %}
</div>
""" ,
name = "alert" ,
). render ( context ) في الواقع ، لا ينبغي أن يكون التخزين المؤقت مشكلة عند استخدام سلاسل القالب ، لأن CachedTemplate يكون بنفس السرعة مثل استخدام المحمل المخبأة مع ملفات القالب.
فيما يتعلق بتنسيق الدعم وتمييز بناء الجملة ، لا يوجد حل جيد لسلاسل القالب. يدعم Pycharm حقن اللغة الذي يسمح لك بإضافة تعليق # language=html قبل سلسلة القالب والحصول على تمييز بناء الجملة ، ومع ذلك ، فإنه يسلط الضوء فقط على HTML وليس علامات Django ، وما زلت تفتقد الدعم للتنسيق. ربما سيضيف المحررين دعمًا أفضل لهذا في المستقبل ، ولكن في الوقت الحالي ستفقد بناء بناء الجملة وتنسيقه إذا ذهبت إلى هذا الطريق. هناك محادثة مفتوحة حول هذا الأمر على ريبو django-components ، والائتمانات إلى Emilstenstrom لتحريك المحادثة إلى الأمام مع فريق VSCode.
في النهاية ، إنها مفاضلة. استخدم الطريقة التي تجعلك أكثر منطقية بالنسبة لك.
تمامًا مثل الإشارات ، يمكن للمكونات العيش في أي مكان ، ولكن عليك التأكد من أن Django يلتقطها عند بدء التشغيل. أسهل طريقة للقيام بذلك هي تحديد مكوناتك في components.py ready()
from django . apps import AppConfig
from django_web_components import component
class MyAppConfig ( AppConfig ):
...
def ready ( self ):
# Implicitly register components decorated with @component.register
from . import components # noqa
# OR explicitly register a component
component . register ( "card" , components . Card ) يمكنك أيضًا unregister مكون موجود ، أو الحصول على مكون من السجل:
from django_web_components import component
# Unregister a component
component . registry . unregister ( "card" )
# Get a component
component . registry . get ( "card" )
# Remove all components
component . registry . clear ()
# Get all components as a dict of name: component
component . registry . all ()سيكون لكل مكون مسجل علامتان متاحتان للاستخدام في القوالب الخاصة بك:
{% card %} ... {% endcard %}{% #user_profile %} . يمكن أن يكون هذا مفيدًا للمكونات التي لا تتطلب بالضرورة جسمًابشكل افتراضي ، سيتم تسجيل المكونات باستخدام العلامات التالية:
{% <component_name> %}{% end<component_name> %}{% #<component_name> %} قد يتم تغيير هذا السلوك من خلال توفير تنسيق علامة مخصصة في إعداداتك. على سبيل المثال ، لتغيير علامات الكتلة إلى {% #card %} ... {% /card %} ، والعلامة المضمنة إلى {% card %} (على غرار النعال) ، يمكنك استخدام التنسيق التالي:
class ComponentTagFormatter :
def format_block_start_tag ( self , name ):
return f"# { name } "
def format_block_end_tag ( self , name ):
return f"/ { name } "
def format_inline_tag ( self , name ):
return name
# inside your settings
WEB_COMPONENTS = {
"DEFAULT_COMPONENT_TAG_FORMATTER" : "path.to.your.ComponentTagFormatter" ,
}يمكنك تمرير البيانات إلى مكونات باستخدام وسيطات الكلمات الرئيسية ، والتي تقبل إما القيم أو المتغيرات المتشددين:
{% with error_message="Something bad happened!" %}
{% #alert type="error" message=error_message %}
{% endwith %} ستتم إضافة جميع السمات في قول attributes التي ستكون متوفرة في سياق القالب:
{
"attributes" : {
"type" : " error " ,
"message" : " Something bad happened! "
}
}يمكنك بعد ذلك الوصول إليه من قالب المكون الخاص بك:
< div class =" alert alert-{{ attributes.type }} " >
{{ attributes.message }}
</ div > يمكنك أيضًا تقديم جميع السمات مباشرة باستخدام {{ attributes }} . على سبيل المثال ، إذا كان لديك المكون التالي
{% alert id="alerts" class="font-bold" %} ... {% endalert %}يمكنك تقديم جميع السمات باستخدام
< div {{ attributes }} >
<!-- Component content -->
</ div >مما سيؤدي إلى تقديم HTML التالي:
< div id =" alerts " class =" font-bold " >
<!-- Component content -->
</ div > يمكنك أيضًا تمرير سمات بأحرف خاصة ( [@:_-.] ) ، بالإضافة إلى سمات بدون قيمة:
{% button @click="handleClick" data-id="123" required %} ... {% endbutton %}الأمر الذي سيؤدي إلى متابعة القولون المتاحة في السياق:
{
"attributes" : {
"@click" : "handleClick" ,
"data-id" : "123" ,
"required" : True ,
}
} وسيتم تقديمها بواسطة {{ attributes }} كـ @click="handleClick" data-id="123" required .
في بعض الأحيان ، قد تحتاج إلى تحديد القيم الافتراضية للسمات ، أو دمج قيم إضافية في بعض سمات المكون. توفر المكتبة علامة merge_attrs التي تساعد في ذلك:
< div {% merge_attrs attributes class =" alert " role =" alert " %} >
<!-- Component content -->
</ div >إذا افترضنا أن هذا المكون يستخدم مثل ذلك:
{% alert class="mb-4" %} ... {% endalert %}سيظهر HTML النهائي للمكون مثل ما يلي:
< div class =" alert mb-4 " role =" alert " >
<!-- Component content -->
</ div > عند دمج السمات التي ليست سمات class ، سيتم اعتبار القيم التي يتم توفيرها لعلامة merge_attrs القيم "الافتراضية" للسمة. ومع ذلك ، على عكس سمة class ، لن يتم دمج هذه السمات مع قيم السمات المحقونة. بدلاً من ذلك ، سيتم الكتابة فوقها. على سبيل المثال ، قد يبدو تطبيق مكون button ما يلي:
< button {% merge_attrs attributes type =" button " %} >
{% render_slot slots.inner_block %}
</ button > لتقديم مكون الزر type مخصص ، قد يتم تحديده عند استهلاك المكون. إذا لم يتم تحديد أي نوع ، فسيتم استخدام نوع button :
{% button type="submit" %} Submit {% endbutton %} سيكون HTML المقدم لمكون button في هذا المثال:
< button type =" submit " >
Submit
</ button > يمكنك أيضًا التعامل مع السمات الأخرى على أنها "تذييل" باستخدام += المشغل:
< div {% merge_attrs attributes data-value+ =" some-value " %} >
<!-- Component content -->
</ div >إذا افترضنا أن هذا المكون يستخدم مثل ذلك:
{% alert data-value="foo" %} ... {% endalert %}سيكون HTML المقدم:
< div data-value =" foo some-value " >
<!-- Component content -->
</ div > بشكل افتراضي ، تتم إضافة جميع السمات إلى attributes dict داخل السياق. ومع ذلك ، قد لا يكون هذا دائمًا ما نريد. على سبيل المثال ، تخيل أننا نريد أن يكون لدينا مكون alert يمكن رفضه ، بينما في نفس الوقت قادرون على تمرير سمات إضافية إلى عنصر الجذر ، مثل id أو class . من الناحية المثالية ، نريد أن نكون قادرين على تقديم مكون مثل هذا:
{% alert id="alerts" dismissible %} Something went wrong! {% endalert %}طريقة ساذجة لتنفيذ هذا المكون ستكون مثل ما يلي:
< div {{ attributes }} >
{% render_slot slots.inner_block %}
{% if attributes.dismissible %}
< button type =" button " class =" btn-close " data-bs-dismiss =" alert " aria-label =" Close " > </ button >
{% endif %}
</ div > ومع ذلك ، فإن هذا سيؤدي إلى إدراج السمة dismissible في عنصر الجذر ، وهو ما نريد:
< div id =" alerts " dismissible >
Something went wrong!
< button type =" button " class =" btn-close " data-bs-dismiss =" alert " aria-label =" Close " > </ button >
</ div > من الناحية المثالية ، نريد فصل السمة dismissible عن attributes لأننا نريد فقط استخدامها في المنطق ، ولكن لا تقدمها بالضرورة إلى المكون.
لتحقيق ذلك ، يمكنك معالجة السياق من المكون الخاص بك من أجل توفير واجهة برمجة تطبيقات أفضل لاستخدام المكونات. هناك عدة طرق للقيام بذلك ، اختر الطريقة التي تجعلك أكثر منطقية بالنسبة لك ، على سبيل المثال:
get_context_data وإزالة السمة dismissible من attributes وإعادتها في السياق بدلاً من ذلك from django_web_components import component
@ component . register ( "alert" )
class Alert ( component . Component ):
template_name = "components/alert.html"
def get_context_data ( self , ** kwargs ):
dismissible = self . attributes . pop ( "dismissible" , False )
return {
"dismissible" : dismissible ,
}render ومعالجة السياق هناك from django_web_components import component
@ component . register ( "alert" )
class Alert ( component . Component ):
template_name = "components/alert.html"
def render ( self , context ):
context [ "dismissible" ] = context [ "attributes" ]. pop ( "dismissible" , False )
return super (). render ( context )سيعمل كل من الحلول المذكورة أعلاه ، ويمكنك فعل الشيء نفسه للمكونات القائمة على الوظائف. يمكن أن يبدو قالب المكون هكذا:
< div {{ attributes }} >
{% render_slot slots.inner_block %}
{% if dismissible %}
< button type =" button " class =" btn-close " data-bs-dismiss =" alert " aria-label =" Close " > </ button >
{% endif %}
</ div >والتي يجب أن تؤدي إلى تقديم HTML الصحيح:
< div id =" alerts " >
Something went wrong!
< button type =" button " class =" btn-close " data-bs-dismiss =" alert " aria-label =" Close " > </ button >
</ div > ستحتاج غالبًا إلى تمرير محتوى إضافي إلى مكوناتك عبر "فتحات". يتم تمرير متغير سياق slots إلى مكوناتك ، والتي تتكون من تمويل مع اسم الفتحة كمفتاح وفتحة كقيمة. يمكنك بعد ذلك تقديم فتحات داخل مكوناتك باستخدام علامة render_slot .
لاستكشاف هذا المفهوم ، دعونا نتخيل أننا نريد نقل بعض المحتوى إلى مكون alert :
{% alert %}
< strong > Whoops! </ strong > Something went wrong!
{% endalert %} بشكل افتراضي ، سيتم توفير هذا المحتوى لمكونك في الفتحة الافتراضية التي تسمى inner_block . يمكنك بعد ذلك تقديم هذه الفتحة باستخدام علامة render_slot داخل المكون الخاص بك:
{% load components %}
< div class =" alert alert-danger " >
{% render_slot slots.inner_block %}
</ div >والتي يجب أن تؤدي إلى تقديم HTML التالي:
< div class =" alert alert-danger " >
< strong > Whoops! </ strong > Something went wrong!
</ div >يمكنك أيضًا إعادة تسمية الفتحة الافتراضية عن طريق تحديدها في إعداداتك:
# inside your settings
WEB_COMPONENTS = {
"DEFAULT_SLOT_NAME" : "inner_block" ,
}في بعض الأحيان ، قد يحتاج المكون إلى تقديم فتحات مختلفة متعددة في مواقع مختلفة داخل المكون. دعنا نعدل مكون التنبيه الخاص بنا للسماح بحقن فتحة "العنوان":
{% load components %}
< div class =" alert alert-danger " >
< span class =" alert-title " >
{% render_slot slots.title %}
</ span >
{% render_slot slots.inner_block %}
</ div > يمكنك تحديد محتوى الفتحة المسماة باستخدام علامة slot . سيتم إضافة أي محتوى ليس ضمن علامة slot صريحة إلى فتحة inner_block الافتراضية:
{% load components %}
{% alert %}
{% slot title %} Server error {% endslot %}
< strong > Whoops! </ strong > Something went wrong!
{% endalert %}سيكون HTML المقدم في هذا المثال:
< div class =" alert alert-danger " >
< span class =" alert-title " >
Server error
</ span >
< strong > Whoops! </ strong > Something went wrong!
</ div >يمكنك تحديد نفس الفتحة المسماة عدة مرات:
{% unordered_list %}
{% slot item %} First item {% endslot %}
{% slot item %} Second item {% endslot %}
{% slot item %} Third item {% endslot %}
{% endunordered_list %}يمكنك بعد ذلك التكرار فوق الفتحة داخل المكون الخاص بك:
< ul >
{% for item in slots.item %}
< li > {% render_slot item %} </ li >
{% endfor %}
</ ul >والتي ستؤدي إلى HTML التالية:
< ul >
< li > First item </ li >
< li > Second item </ li >
< li > Third item </ li >
</ ul > سيكون لمحتوى الفتحة أيضًا الوصول إلى سياق المكون. لاستكشاف هذا المفهوم ، تخيل مكون قائمة يقبل سمة entries التي تمثل قائمة بالأشياء ، والتي ستقوم بعد ذلك بتكرارها وتقديم فتحة inner_block لكل إدخال.
from django_web_components import component
from django_web_components . template import CachedTemplate
@ component . register
def unordered_list ( context ):
context [ "entries" ] = context [ "attributes" ]. pop ( "entries" , [])
return CachedTemplate (
"""
<ul>
{% for entry in entries %}
<li>
{% render_slot slots.inner_block %}
</li>
{% endfor %}
</ul>
""" ,
name = "unordered_list" ,
). render ( context )يمكننا بعد ذلك تقديم المكون على النحو التالي:
{% unordered_list entries=entries %}
I like {{ entry }}!
{% endunordered_list %} في هذا المثال ، يأتي متغير entry من سياق المكون. إذا افترضنا أن entries = ["apples", "bananas", "cherries"] ، ستكون HTML الناتجة:
< ul >
< li > I like apples! </ li >
< li > I like bananas! </ li >
< li > I like cherries! </ li >
</ ul > يمكنك أيضًا تمرير وسيطة ثانية بشكل صريح render_slot :
< ul >
{% for entry in entries %}
< li >
<!-- We are passing the `entry` as the second argument to render_slot -->
{% render_slot slots.inner_block entry %}
</ li >
{% endfor %}
</ ul > عند استدعاء المكون ، يمكنك استخدام السمة الخاصة :let لاتخاذ القيمة التي تم تمريرها render_slot وربطها بمتغير:
{% unordered_list :let="fruit" entries=entries %}
I like {{ fruit }}!
{% endunordered_list %}هذا من شأنه أن يجعل نفس HTML على النحو الوارد أعلاه.
على غرار المكونات ، يمكنك تعيين سمات إضافية إلى فتحات. فيما يلي مكون جدول يوضح فتحات متعددة المسماة مع سمات:
from django_web_components import component
from django_web_components . template import CachedTemplate
@ component . register
def table ( context ):
context [ "rows" ] = context [ "attributes" ]. pop ( "rows" , [])
return CachedTemplate (
"""
<table>
<tr>
{% for col in slots.column %}
<th>{{ col.attributes.label }}</th>
{% endfor %}
</tr>
{% for row in rows %}
<tr>
{% for col in slots.column %}
<td>
{% render_slot col row %}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
""" ,
name = "table" ,
). render ( context )يمكنك استدعاء المكون مثل ذلك:
{% table rows=rows %}
{% slot column :let="user" label="Name" %}
{{ user.name }}
{% endslot %}
{% slot column :let="user" label="Age" %}
{{ user.age }}
{% endslot %}
{% endtable %} إذا افترضنا أن rows = [{ "name": "Jane", "age": "34" }, { "name": "Bob", "age": "51" }] ، سيتم تقديم HTML التالي:
< table >
< tr >
< th > Name </ th >
< th > Age </ th >
</ tr >
< tr >
< td > Jane </ td >
< td > 34 </ td >
</ tr >
< tr >
< td > Bob </ td >
< td > 51 </ td >
</ tr >
</ table >يمكنك أيضًا أن تعشص مكونات لتحقيق عناصر أكثر تعقيدًا. فيما يلي مثال على كيفية تنفيذ مكون الأكورديون باستخدام bootstrap:
from django_web_components import component
from django_web_components . template import CachedTemplate
import uuid
@ component . register
def accordion ( context ):
context [ "accordion_id" ] = context [ "attributes" ]. pop ( "id" , str ( uuid . uuid4 ()))
context [ "always_open" ] = context [ "attributes" ]. pop ( "always_open" , False )
return CachedTemplate (
"""
<div class="accordion" id="{{ accordion_id }}">
{% render_slot slots.inner_block %}
</div>
""" ,
name = "accordion" ,
). render ( context )
@ component . register
def accordion_item ( context ):
context [ "id" ] = context [ "attributes" ]. pop ( "id" , str ( uuid . uuid4 ()))
context [ "open" ] = context [ "attributes" ]. pop ( "open" , False )
return CachedTemplate (
"""
<div class="accordion-item" id="{{ id }}">
<h2 class="accordion-header" id="{{ id }}-header">
<button
class="accordion-button {% if not open %}collapsed{% endif %}"
type="button"
data-bs-toggle="collapse"
data-bs-target="#{{ id }}-collapse"
aria-expanded="{% if open %}true{% else %}false{% endif %}"
aria-controls="{{ id }}-collapse"
>
{% render_slot slots.title %}
</button>
</h2>
<div
id="{{ id }}-collapse"
class="accordion-collapse collapse {% if open %}show{% endif %}"
aria-labelledby="{{ id }}-header"
{% if accordion_id and not always_open %}
data-bs-parent="#{{ accordion_id }}"
{% endif %}}
>
<div class="accordion-body">
{% render_slot slots.body %}
</div>
</div>
</div>
""" ,
name = "accordion_item" ,
). render ( context )يمكنك بعد ذلك استخدامها على النحو التالي:
{% accordion %}
{% accordion_item open %}
{% slot title %} Accordion Item #1 {% endslot %}
{% slot body %}
< strong > This is the first item's accordion body. </ strong > It is shown by default.
{% endslot %}
{% endaccordion_item %}
{% accordion_item %}
{% slot title %} Accordion Item #2 {% endslot %}
{% slot body %}
< strong > This is the second item's accordion body. </ strong > It is hidden by default.
{% endslot %}
{% endaccordion_item %}
{% accordion_item %}
{% slot title %} Accordion Item #3 {% endslot %}
{% slot body %}
< strong > This is the third item's accordion body. </ strong > It is hidden by default.
{% endslot %}
{% endaccordion_item %}
{% endaccordion %} يستخدم المشروع poetry لإدارة التبعيات. تحقق من الوثائق حول كيفية تثبيت الشعر هنا: https://python-poetry.org/docs/#installation
تثبيت التبعيات
poetry installتنشيط البيئة
poetry shellالآن يمكنك إجراء الاختبارات
python runtests.pyأصبح المشروع بعد رؤية كيفية تعامل اللغات / الأطر الأخرى مع المكونات ، والرغبة في إعادة بعض هذه الأفكار إلى Django.
django-components الحالية رائعة بالفعل ويدعم معظم الميزات التي يمتلكها هذا المشروع ، لكنني اعتقدت أن بناء الجملة يمكن تحسينه قليلاً لتشعر بدرجة أقل ، ويضيف بعض الأشياء الإضافية التي تبدو ضرورية ، مثل الدعم للمكونات القائمة على الوظائف وأسلاك النموذج ، والعمل مع السمات<x-alert type="error">Server Error</x-alert> ) ، لكن الحل كان أكثر تعقيدًا وتوصلت إلى استنتاج مفاده أن استخدام مقاربة مماثلة لـ django-components كان هناك الكثير من المعنى في Djangoتستخدم العديد من اللغات / الأطر الأخرى نفس المفاهيم لبناء مكونات (فتحات ، سمات) ، وبالتالي فإن الكثير من المعرفة قابلة للتحويل ، وهناك بالفعل قدر كبير من مكتبات المكونات الموجودة هناك (على سبيل المثال باستخدام Bootstrap ، و Windwind ، وتصميم المواد ، وما إلى ذلك). أوصي بشدة بالنظر إلى بعضهم للحصول على إلهام حول كيفية بناء / هيكل مكوناتك. فيما يلي بعض الأمثلة: