Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 38 additions & 26 deletions src/ext/response-targets.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,55 @@
/** @type {import("../htmx").HtmxInternalApi} */
var api;

const targetAttrPrefix = 'hx-target-';
const targetAttrMinLen = targetAttrPrefix.length - 1;
var attrPrefix = 'hx-target-';

/**
* @param {HTMLElement} elt
* @param {number} respCode
* @returns {HTMLElement | null}
*/
function getRespCodeTarget(elt, respCode) {
if (!elt || !respCode) return null;
function getRespCodeTarget(elt, respCodeNumber) {
if (!elt || !respCodeNumber) return null;

var targetAttr = targetAttrPrefix + respCode;
var targetStr = api.getClosestAttributeValue(elt, targetAttr);
var respCode = respCodeNumber.toString();

if (targetStr) {
if (targetStr === "this") {
return api.findThisElement(elt, targetAttr);
} else {
return api.querySelectorExt(elt, targetStr);
}
} else {
for (let l = targetAttr.length - 1; l > targetAttrMinLen; l--) {
targetAttr = targetAttr.substring(0, l) + '*';
targetStr = api.getClosestAttributeValue(elt, targetAttr);
if (targetStr) break;
}
}
// '*' is the original syntax, as the obvious character for a wildcard.
// The 'x' alternative was added for maximum compatibility with HTML
// templating engines, due to ambiguity around which characters are
// supported in HTML attributes.
//
// Start with the most specific possible attribute and generalize from
// there.
var attrPossibilities = [
respCode,

respCode.substr(0, 2) + '*',
respCode.substr(0, 2) + 'x',

if (targetStr) {
if (targetStr === "this") {
return api.findThisElement(elt, targetAttr);
} else {
return api.querySelectorExt(elt, targetStr);
respCode.substr(0, 1) + '*',
respCode.substr(0, 1) + 'x',
respCode.substr(0, 1) + '**',
respCode.substr(0, 1) + 'xx',

'*',
'x',
'***',
'xxx',
];

for (var i = 0; i < attrPossibilities.length; i++) {
var attr = attrPrefix + attrPossibilities[i];
var attrValue = api.getClosestAttributeValue(elt, attr);
if (attrValue) {
if (attrValue === "this") {
return api.findThisElement(elt, attr);
} else {
return api.querySelectorExt(elt, attrValue);
}
}
} else {
return null;
}

return null;
}

/** @param {Event} evt */
Expand Down
2 changes: 1 addition & 1 deletion src/htmx.js
Original file line number Diff line number Diff line change
Expand Up @@ -3584,7 +3584,7 @@ return (function () {
//====================================================================

function ready(fn) {
if (getDocument().readyState !== 'loading') {
if (getDocument().readyState === 'complete') {
fn();
} else {
getDocument().addEventListener('DOMContentLoaded', fn);
Expand Down
34 changes: 34 additions & 0 deletions test/ext/response-targets.js
Original file line number Diff line number Diff line change
Expand Up @@ -263,4 +263,38 @@ describe("response-targets extension", function() {
htmx.config.responseTargetPrefersExisting = false;
}
});

describe('status code formatting', function()
{
var attributes = [
"hx-target-404",

"hx-target-40*",
"hx-target-40x",

"hx-target-4*",
"hx-target-4x",
"hx-target-4**",
"hx-target-4xx",

"hx-target-*",
"hx-target-x",
"hx-target-***",
"hx-target-xxx",
];

// String replacement because IE11 doesn't support template literals
var btnMarkup = '<button hx-ext="response-targets" HX_TARGET="#d1" hx-get="/test">Click Me!</button>';
// forEach because IE11 doesn't play nice with closures inside for loops
attributes.forEach(function(attribute) {
it('supports ' + attribute, function() {
this.server.respondWith("GET", "/test", [404, {}, "Not found!"]);
var btn = make(btnMarkup.replace("HX_TARGET", attribute));
var div1 = make('<div id="d1"></div>')
btn.click();
this.server.respond();
div1.innerHTML.should.equal("Not found!");
});
});
});
});
5 changes: 3 additions & 2 deletions www/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ build_search_index = false
generate_feed = true

taxonomies = [
{ name = "tag", render = false, feed = true }
{ name = "tag", render = false, feed = true },
{ name = "author", render = false, feed = false }
]

[markdown]
Expand Down Expand Up @@ -35,4 +36,4 @@ paths_keep_dates = true
# Tomorrow
# two-dark
# visual-studio-dark
# zenburn
# zenburn
17 changes: 5 additions & 12 deletions www/content/attributes/hx-on.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@ The `hx-on` attribute allows you to embed scripts inline to respond to events di
`hx-on` improves upon `onevent` by enabling the handling of any event for enhanced [Locality of Behaviour (LoB)](/essays/locality-of-behaviour/). This also enables you to handle any htmx event.

There are two forms of this attribute, one in which you specify the event as part of the attribute name
after a colon (`hx-on:click`, for example), and one that uses the `hx-on` attribute directly. The
latter form should only be used if IE11 support is required.
after a colon (`hx-on:click`, for example), and a deprecated form that uses the `hx-on` attribute directly. The
latter should only be used if IE11 support is required.

### Forms
#### hx-on:* (recommended)
### hx-on:* (recommended)
The event name follows a colon `:` in the attribute, and the attribute value is the script to be executed:

```html
Expand Down Expand Up @@ -40,22 +39,16 @@ events, and omit the "htmx" part:
Adding multiple handlers is easy, you just specify additional attributes:
```html
<button hx-get="/info"
hx-on::before-request="alert('Making a request!'")
hx-on::before-request="alert('Making a request!')"
hx-on::after-request="alert('Done making a request!')">
Get Info!
</button>
```


#### hx-on (deprecated, except for IE11 support)
### hx-on (deprecated)
The value is an event name, followed by a colon `:`, followed by the script:

```html
<div hx-on="click: alert('Clicked!')">Click</div>
```

And htmx events:

```html
<button hx-get="/info" hx-on="htmx:beforeRequest: alert('Making a request!')">
Get Info!
Expand Down
3 changes: 2 additions & 1 deletion www/content/essays/10-tips-for-SSR-HDA-apps.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ title = "10 Tips For Building SSR/HDA applications"
date = 2022-06-13
updated = 2023-06-13
[taxonomies]
author = ["Carson Gross"]
tag = ["posts"]
+++

Expand Down Expand Up @@ -156,4 +157,4 @@ developer.
Hopefully these tips help you adopt hypermedia and server-side rendering as a tool more effectively and smoothly. It
isn't a perfect client-server architecture, and it involves explicit tradeoffs, but it can be extremely effective for
many web applications (far more than most web developers today suspect) and provides a much simpler overall development
experience in those cases.
experience in those cases.
1 change: 1 addition & 0 deletions www/content/essays/_index.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
+++
title = "Essays"
insert_anchor_links = "left"
page_template = "essay.html"
+++

### Hypermedia and REST
Expand Down
1 change: 1 addition & 0 deletions www/content/essays/a-real-world-react-to-htmx-port.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ title = "A Real World React -> htmx Port"
date = 2022-09-29
updated = 2022-10-15
[taxonomies]
author = ["Carson Gross"]
tag = ["posts"]
+++

Expand Down
1 change: 1 addition & 0 deletions www/content/essays/a-response-to-rich-harris.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ title = "A Response To &quot;Have Single-Page Apps Ruined the Web?&quot;"
date = 2021-12-24
updated = 2022-05-27
[taxonomies]
author = ["Carson Gross"]
tag = ["posts"]
+++

Expand Down
3 changes: 2 additions & 1 deletion www/content/essays/architectural-sympathy.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ title = "Architectural Sympathy"
date = 2023-04-06
updated = 2023-04-06
[taxonomies]
author = ["Carson Gross"]
+++


Expand Down Expand Up @@ -90,4 +91,4 @@ way, but these typically did not sacrifice the conceptual coherence of the whole

Adopting an architecturally sympathetic mindset in software development often means sacrificing how you would like to
do things in favor of how an original piece of software did things. While this constraint can chafe at times, it can
also produce well crafted software that is harmonious and that dovetails well with existing software.
also produce well crafted software that is harmonious and that dovetails well with existing software.
1 change: 1 addition & 0 deletions www/content/essays/complexity-budget.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ title = "Complexity Budget"
date = 2020-10-29
updated = 2022-02-06
[taxonomies]
author = ["Carson Gross"]
tag = ["posts"]
+++

Expand Down
24 changes: 13 additions & 11 deletions www/content/essays/hateoas.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@ title = "HATEOAS"
date = 2021-10-16
updated = 2022-02-06
[taxonomies]
author = ["Carson Gross"]
tag = ["posts"]
[extra]
show_title = false
show_author = false
+++

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Lexend+Zetta:wght@900&display=swap&text=HATEOAS" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Lexend+Zetta:wght@900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Source+Serif+Pro:ital,wght@0,400;0,600;0,700;1,400;1,700&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Lexend+Zetta:wght@900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Source+Serif+Pro:ital,wght@0,400;0,600;0,700;1,400;1,700&display=swap" rel="stylesheet">

<h1>HATEOAS</h1>

Expand All @@ -31,13 +33,13 @@ Hypermedia as the Engine of Application State (HATEOAS) is a constraint of the [
With HATEOAS, a client interacts with a network application whose application servers provide information dynamically through [*hypermedia*](https://en.wikipedia.org/wiki/Hypermedia). A REST client needs little to no prior knowledge about how to interact with an application or server beyond a generic understanding of hypermedia.

By contrast, today JSON-based web clients typically interact through a fixed interface shared through documentation via a tool
such as [swagger](https://swagger.io/).
such as [swagger](https://swagger.io/).

The restrictions imposed by HATEOAS decouples client and server. This enables server functionality to evolve independently.

## Example

A user-agent that implements HTTP makes a HTTP request of a REST end point through a simple URL. All subsequent requests the user-agent may make are discovered within the hypermedia responses to each request. The media types used for these representations, and the link relations they may contain, are standardized. The client transitions through application states by selecting from links within a hypermedia representation or by manipulating the representation in other ways afforded by its media type.
A user-agent that implements HTTP makes a HTTP request of a REST end point through a simple URL. All subsequent requests the user-agent may make are discovered within the hypermedia responses to each request. The media types used for these representations, and the link relations they may contain, are standardized. The client transitions through application states by selecting from links within a hypermedia representation or by manipulating the representation in other ways afforded by its media type.

In this way, RESTful interaction is driven by hypermedia, rather than out-of-band information.

Expand Down Expand Up @@ -89,10 +91,10 @@ Only one link is available: to deposit more money. In the accounts current overd
this fact is reflected internally in *the hypermedia*. The web browser does not know about the concept of an overdrawn account or,
indeed, even what an account is. It simply knows how to present hypermedia representations to a user.

Hence we have the notion of the Hypermedia being the Engine of Application State. What actions are possible varies as the
Hence we have the notion of the Hypermedia being the Engine of Application State. What actions are possible varies as the
state of the resource varies and this information is encoded in the hypermedia.

Contrast the HTML response above with a typical JSON API which, instead, might return a representation of the account with a
Contrast the HTML response above with a typical JSON API which, instead, might return a representation of the account with a
status field:

```json
Expand All @@ -111,8 +113,8 @@ HTTP/1.1 200 OK
```

Here we can see that the client must know specifically what the value of the `status` field means and how it might affect
the rendering of a user interface, and what actions can be taken with it. The client must also know what URLs must be used
for manipulation of this resource since they are not encoded in the response. This would typically be achieved by
the rendering of a user interface, and what actions can be taken with it. The client must also know what URLs must be used
for manipulation of this resource since they are not encoded in the response. This would typically be achieved by
consulting documentation for the JSON API.

It is this requirement of out-of-band information that distinguishes this JSON API from a RESTful API that implements
Expand Down Expand Up @@ -164,13 +166,13 @@ HTTP/1.1 200 OK

Here, the "hypermedia controls" are encoded in a `links` property on the account object.

Unfortunately, the client of this API still needs to know quite a bit of additional information:
Unfortunately, the client of this API still needs to know quite a bit of additional information:

* What http methods can be used against these URLs?
* Can it issue a `GET` to these URLs in order to get a representation of the mutation in question?
* If it can `POST` to a given URL, what values are expected?

Compare the above JSON with the following HTTP response, retrieved by a browser after a user has clicked on the
Compare the above JSON with the following HTTP response, retrieved by a browser after a user has clicked on the
link to `/accounts/12345/deposits` found in the first HTML example:

```html
Expand All @@ -187,7 +189,7 @@ HTTP/1.1 200 OK
```

Note that this HTML response encodes all the information necessary to update the account balance, providing a `form` with a `method`
and `action` attribute, as well as the inputs necessary for updating the resource correctly.
and `action` attribute, as well as the inputs necessary for updating the resource correctly.

The JSON representation does not have the same self-contained "uniform interface" as the HTML representation does.

Expand Down
Loading