diff --git a/AI/fasthtml.md b/AI/fasthtml.md deleted file mode 100644 index cfedeac..0000000 --- a/AI/fasthtml.md +++ /dev/null @@ -1,2619 +0,0 @@ - -Things to remember when writing FastHTML apps: - -- Although parts of its API are inspired by FastAPI, it is *not* compatible with FastAPI syntax and is not targeted at creating API services -- FastHTML includes support for Pico CSS and the fastlite sqlite library, although using both are optional; sqlalchemy can be used directly or via the fastsql library, and any CSS framework can be used. Support for the Surreal and css-scope-inline libraries are also included, but both are optional -- FastHTML is compatible with JS-native web components and any vanilla JS library, but not with React, Vue, or Svelte -- Use `serve()` for running uvicorn (`if __name__ == "__main__"` is not needed since it's automatic) -- When a title is needed with a response, use `Titled`; note that that already wraps children in `Container`, and already includes both the meta title as well as the H1 element. - - # Web Devs Quickstart - - -## Installation - -``` bash -pip install python-fasthtml -``` - -## A Minimal Application - -A minimal FastHTML application looks something like this: - -
- -**main.py** - -``` python -from fasthtml.common import * - -app, rt = fast_app() - -@rt("/") -def get(): - return Titled("FastHTML", P("Let's do this!")) - -serve() -``` - -
- -Line 1 -We import what we need for rapid development! A carefully-curated set of -FastHTML functions and other Python objects is brought into our global -namespace for convenience. - -Line 3 -We instantiate a FastHTML app with the `fast_app()` utility function. -This provides a number of really useful defaults that we’ll take -advantage of later in the tutorial. - -Line 5 -We use the `rt()` decorator to tell FastHTML what to return when a user -visits `/` in their browser. - -Line 6 -We connect this route to HTTP GET requests by defining a view function -called `get()`. - -Line 7 -A tree of Python function calls that return all the HTML required to -write a properly formed web page. You’ll soon see the power of this -approach. - -Line 9 -The `serve()` utility configures and runs FastHTML using a library -called `uvicorn`. - -Run the code: - -``` bash -python main.py -``` - -The terminal will look like this: - -``` bash -INFO: Uvicorn running on http://0.0.0.0:5001 (Press CTRL+C to quit) -INFO: Started reloader process [58058] using WatchFiles -INFO: Started server process [58060] -INFO: Waiting for application startup. -INFO: Application startup complete. -``` - -Confirm FastHTML is running by opening your web browser to -[127.0.0.1:5001](http://127.0.0.1:5001). You should see something like -the image below: - -![](quickstart-web-dev/quickstart-fasthtml.png) - -
- -> **Note** -> -> While some linters and developers will complain about the wildcard -> import, it is by design here and perfectly safe. FastHTML is very -> deliberate about the objects it exports in `fasthtml.common`. If it -> bothers you, you can import the objects you need individually, though -> it will make the code more verbose and less readable. -> -> If you want to learn more about how FastHTML handles imports, we cover -> that [here](https://docs.fastht.ml/explains/faq.html#why-use-import). - -
- -## A Minimal Charting Application - -The -[`Script`](https://AnswerDotAI.github.io/fasthtml/api/xtend.html#script) -function allows you to include JavaScript. You can use Python to -generate parts of your JS or JSON like this: - -``` python -import json -from fasthtml.common import * - -app, rt = fast_app(hdrs=(Script(src="https://cdn.plot.ly/plotly-2.32.0.min.js"),)) - -data = json.dumps({ - "data": [{"x": [1, 2, 3, 4],"type": "scatter"}, - {"x": [1, 2, 3, 4],"y": [16, 5, 11, 9],"type": "scatter"}], - "title": "Plotly chart in FastHTML ", - "description": "This is a demo dashboard", - "type": "scatter" -}) - - -@rt("/") -def get(): - return Titled("Chart Demo", Div(id="myDiv"), - Script(f"var data = {data}; Plotly.newPlot('myDiv', data);")) - -serve() -``` - -## Debug Mode - -When we can’t figure out a bug in FastHTML, we can run it in `DEBUG` -mode. When an error is thrown, the error screen is displayed in the -browser. This error setting should never be used in a deployed app. - -``` python -from fasthtml.common import * - -app, rt = fast_app(debug=True) - -@rt("/") -def get(): - 1/0 - return Titled("FastHTML Error!", P("Let's error!")) - -serve() -``` - -Line 3 -`debug=True` sets debug mode on. - -Line 7 -Python throws an error when it tries to divide an integer by zero. - -## Routing - -FastHTML builds upon FastAPI’s friendly decorator pattern for specifying -URLs, with extra features: - -
- -**main.py** - -``` python -from fasthtml.common import * - -app, rt = fast_app() - -@rt("/") -def get(): - return Titled("FastHTML", P("Let's do this!")) - -@rt("/hello") -def get(): - return Titled("Hello, world!") - -serve() -``` - -
- -Line 5 -The “/” URL on line 5 is the home of a project. This would be accessed -at [127.0.0.1:5001](http://127.0.0.1:5001). - -Line 9 -“/hello” URL on line 9 will be found by the project if the user visits -[127.0.0.1:5001/hello](http://127.0.0.1:5001/hello). - -
- -> **Tip** -> -> It looks like `get()` is being defined twice, but that’s not the case. -> Each function decorated with `rt` is totally separate, and is injected -> into the router. We’re not calling them in the module’s namespace -> (`locals()`). Rather, we’re loading them into the routing mechanism -> using the `rt` decorator. - -
- -You can do more! Read on to learn what we can do to make parts of the -URL dynamic. - -## Variables in URLs - -You can add variable sections to a URL by marking them with -`{variable_name}`. Your function then receives the `{variable_name}` as -a keyword argument, but only if it is the correct type. Here’s an -example: - -
- -**main.py** - -``` python -from fasthtml.common import * - -app, rt = fast_app() - -@rt("/{name}/{age}") -def get(name: str, age: int): - return Titled(f"Hello {name.title()}, age {age}") - -serve() -``` - -
- -Line 5 -We specify two variable names, `name` and `age`. - -Line 6 -We define two function arguments named identically to the variables. You -will note that we specify the Python types to be passed. - -Line 7 -We use these functions in our project. - -Try it out by going to this address: -[127.0.0.1:5001/uma/5](http://127.0.0.1:5001/uma/5). You should get a -page that says, - -> “Hello Uma, age 5”. - -### What happens if we enter incorrect data? - -The [127.0.0.1:5001/uma/5](http://127.0.0.1:5001/uma/5) URL works -because `5` is an integer. If we enter something that is not, such as -[127.0.0.1:5001/uma/five](http://127.0.0.1:5001/uma/five), then FastHTML -will return an error instead of a web page. - -
- -> **FastHTML URL routing supports more complex types** -> -> The two examples we provide here use Python’s built-in `str` and `int` -> types, but you can use your own types, including more complex ones -> such as those defined by libraries like -> [attrs](https://pypi.org/project/attrs/), -> [pydantic](https://pypi.org/project/pydantic/), and even -> [sqlmodel](https://pypi.org/project/sqlmodel/). - -
- -## HTTP Methods - -FastHTML matches function names to HTTP methods. So far the URL routes -we’ve defined have been for HTTP GET methods, the most common method for -web pages. - -Form submissions often are sent as HTTP POST. When dealing with more -dynamic web page designs, also known as Single Page Apps (SPA for -short), the need can arise for other methods such as HTTP PUT and HTTP -DELETE. The way FastHTML handles this is by changing the function name. - -
- -**main.py** - -``` python -from fasthtml.common import * - -app, rt = fast_app() - -@rt("/") -def get(): - return Titled("HTTP GET", P("Handle GET")) - -@rt("/") -def post(): - return Titled("HTTP POST", P("Handle POST")) - -serve() -``` - -
- -Line 6 -On line 6 because the `get()` function name is used, this will handle -HTTP GETs going to the `/` URI. - -Line 10 -On line 10 because the `post()` function name is used, this will handle -HTTP POSTs going to the `/` URI. - -## CSS Files and Inline Styles - -Here we modify default headers to demonstrate how to use the [Sakura CSS -microframework](https://github.com/oxalorg/sakura) instead of FastHTML’s -default of Pico CSS. - -
- -**main.py** - -``` python -from fasthtml.common import * - -app, rt = fast_app( - pico=False, - hdrs=( - Link(rel='stylesheet', href='assets/normalize.min.css', type='text/css'), - Link(rel='stylesheet', href='assets/sakura.css', type='text/css'), - Style("p {color: red;}") -)) - -@app.get("/") -def home(): - return Titled("FastHTML", - P("Let's do this!"), - ) - -serve() -``` - -
- -Line 4 -By setting `pico` to `False`, FastHTML will not include `pico.min.css`. - -Line 7 -This will generate an HTML `` tag for sourcing the css for Sakura. - -Line 8 -If you want an inline styles, the `Style()` function will put the result -into the HTML. - -## Other Static Media File Locations - -As you saw, -[`Script`](https://AnswerDotAI.github.io/fasthtml/api/xtend.html#script) -and `Link` are specific to the most common static media use cases in web -apps: including JavaScript, CSS, and images. But it also works with -videos and other static media files. The default behavior is to look for -these files in the root directory - typically we don’t do anything -special to include them. We can change the default directory that is -looked in for files by adding the `static_path` parameter to the -[`fast_app`](https://AnswerDotAI.github.io/fasthtml/api/fastapp.html#fast_app) -function. - -``` python -app, rt = fast_app(static_path='public') -``` - -FastHTML also allows us to define a route that uses `FileResponse` to -serve the file at a specified path. This is useful for serving images, -videos, and other media files from a different directory without having -to change the paths of many files. So if we move the directory -containing the media files, we only need to change the path in one -place. In the example below, we call images from a directory called -`public`. - -``` python -@rt("/{fname:path}.{ext:static}") -async def get(fname:str, ext:str): - return FileResponse(f'public/{fname}.{ext}') -``` - -## Rendering Markdown - -``` python -from fasthtml.common import * - -hdrs = (MarkdownJS(), HighlightJS(langs=['python', 'javascript', 'html', 'css']), ) - -app, rt = fast_app(hdrs=hdrs) - -content = """ -Here are some _markdown_ elements. - -- This is a list item -- This is another list item -- And this is a third list item - -**Fenced code blocks work here.** -""" - -@rt('/') -def get(req): - return Titled("Markdown rendering example", Div(content,cls="marked")) - -serve() -``` - -## Code highlighting - -Here’s how to highlight code without any markdown configuration. - -``` python -from fasthtml.common import * - -# Add the HighlightJS built-in header -hdrs = (HighlightJS(langs=['python', 'javascript', 'html', 'css']),) - -app, rt = fast_app(hdrs=hdrs) - -code_example = """ -import datetime -import time - -for i in range(10): - print(f"{datetime.datetime.now()}") - time.sleep(1) -""" - -@rt('/') -def get(req): - return Titled("Markdown rendering example", - Div( - # The code example needs to be surrounded by - # Pre & Code elements - Pre(Code(code_example)) - )) - -serve() -``` - -## Defining new `ft` components - -We can build our own `ft` components and combine them with other -components. The simplest method is defining them as a function. - -``` python -from fasthtml.common import * -``` - -``` python -def hero(title, statement): - return Div(H1(title),P(statement), cls="hero") - -# usage example -Main( - hero("Hello World", "This is a hero statement") -) -``` - -``` html -
-
-

Hello World

-

This is a hero statement

-
-
-``` - -### Pass through components - -For when we need to define a new component that allows zero-to-many -components to be nested within them, we lean on Python’s `*args` and -`**kwargs` mechanism. Useful for creating page layout controls. - -``` python -def layout(*args, **kwargs): - """Dashboard layout for all our dashboard views""" - return Main( - H1("Dashboard"), - Div(*args, **kwargs), - cls="dashboard", - ) - -# usage example -layout( - Ul(*[Li(o) for o in range(3)]), - P("Some content", cls="description"), -) -``` - -``` html -
-

Dashboard

-
-
    -
  • 0
  • -
  • 1
  • -
  • 2
  • -
-

Some content

-
-
-``` - -### Dataclasses as ft components - -While functions are easy to read, for more complex components some might -find it easier to use a dataclass. - -``` python -from dataclasses import dataclass - -@dataclass -class Hero: - title: str - statement: str - - def __ft__(self): - """ The __ft__ method renders the dataclass at runtime.""" - return Div(H1(self.title),P(self.statement), cls="hero") - -# usage example -Main( - Hero("Hello World", "This is a hero statement") -) -``` - -``` html -
-
-

Hello World

-

This is a hero statement

-
-
-``` - -## Testing views in notebooks - -Because of the ASGI event loop it is currently impossible to run -FastHTML inside a notebook. However, we can still test the output of our -views. To do this, we leverage Starlette, an ASGI toolkit that FastHTML -uses. - -``` python -# First we instantiate our app, in this case we remove the -# default headers to reduce the size of the output. -app, rt = fast_app(default_hdrs=False) - -# Setting up the Starlette test client -from starlette.testclient import TestClient -client = TestClient(app) - -# Usage example -@rt("/") -def get(): - return Titled("FastHTML is awesome", - P("The fastest way to create web apps in Python")) - -print(client.get("/").text) -``` - - - - - - FastHTML is awesome - - -
-

FastHTML is awesome

-

The fastest way to create web apps in Python

-
- - - -## Forms - -To validate data coming from users, first define a dataclass -representing the data you want to check. Here’s an example representing -a signup form. - -``` python -from dataclasses import dataclass - -@dataclass -class Profile: email:str; phone:str; age:int -``` - -Create an FT component representing an empty version of that form. Don’t -pass in any value to fill the form, that gets handled later. - -``` python -profile_form = Form(method="post", action="/profile")( - Fieldset( - Label('Email', Input(name="email")), - Label("Phone", Input(name="phone")), - Label("Age", Input(name="age")), - ), - Button("Save", type="submit"), - ) -profile_form -``` - -``` html -
-
- - - -
- -
-``` - -Once the dataclass and form function are completed, we can add data to -the form. To do that, instantiate the profile dataclass: - -``` python -profile = Profile(email='john@example.com', phone='123456789', age=5) -profile -``` - - Profile(email='john@example.com', phone='123456789', age=5) - -Then add that data to the `profile_form` using FastHTML’s -[`fill_form`](https://AnswerDotAI.github.io/fasthtml/api/components.html#fill_form) -class: - -``` python -fill_form(profile_form, profile) -``` - -``` html -
-
- - - -
- -
-``` - -### Forms with views - -The usefulness of FastHTML forms becomes more apparent when they are -combined with FastHTML views. We’ll show how this works by using the -test client from above. First, let’s create a SQlite database: - -``` python -db = Database("profiles.db") -profiles = db.create(Profile, pk="email") -``` - -Now we insert a record into the database: - -``` python -profiles.insert(profile) -``` - - Profile(email='john@example.com', phone='123456789', age=5) - -And we can then demonstrate in the code that form is filled and -displayed to the user. - -``` python -@rt("/profile/{email}") -def profile(email:str): - profile = profiles[email] - filled_profile_form = fill_form(profile_form, profile) - return Titled(f'Profile for {profile.email}', filled_profile_form) - -print(client.get(f"/profile/john@example.com").text) -``` - -Line 3 -Fetch the profile using the profile table’s `email` primary key - -Line 4 -Fill the form for display. - - - - - - - Profile for john@example.com - - -
-

Profile for john@example.com

-
-
- - - -
- -
-
- - - -And now let’s demonstrate making a change to the data. - -``` python -@rt("/profile") -def post(profile: Profile): - profiles.update(profile) - return RedirectResponse(url=f"/profile/{profile.email}") - -new_data = dict(email='john@example.com', phone='7654321', age=25) -print(client.post("/profile", data=new_data).text) -``` - -Line 2 -We use the `Profile` dataclass definition to set the type for the -incoming `profile` content. This validates the field types for the -incoming data - -Line 3 -Taking our validated data, we updated the profiles table - -Line 4 -We redirect the user back to their profile view - -Line 7 -The display is of the profile form view showing the changes in data. - - - - - - - Profile for john@example.com - - -
-

Profile for john@example.com

-
-
- - - -
- -
-
- - - -## Strings and conversion order - -The general rules for rendering are: - `__ft__` method will be called -(for default components like `P`, `H2`, etc. or if you define your own -components) - If you pass a string, it will be escaped - On other python -objects, `str()` will be called - -As a consequence, if you want to include plain HTML tags directly into -e.g. a `Div()` they will get escaped by default (as a security measure -to avoid code injections). This can be avoided by using `NotStr()`, a -convenient way to reuse python code that returns already HTML. If you -use pandas, you can use `pandas.DataFrame.to_html()` to get a nice -table. To include the output a FastHTML, wrap it in `NotStr()`, like -`Div(NotStr(df.to_html()))`. - -Above we saw how a dataclass behaves with the `__ft__` method defined. -On a plain dataclass, `str()` will be called (but not escaped). - -``` python -from dataclasses import dataclass - -@dataclass -class Hero: - title: str - statement: str - -# rendering the dataclass with the default method -Main( - Hero("

Hello World

", "This is a hero statement") -) -``` - -``` html -
Hero(title='

Hello World

', statement='This is a hero statement')
-``` - -``` python -# This will display the HTML as text on your page -Div("Let's include some HTML here:
Some HTML
") -``` - -``` html -
Let's include some HTML here: <div>Some HTML</div>
-``` - -``` python -# Keep the string untouched, will be rendered on the page -Div(NotStr("

Some HTML

")) -``` - -``` html -

Some HTML

-``` - -## Custom exception handlers - -FastHTML allows customization of exception handlers, but does so -gracefully. What this means is by default it includes all the `` -tags needed to display attractive content. Try it out! - -``` python -from fasthtml.common import * - -def not_found(req, exc): return Titled("404: I don't exist!") - -exception_handlers = {404: not_found} - -app, rt = fast_app(exception_handlers=exception_handlers) - -@rt('/') -def get(): - return (Titled("Home page", P(A(href="/oops")("Click to generate 404 error")))) - -serve() -``` - -We can also use lambda to make things more terse: - -``` python -from fasthtml.common import * - -exception_handlers={ - 404: lambda req, exc: Titled("404: I don't exist!"), - 418: lambda req, exc: Titled("418: I'm a teapot!") -} - -app, rt = fast_app(exception_handlers=exception_handlers) - -@rt('/') -def get(): - return (Titled("Home page", P(A(href="/oops")("Click to generate 404 error")))) - -serve() -``` - -## Cookies - -We can set cookies using the `cookie()` function. In our example, we’ll -create a `timestamp` cookie. - -``` python -from datetime import datetime -from IPython.display import HTML -``` - -``` python -@rt("/settimestamp") -def get(req): - now = datetime.now() - return P(f'Set to {now}'), cookie('now', datetime.now()) - -HTML(client.get('/settimestamp').text) -``` - - - - - FastHTML page - - -

Set to 2024-09-04 18:30:34.896373

- - - -Now let’s get it back using the same name for our parameter as the -cookie name. - -``` python -@rt('/gettimestamp') -def get(now:date): return f'Cookie was set at time {now.time()}' - -client.get('/gettimestamp').text -``` - - 'Cookie was set at time 18:30:34.896405' - -## Sessions - -For convenience and security, FastHTML has a mechanism for storing small -amounts of data in the user’s browser. We can do this by adding a -`session` argument to routes. FastHTML sessions are Python dictionaries, -and we can leverage to our benefit. The example below shows how to -concisely set and get sessions. - -``` python -@rt('/adder/{num}') -def get(session, num: int): - session.setdefault('sum', 0) - session['sum'] = session.get('sum') + num - return Response(f'The sum is {session["sum"]}.') -``` - -## Toasts (also known as Messages) - -Toasts, sometimes called “Messages” are small notifications usually in -colored boxes used to notify users that something has happened. Toasts -can be of four types: - -- info -- success -- warning -- error - -Examples toasts might include: - -- “Payment accepted” -- “Data submitted” -- “Request approved” - -Toasts require the use of the `setup_toasts()` function plus every view -needs these two features: - -- The session argument -- Must return FT components - -``` python -setup_toasts(app) - -@rt('/toasting') -def get(session): - # Normally one toast is enough, this allows us to see - # different toast types in action. - add_toast(session, f"Toast is being cooked", "info") - add_toast(session, f"Toast is ready", "success") - add_toast(session, f"Toast is getting a bit crispy", "warning") - add_toast(session, f"Toast is burning!", "error") - return Titled("I like toast") -``` - -Line 1 -`setup_toasts` is a helper function that adds toast dependencies. -Usually this would be declared right after `fast_app()` - -Line 4 -Toasts require sessions - -Line 11 -Views with Toasts must return FT components. - -## Authentication and authorization - -In FastHTML the tasks of authentication and authorization are handled -with Beforeware. Beforeware are functions that run before the route -handler is called. They are useful for global tasks like ensuring users -are authenticated or have permissions to access a view. - -First, we write a function that accepts a request and session arguments: - -``` python -# Status code 303 is a redirect that can change POST to GET, -# so it's appropriate for a login page. -login_redir = RedirectResponse('/login', status_code=303) - -def user_auth_before(req, sess): - # The `auth` key in the request scope is automatically provided - # to any handler which requests it, and can not be injected - # by the user using query params, cookies, etc, so it should - # be secure to use. - auth = req.scope['auth'] = sess.get('auth', None) - # If the session key is not there, it redirects to the login page. - if not auth: return login_redir -``` - -Now we pass our `user_auth_before` function as the first argument into a -[`Beforeware`](https://AnswerDotAI.github.io/fasthtml/api/core.html#beforeware) -class. We also pass a list of regular expressions to the `skip` -argument, designed to allow users to still get to the home and login -pages. - -``` python -beforeware = Beforeware( - user_auth_before, - skip=[r'/favicon\.ico', r'/static/.*', r'.*\.css', r'.*\.js', '/login', '/'] -) - -app, rt = fast_app(before=beforeware) -``` - -## Server-sent events (SSE) - -With [server-sent -events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events), -it’s possible for a server to send new data to a web page at any time, -by pushing messages to the web page. Unlike WebSockets, SSE can only go -in one direction: server to client. SSE is also part of the HTTP -specification unlike WebSockets which uses its own specification. - -FastHTML introduces several tools for working with SSE which are covered -in the example below. While concise, there’s a lot going on in this -function so we’ve annotated it quite a bit. - -``` python -import random -from asyncio import sleep -from fasthtml.common import * - -hdrs=(Script(src="https://unpkg.com/htmx-ext-sse@2.2.1/sse.js"),) -app,rt = fast_app(hdrs=hdrs) - -@rt -def index(): - return Titled("SSE Random Number Generator", - P("Generate pairs of random numbers, as the list grows scroll downwards."), - Div(hx_ext="sse", - sse_connect="/number-stream", - hx_swap="beforeend show:bottom", - sse_swap="message")) - -shutdown_event = signal_shutdown() - -async def number_generator(): - while not shutdown_event.is_set(): - data = Article(random.randint(1, 100)) - yield sse_message(data) - await sleep(1) - -@rt("/number-stream") -async def get(): return EventStream(number_generator()) -``` - -Line 5 -Import the HTMX SSE extension - -Line 12 -Tell HTMX to load the SSE extension - -Line 13 -Look at the `/number-stream` endpoint for SSE content - -Line 14 -When new items come in from the SSE endpoint, add them at the end of the -current content within the div. If they go beyond the screen, scroll -downwards - -Line 15 -Specify the name of the event. FastHTML’s default event name is -“message”. Only change if you have more than one call to SSE endpoints -within a view - -Line 17 -Set up the asyncio event loop - -Line 19 -Don’t forget to make this an `async` function! - -Line 20 -Iterate through the asyncio event loop - -Line 22 -We yield the data. Data ideally should be comprised of FT components as -that plugs nicely into HTMX in the browser - -Line 26 -The endpoint view needs to be an async function that returns a -[`EventStream`](https://AnswerDotAI.github.io/fasthtml/api/core.html#eventstream) - -
- -> **New content as of September 1, 2024** -> -> - [Forms](../tutorials/quickstart_for_web_devs.html#forms) -> - [Server-side Events -> (SSE)](../tutorials/quickstart_for_web_devs.html#server-sent-events-sse) -> -> We’re going to be adding more to this document, so check back -> frequently for updates. - -
- -## Unwritten quickstart sections - -- Websockets -- Tables
- +++ -title = "Reference" -+++ - -## Contents - -* [htmx Core Attributes](#attributes) -* [htmx Additional Attributes](#attributes-additional) -* [htmx CSS Classes](#classes) -* [htmx Request Headers](#request_headers) -* [htmx Response Headers](#response_headers) -* [htmx Events](#events) -* [htmx Extensions](https://extensions.htmx.org) -* [JavaScript API](#api) -* [Configuration Options](#config) - -## Core Attribute Reference {#attributes} - -The most common attributes when using htmx. - -
- -| Attribute | Description | -|--------------------------------------------------|--------------------------------------------------------------------------------------------------------------------| -| [`hx-get`](@/attributes/hx-get.md) | issues a `GET` to the specified URL | -| [`hx-post`](@/attributes/hx-post.md) | issues a `POST` to the specified URL | -| [`hx-on*`](@/attributes/hx-on.md) | handle events with inline scripts on elements | -| [`hx-push-url`](@/attributes/hx-push-url.md) | push a URL into the browser location bar to create history | -| [`hx-select`](@/attributes/hx-select.md) | select content to swap in from a response | -| [`hx-select-oob`](@/attributes/hx-select-oob.md) | select content to swap in from a response, somewhere other than the target (out of band) | -| [`hx-swap`](@/attributes/hx-swap.md) | controls how content will swap in (`outerHTML`, `beforeend`, `afterend`, ...) | -| [`hx-swap-oob`](@/attributes/hx-swap-oob.md) | mark element to swap in from a response (out of band) | -| [`hx-target`](@/attributes/hx-target.md) | specifies the target element to be swapped | -| [`hx-trigger`](@/attributes/hx-trigger.md) | specifies the event that triggers the request | -| [`hx-vals`](@/attributes/hx-vals.md) | add values to submit with the request (JSON format) | - -
- -## Additional Attribute Reference {#attributes-additional} - -All other attributes available in htmx. - -
- -| Attribute | Description | -|------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------| -| [`hx-boost`](@/attributes/hx-boost.md) | add [progressive enhancement](https://en.wikipedia.org/wiki/Progressive_enhancement) for links and forms | -| [`hx-confirm`](@/attributes/hx-confirm.md) | shows a `confirm()` dialog before issuing a request | -| [`hx-delete`](@/attributes/hx-delete.md) | issues a `DELETE` to the specified URL | -| [`hx-disable`](@/attributes/hx-disable.md) | disables htmx processing for the given node and any children nodes | -| [`hx-disabled-elt`](@/attributes/hx-disabled-elt.md) | adds the `disabled` attribute to the specified elements while a request is in flight | -| [`hx-disinherit`](@/attributes/hx-disinherit.md) | control and disable automatic attribute inheritance for child nodes | -| [`hx-encoding`](@/attributes/hx-encoding.md) | changes the request encoding type | -| [`hx-ext`](@/attributes/hx-ext.md) | extensions to use for this element | -| [`hx-headers`](@/attributes/hx-headers.md) | adds to the headers that will be submitted with the request | -| [`hx-history`](@/attributes/hx-history.md) | prevent sensitive data being saved to the history cache | -| [`hx-history-elt`](@/attributes/hx-history-elt.md) | the element to snapshot and restore during history navigation | -| [`hx-include`](@/attributes/hx-include.md) | include additional data in requests | -| [`hx-indicator`](@/attributes/hx-indicator.md) | the element to put the `htmx-request` class on during the request | -| [`hx-inherit`](@/attributes/hx-inherit.md) | control and enable automatic attribute inheritance for child nodes if it has been disabled by default | -| [`hx-params`](@/attributes/hx-params.md) | filters the parameters that will be submitted with a request | -| [`hx-patch`](@/attributes/hx-patch.md) | issues a `PATCH` to the specified URL | -| [`hx-preserve`](@/attributes/hx-preserve.md) | specifies elements to keep unchanged between requests | -| [`hx-prompt`](@/attributes/hx-prompt.md) | shows a `prompt()` before submitting a request | -| [`hx-put`](@/attributes/hx-put.md) | issues a `PUT` to the specified URL | -| [`hx-replace-url`](@/attributes/hx-replace-url.md) | replace the URL in the browser location bar | -| [`hx-request`](@/attributes/hx-request.md) | configures various aspects of the request | -| [`hx-sync`](@/attributes/hx-sync.md) | control how requests made by different elements are synchronized | -| [`hx-validate`](@/attributes/hx-validate.md) | force elements to validate themselves before a request | -| [`hx-vars`](@/attributes/hx-vars.md) | adds values dynamically to the parameters to submit with the request (deprecated, please use [`hx-vals`](@/attributes/hx-vals.md)) | - -
- -## CSS Class Reference {#classes} - -
- -| Class | Description | -|-----------|-------------| -| `htmx-added` | Applied to a new piece of content before it is swapped, removed after it is settled. -| `htmx-indicator` | A dynamically generated class that will toggle visible (opacity:1) when a `htmx-request` class is present -| `htmx-request` | Applied to either the element or the element specified with [`hx-indicator`](@/attributes/hx-indicator.md) while a request is ongoing -| `htmx-settling` | Applied to a target after content is swapped, removed after it is settled. The duration can be modified via [`hx-swap`](@/attributes/hx-swap.md). -| `htmx-swapping` | Applied to a target before any content is swapped, removed after it is swapped. The duration can be modified via [`hx-swap`](@/attributes/hx-swap.md). - -
- -## HTTP Header Reference {#headers} - -### Request Headers Reference {#request_headers} - -
- -| Header | Description | -|--------|-------------| -| `HX-Boosted` | indicates that the request is via an element using [hx-boost](@/attributes/hx-boost.md) -| `HX-Current-URL` | the current URL of the browser -| `HX-History-Restore-Request` | "true" if the request is for history restoration after a miss in the local history cache -| `HX-Prompt` | the user response to an [hx-prompt](@/attributes/hx-prompt.md) -| `HX-Request` | always "true" -| `HX-Target` | the `id` of the target element if it exists -| `HX-Trigger-Name` | the `name` of the triggered element if it exists -| `HX-Trigger` | the `id` of the triggered element if it exists - -
- -### Response Headers Reference {#response_headers} - -
- -| Header | Description | -|------------------------------------------------------|-------------| -| [`HX-Location`](@/headers/hx-location.md) | allows you to do a client-side redirect that does not do a full page reload -| [`HX-Push-Url`](@/headers/hx-push-url.md) | pushes a new url into the history stack -| `HX-Redirect` | can be used to do a client-side redirect to a new location -| `HX-Refresh` | if set to "true" the client-side will do a full refresh of the page -| [`HX-Replace-Url`](@/headers/hx-replace-url.md) | replaces the current URL in the location bar -| `HX-Reswap` | allows you to specify how the response will be swapped. See [hx-swap](@/attributes/hx-swap.md) for possible values -| `HX-Retarget` | a CSS selector that updates the target of the content update to a different element on the page -| `HX-Reselect` | a CSS selector that allows you to choose which part of the response is used to be swapped in. Overrides an existing [`hx-select`](@/attributes/hx-select.md) on the triggering element -| [`HX-Trigger`](@/headers/hx-trigger.md) | allows you to trigger client-side events -| [`HX-Trigger-After-Settle`](@/headers/hx-trigger.md) | allows you to trigger client-side events after the settle step -| [`HX-Trigger-After-Swap`](@/headers/hx-trigger.md) | allows you to trigger client-side events after the swap step - -
- -## Event Reference {#events} - -
- -| Event | Description | -|-------|-------------| -| [`htmx:abort`](@/events.md#htmx:abort) | send this event to an element to abort a request -| [`htmx:afterOnLoad`](@/events.md#htmx:afterOnLoad) | triggered after an AJAX request has completed processing a successful response -| [`htmx:afterProcessNode`](@/events.md#htmx:afterProcessNode) | triggered after htmx has initialized a node -| [`htmx:afterRequest`](@/events.md#htmx:afterRequest) | triggered after an AJAX request has completed -| [`htmx:afterSettle`](@/events.md#htmx:afterSettle) | triggered after the DOM has settled -| [`htmx:afterSwap`](@/events.md#htmx:afterSwap) | triggered after new content has been swapped in -| [`htmx:beforeCleanupElement`](@/events.md#htmx:beforeCleanupElement) | triggered before htmx [disables](@/attributes/hx-disable.md) an element or removes it from the DOM -| [`htmx:beforeOnLoad`](@/events.md#htmx:beforeOnLoad) | triggered before any response processing occurs -| [`htmx:beforeProcessNode`](@/events.md#htmx:beforeProcessNode) | triggered before htmx initializes a node -| [`htmx:beforeRequest`](@/events.md#htmx:beforeRequest) | triggered before an AJAX request is made -| [`htmx:beforeSwap`](@/events.md#htmx:beforeSwap) | triggered before a swap is done, allows you to configure the swap -| [`htmx:beforeSend`](@/events.md#htmx:beforeSend) | triggered just before an ajax request is sent -| [`htmx:configRequest`](@/events.md#htmx:configRequest) | triggered before the request, allows you to customize parameters, headers -| [`htmx:confirm`](@/events.md#htmx:confirm) | triggered after a trigger occurs on an element, allows you to cancel (or delay) issuing the AJAX request -| [`htmx:historyCacheError`](@/events.md#htmx:historyCacheError) | triggered on an error during cache writing -| [`htmx:historyCacheMiss`](@/events.md#htmx:historyCacheMiss) | triggered on a cache miss in the history subsystem -| [`htmx:historyCacheMissError`](@/events.md#htmx:historyCacheMissError) | triggered on a unsuccessful remote retrieval -| [`htmx:historyCacheMissLoad`](@/events.md#htmx:historyCacheMissLoad) | triggered on a successful remote retrieval -| [`htmx:historyRestore`](@/events.md#htmx:historyRestore) | triggered when htmx handles a history restoration action -| [`htmx:beforeHistorySave`](@/events.md#htmx:beforeHistorySave) | triggered before content is saved to the history cache -| [`htmx:load`](@/events.md#htmx:load) | triggered when new content is added to the DOM -| [`htmx:noSSESourceError`](@/events.md#htmx:noSSESourceError) | triggered when an element refers to a SSE event in its trigger, but no parent SSE source has been defined -| [`htmx:onLoadError`](@/events.md#htmx:onLoadError) | triggered when an exception occurs during the onLoad handling in htmx -| [`htmx:oobAfterSwap`](@/events.md#htmx:oobAfterSwap) | triggered after an out of band element as been swapped in -| [`htmx:oobBeforeSwap`](@/events.md#htmx:oobBeforeSwap) | triggered before an out of band element swap is done, allows you to configure the swap -| [`htmx:oobErrorNoTarget`](@/events.md#htmx:oobErrorNoTarget) | triggered when an out of band element does not have a matching ID in the current DOM -| [`htmx:prompt`](@/events.md#htmx:prompt) | triggered after a prompt is shown -| [`htmx:pushedIntoHistory`](@/events.md#htmx:pushedIntoHistory) | triggered after an url is pushed into history -| [`htmx:responseError`](@/events.md#htmx:responseError) | triggered when an HTTP response error (non-`200` or `300` response code) occurs -| [`htmx:sendError`](@/events.md#htmx:sendError) | triggered when a network error prevents an HTTP request from happening -| [`htmx:sseError`](@/events.md#htmx:sseError) | triggered when an error occurs with a SSE source -| [`htmx:sseOpen`](/events#htmx:sseOpen) | triggered when a SSE source is opened -| [`htmx:swapError`](@/events.md#htmx:swapError) | triggered when an error occurs during the swap phase -| [`htmx:targetError`](@/events.md#htmx:targetError) | triggered when an invalid target is specified -| [`htmx:timeout`](@/events.md#htmx:timeout) | triggered when a request timeout occurs -| [`htmx:validation:validate`](@/events.md#htmx:validation:validate) | triggered before an element is validated -| [`htmx:validation:failed`](@/events.md#htmx:validation:failed) | triggered when an element fails validation -| [`htmx:validation:halted`](@/events.md#htmx:validation:halted) | triggered when a request is halted due to validation errors -| [`htmx:xhr:abort`](@/events.md#htmx:xhr:abort) | triggered when an ajax request aborts -| [`htmx:xhr:loadend`](@/events.md#htmx:xhr:loadend) | triggered when an ajax request ends -| [`htmx:xhr:loadstart`](@/events.md#htmx:xhr:loadstart) | triggered when an ajax request starts -| [`htmx:xhr:progress`](@/events.md#htmx:xhr:progress) | triggered periodically during an ajax request that supports progress events - -
- -## JavaScript API Reference {#api} - -
- -| Method | Description | -|-------|-------------| -| [`htmx.addClass()`](@/api.md#addClass) | Adds a class to the given element -| [`htmx.ajax()`](@/api.md#ajax) | Issues an htmx-style ajax request -| [`htmx.closest()`](@/api.md#closest) | Finds the closest parent to the given element matching the selector -| [`htmx.config`](@/api.md#config) | A property that holds the current htmx config object -| [`htmx.createEventSource`](@/api.md#createEventSource) | A property holding the function to create SSE EventSource objects for htmx -| [`htmx.createWebSocket`](@/api.md#createWebSocket) | A property holding the function to create WebSocket objects for htmx -| [`htmx.defineExtension()`](@/api.md#defineExtension) | Defines an htmx [extension](https://extensions.htmx.org) -| [`htmx.find()`](@/api.md#find) | Finds a single element matching the selector -| [`htmx.findAll()` `htmx.findAll(elt, selector)`](@/api.md#find) | Finds all elements matching a given selector -| [`htmx.logAll()`](@/api.md#logAll) | Installs a logger that will log all htmx events -| [`htmx.logger`](@/api.md#logger) | A property set to the current logger (default is `null`) -| [`htmx.off()`](@/api.md#off) | Removes an event listener from the given element -| [`htmx.on()`](@/api.md#on) | Creates an event listener on the given element, returning it -| [`htmx.onLoad()`](@/api.md#onLoad) | Adds a callback handler for the `htmx:load` event -| [`htmx.parseInterval()`](@/api.md#parseInterval) | Parses an interval declaration into a millisecond value -| [`htmx.process()`](@/api.md#process) | Processes the given element and its children, hooking up any htmx behavior -| [`htmx.remove()`](@/api.md#remove) | Removes the given element -| [`htmx.removeClass()`](@/api.md#removeClass) | Removes a class from the given element -| [`htmx.removeExtension()`](@/api.md#removeExtension) | Removes an htmx [extension](https://extensions.htmx.org) -| [`htmx.swap()`](@/api.md#swap) | Performs swapping (and settling) of HTML content -| [`htmx.takeClass()`](@/api.md#takeClass) | Takes a class from other elements for the given element -| [`htmx.toggleClass()`](@/api.md#toggleClass) | Toggles a class from the given element -| [`htmx.trigger()`](@/api.md#trigger) | Triggers an event on an element -| [`htmx.values()`](@/api.md#values) | Returns the input values associated with the given element - -
- - -## Configuration Reference {#config} - -Htmx has some configuration options that can be accessed either programmatically or declaratively. They are -listed below: - -
- -| Config Variable | Info | -|---------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `htmx.config.historyEnabled` | defaults to `true`, really only useful for testing | -| `htmx.config.historyCacheSize` | defaults to 10 | -| `htmx.config.refreshOnHistoryMiss` | defaults to `false`, if set to `true` htmx will issue a full page refresh on history misses rather than use an AJAX request | -| `htmx.config.defaultSwapStyle` | defaults to `innerHTML` | -| `htmx.config.defaultSwapDelay` | defaults to 0 | -| `htmx.config.defaultSettleDelay` | defaults to 20 | -| `htmx.config.includeIndicatorStyles` | defaults to `true` (determines if the indicator styles are loaded) | -| `htmx.config.indicatorClass` | defaults to `htmx-indicator` | -| `htmx.config.requestClass` | defaults to `htmx-request` | -| `htmx.config.addedClass` | defaults to `htmx-added` | -| `htmx.config.settlingClass` | defaults to `htmx-settling` | -| `htmx.config.swappingClass` | defaults to `htmx-swapping` | -| `htmx.config.allowEval` | defaults to `true`, can be used to disable htmx's use of eval for certain features (e.g. trigger filters) | -| `htmx.config.allowScriptTags` | defaults to `true`, determines if htmx will process script tags found in new content | -| `htmx.config.inlineScriptNonce` | defaults to `''`, meaning that no nonce will be added to inline scripts | -| `htmx.config.inlineStyleNonce` | defaults to `''`, meaning that no nonce will be added to inline styles | -| `htmx.config.attributesToSettle` | defaults to `["class", "style", "width", "height"]`, the attributes to settle during the settling phase | -| `htmx.config.wsReconnectDelay` | defaults to `full-jitter` | -| `htmx.config.wsBinaryType` | defaults to `blob`, the [the type of binary data](https://developer.mozilla.org/docs/Web/API/WebSocket/binaryType) being received over the WebSocket connection | -| `htmx.config.disableSelector` | defaults to `[hx-disable], [data-hx-disable]`, htmx will not process elements with this attribute on it or a parent | -| `htmx.config.withCredentials` | defaults to `false`, allow cross-site Access-Control requests using credentials such as cookies, authorization headers or TLS client certificates | -| `htmx.config.timeout` | defaults to 0, the number of milliseconds a request can take before automatically being terminated | -| `htmx.config.scrollBehavior` | defaults to 'instant', the behavior for a boosted link on page transitions. The allowed values are `auto`, `instant` and `smooth`. Instant will scroll instantly in a single jump, smooth will scroll smoothly, while auto will behave like a vanilla link. | -| `htmx.config.defaultFocusScroll` | if the focused element should be scrolled into view, defaults to false and can be overridden using the [focus-scroll](@/attributes/hx-swap.md#focus-scroll) swap modifier. | -| `htmx.config.getCacheBusterParam` | defaults to false, if set to true htmx will append the target element to the `GET` request in the format `org.htmx.cache-buster=targetElementId` | -| `htmx.config.globalViewTransitions` | if set to `true`, htmx will use the [View Transition](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API) API when swapping in new content. | -| `htmx.config.methodsThatUseUrlParams` | defaults to `["get"]`, htmx will format requests with these methods by encoding their parameters in the URL, not the request body | -| `htmx.config.selfRequestsOnly` | defaults to `true`, whether to only allow AJAX requests to the same domain as the current document | -| `htmx.config.ignoreTitle` | defaults to `false`, if set to `true` htmx will not update the title of the document when a `title` tag is found in new content | -| `htmx.config.scrollIntoViewOnBoost` | defaults to `true`, whether or not the target of a boosted element is scrolled into the viewport. If `hx-target` is omitted on a boosted element, the target defaults to `body`, causing the page to scroll to the top. | -| `htmx.config.triggerSpecsCache` | defaults to `null`, the cache to store evaluated trigger specifications into, improving parsing performance at the cost of more memory usage. You may define a simple object to use a never-clearing cache, or implement your own system using a [proxy object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Proxy) | -| `htmx.config.allowNestedOobSwaps` | defaults to `true`, whether to process OOB swaps on elements that are nested within the main response element. See [Nested OOB Swaps](@/attributes/hx-swap-oob.md#nested-oob-swaps). | - -
- -You can set them directly in javascript, or you can use a `meta` tag: - -```html - -```
- # 🗿 Surreal -### Tiny jQuery alternative for plain Javascript with inline [Locality of Behavior](https://htmx.org/essays/locality-of-behaviour/)! - -![cover](https://user-images.githubusercontent.com/24665/171092805-b41286b2-be4a-4aab-9ee6-d604699cc507.png) -(Art by [shahabalizadeh](https://www.deviantart.com/shahabalizadeh)) - - -## Why does this exist? - -For devs who love ergonomics! You may appreciate Surreal if: - -* You want to stay as close as possible to Vanilla JS. -* Hate typing `document.querySelector` over.. and over.. -* Hate typing `addEventListener` over.. and over.. -* Really wish `document.querySelectorAll` had Array functions.. -* Really wish `this` would work in any inline ` - -``` - -See the [Live Example](https://gnat.github.io/surreal/example.html)! Then [view source](https://github.com/gnat/surreal/blob/main/example.html). - -## 🎁 Install - -Surreal is only 320 lines. No build step. No dependencies. - -[📥 Download](https://raw.githubusercontent.com/gnat/surreal/main/surreal.js) into your project, and add `` in your `` - -Or, 🌐 via CDN: `` - -## ⚡ Usage - -### 🔍️ DOM Selection - -* Select **one** element: `me(...)` - * Can be any of: - * CSS selector: `".button"`, `"#header"`, `"h1"`, `"body > .block"` - * Variables: `body`, `e`, `some_element` - * Events: `event.currentTarget` will be used. - * Surreal selectors: `me()`,`any()` - * Choose the start location in the DOM with the 2nd arg. (Default: `document`) - * 🔥 `any('button', me('#header')).classAdd('red')` - * Add `.red` to any ` - -``` -See the [Live Example](https://gnat.github.io/css-scope-inline/example.html)! Then [view source](https://github.com/gnat/css-scope-inline/blob/main/example.html). - -## 🌘 How does it work? - -This uses `MutationObserver` to monitor the DOM, and the moment a ` - red -
green
-
green
-
green
-
yellow
-
blue
-
green
-
green
- - -
- red -
green
-
green
-
green
-
yellow
-
blue
-
green
-
green
-
-``` - -### CSS variables and child elements -At first glance, **Tailwind Example 2** looks very promising! Exciting ...but: -* 🔴 **Every child style requires an explicit selector.** - * Tailwinds' shorthand advantages sadly disappear. - * Any more child styles added in Tailwind will become longer than vanilla CSS. - * This limited example is the best case scenario for Tailwind. -* 🔴 Not visible on github: **no highlighting for properties and units** begins to be painful. -```html - - - - - - - - - -
- -
Home
-
Team
-
Profile
-
Settings
-
Log Out
-
- - - - - - - - -``` -## 🔎 Technical FAQ -* Why do you use `querySelectorAll()` and not just process the `MutationObserver` results directly? - * This was indeed the original design; it will work well up until you begin recieving subtrees (ex: DOM swaps with [htmx](https://htmx.org), ajax, jquery, etc.) which requires walking all subtree elements to ensure we do not miss a `