Use document in custom element (web component) - javascript

I have two separate custom element components and if I want to do something with the ComponentB's element inside of ComponentA, what is the best way to do it?
index.html
<body>
<componentA>
<div class="container">
<p>I am component A</p>
<p>abc</p>
</div>
</componentA>
<componentB>
<div class="container">
<p>I am component B</p>
<p></p> // Will set data between the p tag in componentA
</div>
</componentB>
</body>
componentA.js
...component template...
class ComponentA extends HTMLElement {
constructor() {
super();
this.attachShadow({mode: 'open'});
this.shadowRoot.appendChild(template.content.cloneNode(true));
}
connectedCallback() {
this.setInnerTextToComponentBElement();
}
setInnerTextToComponentBElement(){
// How to set componentB's second p tag innerText?
}
}
customElements.define('componentA', ComponentA );
I have thought of using document.querySelector to get the componentB element and go from there... but is this the best practice way to do it?

I would suggest NEVER tying two components together without allowing the developer using the components to provide a way to connect them.
Normally communications or interconnections between elements is handled by the parent. The only element that I know of that does this by itself is the <label> element. If this is the parent of an <input>, <select>, <button> or <textarea> then it will pass its focus on to the child element.
But to use it with a sibling element you have to set the for attribute of the <label> to be the id of the other field. And then the other field needs to have its id set.
I answered something like this here:
How to communicate between Web Components (native UI)?
Once you tie two components together these components are no longer usable by themselves unless you write extra code to allow separation.
Instead, either allow the components to dispatch events that the parent will receive and the parent will then pass values on to the other component. Or, follow the example of how <label for=""> works.

Related

How to apply CSS class to child component with two root nodes?

I created a custom alert component using Vue 3 with Vuetify 3 with two root nodes
<template>
<v-alert type="warning" title="Contains button to display dialog" />
<v-dialog>dialog goes here</v-dialog>
</template>
The v-alert component contains a button to toggle the state of the v-dialog component.
In my parent component ( the consuming one ) I would like to apply a CSS class to the child component ( my custom alert component )
<MyAlert class="mb-8" />
<div>Main content goes here</div>
The problem is that this doesn't work, I get the warning
[Vue warn]: Extraneous non-props attributes (class) were passed to component but could not be automatically inherited because component renders fragment or text root nodes
Which makes sense because it doesn't know which component should have this class, both, either alert or dialog or none.
A quick fix would be to apply the class mt-8 to the div below. But my alert component is conditional so I would have to use an if-statement to check if I should apply a margin-top or not.
Reproduction link: https://play.vuetifyjs.com/#eNp9UstuwjAQ/BXLFy4krvpShVIE4swXYA4hGHCJH7I3QRHKv3cdB0ih7SmZnfXseD2rM51bm9aVoBOagVC2zEFMuSYkq5Pc2u63AyqXukeIl828FA5IUebef3KqNskHp4TdOrayni7xDCmMBqGB7I3w5CCcyFjgemE2UA4gzszYwAtCXzhpgXgBVaClsgaHn4kTO9KSnTOKjPASoyt18ReplC0OstyGe2JLxqIcCtExjQfQhE2/vNG4h3PwwnvCczohXSXUUCBgTg8A1k8Yq7Q97tPCKDZDjrlKg1Qi2Ro1e0mf09c3vKuHYT0VXiUbZ05eOJzI6XggDnLX/DWgp1H3KX2Psn0pSD5IMdSvhUuc0FtcuvvX913vD+933IP/MLPlusVdllIf/d0aC9+tcLWOLdeH+DVw3ZtBYwVG6pQ7LfUeUwUSylBZYJIwK55sKgCjCRiCDlGhwW9emtAbE4hasdLHLIJbBC9puzYNAkfbMY33R7vdsun6G20jDc0=
Is it possible to tell Vue it should use the applied class for the inner v-alert component?
So, you are trying to assign classes to a component, but your component contains fragments.
Wrap your Child.vue component tags:
<template>
<div>
<v-alert type="warning" title="Contains button to display dialog" />
<v-dialog>
dialog goes here
</v-dialog>
</div>
</template>
Yes you can!
First thing you need to do is to specify the
inheritAttrs: false
option in your alert component:
<script>
export default {
inheritAttrs: false,
}
</script>
and than you can apply class attribute to the desired component using
:class="$attrs.class"
But you must know, that now every other attribute must be specified in code using $attrs, because your component would not apply them by default anymore
More on this topic can be found here Vue Docs

Move child of shadow dom with slots out of the web component

I'm trying to move a child of a shadow dom of a web component outside it, and has been able to do it if that child has not slots inside.
Let me ilustrate with an example. I have the following 2 web components:
customElements.define('a-child', class extends HTMLElement {
constructor() {
super()
this.attachShadow({mode: 'open'})
this.shadowRoot.innerHTML = `
<slot></slot>
`
}
})
customElements.define('a-parent', class extends HTMLElement {
constructor() {
super()
this.attachShadow({mode: 'open'})
this.shadowRoot.innerHTML = `
<slot name="outside"></slot>
<a-child id=child>
<div>
<slot name="inside"></slot>
</div>
</a-child>
`
}
move() {
const child = this.shadowRoot.getElementById('child')
document.body.append(child)
}
})
And if I have the following HTML, I get what I want: even when slot inside is inside the a-child component in the Shadow DOM from a-parent, it shows up correctly.
<a-parent id="parent">
<div slot="outside">outside</div>
<div slot="inside">inside</div>
</a-parent>
If I run the method move from element with Id parent, the a-child element is moved to the body and I was expecting to see the div with text inside in the body, but it doesn't happen. It seems that the slots only work when they are inside their web component.
My question is, is it possible to move a child of a Shadow DOM containing slots, outside of its web component parent?
You are right. Slots only work when they are inside their enclosing Web Components. In other words, the slots should be physically present in the DOM tree under the web component node.
Further, it is possible to move the children of Shadow DOM but if they contain slots, then it won't work. It will just fail silently without any errors.
That is one of the reasons why it is annoying. Read more for similar question. Also, this twitter thread contains some more related information. In summary, you need to move slot elements outside the component for achieving perfect stacking context for creating components like dialogs, menus, drop-downs, etc.

Using the same component in a child and parent in Vue

I have a VueJS project - which in one view there are parent and child components that are both using the same component called deleteModal.
When I trigger the modal from the child (to show it), it triggers both the child and parent modals (except no data is passed to the modal triggered by the parent). As I understand it, this is because I have used the component twice - in the parent and child - example below. To note, it works as expected from the paren
I've researched and tried a few things: setting a key value for each of the components, changing the components ref name among other things. I have also tried using v-show to only show component just before I trigger the parent model however, this solution is not ideal.
How can I only trigger the modal from the child?
Parent Component
<template>
<div>
<childCompt ref="childCompt" />
<deleteModal
ref="deleteModal"
#deleteTriggerAPI="deleteAPIParent"
/>
</div>
<template>
Child Component - childCompt
<template>
<div>
<deleteModal
ref="deleteModal"
#deleteTriggerAPI="deleteAPIChild"
/>
</div>
<template>
My old answear is not good at all. I personally to show and hide element using jquery in vue. For me right now this is best solution but maybe i don't know some best.
If you want use only vue i using also variable passing to child from parent which will support visable of your modal.
We pass variable with ":" and register event with "#".
<template>
<childComponent :isModalOpen="isModalOpen" #onModalClose="isModalOpen=false">
<template>
export default {
name:"parent",
data: () => {
isModalOpen: false
}
}
In child we catch this by using props. We need to define type of varialbe we pass. Different between props and data is that in props we cannot change value in child component.
export default {
name: "child",
props: {
isModalOpen: Boolean
}
}
And you can use this variable to show or hide modal. Also in child component we can create button to close modal and we emit event to parent in order to change variable value.
To do this we using this.$emit('eventName');
More information right here: https://forum.vuejs.org/t/passing-data-back-to-parent/1201
You could try globally defining the component,
ie, in main.js
Vue.component('deleteModal',deleteModal)

How to get an Element bind from a svelte component

I need the bounding client rect of an anchor element.
I mark that using bind:this={someVariable}
But when I add that code to a svelte component, I get some other object svelte uses to represent a component.
can I access the enclosed element or something like that from that binding?
Or do I have to wrap every component with a sacrificial <div /> and bind that?
Using bind:this does not give you a DOM Element because Svelte does not require that your component has a root element. This is a valid Svelte element:
<script>
...
</script>
<div>...</div>
<div>...</div>
And so is a component that does not have any markup at all:
<script>
...
</script>
In both those cases, it would be impossible to return a bounding client rect for these components because there is no 'root' element.
That said, what you can do is add a bind:this in your component and export that one:
<script>
export let anchor
</script>
<svelte:options accessors></svelte:options>
<a bind:this={anchor}>...</a>
And in your parent you can now get the anchor element using child.anchor (note that by default the props are not accessible this way, that's why you need the svelte:options)
One of the reason this is not so straightforward though is that it is a very 'unsvelte' way of working, you would normally not direcly interact with the markup generated by another component (that is the domain of that component).
Alternatives could be
expose the client rectangle directly instead of the element
move what you are trying to do to the anchor component itself

How can I access existing dom elements when using react render?

I will rendering a React component in to an existing website, an example of the root element might be:
<div id="root"></div>
What I would like to do is, access any html inside the root element to begin with for example:
<div id="root">Hello world</div>
The documentation seems to indicate you can do something along these lines:
ReactDOM.render() does not modify the container node (only modifies
the children of the container). It may be possible to insert a
component to an existing DOM node without overwriting the existing
children.
I have tried to access using refs but I can't access these child DOM elements, does anyone know how I can achieve this?
<div id="root" ref="root">Hello world</div>
Unfortunately this isn't possible in ReactDOM directly.
The "Hello world" string in your <div id="root"> is treated as a child of the container node, and is therefore overwritten.
Instead, you'll need to get the .innerHTML from the div with the ID of root before ReactDOM's render() method is called. You can then do whatever you want with this afterwards:
let root = document.getElementById("root");
let existingContent = root.innerHTML;
ReactDOM.render(yourReactComponent, root);
console.log(existingContent); // Do whatever you need to do with this

Categories