What is the alternative to document.getElementById in React Native? I want to access the element in the DOM. I am using a functional component.
You can use refs. Lets say you have a div, and you want to get a DOM reference.
const MyFuncComp = React.memo(() => {
const divRef = React.useRef(null)
/* your other logic here */
/* refer the div node using divRef.current - its the HTML DOM Node.
return (
<div ref={divRef.current}>
// whatever you want to render
</div>
)
})
Resources: https://reactjs.org/docs/refs-and-the-dom.html
Related
Adding a svelte component (Button) statically in the body section works. Adding the Button via appendChild does not?
Details:
Imagine a database table. For each row I add a line into my HTML body.
How could I add a svelte component (Button.svelte) to each row, too?
The problem: Standard HTML gets appended, but my svelte Button does not. (Probably because svelte needs to render at compile time.)
For example in +page.svelte:
const e = document.getElementById('my_div_container');
if(e)
{
const p = document.createElement("p");
const txt = document.createTextNode("test node");
p.appendChild(txt);
e.appendChild(p); // <-- ok, gets displayed
const b = document.createElement("Button");
e.appendChild(b); // <-- NOT displayed
}
Example lib/Button.svelte:
<script>
function on_click()
{
console.log('clicked');
}
</script>
<button on:click={() => on_click()}>Click</button>
FYI: Statically adding a button to the HTML body works of course:
<p>Some text</p>
<Button />
You can either build components as custom elements, then they can be created with createElement or just use the client component API. You just need to import the component and construct it, setting the target to the element you want to add it to:
new Button({ target: e })
I have a stenciljs component that has a nested stenciljs component:
<c-button-group>
<c-button></c-button>
</c-button-group>
In c-button-group template, I'm not using a slot and instead I'm using #Element() private element: HTMLElement to get all the nested elements after I render them in a loop.
#Component({
tag: 'c-button-group'
})
export class CButtonGroup {
#Element() private element: HTMLElement
render() {
return (
Array.from(this.element.children)
.map((child) => {
const el = child as ButtonInterface
return <c-button variant="grouped">{el.textContent}</c-button>
})
)
}
}
The reason why i use loop because i have to add attribute variant="grouped" for each nested element and i want to add it here, in the template. So this works but i noticed that if i assign a click event handler it doesn't work.
<body>
<c-button-group>
<c-button id="cBtn" #click="btnClickHandler">Years</c-button>
</c-button-group>
<script>
document.querySelector('#cBtn').addEventListener('click', () => {
console.log('Years')
})
</script>
</body>
Click event above doesn't work.
And seems this is obvious because in the render function i create a new 'c-button' based on a nested c-button.
My question is how do I pass all events that were assigned from the nested component to the new component that was created in the render function?
PS:
I noticed that if i use on-click attribute without a value to new c-button element, click event works:
Array.from(this.element.children)
.map((child) => {
const el = child as ButtonInterface
return <c-button
variant="grouped"
on-click
>{el.textContent}</c-button>
})
I just added on-click and it started working but in console i started getting errors:
TypeError: Failed to execute 'addEventListener' on 'EventTarget': parameter 2 is not of type 'Object'.
So at first this is not an option because i am getting that error and at second what if i need not only 'click' event, maybe there will be 5 or 10 events, so in that case i will have to add them all manually, not very comfortable to say the least.
Thank you so much in advance!
You can try using outerHTML and innerHTML
render() {
return (
Array.from(this.element.children)
.map((child) => {
child.setAttribute('variant', 'grouped')
return <div innerHTML={child.outerHTML}></div>
})
)
}
However this will only be able to pass events which are attached by attribute instead of addEventListener, as outerHTML is a string
<c-button-group>
<c-button id="cBtn" onclick="btnClickHandler()">Years</c-button>
</c-button-group>
Here btnClickHandler will work for rendered children elements
So the only way i found myself is to use <slot /> and #Element() private element: HTMLElement together.
#Element() private element: HTMLElement
componentWillLoad() {
Array.from(this.element.children).forEach(el => {
el.setAttribute('variant', 'grouped')
})
}
render() {
return <slot />
}
Using <slot /> allows as to use all events and using this.element.children gives the ability to edit DOM elements before their rendering, we can remove, add any attribute, class, and so on...
I'm creating a chart component using d3js and Vue (v2). In some parts, I want to support custom user content using scoped slots (in this case, custom tooltips)
<my-chart>
<template slot="tooltip" slot-scope="{ data }">
<span>{{ data }}</span>
</template>
</my-chart>
But I'm struggling to render this using d3js on a vuejs component render function. I'm trying to do something like:
g?.append("foreignObject").html(
this.$scopedSlots["tooltip"]({
event,
}),
);
Obviously, the html method isn't appropriated. I can't find anything like this online. All other examples use the component template to insert the foreignObject and Vue component on the SVG. Nothing using d3js
EDIT
As user I refer to developers. This code is for a lib.
Just in case anyone wants to implement something like this, I manage to resolve my issue.
Background
SVG has a concept of foreignObject which allows me to inject HTML inside an SVG. The next step is, somehow, to render the Vue component to HTML.
I'm using vue2, Vuetify, and d3js v6
Rendering the component
this.$scopedSlots["tooltip"]({
event,
}),
returns a VNode[], so using Vue.extend I create a new component, instantiate it and mount it to a div inside the foreignObject like this:
// Call the scoped slot, which returns a vnode[]
const vnodes = this.$scopedSlots["tooltip"]({
event,
});
const id = "RANDOM_GENERATED_ID";
// Append a foreignObject to an g
const fo = g?.append("foreignObject");
// Append a div to the foreignObject, which will be the vue component mount point
fo?.append("xhtml:div").attr("id", id);
// Create a new component which only render our vnodes inside a div
const component = Vue.extend({
render: (h) => {
return h(
"div",
{staticClass: "foreign-object-container"}
vnodes,
);
},
});
// Create a instance of this new component
const c = new component();
// I'm using vuetify, so I inject it here
c.$vuetify = this.$vuetify;
// Mount the component. This call will render the HTML
c.$mount("#" + id);
// Get the component rect
const bbox = c?.$el.getBoundingClientRect();
// Resize the ForeignObject to match our injected component size
return fo
?.attr("width", bbox?.width ?? 0)
.attr("height", bbox?.height ?? 0);
This successfully renders our component inside an SVG using d3js. At least it appears inside the DOM.
Problems
At this point, I faced 2 new problems.
Invalid component size
After rendering the Vue component inside the foreignObject it reported width equals 0. So, based on this I used the next styles:
.foreign-object-container {
display: inline-flex;
overflow: hidden;
}
And voilá, habemus a visible Vue component.
Scroll, ForeignObject, and the old Webkit Bug
My use case is this: The chart is responsive, so it re-renders after every container resizes (with some debounce), but to prevent deformations I set a minimum width to every element. With some screen sizes, this provokes some overflow, which inserts a scrollbar (browser behavior).
This is exactly what I want. But I'm using some Vuetify components on my scopedSlot which have a position: relative style.
Enters, an old bug on WebKit (Google Chrome, Safari, and Edge Chromium). This is better explained here and here
The solution in my case is simple. As I stated before, my foreignObject was resized to match the rendered component. So, to prevent my components to be wrongly drawn, I change my styles a little bit.
.foreign-object-container {
display: inline-flex;
overflow: hidden;
position: sticky;
position: -webkit-sticky;
}
Now, my teammates can use a generic chart with scroll support and customize some pieces of it using any Vue component (at least for now )
I have some problems with this fragment, it creates a div into the DOM so that div appears in every page as it is normal, which is an image.
How can I change it so this div is only being created when I access at this custom component (Model.vue) and no longer being visible once I'm out of the component page.
Thanks!!
container = document.createElement( 'div' );
document.body.appendChild( container );
If your HTML is predictable you should probably add it in your template (as HTML or as a component) and then show/hide it with v-if or v-show
If you need static content, for example depending on the user input, then v-html is the way.
If you need dynamic content (e.g. with event bindings), then check out render functions.
There's also dynamic component like <component :is="someComponent">, where someComponent can be either a string name of the component, or the component itself. But you should probably try the other solution first.
First of all direct DOM manipulation(Add/Del elements) is not at all preferred in vuejs. You can use v-if instead to show the element conditionally.
Or else if you still want to do direct manipulation then you can write the above lines of code in mounted hook of your model.vue component.
mounted() {
container = document.createElement('div');
container.setAttribute("id", "divId");
}
And then in before destroyed hook you can remove this element from the DOM as below.
beforeDestroy() {
var elem = document.querySelector('#divId');
elem.parentNode.removeChild(elem);
}
I have a global directive in vue js
Vue.directive('customDirective',{
bind(el,binding,vnode){
// need to get the parent
// append a div after the input element
}
})
I have the following html code .
<div class="parentDiv">
<input type="text" name="firstName" v-customDirective="Some text"/>
</div>
<div class="parentDiv"> is loading dynamically via jquery. I need to get the parent of input tag and need to append some html after the input tag. I have tried with parent(), but it is showing as parent is not a function. Any suggestions ?
You should probably use the inserted hook instead of bind if you want to access the parent element.
You can get the parent element via el.parentElement.
You probably should use vnode, and Vue nextTick.
import Vue from 'vue';
Vue.directive('customDirective',{
bind(el,binding,vnode){
Vue.bextTick(() => {
let parent = vnode.elm.parentElement;
});
}
})
If you want to use parent() which is the jQuery function, you must get the element by jQuery.
Vue.directive('customDirective',{
bind(el,binding,vnode){
Vue.bextTick(() => {
let id = vnode.elm.id;
let parent = jQuery('#'+id).parent();
});
}
})
I'm not sure, that second code works, but it will be something like that.