I would like to know how to eliminate the mouseOver event from the template by using $ref. I want to control mouseOver behavior inside javascript instead.
Components component has myStats child component. myStats is supposed to be displayed only when I hover over Components.
I am not sure how make ref do what the commented out template code does.
Do I have to use a function of myStats (= onMouseEvent)? It would be easier if I can control mouseOver only inside Components
Components code:
<template>
<div class="pv-lookup" ref="myStats">
<!-- I would like to get rid of the commented out mouse hover event code below by using $ref: -->
<!-- <div #mouseover="mouseOver = true" #mouseout="mouseOver = false"> -->
<div>
<someComponent1 />
<someComponent2 />
<myStats v-if="mouseOver" #mouseMoved="onMouseMoved"/>
</div>
</div>
</template>
<script>
export default class Components extends Vue {
public mouseOver = false;
#Emit()
public onMouseMoved() {
this.mouseOver = !this.mouseOver;
}
mounted() {
this.$nextTick(() => {
if (this.isInhouseClient || this.isInhouse) {
// TODO: set value for this.mouseOver
// Approach 1:
// ERROR: Property 'onMouseEvent' does not exist on type 'Vue | Element | Vue[] | Element[]'.
this.$refs.myStats.onMouseEvent();
// Approach 2:
// ERROR: Property 'onMouseEvent' does not exist on type 'MouseEvent'. Did you mean 'initMouseEvent'?
const myStats= this.$refs.myStatsas HTMLElement;
myStats.addEventListener("mouseover", function( event ) {
event.onMouseEvent();
}, false);
// Approach 3:
// It's the simplest, but how do I connect with 'ref'?
this.mouseOver = !this.mouseOver;
}
});
}
}
</script>
myStats code:
<script>
export default class myStats extends Vue {
public mouseOver: boolean = false;
#Emit()
public mouseMoved(){}
public onMouseEvent() {
this.mouseMoved();
}
}
</script>
Thank you for your insights
You can simply add event listeners for the same events (mouseover and mouseout) to the DOM element. Beware though, these events bubble up from children. In your case it would be better to use mouseenter and mouseleave.
So your code will be:
mounted() {
const myStatsEl = this.$refs.myStats; // get DOM element of the component Components
myStatsEl.addEventListener("mouseenter", (event) => {
this.mouseOver = true;
});
myStatsEl.addEventListener("mouseleave", (event) => {
this.mouseOver = false;
});
},
No need to emit events from myStats component or use its functions.
That being said, the correct 'Vue-way' of doing this would be with #mouseenter and #mouseleave, which is much cleaner. I'm not sure why you wouldn't want to use that. I can't think of any cases where this way might be limiting.
Related
I'm working with Lit Element and I'm trying add an event listener on 'Click' that will a variable state that will set the dropdown to be expand or not. But once the drop down is 'closed' I want to remove that event to avoid unnecessary event calls on 'Click.
Adding the event works great but I cannot remove it.
Here is the idea:
public willUpdate(changedProps: PropertyValues) {
super.willUpdate(changedProps);
if (changedProps.has("_tenantsExpanded")) {
document.removeEventListener("click", (ev) => this._eventLogic(ev, this));
if (this._tenantsExpanded)
document.addEventListener("click", (ev) => this._eventLogic(ev, this));
}
}
The fct logic:
private _eventLogic(e: MouseEvent, component: this) {
const targets = e.composedPath() as Element[];
if (!targets.some((target) => target.className?.includes("tenant"))) {
component._tenantsExpanded = false;
}
}
Code in my render's function:
${this._tenantsExpanded
? html` <div class="tenants-content">${this._tenantsContent()}</div> `
: html``}
Important note: I want the click event to be listened on all the window, not just the component itself. The same for removing the event.
PS: I don't know why e.currentTaget.className doesn't give me the actual className, but results to an undefined.
When you use removeEventListener you have to pass a reference to the same function you used when adding the listener.
In this example the function is stored in fn.
(You might have to change the this reference here, it depends a bit on your whole component).
const fn = (ev) => this._eventLogic(ev, this);
document.addEventListener("click", fn);
document.removeEventListener("click", fn);
I have a component called button.tsx, this components holds a function that does certain things when the button is clicked, this.saveSearch triggers the saveSearch() function.
button.tsx
{((this.test1) || this.selectedExistingId) &&
(<button class="pdp-button primary"
onClick={this.saveSearch}>{this.langSave}</button>)
}
In sentence.tsx i want to be able to see when this button is clicked and show a certain div if the user has clicked it.
sentence.tsx
{onClick={saveSearch} && (<div class="styles-before-arrow">{this.langConfirmSearchSaved}</div>)}
You have a few options:
You can attach a click event listener for the button component in sentence.tsx. Take note that this may be trickier if you are working with elements which are encapsulated in Shadow DOM:
addButtonLister(): void {
document.querySelector('.pdp-button')
.addEventListener('click'), (e) => {
// add your logic here.
});
}
You can use EventEmitter (https://stenciljs.com/docs/events#events). In your button.tsx, you can add this:
#Event({eventName: 'button-event'}) customEvent: EventEmitter;
Then add something like this on button's onClick:
emitEvent() {
customEvent.emit('clicked');
}
render () {
return <button onClick={this.emitEvent}>{this.langSave}</button>
}
then from your sentence.tsx, add an event listener to your button component:
// say your button component's tag is <button-component>
document.querySelector('button-component')
.addEventListener('button-event', (e) => {
// your logic here.
});
You can use Stencil Store, but depending on your overall use-case, I am not sure if this may be an overkill - https://stenciljs.com/docs/stencil-store#store-state
As you may already know, mouseenter and mouseleave events are NOT triggered if the mouse doesn't move, which means that if you scroll over an element without moving the mouse, hover effects are ignored.
This answer describes the strategy to overcome this:
1: Add a scroll listener to the window.
2: In the handler, call document.elementsFromPoint.
3: Manually call the actual mouseover handler for those elements.
4: Manually call the actual mouseleave handler for elements no longer being hovered.
We also must take into account that registering a listener for each component we want to detect the hover of, is a waste of resource.
My idea is to create a singleton object and subscribe to it from each component.
I'm fairly new to react so I will try to write pseudo-react-code to describe my idea:
// A global singleton object that registers the listeners ONCE.
class Singleton {
subscribedElements = {}
subscribe(element, isHovered, setIsHovered) {
subscribedElements[element] = {
isHovered: isHovered,
setIsHovered: setIsHovered
}
}
unsubscribe(element) { ..... }
constructor() {
window.addEventListener('scroll', this.handleScroll);
window.addEventListener('mousemove', this.handleMove);
}
handleMove() {
//omitted
}
handleScroll() {
hoveredElements = document.elementsFromPoint(mousePosition)
// Iterate every element that is subscribed and check if they are in the list of elements that are hovered.
subscribedElements.map( e => {
if (hoveredElements.contains(e)) {
subscribedElements[e].setIsHovered(true)
} else {
subscribedElements[e].setIsHovered(false)
}
})
}
}
// a hook to be used in all elements that want to detect if they are hovered
function useHovered(element) {
[isHovered, setIsHovered] = useState(false);
Singleton.subscribe(element, isHovered, setIsHovered);
return isHovered;
}
// All components that want to check if they are being hovered would do this
function MyComponent(props) {
isHovered = useHovered(this);
return <div>{ isHovered ? 'hovered' : 'not hovered'</div>
}
Now, what is the most efficient and clean way to do something like this?
I have read about useContext but I'm not sure how to apply it to this solution.
I'm not sure how to create this singleton. Does it have to be a component? Can it be done in the new functional way?
I've got the following controller on my HTML page:
...
<div data-controller="parent">
<div data-target="parent.myDiv">
<div data-controller="child">
<span data-target="child.mySpan"></span>
</div>
</div>
</div>
...
This child controller is mapped to the following child_controller.js class:
export default class {
static targets = ["mySpan"];
connect() {
document.addEventListener("myEvent", (event) => this.handleMyEvent(event));
}
handleMyEvent(event) {
console.log(event);
this.mySpanTarget; // Manipulate the span. No problem.
}
}
As you can see, there is an event listener on the connect() of the Stimulus controller, and when it detects that the event was fired up, it logs the event and manipulates the span target.
The problem arises when I replace the contents of the target myDiv from my parent_controller.js:
...
let childControllerHTML = "<div data-controller="child">...</div>"
myDivTarget.innerHTML= childControllerHTML;
...
Now that the myEvent gets fired up, the event listener picks it not once, but twice (because the same event got logged twice). With every subsequent replacement of the child HTML, the event gets logged one more time than it did before.
I know that one can make use of document.removeEventListener to prevent the old controller from still listening to the events:
export default class {
static targets = ["mySpan"];
connect() {
this.myEventListener = document.addEventListener("myEvent", (event) => this.handleMyEvent(event));
}
disconnect() {
document.removeEventListener("myEvent", this.myEventListener);
}
handleMyEvent(event) {
console.log(event);
this.mySpanTarget; // FAILS. Can't find span.
}
}
But doing it like this makes the handleMyEvent method lose the context as it no longer finds the mySpanTarget under this.
How can I remove the listener from the child controller to which I already got no access as it is no longer in the DOM, while retaining the context?
I found the answer on StimulusJS's Discourse page.
One has to make use of the bind method when initializing the controller:
export default class {
static targets = ["mySpan"];
initialize() {
this.boundHandleMyEvent = this.handleMyEvent.bind(this);
}
connect() {
document.addEventListener("myEvent", this.boundHandleMyEvent);
}
disconnect() {
document.removeEventListener("myEvent", this.boundHandleMyEvent);
}
handleMyEvent(event) {
console.log(event);
this.mySpanTarget; // Manipulate the span. No problem.
}
...
}
Now, the event is only listened once, and the context is not lost inside the handleMyEvent method.
I'm using Ionic 3 that comes with Angular 4. I built a component that takes the original (click) event from current element and binds it to a new one (dinamically created in the component). What I'm trying to do, is to remove the original click event from current component.
HTML
<!-- imageZoom() is defined in my view controller: page.ts -->
<inline-spinner
[src]="http://..."
(click)="imageZoom($event)"></inline-spinner>
COMPONENT
export class InlineSpinnerComponent implements OnChanges {
public img: any;
#Output() click: EventEmitter<any> = new EventEmitter();
constructor() {
this.img = new Image();
// I want to unbind/remove "click" from here if possible.
// this.elementRef.nativeElement = <inline-spinner>
}
ngOnChanges() {
// Just to show what I'm doing with the original click event
this.img.addEventListener('click', () => { this.click.emit(this.img); });
}
}
Any help will be really appreciated. Thanks!
I guess your are looking for something like this. This should be in a directive, that you attach to the element on which you want neutralize the click.
It will check if the the click event comes from the concerned component only and in that case only do Nothing
#HostListener('document:click', ['$event', '$event.target'])
onClick(event: Event, targetElement: HTMLElement): void {
if (!targetElement) {
return;
}
if(this.elementRef.nativeElement ==== targetElement){
event.preventDefault();
}
}