Cara sederhana untuk membuat komponen template yang dapat digunakan kembali di Django.
Anda harus terlebih dahulu mendaftarkan komponen Anda
from django_web_components import component
@ component . register ( "card" )
class Card ( component . Component ):
template_name = "components/card.html"Template komponen:
# 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 >Anda sekarang dapat membuat komponen ini dengan:
{% 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 %}Yang akan menghasilkan HTML berikut ini diterjemahkan:
< 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
Kemudian tambahkan django_web_components ke INSTALLED_APPS Anda.
INSTALLED_APPS = [
...,
"django_web_components" ,
] Untuk menghindari keharusan menggunakan {% load components %} di setiap templat, Anda dapat menambahkan tag ke daftar builtins di dalam pengaturan Anda.
TEMPLATES = [
{
...,
"OPTIONS" : {
"context_processors" : [
...
],
"builtins" : [
"django_web_components.templatetags.components" ,
],
},
},
]Perpustakaan mendukung Python 3.8+ dan Django 3.2+.
| Versi Python | Versi 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 |
Ada dua pendekatan untuk menulis komponen: komponen berbasis kelas dan komponen berbasis fungsi.
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 ,
} Komponen akan diterjemahkan dengan memanggil metode render(context) , yang secara default akan memuat file template dan membuatnya.
Untuk komponen kecil, mungkin terasa rumit untuk mengelola kelas komponen dan template komponen. Untuk alasan ini, Anda dapat mendefinisikan templat langsung dari metode 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 ) Komponen juga dapat didefinisikan sebagai fungsi tunggal yang menerima context dan mengembalikan string:
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 )Contoh -contoh dalam panduan ini sebagian besar akan menggunakan komponen berbasis fungsi, karena lebih mudah untuk dicontohkan karena kode komponen dan templat berada di tempat yang sama, tetapi Anda bebas memilih metode mana pun yang Anda sukai.
Perpustakaan menggunakan templat Django biasa, yang memungkinkan Anda untuk memuat templat dari file, atau membuat objek template secara langsung menggunakan string template. Kedua metode didukung, dan keduanya memiliki kelebihan dan kekurangan:
Mengenai caching, perpustakaan menyediakan CachedTemplate , yang akan cache dan menggunakan kembali objek Template selama Anda memberikan name untuk itu, yang akan digunakan sebagai kunci cache:
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 ) Jadi pada kenyataannya, caching tidak boleh menjadi masalah saat menggunakan string templat, karena CachedTemplate secepat menggunakan loader cache dengan file template.
Mengenai dukungan pemformatan dan penyorotan sintaks, tidak ada solusi yang baik untuk string template. Pycharm mendukung injeksi bahasa yang memungkinkan Anda untuk menambahkan komentar # language=html sebelum string template dan mendapatkan sorotan sintaks, namun, hanya menyoroti HTML dan bukan tag Django, dan Anda masih kehilangan dukungan untuk pemformatan. Mungkin editor akan menambahkan dukungan yang lebih baik untuk ini di masa depan, tetapi untuk saat ini Anda akan kehilangan sintaksis sintaksis dan format jika Anda menempuh rute ini. Ada percakapan terbuka tentang ini di repo django-components , kredit ke Emilstenstrom karena memajukan percakapan dengan tim vscode.
Pada akhirnya, ini adalah tradeoff. Gunakan metode yang paling masuk akal bagi Anda.
Sama seperti sinyal, komponen dapat hidup di mana saja, tetapi Anda perlu memastikan Django mengambilnya saat startup. Cara termudah untuk melakukan ini adalah dengan menentukan komponen Anda dalam components.py Submodule dari aplikasi yang mereka hubungkan, dan kemudian hubungkan di dalam metode ready() dari kelas konfigurasi aplikasi Anda.
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 ) Anda juga dapat unregister komponen yang ada, atau mendapatkan komponen dari registri:
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 ()Setiap komponen terdaftar akan memiliki dua tag yang tersedia untuk digunakan di templat Anda:
{% card %} ... {% endcard %}{% #user_profile %} . Ini bisa berguna untuk komponen yang tidak perlu membutuhkan tubuhSecara default, komponen akan terdaftar menggunakan tag berikut:
{% <component_name> %}{% end<component_name> %}{% #<component_name> %} Perilaku ini dapat diubah dengan memberikan formatter tag khusus di pengaturan Anda. Misalnya, untuk mengubah tag blok menjadi {% #card %} ... {% /card %} , dan tag inline ke {% card %} (mirip dengan sandal), Anda dapat menggunakan formatter berikut:
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" ,
}Anda dapat meneruskan data ke komponen menggunakan argumen kata kunci, yang menerima nilai atau variabel hardcoded:
{% with error_message="Something bad happened!" %}
{% #alert type="error" message=error_message %}
{% endwith %} Semua atribut akan ditambahkan dalam dikt attributes yang akan tersedia dalam konteks template:
{
"attributes" : {
"type" : " error " ,
"message" : " Something bad happened! "
}
}Anda kemudian dapat mengaksesnya dari template komponen Anda:
< div class =" alert alert-{{ attributes.type }} " >
{{ attributes.message }}
</ div > Anda juga dapat membuat semua atribut secara langsung menggunakan {{ attributes }} . Misalnya, jika Anda memiliki komponen berikut
{% alert id="alerts" class="font-bold" %} ... {% endalert %}Anda dapat membuat semua atribut menggunakan
< div {{ attributes }} >
<!-- Component content -->
</ div >Yang akan menghasilkan HTML berikut ini diterjemahkan:
< div id =" alerts " class =" font-bold " >
<!-- Component content -->
</ div > Anda juga dapat melewati atribut dengan karakter khusus ( [@:_-.] ), Serta atribut tanpa nilai:
{% button @click="handleClick" data-id="123" required %} ... {% endbutton %}Yang akan menghasilkan dikte follow yang tersedia dalam konteks:
{
"attributes" : {
"@click" : "handleClick" ,
"data-id" : "123" ,
"required" : True ,
}
} Dan akan diterjemahkan oleh {{ attributes }} sebagai @click="handleClick" data-id="123" required .
Terkadang Anda mungkin perlu menentukan nilai default untuk atribut, atau menggabungkan nilai tambahan ke dalam beberapa atribut komponen. Perpustakaan menyediakan tag merge_attrs yang membantu dengan ini:
< div {% merge_attrs attributes class =" alert " role =" alert " %} >
<!-- Component content -->
</ div >Jika kita menganggap komponen ini digunakan seperti itu:
{% alert class="mb-4" %} ... {% endalert %}HTML komponen terakhir yang diberikan akan muncul seperti berikut:
< div class =" alert mb-4 " role =" alert " >
<!-- Component content -->
</ div > Saat menggabungkan atribut yang bukan atribut class , nilai -nilai yang diberikan kepada tag merge_attrs akan dianggap sebagai nilai "default" dari atribut. Namun, tidak seperti atribut class , atribut ini tidak akan digabungkan dengan nilai atribut yang disuntikkan. Sebaliknya, mereka akan ditimpa. Misalnya, implementasi komponen button mungkin terlihat seperti berikut:
< button {% merge_attrs attributes type =" button " %} >
{% render_slot slots.inner_block %}
</ button > Untuk membuat komponen tombol dengan type khusus, itu dapat ditentukan saat mengkonsumsi komponen. Jika tidak ada jenis yang ditentukan, jenis button akan digunakan:
{% button type="submit" %} Submit {% endbutton %} HTML yang diberikan dari komponen button dalam contoh ini adalah:
< button type =" submit " >
Submit
</ button > Anda juga dapat memperlakukan atribut lain sebagai "dapat ditambahkan" dengan menggunakan operator += :
< div {% merge_attrs attributes data-value+ =" some-value " %} >
<!-- Component content -->
</ div >Jika kita menganggap komponen ini digunakan seperti itu:
{% alert data-value="foo" %} ... {% endalert %}HTML yang diberikan adalah:
< div data-value =" foo some-value " >
<!-- Component content -->
</ div > Secara default, semua atribut ditambahkan ke dikt attributes di dalam konteks. Namun, ini mungkin tidak selalu seperti yang kita inginkan. Misalnya, bayangkan kami ingin memiliki komponen alert yang dapat diberhentikan, sementara pada saat yang sama dapat meneruskan atribut tambahan ke elemen root, seperti id atau class . Idealnya kami ingin dapat membuat komponen seperti ini:
{% alert id="alerts" dismissible %} Something went wrong! {% endalert %}Cara naif untuk mengimplementasikan komponen ini akan menjadi sesuatu seperti berikut:
< 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 > Namun, ini akan menghasilkan atribut dismissible dimasukkan dalam elemen root, yang bukan yang kita inginkan:
< div id =" alerts " dismissible >
Something went wrong!
< button type =" button " class =" btn-close " data-bs-dismiss =" alert " aria-label =" Close " > </ button >
</ div > Idealnya kami ingin atribut dismissible dipisahkan dari attributes karena kami hanya ingin menggunakannya dalam logika, tetapi tidak harus memberikannya ke komponen.
Untuk mencapai hal ini, Anda dapat memanipulasi konteks dari komponen Anda untuk memberikan API yang lebih baik untuk menggunakan komponen. Ada beberapa cara untuk melakukan ini, pilih metode yang paling masuk akal bagi Anda, misalnya:
get_context_data dan menghapus atribut dismissible dari attributes dan mengembalikannya dalam konteks 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 dan memanipulasi konteks di sana 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 )Kedua solusi di atas akan berfungsi, dan Anda dapat melakukan hal yang sama untuk komponen berbasis fungsi. Template komponen kemudian dapat terlihat seperti ini:
< 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 >Yang seharusnya menghasilkan HTML yang benar diterjemahkan:
< div id =" alerts " >
Something went wrong!
< button type =" button " class =" btn-close " data-bs-dismiss =" alert " aria-label =" Close " > </ button >
</ div > Anda sering perlu meneruskan konten tambahan ke komponen Anda melalui "slot". Variabel konteks slots diteruskan ke komponen Anda, yang terdiri dari dikt dengan nama slot sebagai kunci dan slot sebagai nilainya. Anda kemudian dapat membuat slot di dalam komponen Anda menggunakan tag render_slot .
Untuk menjelajahi konsep ini, mari kita bayangkan kita ingin meneruskan beberapa konten ke komponen alert :
{% alert %}
< strong > Whoops! </ strong > Something went wrong!
{% endalert %} Secara default, konten itu akan tersedia untuk komponen Anda di slot default yang disebut inner_block . Anda kemudian dapat membuat slot ini menggunakan tag render_slot di dalam komponen Anda:
{% load components %}
< div class =" alert alert-danger " >
{% render_slot slots.inner_block %}
</ div >Yang seharusnya menghasilkan HTML berikut ini diterjemahkan:
< div class =" alert alert-danger " >
< strong > Whoops! </ strong > Something went wrong!
</ div >Anda juga dapat mengganti nama slot default dengan menentukannya di pengaturan Anda:
# inside your settings
WEB_COMPONENTS = {
"DEFAULT_SLOT_NAME" : "inner_block" ,
}Terkadang komponen mungkin perlu membuat beberapa slot berbeda di lokasi yang berbeda dalam komponen. Mari kita ubah komponen peringatan kami untuk memungkinkan suntikan slot "judul":
{% load components %}
< div class =" alert alert-danger " >
< span class =" alert-title " >
{% render_slot slots.title %}
</ span >
{% render_slot slots.inner_block %}
</ div > Anda dapat mendefinisikan konten slot bernama menggunakan tag slot . Konten apa pun yang tidak dalam tag slot eksplisit akan ditambahkan ke slot inner_block default:
{% load components %}
{% alert %}
{% slot title %} Server error {% endslot %}
< strong > Whoops! </ strong > Something went wrong!
{% endalert %}HTML yang diberikan dalam contoh ini adalah:
< div class =" alert alert-danger " >
< span class =" alert-title " >
Server error
</ span >
< strong > Whoops! </ strong > Something went wrong!
</ div >Anda dapat mendefinisikan slot bernama yang sama beberapa kali:
{% unordered_list %}
{% slot item %} First item {% endslot %}
{% slot item %} Second item {% endslot %}
{% slot item %} Third item {% endslot %}
{% endunordered_list %}Anda kemudian dapat mengulangi slot di dalam komponen Anda:
< ul >
{% for item in slots.item %}
< li > {% render_slot item %} </ li >
{% endfor %}
</ ul >Yang akan menghasilkan html berikut:
< ul >
< li > First item </ li >
< li > Second item </ li >
< li > Third item </ li >
</ ul > Konten slot juga akan memiliki akses ke konteks komponen. Untuk menjelajahi konsep ini, bayangkan komponen daftar yang menerima atribut entries yang mewakili daftar hal, yang kemudian akan mengulangi dan membuat slot inner_block untuk setiap entri.
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 )Kita kemudian dapat membuat komponen sebagai berikut:
{% unordered_list entries=entries %}
I like {{ entry }}!
{% endunordered_list %} Dalam contoh ini, variabel entry berasal dari konteks komponen. Jika kita berasumsi bahwa entries = ["apples", "bananas", "cherries"] , HTML yang dihasilkan adalah:
< ul >
< li > I like apples! </ li >
< li > I like bananas! </ li >
< li > I like cherries! </ li >
</ ul > Anda juga dapat secara eksplisit meneruskan argumen kedua ke 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 > Saat memohon komponen, Anda dapat menggunakan atribut khusus :let mengambil nilai yang diteruskan ke render_slot dan mengikatnya ke variabel:
{% unordered_list :let="fruit" entries=entries %}
I like {{ fruit }}!
{% endunordered_list %}Ini akan membuat HTML yang sama seperti di atas.
Mirip dengan komponen, Anda dapat menetapkan atribut tambahan untuk slot. Di bawah ini adalah komponen tabel yang menggambarkan beberapa slot bernama dengan atribut:
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 )Anda dapat memohon komponen seperti itu:
{% table rows=rows %}
{% slot column :let="user" label="Name" %}
{{ user.name }}
{% endslot %}
{% slot column :let="user" label="Age" %}
{{ user.age }}
{% endslot %}
{% endtable %} Jika kita berasumsi bahwa rows = [{ "name": "Jane", "age": "34" }, { "name": "Bob", "age": "51" }] , html berikut akan diberikan:
< 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 >Anda juga dapat bersarang komponen untuk mencapai elemen yang lebih rumit. Berikut adalah contoh bagaimana Anda dapat mengimplementasikan komponen akordeon menggunakan 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 )Anda kemudian dapat menggunakannya sebagai berikut:
{% 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 %} Proyek ini menggunakan poetry untuk mengelola dependensi. Lihat dokumentasi tentang cara menginstal puisi di sini: https://python-poetry.org/docs/#installation
Instal dependensi
poetry installAktifkan lingkungan
poetry shellSekarang Anda dapat menjalankan tes
python runtests.pyProyek ini terjadi setelah melihat bagaimana bahasa / kerangka kerja lain menangani komponen, dan ingin membawa beberapa ide itu kembali ke Django.
django-components yang ada sudah hebat dan mendukung sebagian besar fitur yang dimiliki proyek ini, tetapi saya pikir sintaks dapat ditingkatkan sedikit untuk merasa kurang bertele<x-alert type="error">Server Error</x-alert> ), but the solution was a lot more complicated and I came to the conclusion that using a similar approach to django-components made a lot more sense in DjangoBanyak bahasa / kerangka kerja lain menggunakan konsep yang sama untuk membangun komponen (slot, atribut), sehingga banyak pengetahuan dapat ditransfer, dan sudah ada banyak pustaka komponen yang ada di luar sana (misalnya menggunakan bootstrap, tailwind, desain material, dll.). Saya sangat merekomendasikan melihat beberapa dari mereka untuk mendapatkan inspirasi tentang cara membangun / menyusun komponen Anda. Berikut beberapa contoh: