Reputation: 651
I am trying to listen for text changes in a custom component
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title></title>
<script type="text/javascript" src="component.js"></script>
</head>
<body>
<fancy-p>
<p>Bar</p>
<!-- the input should be listened to -->
<input id="foo" type="text" name="text" placeholder="Hello"></input>
</fancy-p>
</body>
</html>
component.js
customElements.define('fancy-p', class extends HTMLElement {
constructor() {
super();
this.attachShadow({mode: 'open'});
}
get foo() {
return "foo"
}
connectedCallback() {
this.shadowRoot.innerHTML = `
<style>
p {
color: red;
}
:host {
background-color: blue;
}
</style>
<div data-foo="bar">
<slot></slot>
</div>
`;
let input = this.querySelector("input");
console.log("input is:" + input);
}
});
I tried to listen for text changes and use a querySelector
in connectedCallback
but in Chrome 70.xx the selector returns null
.
It seems like when I set a breakpoint that the lightDOM is not filled (i.e. the slot) but then I don't know how to add a event listener to the input.
How can I do that???
Upvotes: 3
Views: 587
Reputation: 31181
When the connectedCallback()
method is called, the <input>
element is not appended to the light DOM yet. So you cannot get it with this.querySelector('input')
at that time.
It's not really a problem: you can listen to the {input}
event on the Custom Element itself, because the event will bubble to the parent element.
this.addEventListener( 'input', ev => console.log( ev.target, ev.target.value ) )
See the running example:
customElements.define('fancy-p', class extends HTMLElement {
constructor() {
super()
this.attachShadow({mode: 'open'})
.innerHTML = `
<style>
::slotted(p) { color: red; }
:host { display:inline-block; background-color: blue; }
</style>
<div data-foo="bar">
<slot></slot>
</div>`
this.addEventListener('input', ev => console.log( '%s value is %s', ev.target, ev.target.value))
}
})
<fancy-p>
<p>Bar</p>
<input id="foo" type="text" name="text" placeholder="Hello">
</fancy-p>
Upvotes: 1
Reputation: 10945
In your example the <input>
is placed into a <slot>
which means that the code outside of the custom element owns the <input>
tag.
In the code below you see that I am using document.querySelector
outside of the custom element to get the <input>
element and not using this.querySelector
or this.shadowRoot.querySelector
inside of the custom element code.
customElements.define('fancy-p', class extends HTMLElement {
constructor() {
super();
this.attachShadow({mode: 'open'});
}
get foo() {
return "foo"
}
connectedCallback() {
this.shadowRoot.innerHTML = `
<style>
p {
color: red;
}
:host {
background-color: blue;
}
</style>
<div data-foo="bar">
<slot></slot>
</div>
`;
let input = this.shadowRoot.querySelector("input");
console.log('inside input=',input); // input will be null
}
});
//Since the `<input>` tag is *owned* by the outside DOM you need to get the events from the outside:
let input = document.querySelector("input");
console.log('outside input=', input);
input.addEventListener('input', () => {
console.log("input is:" + input.value);
});
<fancy-p>
<p>Bar</p>
<input id="foo" type="text" name="text" placeholder="Hello"/></fancy-p>
If you want to access it through the shadowDOM then you need to define it in the shadowDOM:
customElements.define('fancy-p', class extends HTMLElement {
constructor() {
super();
this.attachShadow({mode: 'open'});
}
get foo() {
return "foo"
}
connectedCallback() {
this.shadowRoot.innerHTML = `
<style>
p {
color: red;
}
:host {
background-color: blue;
}
</style>
<div data-foo="bar">
<slot></slot>
<input id="foo" type="text" name="text" placeholder="Hello">
</div>
`;
let input = this.shadowRoot.querySelector("input");
console.log('inside input=',input);
input.addEventListener('input', () => {
console.log("input is:" + input.value);
});
}
});
<fancy-p>
<p>Bar</p>
</fancy-p>
Upvotes: 3