Open petermonte opened 3 years ago
Have you tried bootstrapping your code using the sentinel-load
event described here?
https://cdpn.io/petermonte/debug/Vwwqgjv/LDAmdnPpbExr
I've updated the script to make Sentinel only run once document
triggers the sentinel-load
event - it seems to break in all browser.
...
const _components = [];
function registerComponent(_name, _selector, _callback) {
_components.push({
name: _name,
selector: _selector,
callback: _callback
});
return true;
}
document.addEventListener('sentinel-load', function () {
sentinel.on('!node-inserted', function (el) {
var i = 0;
const lth = _components.length;
for (i; i < lth; i++) {
const component = _components[i];
if (el.matches(component.selector)) {
component.callback(el);
}
}
});
});
...
The sentinel-load
event gets triggered when the sentinel.js script runs so the event listener in the CodePen example won't get executed.
There's a lot of extra code in the example. Can you isolate the bug with a simpler script? For example, does this work when you run it locally on Safari?
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<style>
@keyframes changeColor {
0% {
color: blue;
transform: translateX(-10px);
}
100% {
color: black;
transform: translateX(0);
}
}
.example-cls {
animation-duration: 1s;
animation-name: changeColor, node-inserted;
transform: translateX(0);
}
</style>
<script src="https://cdn.rawgit.com/muicss/sentineljs/0.0.5/dist/sentinel.min.js"></script>
<script>
sentinel.on('!node-inserted', function (el) {
el.innerHTML = "Sentinel is always watching";
});
</script>
</head>
<body>
<!-- element to be parsed by sentinel -->
<div class="example-cls"><div>
</body>
</html>
What is it that you want to do differently?
Here is you're code in a simple way isolate from any other script. https://cdpn.io/petermonte/debug/XWWKRNv/mWAoNzxpxxWr
sentinel-load
didn't seem to work, unless I'm missing something.What is it that you want to do differently?
Imagine a UI Kit that offers (per example):
title="My tooltip information"
tab
data-dropdown-trigger="target"
Let say that all 3 features exist isolated or combined like so:
How would you implement such UI Kit given that you need to trigger all existing elements on DOM at page load and any new added to the DOM via script?
When I click on the codepen link I see a "This debug view expired" message.
From the description of what you're trying to do it sounds like you might be running into a race condition between the page load event and the animationstart
event of the elements that are already on the page. You should be able to avoid the race condition by executing sentinel.on()
before the dom elements are defined (e.g. in the <head>
):
<html>
<head>
<script src="/path/to/sentinel.js"></script>
<script>
sentinel.on('custom-element', function(el) {
el.innerHTML = "Sentinel is always watching";
});
</script>
</head>
<body>
<custom-element></custom-element>
</body>
</html>
Alternatively, if you want to bootstrap your code after the DOMContentLoaded
event then you should be able to so by processing all of the previously defined DOM elements first and then initializing sentinel afterwards. In that case the javascript can be executed before the closing </body>
tag:
<html>
<head>
<script src="/path/to/sentinel.js"></script>
</head>
<body>
<custom-element></custom-element>
<script>
function modifyElement(el) {
el.innerHTML = "This element has been modified";
}
document.addEventListener('DOMContentLoaded', function(ev) {
// process previously defined DOM elements
document.querySelectorAll('custom-element').forEach(modifyElement);
// initialize sentinel
sentinel.on('custom-element', modifyElement);
});
</script>
</body>
</html>
let me put things in a simple way by using your example. Need to slightly change it because you only target one element base on the css animation name. I'm targeting multiple elements, based on multiple kinds of selectors.
Environment:
MacOS Big Sur 11.1
Safari - Version 14.0.2 (16610.3.7.1.9)
Google Chrome - Version 88.0.4324.96 (Official Build) (x86_64)
Firefox - Version 85.0 (64-bit)
This is the markup:
<div class="example-cls" data-attr="abc" title="Title For Tooltip">ON DOM</div>
<!--
I expect a call on Sentinel based on a div tagname
I expect a call on Sentinel based on an attribute named title
I expect a call on Sentinel based on an attribute called data-attr
I expect a call on Sentinel based on a classname example-css
-->
This is the CSS:
@keyframes change-color {
0% {
color: blue;
transform: translateX(-10px);
}
100% {
color: black;
transform: translateX(0);
}
}
div, /* I expect a call on Sentinel based on a tagname */
[title], /* I expect a call on Sentinel based on an attribute named title */
[data-attr], /* I expect a call on Sentinel based on an attribute called data-attr */
.example-cls /* I expect a call on Sentinel based on a classname example-css */
{
animation-duration: 1s;
animation-name: change-color, node-inserted;
transform: translateX(0);
}
This is the JS:
function modifyElement(el) {
// I increment the innerHTML so that I can count how many times an element is caught by Sentinel
el.innerHTML += " Sentinel ";
}
window.addEventListener('DOMContentLoaded', function (ev) {
document.querySelectorAll('[data-attr], .example-cls, [title]').forEach(modifyElement);
sentinel.on('!node-inserted', modifyElement);
});
// Added a click to generate markup on the fly
document.addEventListener('click', function () {
const div = document.createElement('div'); // I expect a call on Sentinel based on a tagname Div
div.textContent = 'ADDED ';
div.className = 'example-cls'; // I expect a call on Sentinel based on classname example-cls
div.dataset.attr = 'abc'; // I expect a call on Sentinel based on attibute data-attr
div.title = 'hello'; // I expect a call on Sentinel based on attibute title
document.body.append(div);
});
This is what I get (Safari, Firefox, Chrome):
This is what I expected:
This is the code editor: https://codepen.io/petermonte/pen/XWWKRNv This is the code in editor mode, so stripped out of any other script: https://cdpn.io/petermonte/debug/XWWKRNv/nqkwvzGpyEOA
First, with regards to "ON DOM Sentinel" I think the difference in behavior between the browsers is related to a race condition between DOMContentLoaded
and animationstart
in Safari. Firefox and Chrome seem to reliably call animationstart
after DOMContentLoaded
which results in modifyElement()
being executed twice. However, sometimes Safari seems to call animationstart
before DOMContentLoaded
which results in modifyElement()
only getting called once. If you want to prevent multiple calls you can use a flag:
function modifyElement(el) {
(if (!el._upgraded) el.innerHTML != " Sentinel ";
el._upgraded = true;
}
window.addEventListener("DOMContentLoaded", function(ev) {
document.querySelectorAll('[data-attr], .example-cls, [title]').forEach(\
modifyElement);
sentinel.on('!node-inserted', modifyElement);
});
This will result in consistent "ON DOM Sentinel" behavior across browsers.
Second, with regards to "ADDED Sentinel Sentinel Sentinel" it sounds like what you're expecting is for the browser to trigger animationstart
3 times, once each for the "[data-attr]", ".example-css", "[title]" css selectors. However, browsers only trigger the event once per element even if multiple CSS rules match. If you want to execute different code based on the CSS selector that matched you can do some post processing to see which css selectors match.
@amorey
Thanks man. I was almost going crazy trying to figure out what was happening. My purpose with this thread was to understand how sensible Sentinel is with how "each browser deals differently with events for script execution and page load".
On another topic but related to this thread I realised that Sentinel when used with the !node-inserted
logic doesn't quite help to distinguish what to do on an element on callback. Like in my case a one single element can have randomly up to 3 functionalities and each independent form another.
!node-inserted
needs to be added or pushed to the existing styles of the element/selector.
sentinel.on('.my-target', function(){}, true);
// Last boolean parameter stands for using the generic animation name [node-inserted] to catch any element on DOM
// So if a specific selector has already an animation name declared an additional one will be added: my-animation, node-inserted
This means that we could use the following logic:
// Both these events can occur or not on a same element
sentinel.on('.my-target', function(){
// My code specifically for any new element added with the classname .my-target
}, true);
sentinel.on('[title]', function(){
// My code specifically for any new element added that has a title attribute to use has a tooltip
}, true);
In the meantime this is my workaround (Click anywhere to add new elements to DOM):
Edit: Forgot to include the css.
@keyframes my-animation {
0% {
color: blue;
}
100% {
color: black;
}
}
[title],
[data-attr],
.tabs {
animation-duration: 1s;
animation-name: my-animation, node-inserted;
}
// Interface to record every single constructor that can be called on an element based on its selector
const components = [];
function registerComponent(_name, _selector, _callback) {
// Register our component
components.push({
name: _name,
selector: _selector,
callback: _callback
});
return true;
}
function initElement(el) {
// Run the element through our components and find matches
components.forEach(component => el.matches(component.selector) ? component.callback(el) : null);
return true;
}
// Interface to manage elements on DOM and when added to DOM
let sentinelRanAtDomContentLoaded = false;
sentinel.on('!node-inserted', el => {
if (!sentinelRanAtDomContentLoaded){
sentinelRanAtDomContentLoaded = true;
}
initElement(el);
});
document.addEventListener('DOMContentLoaded', e => {
if (!sentinelRanAtDomContentLoaded){
components.forEach(function(component){
document.querySelectorAll(component.selector).forEach(function(el){
component.callback(el);
});
});
sentinelRanAtDomContentLoaded = true;
}
}, true);
/**
* Usage. Every single selector has its own context
*/
registerComponent(
'tabs',
'.tabs',
function tabs(el) {
// RUN EVERYTHING NEEDED FOR THE COMPONENT
return el;
}
);
registerComponent(
'title-attr',
'[title]',
function attrTitle(el) {
// RUN EVERYTHING NEEDED FOR THE COMPONENT
return el;
}
);
registerComponent(
'data-attr',
'[data-attr]',
function dataAttr(el) {
// RUN EVERYTHING NEEDED FOR THE COMPONENT
return el;
}
);
No problem. Happy to hear that helped.
Would be nice if Sentinel packed a listener to make sure that it ran on script execution.
Can you describe this in more detail? What do you mean by script execution time? The time when the browser parses and executes the "sentinel.js" script?
Would be nice that we could simply register a listener with Sentinel and have it check if the animation name !node-inserted needs to be added or pushed to the existing styles of the element/selector.
It sounds like what you want is SentinelJS to execute all three callbacks in this example:
<script>
sentinel.on('div', function callback1() {});
sentinel.on('.example-cls', function callback2() {});
sentinel.on('[example-attr]', function callback3() {});
</script>
<div class="example-cls" example-attr="value"></div>
Is that correct?
Can you describe this in more detail? What do you mean by script execution time? The time when the browser parses and executes the "sentinel.js" script?
The moment the browser as fully loaded HTML (DOM tree is built and available) yet any external resources like
Tests: If you make script run at execution time you will see that Safari ignores all markup on DOM at hard refresh. If you make script run only on Window Load event Safari ignores all markup on DOM at execution time. If you make script run only on Document readystatechange event Safari ignores all markup on DOM on hard refresh. If you make script run only on Window DOMContentLoaded event all 3 browsers ignore markup on DOM at execution time.
Code: Editor mode - https://codepen.io/petermonte/pen/Vwwqgjv?editors=0010 Debug mode - https://cdpn.io/petermonte/debug/Vwwqgjv/PNrvYXGWZBPM
Specs:
Screen recordings: https://user-images.githubusercontent.com/4997381/106580084-652e3380-6539-11eb-8137-9ffea143c4ec.mov https://user-images.githubusercontent.com/4997381/106580430-d0780580-6539-11eb-8b80-d9ce14444d18.mov
This is a request by @amorey in https://github.com/muicss/sentineljs/issues/8#issuecomment-771403307