I am having a little trouble figuring out how to dynamically add elements in Vue. I tried using the V-for as a for loop, however, the output is just the second item in the array. My goal is to create a new box for each of the array items.
The first block is my script code containing my two arrays. The second block is the template or HTML. I tried to loop through each of the numbs array and create a new box in each instance, however, I am having trouble creating new boxes for each element in the array.
<script>
export default{
data(){
return{
numbs: ['Application 1', 'Application 2'],
applications: [
{ ver: '0.99911', status: 'Ready for Sale', deploymentD: '09/10/2020', deploymentC: 'XXXX'},
{ ver: '0.99911', status: 'Ready for Sale', deploymentD: '09/10/2020', deploymentC: 'XXXX'}
]
}
}
}
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<template>
<div class="body">
<div class="box"
v-for="(demo, index) in numbs"
:key="index">
<div class="heading">
{{demo}}
</div>
<button class="info"><router-link to = "/viewapp">View</router-link></button>
<button class="edit"><router-link to = "/editapp">Edit</router-link></button>
<img class ="place" src="../images/imageinsert.jpg" />
<div class ="summary"
v-for="(apps, index) of applications"
:key="index">
Application Version {{apps.ver}}
{{apps.status}}, {{apps.deploymentD}}, {{apps.deploymentC}}
</div>
</div>
</div>
</template>
Here is an image of how the code currently runs. It prints out the last element of both arrays
Any help is greatly appreciated!!
To loop through an array and create an element for each item, you use the v-for directive. You use it like so:
<div class="summary" v-for="(apps, index) in applications" :key="index">
Application Version {{apps.ver}}
{{apps.status}}, {{apps.deploymentD}}, {{apps.deploymentC}}
</div>
You have v-for=(apps, index) for applications" (it should be in not for)
Also, you are using the index variable in both the inner and outer for loops. That's bound to cause an issue.
Related
I am displaying a list of JSON objects containing user chats. If the user types something into the searchbar, the matching JSON objects from the original chats list are being pushed into the new filtered_chats array. I didn't include this code logic since this is working and filtered_chats is being populated. (Note: filtered_chats starts out as an empty array in the script tag. Maybe this is helpful)
The problem I'm facing is that SvelteKit won't feed the new array to the HTML to show only the elements from filtered_chats instead of all of the chats from chats. It checks for the original array only and leaves the rendered contents as they already are.
Script:
<script lang="ts">
import ChatSidebarElement from "$components/ChatSidebarElement.svelte";
export let chats : JSON;
var filtered_chats = [];
var searchval = "";
function search() {
// this pushes chat objects into "filtered_chats" if they match the search pattern
}
HTML:
<div class="mb-4">
<input type="search" class="form-control text-dark" bind:value={searchval} on:input={search} placeholder="Search chats">
</div>
<h3 class="text-light">Open Chats</h3>
{#if chats.length > 0}
<div class="chats">
{#if filtered_chats.length > 0}
<h1 class="text-light">{searchval}</h1>
{#each filtered_chats as chat}
<ChatSidebarElement chat={chat}></ChatSidebarElement>
{/each}
{:else}
{#each chats as chat}
<ChatSidebarElement chat={chat}></ChatSidebarElement>
{/each}
{/if}
</div>
{:else}
<li>
<div class="info">
<div>You don't have any open chats!</div>
</div>
</li>
{/if}
Svelte only updates the UI when a variable is reassigned. With that in mind, if you mutate an array instead of reassigning it with a new state, it will not update the UI. For example:
<script>
let items = ['1', '2', '3']
// Wrong! UI won't be updated
items.push('4')
// Right! UI gets updated
items = [...items, '4']
// Or you can also do this
items = items.concat('4')
</script>
My question has to do with implementing the dragula library into my laravel app.
I have got dragula to work, but it only works on one 'team' object. I'm trying to create a loop where each team object will accept the nametag div. I've put the {{ $teamvalue->id }} in the id in order to create a unique id for each object, but this only lets the last team object that was generated be interacted with.
<div class="nameroll" id="nameroll">
#foreach($names as $key => $value)
<div class="nametag" draggable="true">
<p>{{ $value->name }}</p>
</div>
#endforeach
</div>
<div class="modroll">
#foreach($mods as $key => $value)
#foreach($modteams as $key => $teamvalue)
#if($teamvalue->mod_id === $value->id)
<div class="col-md-4">
<div class="title">
<h1>{{ $value->mod_intent }}</h1>
<h1>{{ $teamvalue->modteam_description }}</h1>
</div>
<div class="team" id="team{{ $teamvalue->id }}">
</div>
</div>
#endif
#endforeach
#endforeach
</div>
I then tried this in combination with putting a {{ $teamvalue->id }} in the js code.
<script type="text/javascript">
#foreach($modteams as $key => $teamvalue)
dragula([document.getElementById('nameroll'), document.getElementById('team{{ $teamvalue->id }}')], {
copy: function (el, source) {
return source === document.getElementById('nameroll')
},
accepts: function (el, target) {
return target !== document.getElementById('nameroll')
}
});
#endforeach
</script>
But because this creates multiple versions of the same code, they conflict and none of the team objects can be interacted with.
I just want to know how to create a loop so that the js code will allow dragula to work properly with each team object, so I can drag a nametag to each one and it'll stay there.
Thanks!
Edit: Clarification
The div Nameroll holds a collection of Users (Nametag).
The div Modroll holds a collection of Teams (Team).
I want to be able to drag a 'Nametag' object to any of the 'Team' Objects and have it be stored there. Currently, this works, but with only the last 'team' object in the collection. I've deduced that the js function for Dragula only fires off for the last collection as I've passed a value ( $teamvalue->id) and only the last id in the group exists in the function.
I think you can get rid of the foreach in the javascript. Try using the draggable="true" ondragstart="myFunction(event)" for the div class='team' and wrap the drag logic in a function.
<div class="team" id="team{{ $teamvalue->id }}" draggable="true" ondragstart="dragMe('{{$teamValue}}')">
</div>
When the div is dragged, The dragMe function would be called where laravel would convert the $teamValue to json so you can use directly in the javascript.
<script type="text/javascript">
function dragMe(teamValue){
dragula([document.getElementById('nameroll'), document.getElementById('team'+teamValue.id)], {
copy: function (el, source) {
return source === document.getElementById('nameroll')
},
accepts: function (el, target) {
return target !== document.getElementById('nameroll')
}
});
}
</script>
Suppose I have an input field in Vue.JS that v-model bind to a String data property, and a long list of random numbers that are completely unrelated to that first String.
data: {
input: "",
randoms: []
}
<input type="text" v-model="input">
<p v-for="random in randoms" v-text="random"></p>
When I put both in the same Vue, I see a huge slowdown when typing in the input field, as it appears Vue is reevaluating the DOM for each list entry after every input event, although they really have nothing to do with each other.
https://jsfiddle.net/5jf3fmb8/2/
When I however move the v-for to a child component where I bind randoms to a prop, I experience no such slowdown
https://jsfiddle.net/j601cja8/1/
Is there a way I can achieve the performance of the second fiddle without using a child-component?
Is there a way I can achieve the performance of the second fiddle without using a child-component?
Short answer
No.
Long answer
Whenever any dependency of the template changes, Vue has to re-run the render function for the entire component and diff the new virtualDOM against the new one. It can't do this for this or that part of the template only, and skip the rest. Therefore, each time the input value changes, the entire virutalDOM is re-rendered.
Since your v-for is producing quite a bit of elements, this can take a few 100ms, enough to be noticable when you type.
Extracting the heavy part of the template into its own component is in fact the "official" way to optimize that.
As Alex explained, v-model.lazy might improve the situation a bit, but does not fix the core of the issue.
Shortest, simplest answer: change v-model to v-model.lazy.
When I put both in the same Vue, I see a huge slowdown when typing in the input field, as it appears Vue is reevaluating the DOM for each list entry after every input event, although they really have nothing to do with each other.
Note that the OnceFor sample still chugs like mad despite not actually being reactive any more. I don't understand Vue well enough to say if that's intentional or not.
const Example = {
data() { return { input: "", randoms: [] } },
created() { this.newRandoms() },
methods: {
newRandoms() { this.randoms = Array(50000).fill().map(() => Math.random()) }
}
}
new Vue({
el: "#vue-root",
data(){ return {example: 'lazy-model'}},
components: {
LazyModel: {...Example, template: "#lazy-model"
},
OnceFor: {...Example, template: "#once-for"
},
InlineTemplate: {...Example, template: "#inline-template",
components: {
Welp: {
props: ['randoms']
}
}
}
}
})
button,
input,
div {
margin: 2px;
}
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="vue-root">
<span><button v-for="(component, name) in $options.components" #click="$set($data, 'example', name)">{{name}}</button></span>
<component :is="example"></component>
</div>
<template id="lazy-model">
<div>
<input type="text" v-model.lazy="input"><br>
<input type="submit" value="Regenerate" #click="newRandoms">
<p v-for="random of randoms" v-text="random"></p>
</div>
</template>
<template id="once-for">
<div>
<input type="text" v-model="input"><br>
<input type="submit" value="Regenerate" #click="newRandoms">
<p v-for="random of randoms" v-text="random" v-once></p>
</div>
</template>
<template id="inline-template">
<div>
<input type="text" v-model="input"><br>
<input type="submit" value="Regenerate" #click="newRandoms">
<welp :randoms="randoms" inline-template>
<div>
<p v-for="(random, index) of randoms" :key="index"> {{index}}: {{random}} </p>
</div>
</welp>
</div>
</template>
I am using sortable js for drag and drop action. (Link to github page) As I am using vue.js as well, I am using this plugin to bridge them. I am new to both of the library and so I am trying to duplicate the example given in the plugin. (i.e. this example)
HTML section:
<div class="drag">
<h2>List 1 v-dragable-for</h2>
<div class="dragArea" >
<template v-dragable-for="element in list1" options='{"group":{ "name":"people", "pull":"clone", "put":false }}' track-by="$index">
<p>{{element.name}}</p>
</template>
</div>
<h2>List 2 v-dragable-for</h2>
<div class="dragArea">
<div v-dragable-for="element in list2" options='{"group":"people"}' track-by="$index">{{element.name}}</div>
</div>
</div>
Javascript part:
var vm = new Vue({
el: "#main",
data: {
list1:[{name:"John" , id:"1"},
{name:"Joao", id:"2"},
{name:"Jean", id:"3"} ],
list2:[{name:"Juan", id:"4"},
{name:"Edgard", id:"5"},
{name:"Johnson", id:"6"} ]
},
methods:{
}
});
It works fine on jsfiddle, but when I try to duplicate the case on my local server, it always return 0 for both oldIndex and newIndex in the onUpdate event. This makes the element always insert at the beginning of the second list. Any clue on what can I miss to cause this problem?
I'm running into a problem with creating a dynamic class name based on the Angular 2 ngFor loop index. I had to use the following syntax because Angular 2 does not like ngFor and ngIf on the same element.
With this syntax, how can I create a dynamic class name with the value of index at {{index}}. I know this isn't proper A2 code, but I put it in my code example to show you where I would like the value to appear.
<div class="product-detail__variants">
<template ngFor #variant [ngForOf]="variants" #index="index">
<div *ngIf="currentVariant == index">
<div class="product-detail-carousel-{{index}}">
</div>
</div>
</template>
</div>
The value "variants" is an empty array of a set length. "variant" thus has no value.
"currentVariant" is a number that by default equals 0.
EDIT: This code above is correct. I had another extraneous error that I thought was connected to this code.
I don't really understand your problem ;-)
There are two ways to set classes for a specific element:
The way you do with curly brackets:
#Component({
selector: 'my-app',
template: `
<div class="product-detail__variants">
<template ngFor #variant [ngForOf]="variants" #index="index">
<div *ngIf="currentVariant == index">
<div class="product-detail-carousel-{{index}}">
Test
</div>
</div>
</template>
</div>
`,
styles: [
'.product-detail-carousel-2 { color: red }'
]
})
Test is displayed only for the third element (index 2) and in red.
As suggested by #Langley using the ngClass directive
import {NgClass} from 'angular2/common';
#Component({
selector: 'my-app',
template: `
<div class="product-detail__variants">
<template ngFor #variant [ngForOf]="variants" #index="index">
<div *ngIf="currentVariant == index">
<div class="product-detail-carousel-{{index}}">
Test
</div>
</div>
</template>
</div>
`,
styles: [
'.product-detail-carousel-2 { color: red }'
],
directives: [ NgClass ]
})
The different is that you need to specify the NgClass directive within the providers attribute of your component. Again Test is displayed only for the third element (index 2) and in red.
Your following sentences: "The value variants is an empty array of a set length. variant thus has no value. currentVariant is a number that by default equals 0.". How do you expect something to be displayed if your array is empty. An ngFor is an iteration...
Hope it helps you,
Thierry
In case anyone is looking to add a specific class based on the index, you can do something like this. I personally prefer the syntax here too. Let's say you wanted to add last-para to the last paragraph from an array:
<p
*ngFor="let p of paragraphs; let i = index"
[class.last-para]="i >= paragraphs.length - 1">
{{p}}</p>