Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Class tools clasess event #83

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
12 changes: 12 additions & 0 deletions src/class-tools/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ Within a run, a `,` character separates distinct class operations.
A class operation is an operation name `add`, `remove`, or `toggle`, followed by a CSS class name,
optionally followed by a colon `:` and a time delay.

## On event trigger class manipulation
This feature allows for dynamic class manipulation in response to specific events using the `classes-event-trigger` or `data-classes-event-trigger` attribute.


## Out-of-band class manipulation

There is also the option to use `apply-parent-classes`, or `data-apply-parent-classes`, which take the same format as `classes`
Expand Down Expand Up @@ -44,6 +48,14 @@ so it should ideally be used as part of an `hx-swap-oob="beforeend: #some-elemen
<div classes="toggle foo:1s"/> <!-- toggles the class "foo" every 1s -->
</div>

<!-- The following performs class manipulation only after the event is triggered -->
<div hx-ext="class-tools">
<div classes="add foo:1s" classes-event-trigger="my-custom-event">Class foo wil be added after 1s</div> <!-- Adds the class "foo" 1 second after the event is triggered -->
<div classes="toggle foo:!" classes-event-trigger="my-custom-event">Class foo wil be toggled</div> <!-- "!" means toggle immediately with no delay -->
<div classes="toggle foo:!, toggle bar:!" classes-event-trigger="my-custom-event">Class foo & bar wil be toggled</div> <!-- toggles multiple classes -->
<button hx-on-click="htmx.trigger('body', 'my-custom-event')">toggle</button>
</div>

<!-- The following OOB update surgically applies CSS classes to "my-element" -->
<div hx-swap-oob="beforeend: #my-element">
<div hx-ext="class-tools"
Expand Down
48 changes: 32 additions & 16 deletions src/class-tools/class-tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
if (classDef.indexOf(':') > 0) {
var splitCssClass = classDef.split(':')
cssClass = splitCssClass[0]
delay = htmx.parseInterval(splitCssClass[1])
delay = splitCssClass[1] === '!' ? null : htmx.parseInterval(splitCssClass[1])
} else {
cssClass = classDef
delay = 100
Expand All @@ -28,16 +28,20 @@
}
}

function performOperation(elt, classOperation, classList, currentRunTime) {
function call(elt, classOperation) {
elt.classList[classOperation.operation].call(elt.classList, classOperation.cssClass)
}

function performOperation(elt, classOperation, currentRunTime) {
setTimeout(function() {
elt.classList[classOperation.operation].call(elt.classList, classOperation.cssClass)
call(elt, classOperation)
}, currentRunTime)
}

function toggleOperation(elt, classOperation, classList, currentRunTime) {
function toggleOperation(elt, classOperation, currentRunTime) {
setTimeout(function() {
setInterval(function() {
elt.classList[classOperation.operation].call(elt.classList, classOperation.cssClass)
call(elt, classOperation)
}, classOperation.delay)
}, currentRunTime)
}
Expand All @@ -54,11 +58,15 @@
var classOperation = parseClassOperation(trimmedValue)
if (classOperation) {
if (classOperation.operation === 'toggle') {
toggleOperation(elt, classOperation, classList, currentRunTime)
currentRunTime = currentRunTime + classOperation.delay
if (classOperation.delay) {
toggleOperation(elt, classOperation, currentRunTime)
currentRunTime += classOperation.delay
} else {
call(elt, classOperation)
}
} else {
currentRunTime = currentRunTime + classOperation.delay
performOperation(elt, classOperation, classList, currentRunTime)
currentRunTime += classOperation.delay
performOperation(elt, classOperation, currentRunTime)
}
}
}
Expand All @@ -68,7 +76,15 @@
function maybeProcessClasses(elt) {
if (elt.getAttribute) {
var classList = elt.getAttribute('classes') || elt.getAttribute('data-classes')
if (classList) {
var eventTrigger = elt.getAttribute('classes-event-trigger') || elt.getAttribute('data-classes-event-trigger')
if (eventTrigger) {
var handleEvent = function() {
processClassList(elt, classList)
elt.removeEventListener(eventTrigger, handleEvent)
}
elt.addEventListener(eventTrigger, handleEvent, { once: true })
document.addEventListener(eventTrigger, handleEvent)
} else if (classList) {
processClassList(elt, classList)
}
}
Expand All @@ -79,14 +95,14 @@
if (name === 'htmx:afterProcessNode') {
var elt = evt.detail.elt
maybeProcessClasses(elt)
var classList = elt.getAttribute("apply-parent-classes") || elt.getAttribute("data-apply-parent-classes");
var classList = elt.getAttribute('apply-parent-classes') || elt.getAttribute('data-apply-parent-classes')
if (classList) {
var parent = elt.parentElement;
parent.removeChild(elt);
parent.setAttribute("classes", classList);
maybeProcessClasses(parent);
var parent = elt.parentElement
parent.removeChild(elt)
parent.setAttribute('classes', classList)
maybeProcessClasses(parent)
} else if (elt.querySelectorAll) {
var children = elt.querySelectorAll('[classes], [data-classes]')
var children = elt.querySelectorAll('[classes], [data-classes], [classes-event-trigger], [data-classes-event-trigger]')
for (var i = 0; i < children.length; i++) {
maybeProcessClasses(children[i])
}
Expand Down
85 changes: 85 additions & 0 deletions src/class-tools/test/ext/class-tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,89 @@ describe('class-tools extension', function() {
done()
}, 100)
})

it('does not add classes if event is not triggered', function(done) {
var div = make('<div hx-ext="class-tools"><div classes="add foo" classes-event-trigger="my-custom-event">Test</div></div>')
should.equal(div.classList.contains('foo'), false)
setTimeout(function() {
should.equal(div.classList.contains('foo'), false)
done()
}, 100)
})

it('toggles classes properly on custom event trigger, class set', function(done) {
var div = make(`
<div hx-ext="class-tools">
<div classes="toggle foo:!" classes-event-trigger="my-custom-event">Test</div>
<button hx-on-click="htmx.trigger('body', 'my-custom-event')">toggle</button>
</div>
`)
var toggleDiv = div.querySelector('[classes]')
should.equal(toggleDiv.classList.contains('foo'), false)
var button = div.querySelector('button')
button.click()
setTimeout(function() {
should.equal(toggleDiv.classList.contains('foo'), true)
done()
}, 100)
})

it('toggles classes properly on custom event trigger, class unset', function(done) {
var div = make(`
<div hx-ext="class-tools">
<div class="foo" classes="toggle foo:!" classes-event-trigger="my-custom-event">Test</div>
<button hx-on-click="htmx.trigger('body', 'my-custom-event')">toggle</button>
</div>
`)
var toggleDiv = div.querySelector('[classes]')
should.equal(toggleDiv.classList.contains('foo'), true)
var button = div.querySelector('button')
button.click()
setTimeout(function() {
should.equal(toggleDiv.classList.contains('foo'), false)
done()
}, 100)
})

it('toggles classes properly on multiple elements with custom event', function(done) {
var container = make(`
<div hx-ext="class-tools">
<div id="div1" classes="toggle foo:!" classes-event-trigger="my-custom-event">Test 1</div>
<div id="div2" classes="toggle bar:!" classes-event-trigger="my-custom-event">Test 2</div>
<button hx-on-click="htmx.trigger('body', 'my-custom-event')">Toggle Classes</button>
</div>
`)
var div1 = container.querySelector('#div1')
var div2 = container.querySelector('#div2')
should.equal(div1.classList.contains('foo'), false)
should.equal(div2.classList.contains('bar'), false)
var button = container.querySelector('button')
button.click()
setTimeout(function() {
should.equal(div1.classList.contains('foo'), true)
should.equal(div2.classList.contains('bar'), true)
done()
}, 100)
})

it('toggles multiple classes properly on custom event trigger', function(done) {
var div = make(`
<div hx-ext="class-tools">
<div classes="toggle foo:!, toggle bar:!" classes-event-trigger="my-custom-event">Test</div>
<button hx-on-click="htmx.trigger('body', 'my-custom-event')">toggle</button>
</div>
`)
var toggleDiv = div.querySelector('[classes]')
should.equal(toggleDiv.classList.contains('foo'), false)
should.equal(toggleDiv.classList.contains('bar'), false)
var button = div.querySelector('button')
button.click()
setTimeout(function() {
should.equal(toggleDiv.classList.contains('foo'), true)
should.equal(toggleDiv.classList.contains('bar'), true)
done()
}, 100)
})

// add
})