Comet
Comet is a template system for Picomet.
The picomet.backends.picomet.PicometTemplates class implements the template backend for Django.
The picomet/comet.js module provides comet templates it’s client side SPA(Single Page Application) capabilities with the help of Alpinejs.
Layout
A Layout is used by a page or a nested layout
<!-- comets/Base.html -->
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<main>
<Outlet />
</main>
</body>
</html>
<!-- apps/core/comets/pages/About.html -->
<Layout @="Base">
<Helmet>
<title>About</title>
</Helmet>
<section>
<h1>This is the about page</h1>
</section>
</Layout>
Warning
Everything outside of Layout tag will be ignored.
Variable
To embed any data into a template, use the {$ $} syntax or s-text attribute.
<span>{$ request.method $}</span>
or
<span s-text="request.method"></span>
Expression in s-text and {$ $} is evaluated using the python’s built in eval function.
DTL
If the comet syntax is not enough for you, Picomet provides Django Template Language’s two features in comet template.
Variable
You can use DTL’s double curly braces syntax if you want to use any filter.
<div>
{{ request.method|lower }}
</div>
Tag
You can use single DTL tags inside comet templates.
<div>
{% url 'core:index' %}
</div>
Warning
Comet template doesn’t support multi tags like {% comment %}{% endcomment %}
Targets
Targets is a list of strings, sent as a request header which picomet uses to partially render a page.
s-group
Picomet uses the s-group attribute to partially render multiple elements on a page with the same group name.
See how to use s-group in the Action guide.
s-param
When you navigate from /&bookmarksPage=1 to /&bookmarksPage=2, Picomet will partially render elements with s-param="bookmarksPage" attribute in that page.
Form
For submitting forms, Picomet provides a custom Alpine.js directive named x-form
When the form is submitted, only the form element is partially rendered on the server.
<!-- apps/core/comets/Login.html -->
<form method="post" x-form>
{% csrf_token %}
<input type="text" name="username" s-bind:value="form['username'].value() or ''" />
<input type="password" name="password" s-bind:value="form['password'].value() or ''" />
<button type="submit">Login</button>
</form>
# apps/core/views.py
from django.contrib.auth import authenticate, login
from django.contrib.auth.forms import AuthenticationForm
from django.http import HttpRequest
from picomet.decorators import template
from picomet.views import render
@template("Login")
def login(request: HttpRequest):
context = {}
form = AuthenticationForm(request.user)
if request.method == "POST" and not request.action:
form = AuthenticationForm(request.POST)
if form.is_valid():
username = form.cleaned_data.get("username")
password = form.cleaned_data.get("password")
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
context["form"] = form
return render(request, context)
Head
Put content inside the head tag from outside.
Helmet
Put title and meta tags inside the head tag
<!-- apps/core/comets/Home.html -->
<Layout @="Base">
<Helmet>
<title>Home</title>
<meta name="title" content="..." />
<meta name="description" content="..." />
</Helmet>
<section>
Home page
</section>
</Layout>
Warning
Tags supported inside the Helmet tag are title and meta.
Group
Define a place for a group of Css or Scss files
<head>
...
<Group name="styles" />
...
</head>
Assets
Css
/* apps/core/comets/Page.css or apps/core/assets/Page.css */
div a {
color: red;
}
Load it in a Group
<!-- apps/core/comets/Page.html -->
<Css @="Page.css" group="styles" />
<div>
<a>Link</a>
</div>
To use a remote css file, use the http url.
<Css @="https://example.com/styles.css" group="styles" />
Sass
// apps/core/comets/Page.scss or apps/core/assets/Page.scss
div {
a {
color: red;
}
}
Load it in a Group
<!-- apps/core/comets/Page.html -->
<Sass @="Page.scss" group="styles" />
<div>
<a>Link</a>
</div>
Important
Sass requires sass and javascript. Run npm i sass and uv add javascript
Js
To load any javascript es module.
/* apps/core/comets/Page.js or apps/core/assets/Page.js */
export say(value){
alert(value);
}
<!-- apps/core/comets/Page.html -->
<Js @="Page.js" />
<button @click="say('hello')">say hello</button>
To use a remote js esm, use the http url.
<Js @="https://example.com/script.esm.js" />
Ts
To load any typescript es module..
// apps/core/comets/Page.ts or apps/core/assets/Page.ts
export say(value: string){
alert(value);
}
<!-- apps/core/comets/Page.html -->
<Ts @="Page.ts" />
<button @click="say('hello')">say hello</button>
Important
Ts requires esbuild and javascript. Run npm i esbuild and uv add javascript
s-asset:
Import any asset from app/assets or ASSETFILES_DIRS
<img s-asset:src="images/icon.png" />
Directive
s-context
Set a context for a block
<div s-context="core.get_message">
<span>{$ message $}</span>
</div>
# apps/core/contexts.py
def get_message(context):
return {
"message": f"hi, {context['user'].username}",
}
s-bind:
Bind data to an attribute
<a s-bind:href="blog.slug" x-link>{$ blog.title $}</a>
s-toggle:
Toggle boolean attribute
<button s-toggle:disabled="not user.is_authenticated"></button>
s-csrf
Set the csrftoken cookie for Action calls
<button x-on:click="call('core.hi', {})" s-csrf>hi</button>
s-static:
Import any static file from app/static or STATICFILES_DIRS
<link rel="stylesheet" s-static:href="styles/main.css" />
Component
Defining a component
<!-- apps/core/comets/Counter.html -->
<div x-data={count: 0}>
<button @click="count++">+</button>
<span x-text="count"></span>
<button @click="count--">-</button>
</div>
Using the component
<Include @="Counter" />
or
<Import.Counter @="Counter" />
<Counter />
Children
Defining a component with children
<!-- apps/core/comets/Card.html -->
<div class="card">
<Children />
</div>
Using the component
<Include @="Card">
card body
</Include>
or
<Import.Card @="Card" />
<Card>
card body
</Card>
Default
Setting default context props in a component
<!-- apps/core/comets/ProductItem.html -->
<Default show_add="True">
<div s-if="show_add">
add to cart
</div>
</Default>
Using the component
<Include @="ProductItem" /> <!-- show_add is True -->
or
<Include @="ProductItem" .show_add="False" /> <!-- show_add is False -->
Note
Use dot(.) prefix to provide a context variable to a component.
s-props
Pass normal attributes to a component
<!-- apps/core/comets/Component.html -->
<button s-props>click</button>
<Include @="Component" class="text-red-500" />
Condition
<div s-if="user.is_superuser">
hi admin
</div>
<div s-elif="user.is_authenticated">
hi user
</div>
<div s-else>
please login
</div>
<div s-show="user.is_superuser" s-group="auth">
hi admin
</div>
Loop
<div s-for="blog" s-in="blogs">
<div>
{$ blog.title $}
</div>
</div>
<div s-empty>
No blogs found
</div>
Since django ORM querysets are lazy, we can also fetch a single object from the database and partially render a list item.
To partially update any element in a list item, picomet requires s-of, s-key and s-keys attributes.
See how to use s-of, s-key and s-keys in the Action guide.
Fragment
Wrap multiple elements in a single conditional block.
<Fragment s-if="user.is_superuser">
<h2>hi</h2>,
<span>{$ user.username $}</span>
</Fragment>
With
Pass a variable to a part of template
<With username="user.username">
{$ username $}
</With>
Debug
Contents inside the Debug tag will only be parsed when Debug=True in settings.
<Debug>
<Js @="picomet/hmr.js" />
</Debug>
Pro
Contents inside the Pro tag will only be parsed when Debug=False in settings.
<Pro>
<Js @="analytics.js" />
</Pro>
Tailwind
Currently, picomet supports tailwindcss v3.
<!-- comets/Base.html -->
<!doctype html>
<html lang="en">
<head>
...
<Tailwind @="base" />
...
</head>
<body>
...
</body>
</html>
Warning
The Tailwind tag must be inside the head tag.
Important
Tailwind requires tailwindcss and javascript. Run npm i tailwindcss and uv add javascript
Note
To minify the css bundle on production, just do npm i cssnano
For tailwind to work, picomet requires 3 files.
/* comets/base.tailwind.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
/** comets/base.tailwind.js */
/** @type {import('tailwindcss').Config} */
module.exports = {
theme: {},
plugins: [],
};
/** comets/base.postcss.js */
const tailwindcss = require("tailwindcss");
module.exports = {
plugins: [tailwindcss],
};
Comet.js
The picomet/comet.js module provides comet templates it’s client side routing, partial updating etc capabilities.
It also provides some utility functions to help you update your pages.
go
Use this function to navigate to a page
go(path: string, scrollToTop?: boolean): Promise<void>
Parameter |
Default |
Description |
|---|---|---|
path: string |
Path to navigate to |
|
scrollToTop?: boolean |
false |
Whether to scroll to the top of the page |
update
Use this function to partially update a page
update(
targets: string[],
url?: string,
scrollToTop?: boolean
): Promise<void>
Parameter |
Default |
Description |
|---|---|---|
targets: string[] |
Targets list |
|
url?: string |
location.toString() |
Url to navigate to |
scrollToTop?: boolean |
false |
Whether to scroll to the top of the page |
call
Use this function to call an action
Learn more about call and actions in the Action guide.
Alpinejs SSR
The cool thing about picomet is it’s ability to render some Alpine.js directives on the server.
Although you can build simple SPA websites without any Alpinejs server side rendering, but if you want to build a SPA website with interactive and complex client side logics, then you might need to use Alpinejs SSR. The client will be able to update ui after the initial server render.
Note
Alpine.js directives supported on the server are x-data, x-show, x-text, x-bind. Learn more about these on alpinejs.dev
Important
To render Alpinejs syntax on the server Picomet requires mini-racer. Run uv add mini-racer
$S
To access any data from the python context dictionary to the server javascript context, use the $S function.
# apps/core/views.py
from picomet.decorators import template
from picomet.views import render
@template("Page")
def page(request):
context = {"variable": "hello world"}
return render(request, context)
<!-- apps/core/comets/Page.html -->
<div x-data="{var: $S(`variable`)}" server>
<span x-text="var"></span>
</div>
Important
The server attribute is required to know if the alpine directives inside a block should be rendered on the server. The client attribute can be used inside a server block to exclude a block from being rendered on the server.
isServer
Check if alpine is being rendered on server or client.
<div x-show="isServer">
<span>visible on server</span>
</div>
<div x-show="!isServer">
<span>visible on client</span>
</div>
Builtins
Picomet provides some helpful builtins to use inside templates.
safe
Mark a string as safe for use as raw HTML.
<div>
<span>{$ safe(blog.content) $}</span>
or
<span s-text="safe(blog.content)"></span>
</div>
csrf_token
Get the CSRF input.
<form>
{% csrf_token %}
</form>
csrf_input
Get the CSRF input value.
<form>
<input type="hidden" name="csrf_token" s-bind:value="csrf_input()" />
</form>