Comet

The picomet.backends.picomet.PicometTemplates class implements Picomet’s template backend API for Django.

Layout

A Layout is used by a page

<!-- 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>
    <div s-group="page">
      <Outlet />
    </div>
  </body>
</html>
<!-- apps/core/comets/pages/About.html -->
<Layout @="Base">
  <div>
    <Helmet>
      <title>About</title>
    </Helmet>
    <h1>This is the about page</h1>
  </div>
</Layout>

Warning

The Layout tag must have one and only single html tag children.

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 parts of a page on the server.

See how to use s-group in the Action guide.

s-param

When you navigate from /&bookmarksPage=1 to /&bookmarksPage=2, Picomet partially renders s-param="bookmarksPage" elements 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>
  <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>

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 pip install javascript

Js

/* 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>

Ts

// 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 pip install javascript

s-asset:

Import any asset from app/assets or ASSETFILES_DIRS

<img s-asset:src="images/icon.png" />

Attribute

s-bind:

<a s-bind:href="blog.slug">{$ blog.title $}</a>

s-toggle:

Toggle boolean attribute

<button s-toggle:disabled="not user.is_authenticated"></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>

Warning

Use s-show instead of s-if with s-group. Learn more about s-group in the Action guide.

Loop

<div s-for="blog" s-in="blogs">
  <div>
    {$ blog.title $}
  </div>
</div>
<div s-empty>
  No blogs found
</div>

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>

Tailwind

<!-- 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 pip install javascript

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],
};

Alpine SSR

The cool thing about picomet is it’s ability to render alpine.js on the server

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 Alpine.js syntax on the server Picomet requires py-mini-racer. Run pip install py-mini-racer

s-prop

To pass any data from the server context dictionary to the javascript context, use the s-prop directive.

# 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 s-prop:_var="variable" x-data="{var: _var}" 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.