Problem with creating a div into the document (DOM) - vue.js

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);
}

Related

React does not recognize the `hasCreate` prop on a DOM element

Since migration to 4.2.7, all my list have an issue :
Warning: React does not recognize the hasCreate prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercase hascreate instead. If you accidentally passed it from a parent component, remove it from the DOM element.
at div
at http://localhost:3000/static/js/bundle.js:15429:66
at Toolbar (http://localhost:3000/static/js/bundle.js:49221:82)
at http://localhost:3000/static/js/bundle.js:15429:66
at TopToolbar
at ListActions (http://localhost:3000/static/js/bundle.js:62975:25)
at div
at http://localhost:3000/static/js/bundle.js:15429:66
at Toolbar (http://localhost:3000/static/js/bundle.js:49221:82)
How to solve this?
thank you
in the most of the cases its happend when you add your custom prop to exist component so the DOM is doesn’t know what is it, so you can add $ like in this example:
const Component = styled.div`
font-size: ${({ $isMobile }) => ($isMobile ? '9px' : '16px')};
`
<Component $isMobile={true} />

Insert scopedSlot into d3 svg using foreignObject

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 )

Prevent DOM reuse within lit-html/lit-element

I am looking for a way to NOT reuse DOM elements within lit-html/lit-element (yes, I know, I'm turning off one of the prime features). The particular scenario is moving an existing system to lit-element/lit-html that at certain points embeds the trumbowyg WYSIWYG editor. This editor attaches itself to a <div> tag made within lit-element and modifies its own internal DOM, but of course lit-html does not know that this has happened, so it will often reuse the same <div> tag instead of creating a new one. I am looking for something similar to the vue.js key attribute (e.g., preventing Vue from aggresively reusing dom-elements)
I feel like the live() directive in lit-html should be useful for this, but that guards against reuse based on a given attribute, and I want to prevent reuse even if all attributes are identical. Thanks!
I have had similar issues with rich text editors and contenteditable - due to how templates update the DOM you don't want that to be part of a template.
You do this by adding a new element with the non-Lit DOM and then adding that to the DOM that Lit does manage:
class TrumbowygEditor
extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({mode: 'open'});
const div = document.createElement('div');
shadow.appendChild(div);
const style = document.createElement('style');
// Add CSS required
shadow.appendChild(style);
$(div).trumbowyg(); //init
}
}
customElements.define('trumbowyg-editor', TrumbowygEditor);
As this is running in a custom element's shadow DOM Lit won't touch it, you can do:
html`
<div>Lit managed DOM</div>
<trumbowyg-editor></trumbowyg-editor>`;
However, you will have to implement properties and events on TrumbowygEditor to add everything you want to pass to or get from the nested jQuery component.
You can add the scripts with import if you can get module versions of jQuery/Trumbowyg (or your build tools support it) or you can add <script> tags to your component, add fallback loading DOM content in the constructor, and then on the load event of the <script> call the $(div).trumbowyg() to init the component.
While messier and more work I'd recommend the latter as both components are large and (thanks to jQuery being built on assumptions that are now 15 years old) need to load synchronously (<script async or <script defer don't work). Especially on slower connections Lit will be ready long before jQuery/Trumbowyg have loaded in, so you want <trumbowyg-editor> to look good (show spinner, layout in the right amount of space etc) while that's happening.
You write that you attach the external library directly to an element managed by lit-html. It sounds like you're doing essentially this:
render(html`<section><div id=target></div></section>`, document.body)
external_lib.render_to(document.querySelector("#target"))
If this is what you do instead try to create your own div, let the external lib render to that div, and finally attach that div to lit-html:
let target_div = document.createElement('div')
render(html`<section>${div}</section>`, document.body)
external_lib.render_to(target_div)
The most up-to-date answer to this problem is to use Lit's built-in keyed directive. This scenario is exactly what it's for:
https://lit.dev/docs/templates/directives/#keyed
Associates a renderable value with a unique key. When the key changes, the previous DOM is removed and disposed before rendering the next value, even if the value—such as a template—is the same.
#customElement('my-element')
class MyElement extends LitElement {
#property()
userId: string = '';
render() {
return html`
<div>
${keyed(this.userId, html`<user-card .userId=${this.userId}></user-card>`)}
</div>`;
}
}

Put a slot in every component in Vue.js

I have a component called vue-select that is a third-party packaged that I installed. I want to put a slot template in every instance of this component.
I mean I want to do something like this:
<v-select>
<span slot="no-options">
<li>sample text</li>
</span>
</v-select>
and I don't want to do this in every v-select that I have in my project.
How can I do this to dry my code ?
thank You :)
The slot is useful when you want to make parts of component's template different. If you always want it to be the same piece of template, then don't make it a slot. Simply add the markup you want into the template of the component.
This is similar to not putting something as an argument of a function if you don't want to be possible to change it.
function spin (element) {
const angle = 360
}
If you want an option to have some common content but still change it sometimes, put the default content in the <slot> tags in the template of the component.
This is similar to adding a default argument in a function:
function spin (element, angle = 360) { }
If you already have a third-party component which has defined slots and their default content, and thus you cannot change them, wrap them in a different component firstly and then use the wrapper component in the rest of the code.
This is similar to adding a new function which calls the previous one, but hard-codes some arguments.
function halfSpin (element) {
spin(element, 180)
}

VueJS find element by key

I've just wanted to know if it is possible to find a DOM element by the key attribute of vue?
I'm currently working with a list. I'm displaying it via v-for directive on a div. I'm binding the key with the index of the elements.
v-for="(apk, index) in project.apks" v-bind:key="index"
It would really help me if i could compute something for each of these elements as soon as they are fetch from my server and displayed. It's just parsing a file and looking for keyword, and accordingly choosing a css class for the items.
The problem is I dont know how to call a method for each of these elements as soon as they are added to the DOM. They are a lot of html events but i couldnt find one representing the object beeing inserted to dom :(
The purpose of key is not selecting element. Even if it can be done, don't do it.
The proper way to do it is by using ref.
for example, add ref attribute to your html like this
v-for="(apk, index) in project.apks" v-bind:key="index" :ref="'sample-ref-'+index"
and then in your methods, you can get the DOM using this.$refs['sample-ref-0'],this.$refs['sample-ref-1'] and so on.
Hope this helps.
I found that if you give the 'ref' the same name in a v-for, like this:
<div v-for="data in list" :key="data.id" ref="bar"></div>
Then you will find they just store in an array called 'bar' and you can visit them by:
this.$refs['bar'][index]
something like this could allow you to find a component by key :
this.$children.forEach(child=>{
print("child.$vnode.key")
})
also use mounted , as it gets called when the component is added to the dom:
mounted:function(){
this.$el.querySelector("#ele1")...
}
The problem is I dont know how to call a method for each of these elements as soon as they are added to the DOM. They are a lot of html events but i couldnt find one representing the object beeing inserted to dom :(
You can create a new component with your v-for and just call the created() hook.
Example
/* On your main function */
<div v-for="apk in project.apks">
<apk :data="apk"></apk>
</div>
/* On your 'apk' component */
export default {
props: [ "data" ],
created() {
console.log("Created !");
}
}