-
Notifications
You must be signed in to change notification settings - Fork 59
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
3,076 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
name: Tests - attribute-tools | ||
|
||
on: | ||
push: | ||
branches: [ main ] | ||
paths: | ||
- 'src/attribute-tools/**' | ||
- '.github/workflows/attribute-tools.yml' | ||
pull_request: | ||
branches: [ main ] | ||
paths: | ||
- 'src/attribute-tools/**' | ||
- '.github/workflows/attribute-tools.yml' | ||
workflow_dispatch: | ||
|
||
jobs: | ||
test_suite: | ||
runs-on: ubuntu-22.04 | ||
steps: | ||
- uses: actions/checkout@v4 | ||
- name: Use Node.js | ||
uses: actions/setup-node@v4 | ||
with: | ||
node-version: '20.x' | ||
- run: npm ci | ||
working-directory: ./src/attribute-tools | ||
- run: npm test | ||
working-directory: ./src/attribute-tools |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
BSD Zero Clause License | ||
|
||
Copyright (c) 2023, Alexander Petros | ||
|
||
Permission to use, copy, modify, and/or distribute this software for any | ||
purpose with or without fee is hereby granted. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | ||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
# `attribute-tools` - Set or Remove Attributes | ||
|
||
## Manipulate attributes of an element (e.g. for attributes like "open", "aria-busy", "progress[value]") | ||
|
||
The `attribute-tools` extension allows you to specify attributes that will be set or removed from elements by using | ||
an `attribute` or `data-attribute` attribute. | ||
|
||
An `attributes` attribute value consists of "runs", which are separated by an `&` character. All | ||
attributes operations within a given run will be applied sequentially, with the delay specified. | ||
|
||
Within a run, a `,` character separates distinct attribute operations. | ||
|
||
An attribute operation is an operation name `set` or `remove`, followed by an attribute name, | ||
optionally followed by an equal `=` and a value, optionally followed by a colon `:` and a time delay (e.g. 1s). | ||
|
||
## Out-of-band attribute manipulation | ||
|
||
There is also the option to use `apply-parent-attributes`, or `data-apply-parent-attributes`, which take the same format as `attributes` | ||
but is instead designed for out-of-band updates, allowing you to manipulate attributes of an existing element in the DOM | ||
without otherwise knowing or altering its state. | ||
|
||
Any element with this property will schedule attributes to be applied to its _parent_ element, _removing_ itself afterwards, | ||
so it should ideally be used as part of an `hx-swap-oob="beforeend: #some-element"` to add them to the end of the target element. | ||
|
||
## Install | ||
|
||
```html | ||
<script src="https://unpkg.com/htmx-ext-attribute-tools@2.0.1/attribute-tools.js"></script> | ||
``` | ||
|
||
## Usage | ||
|
||
```html | ||
<!-- The following DOM has attributes swapped in and out as scheduled --> | ||
<div hx-ext="attribute-tools"> | ||
<div attributes="add foo=bar"/> <!-- adds the attribute "foo" with the value "bar" after 0ms --> | ||
<div bar="baz" attributes="remove bar:1s"/> <!-- removes the atttribute "bar" after 1s --> | ||
<div bar="baz" foo="blah" attributes="remove bar:1s, set foo=hello:1s, set open:1s"/> <!-- removes the attribute "bar" after 1s | ||
then sets the attribute "foo" to "hello" 1s after that then sets the attribute "open" 1s after that --> | ||
<div bar="baz" attributes="remove bar:1s & set foo:1s"/> <!-- removes the attribute "bar" and adds | ||
attribute "foo" after 1s --> | ||
</div> | ||
|
||
<!-- The following OOB update surgically applies attributes to "my-element" --> | ||
<div hx-swap-oob="beforeend: #my-element"> | ||
<div hx-ext="attribute-tools" | ||
apply-parent-attribute="add foo, remove foo:10s"/> <!-- adds the attribute "foo" to "my-element" for 10s --> | ||
</div> | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
(function() { | ||
function splitOnWhitespace(trigger) { | ||
return trigger.split(/\s+/) | ||
} | ||
|
||
function parseAttributeOperation(trimmedValue) { | ||
var split = splitOnWhitespace(trimmedValue) | ||
if (split.length > 1) { | ||
var operation = split[0] | ||
var attributeDef = split[1].trim() | ||
var attribute | ||
var delay | ||
if (attributeDef.indexOf(':') > 0) { | ||
var splitAttribute = attributeDef.split(':') | ||
attribute = splitAttribute[0] | ||
delay = htmx.parseInterval(splitAttribute[1]) | ||
} else { | ||
attribute = attributeDef | ||
delay = 0 | ||
} | ||
return { | ||
operation, | ||
attribute, | ||
delay | ||
} | ||
} else { | ||
return null | ||
} | ||
} | ||
|
||
function setOperation(elt, attributeOperation, attributeList, currentRunTime) { | ||
setTimeout(function() { | ||
if (attributeOperation) { | ||
var split = attributeOperation.attribute.split(/=/) | ||
var name = split[0] | ||
var value = split.length > 1 ? split[1] : '' | ||
elt.setAttribute(name, value) | ||
} | ||
|
||
}, currentRunTime) | ||
} | ||
|
||
function removeOperation(elt, attributeOperation, attributeList, currentRunTime) { | ||
setTimeout(function() { | ||
if (attributeOperation) { | ||
var split = attributeOperation.attribute.split(/=/) | ||
var name = split[0] | ||
var value = split.length > 1 ? split[1] : '' | ||
elt.removeAttribute(name) | ||
} | ||
|
||
}, currentRunTime) | ||
} | ||
|
||
function processAttributeList(elt, attributeList) { | ||
var runs = attributeList.split('&') | ||
for (var i = 0; i < runs.length; i++) { | ||
var run = runs[i] | ||
var currentRunTime = 0 | ||
var attributeOperations = run.split(',') | ||
for (var j = 0; j < attributeOperations.length; j++) { | ||
var value = attributeOperations[j] | ||
var trimmedValue = value.trim() | ||
var attributeOperation = parseAttributeOperation(trimmedValue) | ||
if (attributeOperation) { | ||
if (attributeOperation.operation === 'remove') { | ||
currentRunTime = currentRunTime + attributeOperation.delay | ||
removeOperation(elt, attributeOperation, attributeList, currentRunTime) | ||
} else if (attributeOperation.operation === 'set') { | ||
currentRunTime = currentRunTime + attributeOperation.delay | ||
setOperation(elt, attributeOperation, attributeList, currentRunTime) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
function maybeProcessAttributes(elt) { | ||
if (elt.getAttribute) { | ||
var attributeList = elt.getAttribute('attributes') || elt.getAttribute('data-attributes') | ||
if (attributeList) { | ||
processAttributeList(elt, attributeList) | ||
} | ||
} | ||
} | ||
|
||
htmx.defineExtension('attribute-tools', { | ||
onEvent: function(name, evt) { | ||
if (name === 'htmx:afterProcessNode') { | ||
var elt = evt.detail.elt | ||
maybeProcessAttributes(elt) | ||
var attributeList = elt.getAttribute("apply-parent-attributes") || elt.getAttribute("data-apply-parent-attributes"); | ||
if (attributeList) { | ||
var parent = elt.parentElement; | ||
parent.removeChild(elt); | ||
parent.setAttribute("data-attributes", attributeList); | ||
maybeProcessAttributes(parent); | ||
} else if (elt.querySelectorAll) { | ||
var children = elt.querySelectorAll('[attributes], [data-attributes]') | ||
for (var i = 0; i < children.length; i++) { | ||
maybeProcessAttributes(children[i]) | ||
} | ||
} | ||
} | ||
} | ||
}) | ||
})() |
Oops, something went wrong.