Hello I took over the vue project and right now i'm working on module that dynamicly adds and delate sections. Each section has opton to ceate elements also by v-for. My problem is that buttons for delateing elemnts don't konw in which section exists.
symptoms:
In first section i'm adding 3 elements and console returns
this.$ref.index > (3) [tr, tr, tr]
nex i'm creating another section and adding one elemnt then clg shows
this.$ref.index > (4) [tr, tr, tr, tr,]
i't should't be like that righ?
do you have any ide how to compare elemnts with right sections
Visualisation of components
Based on your given visualization of layout, I believe the structure of your element should be:
Main (adds Sestions and listens for Sestion removal)
Sestion (adds Elements, listens for Element removal, emit remove event)
Element (emit remove event)
Element
Sestion
Element
Element
the code would look something like this:
/** Sestion.vue **/
<template>
<div>
<ElementCard
v-for="(element, index) in elements"
:key="index"
#remove="onRemoveElement(index)"
/>
<button #click="onAddElement">
Add
</button>
<button #click="$emit('remove')">
Remove
</button>
</div>
</template>
<script>
export default {
data() {
return {
elements: []
}
},
methods: {
onAddElement() {
this.elements.push();
},
onRemoveElement(index) {
this.elements.splice(index, 1)
}
}
}
</script>
Related
I try to understand how the mounted hook works on Vue.js. If I understand correctly, the mounted function is called when all DOM elements are rendered. So in this scenario, I will have access to the DOM elements with document.getElementById or this.$el.somenthing.
Is this right?
But why I get the following behavior? Lets say I have the following structure:
mounted() {
this.buildTextTargetWidth();
},
methods: {
buildTextTargetWidth() {
console.log(document.getElementsByClassName('typographyTextPreview')); // Empty HTML-Collection. But, curiously, when I open the array in the console, there is the right element.
console.log(document.getElementsByClassName('typographyTextPreview')[0]); // Undefined
},
}
So far so good. The result is undefined. But for my understanding it should not!
Lets try to add some async stuff to wait 500ms:
mounted() {
this.buildTextTargetWidth();
},
methods: {
async buildTextTargetWidth() {
await new Promise(r => setTimeout(r, 500));
console.log(document.getElementById('test')); // The element is there
},
}
Now the element is there!
But why I have to wait other 500ms to get the DOM data if the mounted() is calling after the DOM is ready? It makes no sense for me.
By the way: The element typographyTextPreview which I try to call is embedded inside a v-for. Could this be the reason for this behavior? If yes, how could I wait until the v-for is ready?
--EDIT--
I wanted to create this topic as clean and simple as possible. But here I add additional data from the component, for example the entire for loop where the element typographyTextPreview is included:
<div ref="builderLayer" class="vconf-create-builder column" :class="getColumnLength">
<div class="vconf-create-builder-layers" :key="layersKey">
<div v-for="layer of getLayers" class="vconf-create-builder-layer" :key="layer.uid" :style="{ background: 'url(' + getLayerImage(layer) + ') center / contain no-repeat' }">
<template v-for="tile of layer.data">
<!-- <VueDragResize :key="tile.uid" v-if="tile.type=='typography'" :parentLimitation="true" :isActive="true" :x="calculatePositionX(tile)" :y="calculatePositionY(tile)" :w="tile.editBoxWidth" :h="tile.editBoxHeight" #resizestop="updateTypoBoxSize($event, layer, tile)" #dragstop="updateTypoBoxPosition($event, layer, tile)">
<h2 class="typographyTextPreview" :style="{'font-size':calculateFontSize(tile) + 'px', color: tile.fontColor}">{{tile.text ? tile.text : "Please enter an initial text in the settings"}}</h2>
</VueDragResize> -->
<div v-if="tile.type=='typography'" class="container" :key="tile.uid">
<div class="target" :style="buildTextTargetStyle(tile)">
<h2 id="test" class="typographyTextPreview" :style="{color: tile.fontColor}">{{tile.text ? tile.text : "Please enter an initial text in the settings"}}</h2>
</div>
<Moveable
className="moveable"
:target="['.target']"
:keepRatio="true"
:throttleResize="1"
:bounds=' {"left":0,"top":0, "right": clientWidth, "bottom": clientHeight}'
:origin="true"
:snappable="true"
:zoom="1"
:draggable="true"
:scalable="true"
:rotatable="true"
#drag="onDrag($event, tile)"
#scale="onScale($event, tile)"
#rotate="onRotate($event, tile)"
/>
</div>
</template>
</div>
</div>
</div>
From the docs:
A component is considered mounted after:
All of its synchronous child components have been mounted (does not include async components or components inside trees).
Its own DOM tree has been created and inserted into the parent container. Note it only guarantees that the component's DOM tree is in-document if the application's root container is also in-document.
If you need to wait for DOM changes, I suggest you to use the nextTick utility, which waits for the DOM to be updated. More details here
I'm trying to add components to the DOM dynamically on user input. I effectively have a situation with ±200 buttons/triggers which, when clicked, need to create/show an instance of childComponent (which is a sort of infowindow/modal).
I would also then need to be able to remove/hide them later when the user 'closes' the component.
I'm imagining something like this?
<template>
<div ref="container">
<button #click="createComponent(1)" />
...
<button #click="createComponent(n)" />
<childComponent ref="cc53" :num="53" v-on:kill="destroyComponent" />
...
<childComponent ref="ccn" :num="n" v-on:kill="destroyComponent"/>
</div>
</template>
<script>
import childComponent from '#/components/ChildComponent'
export default {
components: {childComponent},
methods: {
createComponent (num) {
// How do I create an instance of childComponent with prop 'num' and add it to this.$refs.container?
},
destroyComponent (vRef) {
// How do I destroy an instance of childComponent?
this.vRef.$destroy();
}
}
}
</script>
The number of possible childComponent instances required is finite, immutable and known before render, so I could loop and v-show them, but your typical user will probably only need to look at a few, and certainly only a few simultaneously.
My questions:
Firstly, given there are ±200 of them, is there any performance benefit to only creating instances dynamically as and when required, vs. v-for looping childComponents and let Vue manage the DOM?
Secondly, even if v-for is the way to go for this particular case, how would one handle this if the total number of possible childComponents is not known or dynamic? Is this a job for Render Functions and JSX?
If I understand, you want to display a list of the same component that take :num as a prop.
First, you have to keep in mind that Vue is a "Data driven application", wich means that you need to represent your list as Data in an array or an object, in your case you can use a myList array and v-for loop to display your child components list in the template.
The add and remove operations must be donne on the myList array it self, once done, it will be automatically applied on your template.
To add a new instance just use myList.push(n)
To remove an instance use myLsit.splice(myLsit.indexOf(n), 1);
The result should look like this :
<template>
<input v-model="inputId" />
<button #click="addItem(inputId)">Add Item</button>
<childComponent
v-for="itemId in myList"
:key="itemId"
:ref="'cc' + itemId"
:num="itemId"
#kill="removeItem(itemId)"
/>
</template>
<script>
data(){
return{
inputId : 0,
myList : []
}
},
methods:{
addItem(id){
this.myList.push(id)
},
removeItem(id){
this.myLsit.splice(this.myLsit.indexOf(id), 1)
}
}
</script>
Ps :
Didn't test the code, if there is any error just tell me
#kill method must be emitted by the childComponent, $emit('kill', this.num)
Here is an excellent tutorial to better understand v-for
Performance Penalties
As there is only a limited possibility of ±200 elements, I highly doubt that it can cause any performance issue, and for further fine-tuning, instead of using v-show, you can use v-if it'll reduce the total memory footprint, but increases the render time if you're going to change the items constantly.
Other Approaches
If there weren't limited possibilities of x elements, it'd be still and v-for having items which contain the v-if directive.
But if the user could only see one item (or multiple but limited items) at the same time, instead of v-for, It'd much better to directly bind the properties to the childComponent.
For example, if the child component is a modal that'll be shown by the application when a user clicked on the edit button for a row of a table. Instead of having x number of modals, each having editable contents of a row and showing the modal related to the edit button, we can have one modal and bind form properties to it. This approach usually implemented by having a state management library like vuex.
Finally, This is an implementation based on vuex, that can be used, if the user could only see one childComponent at the same time, it can be easily extended to support multiple childComponent viewed at the same time.
store.js
export Store {
state: {
childComponentVisible: false,
childComponentNumber: 0
},
mutations: {
setChildComponentNumber(state, value) {
if(typeof value !== 'number')
return false;
state.childComponentNumber = value;
},
setChildComponentVisibility(state, value) {
if(typeof value !== 'boolean')
return false;
state.childComponentVisible = value;
}
}
}
child-component.vue
<template>
<p>
{{ componentNumber }}
<span #click="close()">Close</span>
</p>
</template>
<script>
export default {
methods: {
close() {
this.$store.commit('setChildComponentVisibility', false);
}
}
computed: {
componentNumber() {
return this.$store.state.childComponentNumber;
}
}
}
</script>
list-component.vue
<template>
<div class="list-component">
<button v-for="n in [1,2,3,4,5]" #click="triggerChildComponent(n)">
{{ n }}
</button>
<childComponent v-if="childComponentVisible"/>
</div>
</template>
<script>
export default {
methods: {
triggerChildComponent(n) {
this.$store.commit('setChildComponentNumber', n);
this.$store.commit('setChildComponentVisibility', true);
}
},
computed: {
childComponentVisible() {
return this.$store.state.childComponentVisible;
}
}
}
</script>
Note: The code written above is abstract only and isn't tested, you might need to change it a little bit to make it work for your own situation.
For more information on vuex check out its documentation here.
I'm trying to use a vuejs custom directive called focus on a component from vuetify which is v-field-text.
directives: {
focus: {
// directive definition
inserted: function(el) {
el.focus();
}
}
}
I have a todo list, and my todos are printed with v-for, I also have an option to edit todos, whenever i click on edit button todo dispears and todo edit input apears.
I am using this focus directive to auto focusing the input.
However when i use this like this is not working:
<v-field-text v-focus></v-field-text>
But it works like this:
<input v-focus />
When i console.log the el from the directive, i see that its referring to a div element created by vuetify.
How to fix this issue?
The reason you're seeing a div when using v-focus on those elements is because they are being wrapped in a div. To get around this with third party components you don't control the code to, you may use something like the following function:
import Vue from 'vue'
Vue.directive('focus', {
inserted: function(el) {
// Recursion based function for finding an input
// nested within other elements.
let findInput = (el, max_depth = 5) => {
// We found the input, so we return it, which causes
// the entire function stack to pop
if (el.nodeName === 'INPUT') {
return el
}
// Prevent infinite recursion by providing a maximum
// depth, and returning when we've reached that depth
if (max_depth === 0) {
return null
}
// Our current element is not an input, so we need to loop
// over its children and call findInput recursively
for (let child of el.children) {
let input = findInput(child, max_depth - 1)
// We've found our input, return it to unwind the stack
// otherwise, continue through the loop
if (input) {
return input
}
}
// Fallback in case for when el has no children, or we reached the end of the loop with no input
return null
}
// Start searching for the input. We can optionally
// pass a higher max_depth. Use with caution.
let input = findInput(el, 20)
if (input) {
input.focus()
}
}
})
This is using recursion to step through each elements children, searching for an element with nodeName === 'INPUT'.
As an example, the following complex structure would be parsed and the first input found would be focused:
<div v-focus>
<div>
<div>
<div>
<div>
<div>
<div>
<div>
Hello
</div>
<p>
world
</p>
<span>!</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div>
<div>
<div>
<input type="text" value="I will be focused">
</div>
</div>
</div>
</div>
Please try this solution. It's working for me:
directives: {
focus: {
// directive definition
inserted: function (el) {
let childData = el.querySelectorAll("input")[0];
childData.focus()
}
}
}
I have a Vue component that lists a bunch of clickable tags. When you click on a tag, it takes you to another page with a list of objects containing that tag.
The relevant parts of the component code are:
<template>
<div>
<h2>All Tags</h2>
<TagList v-bind:tags="tags"/>
</div>
</template>
...
<script>
import TagList from './TagList'
export default {
name: 'AllTags',
components: {
TagList
},
data () {
return {
tags: []
}
},
mounted () {
tags = // array loaded from a database
}
}
</script>
This all works fine when I initially view the page. However if I browse away from this list, e.g. by clicking on a single tag, and then browse back, I only see the <h2>All Tags</h2> header. Using the Vue debugger in the browser, I can see that the data are still there.
I'm using <router-view :key="$route.fullPath"> to control the overall app and suspect the problem lies with the keys somehow.
Can someone point me in the right direction here? How can I get the TagList component to render every time I visit that page of the app?
EDIT: Here's the code of the TagList component:
<template>
<div class="tags">
<Tag v-for="tag in tags" v-bind:tag="tag" v-bind:key="tag" />
</div>
</template>
<script>
import Tag from './Tag'
export default {
name: 'TagList',
props: ['tags'],
components: {
Tag
}
}
</script>
You can try removing v-bind all thought its not required to use, I've checked your code it seems to work fine after visiting a tag and going back, all tags are still rendered. You can take a look at this working sample .
https://codesandbox.io/s/vue-template-3tcs4?fontsize=14
I'd like to create a custom Vue directive to omit the tag but render the tag's contents when the directive is true.
So for example, if the data for my vue instance is defined as
data:{
omitIt: true
}
And if the markup looks like this:
<div v-omit="omitIt" class="someClass">
Hello world!
</div>
When omitIt is set to false as it is above, I'd like the following rendered into the dom:
<div class="someClass">
Hello world!
</div>
But when omitIt is true I'd like only the following rendered into the dom:
Hello world!
I initially tried to solve this by doing the following (admittedly not a custom vue directive):
<template v-if="!omitIt">
<div class="someClass">
</template>
Hello world!
<template v-if="!omitIt">
</div>
</template>
The above isn't pretty but I thought perhaps it would work. But alas what gets rendered into the dom when omitIt is false is:
<div class="someClass"></div>
Hello world!
Any suggestions on how to achieve the results I'm looking for?
I thought #Nit's answer was a great and simple one and upvoted it, but it does have one flaw: a slot may not be a root element so the component will fail when the wrapper needs to be omitted. This is because slots can contain more than one element and if the slot does contain more than one, there could end up being more than one root element, which is not allowed.
I have a partial solution that renders just the first element in the slot if the component should not wrap.
Vue.component("wrapper", {
props:{
nowrap: {type: Boolean, default: false}
},
render(h){
// This will *only* render the *first* element contained in
// the default slot if `nowrap` is set. This is because a component
// *must* have a single root element
if (this.nowrap) return this.$slots.default[0]
// Otherwise, wrap the contents in a DIV and render the contents
return h('div', this.$slots.default)
}
})
Here is an example of it working.
console.clear()
Vue.component("wrapper", {
props:{
nowrap: {type: Boolean, default: false}
},
render(h){
// Log a warning if content is being omitted
const omissionMessage = "Wrapper component contains more than one root node with nowrap specified. Only the first node will be rendered."
if (this.$slots.default.length > 1 && this.nowrap)
console.warn(omissionMessage)
// This will *only* render the *first* element contained in
// the default slot if `nowrap` is set. This is because a component
// *must* have a single root element
if (this.nowrap) return this.$slots.default[0]
// Otherwise, wrap the contents in a DIV and render the contents
return h('div', this.$slots.default)
}
})
new Vue({
el: "#app"
})
.someClass{
color: blue
}
<script src="https://unpkg.com/vue#2.4.2"></script>
<div id="app">
<wrapper class="someClass">Hello World</wrapper>
<wrapper nowrap>No wrap, single root</wrapper> <br>
<wrapper nowrap>
No wrap, two roots. Paragraph is ommitted.
<p>Some other content</p>
</wrapper>
</div>
A couple notes: The component will always wrap unless you add nowrap as an attribute. Also, notice the class is added to the wrapped container without specifying it as a prop. This is because Vue automatically renders attributes that aren't specified as props on the root element of a component, unless you tell it not to.
This answer is wrong, slots cannot be used in this manner. Please see Bert's answer instead.
The easiest solution would be to create a wrapper component with slots for this purpose, passing the omitting argument as a prop.
The content distribution part becomes rather straightforward.
In the wrapper component template:
<slot v-if="omitIt"></slot>
<div v-else>
<slot></slot>
</div>
Wherever you want to use the wrapper:
<wrapper v-bind:omitIt="omitIt">
// Content
</wrapper>