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)

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>