-
Notifications
You must be signed in to change notification settings - Fork 125
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
Reprocess elements after specific changes are made to them #156
base: main
Are you sure you want to change the base?
Changes from all commits
e00eb8a
c371923
5ba6367
b5492d2
8a01080
361ca3e
9cd8679
9f7e1f9
7580734
c61ee86
cdbcfe8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import LocalTime from "./local_time" | ||
|
||
{elementMatchesSelector} = LocalTime | ||
|
||
class LocalTime.ElementObservations | ||
OBSERVABLE_ATTRIBUTES = [ "datetime", "data-local", "data-format", "data-format24" ] | ||
|
||
constructor: (@selector, @callback) -> | ||
@elements = new Map() | ||
|
||
include: (element) => | ||
unless @elements.get(element) | ||
observer = @observe(element) | ||
@register(element, observer) | ||
|
||
disregard: (element) => | ||
if observer = @elements.get(element)?.observer | ||
observer.disconnect() | ||
@elements.delete(element) | ||
|
||
observe: (element) => | ||
observer = new MutationObserver(@processMutations) | ||
observer.observe(element, characterData: true, subtree: true, attributes: true, attributeFilter: OBSERVABLE_ATTRIBUTES) | ||
observer | ||
|
||
register: (element, observer) => | ||
@elements.set(element, { observer: observer, updates: -1 }) | ||
@incrementUpdates(element) | ||
|
||
processMutations: (mutations) => | ||
for mutation in mutations | ||
target = mutation.target | ||
|
||
element = if target.nodeType is Node.TEXT_NODE | ||
target.parentNode | ||
else | ||
target | ||
Comment on lines
+34
to
+37
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Turbo morphs could change either/or — the data attributes on the element itself, or the value of the nested text node |
||
|
||
if elementMatchesSelector(element, @selector) | ||
@processLingeringElement(element) | ||
break | ||
|
||
processLingeringElement: (element) => | ||
@incrementUpdates(element) | ||
@callback(element) | ||
|
||
incrementUpdates: (element) => | ||
@elements.get(element).updates++ | ||
element.setAttribute("data-updates", @elements.get(element).updates) | ||
Comment on lines
+47
to
+49
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Only done for testing purposes. We mutate the element as a response to mutations, so this PR is prone to infinite loops if we're not careful. I think asserting the number of updates is a good protection against this. |
||
|
||
size: -> | ||
@elements.size |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import { devices } from "@playwright/test" | ||
|
||
const config = { | ||
projects: [ | ||
{ | ||
name: "chrome", | ||
use: { | ||
...devices["Desktop Chrome"], | ||
contextOptions: { | ||
timeout: 5000 | ||
} | ||
} | ||
}, | ||
{ | ||
name: "firefox", | ||
use: { | ||
...devices["Desktop Firefox"], | ||
contextOptions: { | ||
timeout: 5000 | ||
} | ||
} | ||
}, | ||
{ | ||
name: "webkit", | ||
use: { | ||
...devices["Desktop Safari"], | ||
contextOptions: { | ||
timeout: 5000 | ||
} | ||
} | ||
} | ||
], | ||
browserStartTimeout: 60000, | ||
testDir: "./test/javascripts/", | ||
testMatch: /integration\/.*_test\.js/, | ||
webServer: { | ||
command: "yarn start" | ||
}, | ||
use: { | ||
baseURL: "http://localhost:9000/" | ||
} | ||
} | ||
|
||
export default config |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
<!DOCTYPE html> | ||
<head> | ||
<meta charset="utf-8"> | ||
<title>Integration Tests</title> | ||
<script src="/current-build"></script> | ||
</head> | ||
<body> | ||
<script> | ||
LocalTime.start() | ||
|
||
function simulateMorph() { | ||
const textNode = document.getElementById("one").firstChild | ||
textNode.nodeValue = "changed" | ||
} | ||
Comment on lines
+11
to
+14
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
function changeDatetime() { | ||
const element = document.getElementById("one") | ||
element.setAttribute("datetime", "2015-12-04T14:00:00Z") | ||
} | ||
|
||
function changeToRelative() { | ||
const element = document.getElementById("one") | ||
element.setAttribute("data-local", "time-ago") | ||
} | ||
|
||
function changeFormat() { | ||
const element = document.getElementById("one") | ||
element.setAttribute("data-format", "%b %e") | ||
} | ||
|
||
function changeFormat24() { | ||
LocalTime.config.useFormat24 = true | ||
LocalTime.run() | ||
|
||
const element = document.getElementById("one") | ||
element.setAttribute("data-format24", "%B %e") | ||
} | ||
|
||
function changeFoo() { | ||
const element = document.getElementById("one") | ||
element.setAttribute("data-foo", "bar") | ||
} | ||
|
||
function reprocess() { | ||
LocalTime.run() | ||
} | ||
|
||
function removeElement() { | ||
const element = document.getElementById("one") | ||
element.parentNode.removeChild(element) | ||
} | ||
</script> | ||
|
||
<button id="morph" onclick="simulateMorph()">Morph</button> | ||
<button id="change-datetime" onclick="changeDatetime()">Change Datetime</button> | ||
<button id="change-relative" onclick="changeToRelative()">Change To Relative</button> | ||
<button id="change-format" onclick="changeFormat()">Change Format</button> | ||
<button id="change-format24" onclick="changeFormat24()">Change Format24</button> | ||
<button id="change-foo" onclick="changeFoo()">Change Foo</button> | ||
|
||
<button id="reprocess" onclick="reprocess()" style="background-color: darkseagreen;">Reprocess</button> | ||
<button id="remove" onclick="removeElement()" style="background-color: firebrick; color: white;">Remove Element</button> | ||
|
||
<br> | ||
|
||
<time id="one" datetime="2024-12-04T14:00:00Z" data-format="%B %e, %Y" data-local="time"></time> | ||
</body> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per Mozilla, setting
textContent
(which is what this library uses to update the element) removes all of the node's children and replaces them with a single text node with the given string value. So it shouldn't cause infinite loops due to this configuration.This was also my experience while testing. But of course we'll want to thoroughly test on our apps before a public release.