diff --git a/CHANGELOG.md b/CHANGELOG.md index 894a1390..20a85b93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,18 @@ Using the following categories, list your changes in this order: ## [Unreleased] -- Nothing (Yet) +### Added + +- `use_origin` hook to return the browser's `location.origin`. + +### Changed + +- `use_mutation` and `use_query` will now log any query failures. + +### Fixed + +- Allow `use_mutation` to have `refetch=None`, as the docs suggest is possible. +- `use_query` will now prefetch all fields to prevent `SynchronousOnlyOperation` exceptions. ## [1.2.0] - 2022-09-19 diff --git a/README.md b/README.md index dfee8134..1c7e4fbc 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ You'll need a file to define your [IDOM](https://github.com/idom-team/idom) comp -```python title="components.py" +```python linenums="1" from idom import component, html @component @@ -51,7 +51,7 @@ Additonally, you can pass in keyword arguments into your component function. For -```jinja title="my-template.html" +```jinja linenums="1" {% load idom %} diff --git a/docs/includes/examples.md b/docs/includes/examples.md index 623e60c3..3ed273fd 100644 --- a/docs/includes/examples.md +++ b/docs/includes/examples.md @@ -1,6 +1,6 @@ -```python +```python linenums="1" from django.http import HttpResponse def hello_world_view(request, *args, **kwargs): @@ -11,7 +11,7 @@ def hello_world_view(request, *args, **kwargs): -```python +```python linenums="1" from django.http import HttpResponse from django.views import View @@ -20,4 +20,15 @@ class HelloWorldView(View): return HttpResponse("Hello World!") ``` - \ No newline at end of file + + + + +```python linenums="1" +from django.db import models + +class TodoItem(models.Model): + text = models.CharField(max_length=255) +``` + + diff --git a/docs/src/contribute/django-idom.md b/docs/src/contribute/code.md similarity index 100% rename from docs/src/contribute/django-idom.md rename to docs/src/contribute/code.md diff --git a/docs/src/features/components.md b/docs/src/features/components.md index 8d95c062..5886fc23 100644 --- a/docs/src/features/components.md +++ b/docs/src/features/components.md @@ -1,10 +1,14 @@ +???+ summary + + Prefabricated components can be used within your `components.py` to help simplify development. + ## View To Component Convert any Django view into a IDOM component by usng this decorator. Compatible with sync/async [Function Based Views](https://docs.djangoproject.com/en/dev/topics/http/views/) and [Class Based Views](https://docs.djangoproject.com/en/dev/topics/class-based-views/). === "components.py" - ```python + ```python linenums="1" from idom import component, html from django_idom.components import view_to_component from .views import hello_world_view @@ -47,7 +51,7 @@ Convert any Django view into a IDOM component by usng this decorator. Compatible === "components.py" - ```python + ```python linenums="1" from idom import component, html from django_idom.components import view_to_component from .views import HelloWorldView @@ -69,7 +73,7 @@ Convert any Django view into a IDOM component by usng this decorator. Compatible === "components.py" - ```python + ```python linenums="1" from idom import component, html from django_idom.components import view_to_component from .views import hello_world_view @@ -99,7 +103,7 @@ Convert any Django view into a IDOM component by usng this decorator. Compatible === "components.py" - ```python + ```python linenums="1" from idom import component, html from django_idom.components import view_to_component from .views import hello_world_view @@ -125,7 +129,7 @@ Convert any Django view into a IDOM component by usng this decorator. Compatible === "components.py" - ```python + ```python linenums="1" from idom import component, html from django_idom.components import view_to_component from .views import hello_world_view @@ -153,7 +157,7 @@ Convert any Django view into a IDOM component by usng this decorator. Compatible === "components.py" - ```python + ```python linenums="1" from idom import component, html from django_idom.components import view_to_component from .views import hello_world_view @@ -174,7 +178,7 @@ Convert any Django view into a IDOM component by usng this decorator. Compatible === "views.py" - ```python + ```python linenums="1" from django.http import HttpResponse def hello_world_view(request, *args, **kwargs): @@ -185,17 +189,33 @@ Convert any Django view into a IDOM component by usng this decorator. Compatible Allows you to defer loading a CSS stylesheet until a component begins rendering. This stylesheet must be stored within [Django's static files](https://docs.djangoproject.com/en/dev/howto/static-files/). -```python title="components.py" -from idom import component, html -from django_idom.components import django_css +=== "components.py" + + ```python linenums="1" + from idom import component, html + from django_idom.components import django_css + + @component + def my_component(): + return html.div( + django_css("css/buttons.css"), + html.button("My Button!"), + ) + ``` -@component -def my_component(): - return html.div( - django_css("css/buttons.css"), - html.button("My Button!"), - ) -``` +??? example "See Interface" + + **Parameters** + + | Name | Type | Description | Default | + | --- | --- | --- | --- | + | static_path | `str` | The path to the static file. This path is identical to what you would use on a `static` template tag. | N/A | + + **Returns** + + | Type | Description | + | --- | --- | + | `Component` | An IDOM component. | ??? question "Should I put `django_css` at the top of my component?" @@ -207,7 +227,7 @@ def my_component(): Here's an example on what you should avoid doing for Django static files: - ```python + ```python linenums="1" from idom import component, html from django.templatetags.static import static @@ -225,7 +245,7 @@ def my_component(): For external CSS, substitute `django_css` with `html.link`. - ```python + ```python linenums="1" from idom import component, html @component @@ -246,17 +266,33 @@ def my_component(): Allows you to defer loading JavaScript until a component begins rendering. This JavaScript must be stored within [Django's static files](https://docs.djangoproject.com/en/dev/howto/static-files/). -```python title="components.py" -from idom import component, html -from django_idom.components import django_js +=== "components.py" + + ```python linenums="1" + from idom import component, html + from django_idom.components import django_js + + @component + def my_component(): + return html.div( + html.button("My Button!"), + django_js("js/scripts.js"), + ) + ``` -@component -def my_component(): - return html.div( - html.button("My Button!"), - django_js("js/scripts.js"), - ) -``` +??? example "See Interface" + + **Parameters** + + | Name | Type | Description | Default | + | --- | --- | --- | --- | + | static_path | `str` | The path to the static file. This path is identical to what you would use on a `static` template tag. | N/A | + + **Returns** + + | Type | Description | + | --- | --- | + | `Component` | An IDOM component. | ??? question "Should I put `django_js` at the bottom of my component?" @@ -268,7 +304,7 @@ def my_component(): Here's an example on what you should avoid doing for Django static files: - ```python + ```python linenums="1" from idom import component, html from django.templatetags.static import static @@ -286,7 +322,7 @@ def my_component(): For external JavaScript, substitute `django_js` with `html.script`. - ```python + ```python linenums="1" from idom import component, html @component diff --git a/docs/src/features/decorators.md b/docs/src/features/decorators.md index 29a32d96..8b28c08a 100644 --- a/docs/src/features/decorators.md +++ b/docs/src/features/decorators.md @@ -1,23 +1,29 @@ +???+ summary + + Decorator utilities can be used within your `components.py` to help simplify development. + ## Auth Required You can limit access to a component to users with a specific `auth_attribute` by using this decorator. By default, this decorator checks if the user is logged in, and his/her account has not been deactivated. -Common uses of this decorator are to hide components from [`AnonymousUser`](https://docs.djangoproject.com/en/dev/ref/contrib/auth/#django.contrib.auth.models.AnonymousUser), or render a component only if the user [`is_staff`](https://docs.djangoproject.com/en/dev/ref/contrib/auth/#django.contrib.auth.models.User.is_staff) or [`is_superuser`](https://docs.djangoproject.com/en/dev/ref/contrib/auth/#django.contrib.auth.models.User.is_superuser). +This decorator is commonly used to selectively render a component only if a user [`is_staff`](https://docs.djangoproject.com/en/dev/ref/contrib/auth/#django.contrib.auth.models.User.is_staff) or [`is_superuser`](https://docs.djangoproject.com/en/dev/ref/contrib/auth/#django.contrib.auth.models.User.is_superuser). This decorator can be used with or without parentheses. -```python title="components.py" -from django_idom.decorators import auth_required -from django_idom.hooks import use_websocket -from idom import component, html +=== "components.py" -@component -@auth_required -def my_component(): - return html.div("I am logged in!") -``` + ```python linenums="1" + from django_idom.decorators import auth_required + from django_idom.hooks import use_websocket + from idom import component, html + + @component + @auth_required + def my_component(): + return html.div("I am logged in!") + ``` ??? example "See Interface" @@ -40,50 +46,56 @@ def my_component(): You can use a component with the `fallback` argument, as seen below. - ```python title="components.py" - from django_idom.decorators import auth_required - from idom import component, html + === "components.py" - @component - def my_component_fallback(): - return html.div("I am NOT logged in!") + ```python linenums="1" + from django_idom.decorators import auth_required + from idom import component, html - @component - @auth_required(fallback=my_component_fallback) - def my_component(): - return html.div("I am logged in!") - ``` + @component + def my_component_fallback(): + return html.div("I am NOT logged in!") + + @component + @auth_required(fallback=my_component_fallback) + def my_component(): + return html.div("I am logged in!") + ``` ??? question "How do I render a simple `idom.html` snippet if authentication fails?" You can use a `idom.html` snippet with the `fallback` argument, as seen below. - ```python title="components.py" - from django_idom.decorators import auth_required - from django_idom.hooks import use_websocket - from idom import component, html + === "components.py" - @component - @auth_required(fallback=html.div("I am NOT logged in!")) - def my_component(): - return html.div("I am logged in!") - ``` + ```python linenums="1" + from django_idom.decorators import auth_required + from django_idom.hooks import use_websocket + from idom import component, html + + @component + @auth_required(fallback=html.div("I am NOT logged in!")) + def my_component(): + return html.div("I am logged in!") + ``` ??? question "How can I check if a user `is_staff`?" You can set the `auth_attribute` to `is_staff`, as seen blow. - ```python title="components.py" - from django_idom.decorators import auth_required - from django_idom.hooks import use_websocket - from idom import component, html + === "components.py" + ```python linenums="1" + from django_idom.decorators import auth_required + from django_idom.hooks import use_websocket + from idom import component, html - @component - @auth_required(auth_attribute="is_staff") - def my_component(): - return html.div("I am logged in!") - ``` + + @component + @auth_required(auth_attribute="is_staff") + def my_component(): + return html.div("I am logged in!") + ``` ??? question "How can I check for a custom attribute?" @@ -91,24 +103,28 @@ def my_component(): For example, if your user model has the field `is_really_cool` ... - ```python - from django.contrib.auth.models import AbstractBaseUser + === "models.py" - class CustomUserModel(AbstractBaseUser): - @property - def is_really_cool(self): - return True - ``` + ```python linenums="1" + from django.contrib.auth.models import AbstractBaseUser + + class CustomUserModel(AbstractBaseUser): + @property + def is_really_cool(self): + return True + ``` ... then you would do the following within your decorator: - ```python title="components.py" - from django_idom.decorators import auth_required - from django_idom.hooks import use_websocket - from idom import component, html + === "components.py" - @component - @auth_required(auth_attribute="is_really_cool") - def my_component(): - return html.div("I am logged in!") - ``` + ```python linenums="1" + from django_idom.decorators import auth_required + from django_idom.hooks import use_websocket + from idom import component, html + + @component + @auth_required(auth_attribute="is_really_cool") + def my_component(): + return html.div("I am logged in!") + ``` diff --git a/docs/src/features/hooks.md b/docs/src/features/hooks.md index 09c8daa9..e9e67e39 100644 --- a/docs/src/features/hooks.md +++ b/docs/src/features/hooks.md @@ -1,14 +1,22 @@ -???+ tip "Looking for more hooks?" +???+ summary - Check out the [IDOM Core docs](https://idom-docs.herokuapp.com/docs/reference/hooks-api.html#basic-hooks) on hooks! + Prefabricated hooks can be used within your `components.py` to help simplify development. + +??? tip "Looking for standard ReactJS hooks?" + + Standard ReactJS hooks are contained within [`idom-team/idom`](https://github.com/idom-team/idom). Since `idom` is installed by default alongside `django-idom`, you can import them at any time. + + Check out the [IDOM Core docs](https://idom-docs.herokuapp.com/docs/reference/hooks-api.html#basic-hooks) to see what hooks are available! ## Use Query The `use_query` hook is used fetch Django ORM queries. +The function you provide into this hook must return either a `Model` or `QuerySet`. + === "components.py" - ```python + ```python linenums="1" from example_project.my_app.models import TodoItem from idom import component, html from django_idom.hooks import use_query @@ -32,19 +40,41 @@ The `use_query` hook is used fetch Django ORM queries. === "models.py" - ```python + ```python linenums="1" from django.db import models class TodoItem(models.Model): text = models.CharField(max_length=255) ``` +??? example "See Interface" + + **Parameters** + + | Name | Type | Description | Default | + | --- | --- | --- | --- | + | query | `Callable[_Params, _Result | None]` | A callable that returns a Django `Model` or `QuerySet`. | N/A | + | *args | `_Params.args` | Positional arguments to pass into `query`. | N/A | + | **kwargs | `_Params.kwargs` | Keyword arguments to pass into `query`. | N/A | + + **Returns** + + | Type | Description | + | --- | --- | + | `Query[_Result | None]` | A dataclass containing `loading`/`error` states, your `data` (if the query has successfully executed), and a `refetch` callable that can be used to re-run the query. | + ??? question "Can I make ORM calls without hooks?" Due to Django's ORM design, database queries must be deferred using hooks. Otherwise, you will see a `SynchronousOnlyOperation` exception. This may be resolved in a future version of Django with a natively asynchronous ORM. +??? question "Why does the example `get_items` function return a `Model` or `QuerySet`?" + + This was a technical design decision to [based on Apollo](https://www.apollographql.com/docs/react/data/mutations/#usemutation-api), but ultimately helps avoid Django's `SynchronousOnlyOperation` exceptions. + + The `use_query` hook ensures the provided `Model` or `QuerySet` executes all [deferred](https://docs.djangoproject.com/en/dev/ref/models/instances/#django.db.models.Model.get_deferred_fields)/[lazy queries](https://docs.djangoproject.com/en/dev/topics/db/queries/#querysets-are-lazy) safely prior to reaching your components. + ??? question "What is an "ORM"?" A Python **Object Relational Mapper** is an API for your code to access a database. @@ -53,11 +83,13 @@ The `use_query` hook is used fetch Django ORM queries. ## Use Mutation -The `use_mutation` hook is used to modify Django ORM objects. +The `use_mutation` hook is used to create, update, or delete Django ORM objects. + +The function you provide into this hook will have no return value. === "components.py" - ```python + ```python linenums="1" from example_project.my_app.models import TodoItem from idom import component, html from django_idom.hooks import use_mutation @@ -67,8 +99,11 @@ The `use_mutation` hook is used to modify Django ORM objects. @component def todo_list(): - item_mutation = use_mutation(add_item) + def submit_event(event): + if event["key"] == "Enter": + item_mutation.execute(text=event["target"]["value"]) + item_mutation = use_mutation(add_item) if item_mutation.loading: mutation_status = html.h2("Adding...") elif item_mutation.error: @@ -76,10 +111,6 @@ The `use_mutation` hook is used to modify Django ORM objects. else: mutation_status = "" - def submit_event(event): - if event["key"] == "Enter": - item_mutation.execute(text=event["target"]["value"]) - return html.div( html.label("Add an item:"), html.input({"type": "text", "onKeyDown": submit_event}), @@ -89,61 +120,121 @@ The `use_mutation` hook is used to modify Django ORM objects. === "models.py" - ```python - from django.db import models + {% include-markdown "../../includes/examples.md" start="" end="" %} - class TodoItem(models.Model): - text = models.CharField(max_length=255) - ``` +??? example "See Interface" + + **Parameters** + + | Name | Type | Description | Default | + | --- | --- | --- | --- | + | mutate | `Callable[_Params, bool | None]` | A callable that performs Django ORM create, update, or delete functionality. If this function returns `False`, then your `refetch` function will not be used. | N/A | + | refetch | `Callable[..., Any] | Sequence[Callable[..., Any]] | None` | A `query` function (used by the `use_query` hook) or a sequence of `query` functions that will be called if the mutation succeeds. This is useful for refetching data after a mutation has been performed. | `None` | + + **Returns** + + | Type | Description | + | --- | --- | + | `Mutation[_Params]` | A dataclass containing `loading`/`error` states, a `reset` callable that will set `loading`/`error` states to defaults, and a `execute` callable that will run the query. | ??? question "Can `use_mutation` trigger a refetch of `use_query`?" Yes, `use_mutation` can queue a refetch of a `use_query` via the `refetch=...` argument. The example below is a merge of the `use_query` and `use_mutation` examples above with the addition of a `refetch` argument on `use_mutation`. - + Please note that any `use_query` hooks that use `get_items` will be refetched upon a successful mutation. - ```python title="components.py" - from example_project.my_app.models import TodoItem - from idom import component, html - from django_idom.hooks import use_mutation + === "components.py" - def get_items(): - return TodoItem.objects.all() + ```python linenums="1" + from example_project.my_app.models import TodoItem + from idom import component, html + from django_idom.hooks import use_mutation - def add_item(text: str): - TodoItem(text=text).save() + def get_items(): + return TodoItem.objects.all() - @component - def todo_list(): - item_query = use_query(get_items) - if item_query.loading: - rendered_items = html.h2("Loading...") - elif item_query.error: - rendered_items = html.h2("Error when loading!") - else: - rendered_items = html.ul(html.li(item, key=item) for item in item_query.data) + def add_item(text: str): + TodoItem(text=text).save() - item_mutation = use_mutation(add_item, refetch=get_items) - if item_mutation.loading: - mutation_status = html.h2("Adding...") - elif item_mutation.error: - mutation_status = html.h2("Error when adding!") - else: - mutation_status = "" + @component + def todo_list(): + def submit_event(event): + if event["key"] == "Enter": + item_mutation.execute(text=event["target"]["value"]) - def submit_event(event): - if event["key"] == "Enter": - item_mutation.execute(text=event["target"]["value"]) + item_query = use_query(get_items) + if item_query.loading: + rendered_items = html.h2("Loading...") + elif item_query.error: + rendered_items = html.h2("Error when loading!") + else: + rendered_items = html.ul(html.li(item, key=item) for item in item_query.data) - return html.div( - html.label("Add an item:"), - html.input({"type": "text", "onKeyDown": submit_event}), - mutation_status, - rendered_items, - ) - ``` + item_mutation = use_mutation(add_item, refetch=get_items) + if item_mutation.loading: + mutation_status = html.h2("Adding...") + elif item_mutation.error: + mutation_status = html.h2("Error when adding!") + else: + mutation_status = "" + + return html.div( + html.label("Add an item:"), + html.input({"type": "text", "onKeyDown": submit_event}), + mutation_status, + rendered_items, + ) + ``` + + === "models.py" + + {% include-markdown "../../includes/examples.md" start="" end="" %} + +??? question "Can I make a failed `use_mutation` try again?" + + Yes, a `use_mutation` can be re-performed by calling `reset()` on your `use_mutation` instance. + + For example, take a look at `reset_event` below. + + === "components.py" + + ```python linenums="1" + from example_project.my_app.models import TodoItem + from idom import component, html + from django_idom.hooks import use_mutation + + def add_item(text: str): + TodoItem(text=text).save() + + @component + def todo_list(): + def reset_event(event): + item_mutation.reset() + + def submit_event(event): + if event["key"] == "Enter": + item_mutation.execute(text=event["target"]["value"]) + + item_mutation = use_mutation(add_item) + if item_mutation.loading: + mutation_status = html.h2("Adding...") + elif item_mutation.error: + mutation_status = html.button({"onClick": reset_event}, "Error: Try again!") + else: + mutation_status = "" + + return html.div( + html.label("Add an item:"), + html.input({"type": "text", "onKeyDown": submit_event}), + mutation_status, + ) + ``` + + === "models.py" + + {% include-markdown "../../includes/examples.md" start="" end="" %} ??? question "Can I make ORM calls without hooks?" @@ -161,48 +252,122 @@ The `use_mutation` hook is used to modify Django ORM objects. ## Use Websocket -You can fetch the Django Channels websocket at any time by using `use_websocket`. +You can fetch the Django Channels [websocket](https://channels.readthedocs.io/en/stable/topics/consumers.html#asyncjsonwebsocketconsumer) at any time by using `use_websocket`. -```python title="components.py" -from idom import component, html -from django_idom.hooks import use_websocket +=== "components.py" -@component -def my_component(): - my_websocket = use_websocket() - return html.div(my_websocket) -``` + ```python linenums="1" + from idom import component, html + from django_idom.hooks import use_websocket + + @component + def my_component(): + my_websocket = use_websocket() + return html.div(my_websocket) + ``` + +??? example "See Interface" + + **Parameters** + + `None` + + **Returns** + + | Type | Description | + | --- | --- | + | `IdomWebsocket` | The component's websocket. | ## Use Scope -This is a shortcut that returns the Websocket's `scope`. +This is a shortcut that returns the Websocket's [`scope`](https://channels.readthedocs.io/en/stable/topics/consumers.html#scope). + +=== "components.py" + + ```python linenums="1" + from idom import component, html + from django_idom.hooks import use_scope + + @component + def my_component(): + my_scope = use_scope() + return html.div(my_scope) + ``` + +??? example "See Interface" + + **Parameters** -```python title="components.py" -from idom import component, html -from django_idom.hooks import use_scope + `None` -@component -def my_component(): - my_scope = use_scope() - return html.div(my_scope) -``` + **Returns** + + | Type | Description | + | --- | --- | + | `dict[str, Any]` | The websocket's `scope`. | ## Use Location +This is a shortcut that returns the Websocket's `path`. + +You can expect this hook to provide strings such as `/idom/my_path`. + +=== "components.py" + + ```python linenums="1" + from idom import component, html + from django_idom.hooks import use_location + + @component + def my_component(): + my_location = use_location() + return html.div(my_location) + ``` + ??? info "This hook's behavior will be changed in a future update" - This hook will be updated to return the browser's current URL. This change will come in alongside IDOM URL routing support. + This hook will be updated to return the browser's currently active path. This change will come in alongside IDOM URL routing support. - Check out [idom-team/idom#569](https://github.com/idom-team/idom/issues/569) for more information. + Check out [idom-team/idom-router#2](https://github.com/idom-team/idom-router/issues/2) for more information. -This is a shortcut that returns the Websocket's `path`. +??? example "See Interface" + + **Parameters** + + `None` + + **Returns** + + | Type | Description | + | --- | --- | + | `Location` | A object containing the current URL's `pathname` and `search` query. | + +## Use Origin + +This is a shortcut that returns the Websocket's `origin`. + +You can expect this hook to provide strings such as `http://example.com`. + +=== "components.py" + + ```python linenums="1" + from idom import component, html + from django_idom.hooks import use_origin + + @component + def my_component(): + my_origin = use_origin() + return html.div(my_origin) + ``` + +??? example "See Interface" + + **Parameters** + + `None` -```python title="components.py" -from idom import component, html -from django_idom.hooks import use_location + **Returns** -@component -def my_component(): - my_location = use_location() - return html.div(my_location) -``` + | Type | Description | + | --- | --- | + | `str | None` | A string containing the browser's current origin, obtained from websocket headers (if available). | diff --git a/docs/src/features/settings.md b/docs/src/features/settings.md index b003ae3c..c6a7c020 100644 --- a/docs/src/features/settings.md +++ b/docs/src/features/settings.md @@ -1,24 +1,28 @@ -Django IDOM uses your **Django project's** `settings.py` file to modify some behaviors of IDOM. +???+ summary -Here are the configurable variables that are available. + Django IDOM uses your **Django project's** `settings.py` file to modify the behavior of IDOM. - +## Primary Configuration -```python title="settings.py" -# If "idom" cache is not configured, then we'll use "default" instead -CACHES = { -"idom": {"BACKEND": ...}, -} +=== "settings.py" -# Maximum seconds between two reconnection attempts that would cause the client give up. -# 0 will disable reconnection. -IDOM_WS_MAX_RECONNECT_TIMEOUT = 604800 + -# The URL for IDOM to serve websockets -IDOM_WEBSOCKET_URL = "idom/" -``` + ```python linenums="1" + # If "idom" cache is not configured, then we'll use "default" instead + CACHES = { + "idom": {"BACKEND": ...}, + } - + # Maximum seconds between two reconnection attempts that would cause the client give up. + # 0 will disable reconnection. + IDOM_WS_MAX_RECONNECT_TIMEOUT = 604800 + + # The URL for IDOM to serve websockets + IDOM_WEBSOCKET_URL = "idom/" + ``` + + ??? question "Do I need to modify my settings?" diff --git a/docs/src/features/templatetag.md b/docs/src/features/templatetag.md index 43910f4c..3e7731d9 100644 --- a/docs/src/features/templatetag.md +++ b/docs/src/features/templatetag.md @@ -1,6 +1,12 @@ -Integrated within Django IDOM, we bundle a template tag. Within this tag, you can pass in keyword arguments directly into your component. +???+ summary -{% include-markdown "../../../README.md" start="" end="" %} + Template tags can be used within your Django templates such as `my-template.html` to import IDOM features. + +## Component + +=== "my-template.html" + + {% include-markdown "../../../README.md" start="" end="" %} @@ -8,23 +14,27 @@ Integrated within Django IDOM, we bundle a template tag. Within this tag, you ca Our pre-processor relies on the template tag containing a string. - **Do not** use a Django context variable for the path string. Failure to follow this warning will result in a performance penalty and also jankiness when using the Django autoreloader. + **Do not** use Django template/context variables for the component path. Failure to follow this warning will result in unexpected behavior. For example, **do not** do the following: - ```python title="views.py" - def example_view(): - context_vars = {"dont_do_this": "example_project.my_app.components.hello_world"} - return render(request, "my-template.html", context_vars) - ``` + === "my-template.html" - ```jinja title="my-template.html" - - {% component dont_do_this recipient="World" %} + ```jinja linenums="1" + + {% component dont_do_this recipient="World" %} - - {% component "example_project.my_app.components.hello_world" recipient="World" %} - ``` + + {% component "example_project.my_app.components.hello_world" recipient="World" %} + ``` + + === "views.py" + + ```python linenums="1" + def example_view(): + context_vars = {"dont_do_this": "example_project.my_app.components.hello_world"} + return render(request, "my-template.html", context_vars) + ``` @@ -36,30 +46,50 @@ Integrated within Django IDOM, we bundle a template tag. Within this tag, you ca - `class` allows you to apply a HTML class to the top-level component div. This is useful for styling purposes. - `key` allows you to force the component to use a [specific key value](https://idom-docs.herokuapp.com/docs/guides/understanding-idom/why-idom-needs-keys.html?highlight=key). You typically won't need to set this. - ```jinja title="my-template.html" - ... - {% component "example.components.my_component" class="my-html-class" key=123 %} - ... - ``` + === "my-template.html" + + ```jinja linenums="1" + ... + {% component "example.components.my_component" class="my-html-class" key=123 %} + ... + ``` + +??? example "See Interface" + + **Parameters** + + | Name | Type | Description | Default | + | --- | --- | --- | --- | + | dotted_path | `str` | The dotted path to the component to render. | N/A | + | **kwargs | `Any` | The keyword arguments to pass to the component. | N/A | + + **Returns** + + | Type | Description | + | --- | --- | + | `Component` | An IDOM component. | + ??? question "Can I use multiple components on one page?" You can add as many components to a webpage as needed by using the template tag multiple times. Retrofitting legacy sites to use IDOM will typically involve many components on one page. - ```jinja - {% load idom %} - - -
- {% component "example_project.my_app.components.hello_world" recipient="World" %} - {% component "example_project.my_app_2.components.class_component" class="bold small-font" %} -