diff --git "a/llms-ctx-fastHtml.txt" "b/llms-ctx-fastHtml.txt" new file mode 100644--- /dev/null +++ "b/llms-ctx-fastHtml.txt" @@ -0,0 +1,3274 @@ +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()`](https://docs.fastht.ml/api/core.html#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://docs.fastht.ml/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()`](https://docs.fastht.ml/api/xtend.html#style) function will +put the result into the HTML. + +## Other Static Media File Locations + +As you saw, [`Script`](https://docs.fastht.ml/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` 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://docs.fastht.ml/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()`](https://docs.fastht.ml/api/core.html#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-26 15:33:48.141869

+ + + +Now let’s get it back using the same name for our parameter as the +cookie name. + +``` python +@rt('/gettimestamp') +def get(now:parsed_date): return f'Cookie was set at time {now.time()}' + +client.get('/gettimestamp').text +``` + + 'Cookie was set at time 15:33:48.141903' + +## 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 or FtResponse components. + +💡 `setup_toasts` takes a `duration` input that allows you to specify +how long a toast will be visible before disappearing. For example +`setup_toasts(duration=5)` sets the toasts duration to 5 seconds. By +default toasts disappear after 10 seconds. + +## 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://docs.fastht.ml/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://docs.fastht.ml/api/core.html#eventstream) + +## Websockets + +With websockets we can have bi-directional communications between a +browser and client. Websockets are useful for things like chat and +certain types of games. While websockets can be used for single +direction messages from the server (i.e. telling users that a process is +finished), that task is arguably better suited for SSE. + +FastHTML provides useful tools for adding websockets to your pages. + +``` python +from fasthtml.common import * +from asyncio import sleep + +app, rt = fast_app(exts='ws') + +def mk_inp(): return Input(id='msg', autofocus=True) + +@rt('/') +async def get(request): + cts = Div( + Div(id='notifications'), + Form(mk_inp(), id='form', ws_send=True), + hx_ext='ws', ws_connect='/ws') + return Titled('Websocket Test', cts) + +async def on_connect(send): + print('Connected!') + await send(Div('Hello, you have connected', id="notifications")) + +async def on_disconnect(ws): + print('Disconnected!') + +@app.ws('/ws', conn=on_connect, disconn=on_disconnect) +async def ws(msg:str, send): + await send(Div('Hello ' + msg, id="notifications")) + await sleep(2) + return Div('Goodbye ' + msg, id="notifications"), mk_inp() +``` + +Line 4 +To use websockets in FastHTML, you must instantiate the app with `exts` +set to ‘ws’ + +Line 6 +As we want to use websockets to reset the form, we define the `mk_input` +function that can be called from multiple locations + +Line 12 +We create the form and mark it with the `ws_send` attribute, which is +documented here in the [HTMX websocket +specification](https://v1.htmx.org/extensions/web-sockets/). This tells +HTMX to send a message to the nearest websocket based on the trigger for +the form element, which for forms is pressing the `enter` key, an action +considered to be a form submission + +Line 13 +This is where the HTMX extension is loaded (`hx_ext='ws'`) and the +nearest websocket is defined (`ws_connect='/ws'`) + +Line 16 +When a websocket first connects we can optionally have it call a +function that accepts a `send` argument. The `send` argument will push a +message to the browser. + +Line 18 +Here we use the `send` function that was passed into the `on_connect` +function to send a `Div` with an `id` of `notifications` that HTMX +assigns to the element in the page that already has an `id` of +`notifications` + +Line 20 +When a websocket disconnects we can call a function which takes no +arguments. Typically the role of this function is to notify the server +to take an action. In this case, we print a simple message to the +console + +Line 23 +We use the `app.ws` decorator to mark that `/ws` is the route for our +websocket. We also pass in the two optional `conn` and `disconn` +parameters to this decorator. As a fun experiment, remove the `conn` and +`disconn` arguments and see what happens + +Line 24 +Define the `ws` function as async. This is necessary for ASGI to be able +to serve websockets. The function accepts two arguments, a `msg` that is +user input from the browser, and a `send` function for pushing data back +to the browser + +Line 25 +The `send` function is used here to send HTML back to the page. As the +HTML has an `id` of `notifications`, HTMX will overwrite what is already +on the page with the same ID + +Line 27 +The websocket function can also be used to return a value. In this case, +it is a tuple of two HTML elements. HTMX will take the elements and +replace them where appropriate. As both have `id` specified +(`notifications` and `msg` respectively), they will replace their +predecessor on the page. + +## File Uploads + +A common task in web development is uploading files. The examples below +are for uploading files to the hosting server, with information about +the uploaded file presented to the user. + +
+ +> **File uploads in production can be dangerous** +> +> File uploads can be the target of abuse, accidental or intentional. +> That means users may attempt to upload files that are too large or +> present a security risk. This is especially of concern for public +> facing apps. File upload security is outside the scope of this +> tutorial, for now we suggest reading the [OWASP File Upload Cheat +> Sheet](https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html). + +
+ +### Single File Uploads + +``` python +from fasthtml.common import * +from pathlib import Path + +app, rt = fast_app() + +upload_dir = Path("filez") +upload_dir.mkdir(exist_ok=True) + +@rt('/') +def get(): + return Titled("File Upload Demo", + Article( + Form(hx_post=upload, hx_target="#result-one")( + Input(type="file", name="file"), + Button("Upload", type="submit", cls='secondary'), + ), + Div(id="result-one") + ) + ) + +def FileMetaDataCard(file): + return Article( + Header(H3(file.filename)), + Ul( + Li('Size: ', file.size), + Li('Content Type: ', file.content_type), + Li('Headers: ', file.headers), + ) + ) + +@rt +async def upload(file: UploadFile): + card = FileMetaDataCard(file) + filebuffer = await file.read() + (upload_dir / file.filename).write_bytes(filebuffer) + return card + +serve() +``` + +Line 13 +Every form rendered with the +[`Form`](https://docs.fastht.ml/api/xtend.html#form) FT component +defaults to `enctype="multipart/form-data"` + +Line 14 +Don’t forget to set the `Input` FT Component’s type to `file` + +Line 32 +The upload view should receive a [Starlette +UploadFile](https://www.starlette.io/requests/#request-files) type. You +can add other form variables + +Line 33 +We can access the metadata of the card (filename, size, content_type, +headers), a quick and safe process. We set that to the card variable + +Line 34 +In order to access the contents contained within a file we use the +`await` method to read() it. As files may be quite large or contain bad +data, this is a seperate step from accessing metadata + +Line 35 +This step shows how to use Python’s built-in `pathlib.Path` library to +write the file to disk. + +### Multiple File Uploads + +``` python +from fasthtml.common import * +from pathlib import Path + +app, rt = fast_app() + +upload_dir = Path("filez") +upload_dir.mkdir(exist_ok=True) + +@rt('/') +def get(): + return Titled("Multiple File Upload Demo", + Article( + Form(hx_post=upload_many, hx_target="#result-many")( + Input(type="file", name="files", multiple=True), + Button("Upload", type="submit", cls='secondary'), + ), + Div(id="result-many") + ) + ) + +def FileMetaDataCard(file): + return Article( + Header(H3(file.filename)), + Ul( + Li('Size: ', file.size), + Li('Content Type: ', file.content_type), + Li('Headers: ', file.headers), + ) + ) + +@rt +async def upload_many(files: list[UploadFile]): + cards = [] + for file in files: + cards.append(FileMetaDataCard(file)) + filebuffer = await file.read() + (upload_dir / file.filename).write_bytes(filebuffer) + return cards + +serve() +``` + +Line 13 +Every form rendered with the +[`Form`](https://docs.fastht.ml/api/xtend.html#form) FT component +defaults to `enctype="multipart/form-data"` + +Line 14 +Don’t forget to set the `Input` FT Component’s type to `file` and assign +the multiple attribute to `True` + +Line 32 +The upload view should receive a `list` containing the [Starlette +UploadFile](https://www.starlette.io/requests/#request-files) type. You +can add other form variables + +Line 34 +Iterate through the files + +Line 35 +We can access the metadata of the card (filename, size, content_type, +headers), a quick and safe process. We add that to the cards variable + +Line 36 +In order to access the contents contained within a file we use the +`await` method to read() it. As files may be quite large or contain bad +data, this is a seperate step from accessing metadata + +Line 37 +This step shows how to use Python’s built-in `pathlib.Path` library to +write the file to disk.
+++ +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](/extensions) +* [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`](@/headers/hx-redirect.md) | 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:beforeTransition`](@/events.md#htmx:beforeTransition) | triggered before the [View Transition](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API) wrapped swap occurs +| [`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 a url is pushed into history +| [`htmx:replacedInHistory`](@/events.md#htmx:replacedInHistory) | triggered after a url is replaced in history +| [`htmx:responseError`](@/events.md#htmx:responseError) | triggered when an HTTP response error (non-`200` or `300` response code) occurs +| [`htmx:sendAbort`](@/events.md#htmx:sendAbort) | triggered when a request is aborted +| [`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://htmx.org/extensions) +| [`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://htmx.org/extensions) +| [`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.disableInheritance` | defaults to `false`. If it is set to `true`, the inheritance of attributes is completely disabled and you can explicitly specify the inheritance with the [hx-inherit](@/attributes/hx-inherit.md) attribute. +| `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 scroll behavior when using the [show](@/attributes/hx-swap.md#scrolling-scroll-show) modifier with `hx-swap`. The allowed values are `instant` (scrolling should happen instantly in a single jump), `smooth` (scrolling should animate smoothly) and `auto` (scroll behavior is determined by the computed value of [scroll-behavior](https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-behavior)). | +| `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", "delete"]`, 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.responseHandling` | the default [Response Handling](@/docs.md#response-handling) behavior for response status codes can be configured here to either swap or error | +| `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 `