I'm trying to add scroll event listeners to x-refs (I don't want to add them in the DOM with #scroll.debounce, because I want it to be as portable as possible).
Codepen here: https://codepen.io/skttl/pen/vYXowBY?editors=1111
<div x-data="xOverflow()" x-init="init()" style="width:50%;border:1px solid red;position:relative;overflow:hidden;">
<div x-ref="wrapper" style="overflow-x:auto;">
<div x-ref="content" style="width:1000px;border:1px solid blue; height:500px;">
<div>wrapper.clientWidth: <span x-text="$refs.wrapper.clientWidth"></span></div>
<div>content.clientWidth: <span x-text="$refs.content.clientWidth"></span></div>
<div>overflow: <span x-text="overflow"></span>
</div>
</div>
<div x-show="overflow" style="position:absolute;top:0;left:90%;right:0;bottom:0;background:rgba(0,0,0,.15);"></div>
</div>
function xOverflow() {
return {
overflow:false,
// methods
setFromResize() {
this.overflow = this.$refs?.wrapper?.clientWidth < this.$refs?.content?.clientWidth
console.log(`resize: overflow is ${this.overflow}`);
},
setFromScroll(e) {
this.overflow = !(e.target.scrollLeft == this.$refs?.content?.clientWidth - this.$refs?.wrapper?.clientWidth);
console.log(`scroll: overflow is ${this.overflow}`);
},
init() {
window.addEventListener("resize", event => _.debounce(this.setFromResize(), 250));
this.$refs?.wrapper.addEventListener("scroll", event => _.debounce(this.setFromScroll(event)));
}
}
}
What I am trying to detect if the clientWidth of $refs.content is larger than $refs.wrapper. When the window resizes, this should be detected again.
In addition to that, I want to show an overlay (if content is overflowing), that should be removed when scrolled to the end.
I could do it by just slapping #resize.window.debounce on the root element, and #scroll.debounce on the wrapper element. But I want this component to be portable, by just adding x-data, x-init and x-refs, without worrying about attaching event listeners.
I tried adding lodash debounce to the eventlisteners, but the functions gets called for each event, and not debounced. Check the console.log for proof.
Can anyone help me get this right?
So turns out I am an idiot, and I just needed to move things around a bit :)
From
window.addEventListener("resize", event => _.debounce(this.setFromResize(), 250));
this.$refs?.wrapper.addEventListener("scroll", event => _.debounce(this.setFromScroll(event)));
to
window.addEventListener("resize", _.debounce(event => this.setFromResize(), 250));
this.$refs?.wrapper.addEventListener("scroll", _.debounce(event => this.setFromScroll(event)));
Related
I have multiple scrollbars to my page, but can't make them working properly :
<div class="dates-container" v-for="id in ids">
<overlay-scrollbars
:ref="`datesHeader`+id"
:options="datesScrollOptions"
:key="id"
>
</div>
......
resetScroller() {
this.$nextTick(() => {
if (this.$refs[`datesHeader${this.currentRoom}`][0]) {
const inst = this.$refs[`datesHeader${this.currentRoom}`][0].osInstance();
if (inst == null) return;
const state = inst.getState();
if (state.hasOverflow.x) {
inst.scroll({ x: 0 });
}
this.updateScrollButtons();
}
});
},
Like that is working fine; The problem is when I tried to identify which scrollbar was moved and how I can update this.currentRoom when scrollbar is moved;
Firsty u have wrong :ref value - should be :ref="`datesHeader-${id}`"
then on onMounted you can add event listners to $refs
onMounted() {
this.$refs['datesHeader-0'].addEventLister('scroll', watchFunc)
}
DOCS:
https://developer.mozilla.org/pl/docs/Web/API/EventTarget/addEventListener
remember to remove event listeners in onBeforeUnmount
also, IntersectionObserver could be good for tracking scroll
DOCS: https://developer.mozilla.org/enUS/docs/Web/API/Intersection_Observer_API
I would like to use a code when I press the space bar a shape appears and it disappears when I press it again. I'm trying to get the addEventListener to work with a sample:
hello = document.querySelector('#Player');
with player being the id of the shape that I want to control. I declared hello above and initialized it in setup (I am using JavaScript), the Player id has also been initialized in HTML and given a shape in CSS. When I use
hello.addEventListener('keypress', (event) => {
console.log(event.key)
})
nothing happens, but when I use
window.addEventListener('keypress', (event) => {
console.log(event.key)
})
it works. Is there anything that I am doing wrong?
It is because by default div is not selectable. In order to make it selectable you need to use tabindex attribute on your div. It will make your div selectable.
const hello = document.querySelector('#player');
hello.addEventListener("keypress", evt => {
console.log(evt.key)
})
<div id="player" tabindex="0">
Player Shape
</div>
It will show a boundary around your div which can be remove by using css -
outline: none;
That's what should happen.
You can add the keypress event to the window or document.
And, if you add it to both, the window wins over for some reason – someone else might clarify this to both of us.
const el = document.getElementById("el");
el.addEventListener("keypress", event => keyPressed(event, "blue"));
document.addEventListener("keypress", event => keyPressed(event, "green"));
window.addEventListener("keypress", event => keyPressed(event, "purple"));
function keyPressed(event, color) {
if (event.key = " ")
el.style.backgroundColor = color;
}
#el {
width: 100px;
height: 100px;
background: red;
}
<div id="el"></div>
I have an Angular web component installed on my site. It uses Shadow DOM so it's super fast (which it has to be in my case).
On my site I also have a shortcut on h which opens up a popup that displays some helpful information. It's a must that this h keybinding stays as it is. Example code of how it was implemented can be seen here: https://jsfiddle.net/js1edv37/
It's a simple event listener that listens on document:
$(document).on("keyup", function(e) {
}
However, this also gets triggered when my web component has focused textarea or input elements. This happens because it uses Shadow DOM, which a script from the outside cannot access.
You can test it by pressing h on the keyboard inside and outside of the input and textarea elements.
Is there a way to let my script from outside of the Shadow DOM web component, still listen for the keyup event, but make it listen for all elements on the page? Even the ones inside the Shadow DOM.
In the Web Component, get the input element with a querySelector() call on the shadowRoot property:
let textareainshadow = div.shadowRoot.querySelector( 'textarea' )
Then listen to the keyup event and stop its propagation with the help of the stopImmediatePropagation() method.
textareainshadow.addEventListener( 'keyup' , ev => {
console.log( 'caught', ev.type )
ev.stopImmediatePropagation()
})
https://jsfiddle.net/7mkrxh25/1/
If you save the reference to the shadow root you can always access it's children as search on those
$(document).on("keyup", function(e) {
let focusedInputs = $("input:focus, textarea:focus").length + $(shadow).children("input:focus, textarea:focus").length;
if (focusedInputs > 0) {
return true;
}
if (e.keyCode === 72) {
trigger();
}
});
function trigger() {
alert("If this was triggered, everything is perfectly fine");
}
let div = document.querySelector("div");
let shadow = div.createShadowRoot();
shadow.innerHTML = "<textarea>This shouldn't fail</textarea>";
textarea {
width: 500px;
height: 100px;
}
input {
width: 250px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<textarea>Some stuff here</textarea>
<br />
<input type="text" value="Some more text here" />
<br />
<br />
<h1>Shadow DOM element WON'T fail now :)</h1>
<div></div>
Fiddle
I'm trying to remove all jQuery from my code. Until now I used
if ($(selector).find(':focus').length === 0) {
// focus is outside of my element
} else {
// focus is inside my element
}
to distinguish wether the focus is inside of one of my elements. Can you show me a jQuery-free way of doing it?
You can use Node.contains native DOM method for this.
el.contains(document.activeElement);
will check if activeElement is a descendant of el.
If you have multiple elements to check, you can use a some function to iterate.
It is possible with Element's matches() method and with a simple selector string as follows:
let hasFocused = elem.matches(':focus-within:not(:focus)');
let focusedOrHasFocused = elem.matches(':focus-within');
Use CSS :focus pseudo-class in querySelectorAll()
setTimeout(function(){
if (document.querySelectorAll("div :focus").length === 0)
console.log("not focused");
else
console.log("focused")
}, 2000);
<div>
<input type="text">
</div>
Depending on your situation, using events might be more performant.
You can use the focusin and focusout events in that case.
const el = document.getElemen
el.addEventListener("focusin", () => console.log("focus!"));
el.addEventListener("focusout", () => console.log("blur!"));
Note that during focusout events the document.activeElement will be the document body. To work around this issue, you can make use of FocusEvent.relatedTarget.
If you have issue where document.activeElement is returning <body> element after blur event, you just need to wrap it with setTimeout() and it will return correct element.
handleBlur() {
setTimeout(() => {
console.log(document.activeElement); // this actually return active/focused element
});
}
if you are using it standalone without timeout
handleBlur() {
console.log(document.activeElement); // this is returning <body> element
}
Combined some of answers posted here. Using a combination of focusin, focusout, contains and relatedTarget, you should be able to know when focus is on the children of a particular element.
const elm = document.getElementById('check-focus-here')
elm.addEventListener('focusin', (event) => {
console.log(event.target, event.relatedTarget)
// console.log(elm.contains(event.relatedTarget))
})
elm.addEventListener('focusout', (event) => {
console.log(event.target, event.relatedTarget)
console.log(elm.contains(event.relatedTarget))
})
#check-focus-here {
border: 2px solid;
padding: 8px;
margin: 8px;
}
<div id="check-focus-here">
<input id="first-name" type="text" />
<input id="middle-name" type="text" />
<input id="last-name" type="text" />
<button type="button">Save Name</button>
</div>
<button type="button">Tab to this for outside focus</button>
Here's a working example following #Northern and #Adam Šipický answers...
const tr = document.querySelector("table tbody tr");
tr.addEventListener('blur', () => {
setTimeout(() => {
if (!tr.contains(document.activeElement)) {
// Execute your condition code here...
}
}, 200);
}, true);
In 2021 you can probably avoid javascript altogether to check if an element or any of the element's child nodes have focus – unless you are manipulating DOM elements outside of a parent element.
For example:
<div class="parent">
<button>foo</button>
<button>food</button>
<button>foosh</button>
</div>
.parent { background: white }
.parent:focus-within { background: red }
.parent:focus-within button:not(:focus) { opacity: .5 }
None of these existing non CSS based solutions account for the situation where the JavaScript context does not match the frame the node was rendered in. To account for this you would want to do something like the following:
el.contains(el.ownerDocument.activeElement)
To retrieve the selected element you can use:
let activeElement = document.activeElement
To check a specific element:
let elem = document.getElementById('someId');
let isFocused = (document.activeElement === elem);
Having two Observables, one emits a mouseover event (debounced by 500ms) and the other one a mouseout event, I'm looking for a possibility to stop the first Observable (mouseover) from emiting when the second Observable (mouseout) occurs.
let mouseOutObservable = Observable.fromEvent($('.row'), 'mouseout')
Observable.fromEvent($('.row'), 'mouseover')
.debounceTime(500)
// .stopEmitingWhen(mouseOutObservable) --> how? possible?
.subscribe(event => {
// show tooltip
mouseOutObservable.first()
.subscribe(() => {
// destroy tooltip
});
});
takeUntil does exactly what you want.
Matt Burnell's and Ivan Malagon's suggested solutions work fine if no neighbouring elements. But my row elements do occur within a table. I did write my question kinda interpretable. Applying their code suggestion will unsubscribe/dispose the subscription completely but I do need a solution to stop only current emited value from arriving in subscribe.
However, both answers do solve my question above. ;-) Therefore I accepted Matt Burnell's short answer.
In order to include my additional requirement, I came up with another solution which merges both observable to one, followed by using a debounce time and continue only if the last event is a mouseover event.
Observable.fromEvent($('.row'), 'mouseover')
.merge(mouseOutObservable)
.debounceTime(500)
.filter(event => event[ 'type' ] === 'mouseover')
.subscribe(event => {
// ....
});
You can get the subscription object for the mouseover event and then dispose that subscription within the mouseout function.
let mouseOutObservable = Rx.Observable.fromEvent($('.row'), 'mouseout')
let mouseOverObservable = Rx.Observable.fromEvent($('.row'), 'mouseover')
.debounce(500);
let mouseOverObservableSubscription = mouseOverObservable.subscribe(() => { $('#output').append('<p>mouseover</p>'); });
mouseOutObservable.subscribe(() => {
$('#output').append('<p>mouseout</p>');
mouseOverObservableSubscription.dispose();
})
.row {
min-height: 48px;
background-color: orange;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.0.6/rx.all.js"></script>
<div class="row">Mouse over me!</div>
<div id="output"></div>