How to fix setInterval bugg? - javascript

I am having a h1 with v-for and i am writing out things from my array ,it looks like this:
<h1
v-for="(record, index) of filteredRecords"
:key="index"
:record="record"
:class="getActiveClass(record, index)"
>
<div :class="getClass(record)">
<strong v-show="record.path === 'leftoFront'"
>{{ record.description }}
</strong>
</div>
</h1>
as you can see i am bindig a class (getActiveClass(record,index) --> passing it my record and an index)
This is my getActiveClass method:
getActiveClass(record, index) {
this.showMe(record);
return {
"is-active": index == this.activeSpan
};
}
i am calling a function called showMe passing my record to that and thats where the problem begins
the showMe method is for my setInterval so basically what it does that i am having multiple objects in my array and it is setting up the interval so when the record.time for that one record is over then it switches to the next one. Looks like this:
showMe(record) {
console.log(record.time)
setInterval(record => {
if (this.activeSpan === this.filteredRecords.length - 1) {
this.activeSpan = 0;
} else {
this.activeSpan++;
}
}, record.time );
},
this activeSpan is making sure that the 'is-active' class (see above) is changing correctly.
Now my problem is that the record.time is not working correctly when i print it out it gives me for example if iam having two objects in my array it console logs me both of the times .
So it is not changing correctly to its record.time it is just changing very fastly, as time goes by it shows just a very fast looping through my records .
Why is that? how can i set it up correctly so that when i get one record its interval is going to be the record.time (what belongs to it) , and when a record changes it does again the same (listening to its record.time)
FOR EXAMPLE :
filteredRecords:[
{
description:"hey you",
time:12,
id:4,
},
{
description:"hola babe",
time:43,
id:1
},
]
it should display as first the "hey you" text ,it should be displayed for 12s, and after the it should display the "hola babe" for 43 s.
thanks

<template>
<h1 ...>{{ filteredRecords[index].description }}</h1>
</template>
<script>
{
data() {
return {
index: 0,
// ...
};
},
methods: {
iterate(i) {
if (this.filteredRecords[i]) {
this.index = i;
window.setTimeout(() => iterate(i + 1), this.filteredRecords[i].time * 1000);
}
},
},
mounted() {
this.iterate(0);
},
}
</script>
How about this? Without using v-for.

Related

Apply CSS conditionally in a loop - VueJS

I have a JSON list of items that I import in my vue component,
I loop though that file in order to show them. Each item belong to a specific 'group' :
See IMG
E.g. :
{
"type": "Simple list",
"title": "Simple list",
"id": 1,
"group": "list-component",
"properties": "lorem lipsum"
},
I would like to apply a CSS 'border-top-color' to each item according to its group.
I was trying to apply the conditions when mouted(){} but I'm not sure if I'm doing it right. Here's my atempt :
The template (I'm using VueDraggable, don't mind it) :
<div class="item drag" :key="element" :style="[{ 'border-top-color': 'brdrTpClr' }]">
{{ element.title }}
<div class="addico" :key="index">
<i class="fas fa-add"#click="$emit('pushNewElt', element.id)"></i>
</div>
</div>
The script :
data() {
return {
dragItems: dragItemsList,
brdrTpClr: "",
};
},
mounted() {
for (let i = 0; i <= 15; i++) {
if (this.dragItems[i].group == "list-component") {
// I'm not sure how to do it
// the color I want to apply : #00A3A1b
} else if (this.dragItems[i].group == "location-media-component") {
// #005EB8
} else if (this.dragItems[i].group == "container-component") {
// #0091DA
} else if (this.dragItems[i].group == "UI-component") {
// #6D2077
} else if (this.dragItems[i].group == "reader-scanner-component") {
// #470A68
}
}
},
I'm using i<=15 instead of i<=this.dragItems.length because of a bug, don't mind it too.
Probably the most efficient (performance wise) and the most readable solution would be to declare a constant colorMap, outside the component, and then return the correct value or a fallback, using a method:
<script>
const colorMap = {
"list-component": '#00A3A1',
"location-media-component": '#005EB8',
"container-component": '#0091DA',
"UI-component": '#6D2077',
"reader-scanner-component": '#470A68'
}
export default {
//...
methods: {
borderColor(group) {
return colorMap[group] || '#000'
}
}
}
</script>
<template>
...
<div :style="{borderColor: borderColor(element.group)}">
content...
</div>
</template>
As a general rule, you want to take anything more complicated than a simple ternary outside of the template and provide it via either computed or methods.
Side note: the above method can also be written as computed:
computed: {
borderColor: group => colorMap[group] || '#000'
}
If you find yourself needing the colorMap in more than one component, export it from a constants.(js|ts) file and import everywhere needed. I typically name that file helpers, as it typically also contains static functions or maps (anything I reuse across multiple components/modules).
Important: you're currently passing an array to :style. You should be passing an object.
I would make a method called getBorderColor(item) which returns the colour based on the group, and then dynamically bind it using Vue.
<div
class="item drag"
:style="[{ 'border-top-color': getBorderColor(element) }]"
>
{{ element.title }}
// Add icon etc.
</div>
getBorderColor(element) {
// Can use a switch statement, but here's a simple ternary if/else as an example
return element.group === "list-component" ? `#00A3A1b`
: element.group === "location-media-component" ? `#005EB8`
: element.group === "container-component" ? `#0091DA`
: element.group === "UI-component" ? `#6D2077`
: element.group === "reader-scanner-component" ? `#470A68`
: `#000000`; // Default colour
}
or for a cleaner option you can have an object with your groups as keys and colours as values in data e.g.
return {
dragItems: dragItemsList,
brdrTpClr: "",
colors: {
"list-component": `#00A3A1b`,
"location-media-component": `#005EB8`,
// etc.
},
};
getBorderColor(element) {
return this.colors[element.group] || `#000`;
}

How to properly delete popup

I have a simple popUp component that I use in my entire app, it get's called by emitting an event on click. There are two types of popUps, success and danger. The success popUp should disappear on it's own after 5 seconds, the danger should get closed when clicked on the x sign. Currently it works the way I have created it, but if the user creates more than one danger popUp, then a success one and again a danger one, the danger one disappears after 5 seconds and not the success one. How can I make so that my success popUp disappears properly after 5 seconds? I am calling it here like this but it seems that it does not delete them correctly:
if(obj.type === 'success') {
setTimeout(this.closePopUp, 5000);
}
Here is my code:
<template>
<div class="popUp-wrapper">
<div
v-for="item in allItems"
:key="item.id"
:class="['popUp', `popUp--type--${item.newPopUpType}`]"
>
<div class="popUp-side">
<p class="exclamation-mark">!</p>
</div>
<h5 class="popUp-message">{{item.message}}</h5>
<div class="popUp-side">
<p class="closing-x" #click="closePopUp(item)" v-if="item.newPopUpType
=== 'danger'">X</p>
</div>
</div>
</div>
</template>
<script>
export default {
data: () => ({
allItems: []
}),
methods: {
closePopUp(item) {
const index = this.allItems.indexOf(item);
this.allItems.splice(index, 1);
},
onPopUpCall(obj) {
var newPopUp = {
newPopUpType: obj.type,
message: obj.message,
id: obj.id
};
if(obj.type === 'success') {
setTimeout(this.closePopUp, 5000);
}
this.allItems.push(newPopUp);
}
},
created() {
this.$root.$on('call-popUp', this.onPopUpCall);
},
destroyed() {
this.$root.$off('call-popUp', this.onPopUpCall);
}
};
</script>
closePopUp requires an item argument based on your code which you are not providing.
Try this:
if(obj.type === 'success') {
setTimeout(() => this.closePopUp(newPopUp), 5000);
}

Deleting specific component in v-for array

I have below array, that contains a number of columns. Below example contains three columns, but columns can be added/removed dynamically:
[['position', '30'], ['position', '60'], ['position', '90']]
I am facing issues when deleting the correct column (index in array) with Vue.
Consider below snippet:
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!',
columns: [['position', '30'], ['position', '60'], ['position', '90']]
},
methods: {
deleteColumn: function(index) {
this.columns.splice(index, 1);
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="(item, index) in columns" :key="index">
Column #: {{index}} - <a #click="deleteColumn(index)">Delete me</a>
</div>
</div>
If you run the above code snippet end try to delete the #1 column, it will actually remove the #2 column (last item of the array). Same goes for #0.
I thought that by providing the index to my deleteColumn function, I could remove the "right" index from the array.
Any help is appreciated.
Just give them a property name and you are done. Notice what I changed here. Columns is no more a 2D array, but objects. Use this.$delete(this.columns, index); to delete the objects.
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!',
columns: {
'1': {
position: 30
},
'2': {
position: 60
},
'3': {
position: 90
}
}
},
methods: {
deleteColumn: function(index) {
this.$delete(this.columns, index);
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="(item, index) in columns" :key="index">
Column #: {{index}} - <a #click="deleteColumn(index)">Delete me</a>
</div>
</div>
{
'1': {
position: 30
},
'2': {
position: 60
},
'3': {
position: 90
}
}
Here, '1' is a property name and it's value is another object. It's like giving ids to your data.
The format for value of object is this
{ property_name : value }
Here, value is another object, and in that object, there is another property, named "position" with your corresponding values.
When you clicked any item you are removing it in the right way, your index is your key, that's the problem, but is visually, in the logic it's right. Display your position in your template just for you can see it. ANd for me your data it's not in the right way.
<div id="app">
<div v-for="(item, index) in columns" :key="index">
Column #: {{index}}-{{item.position}} -
<a #click="deleteColumn(index)">Delete me</a>
</div>
</div>
and your script for you can see the change
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!',
columns: [{position: 30}, {position: 60}, {position: 90}]
},
methods: {
deleteColumn: function(index) {
this.columns.splice(index, 1);
}
}
})
The splice method reindexes the array, moving all elements after the splice point up or down so that any new inserted values will fit and so that the array indices remain contiguous. You can see it more clearly if you also display the values of the items in your list:
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!',
columns: ['foo', 'bar', 'baz']
},
methods: {
deleteColumn: function(index) {
this.columns.splice(index, 1);
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="(item, index) in columns" :key="index">
Column #{{index}} = {{item}} - <a #click="deleteColumn(index)" style="cursor:pointer">Delete me</a>
</div>
</div>
Initially, the snippet above will render like this:
Column #0 = foo - Delete me
Column #1 = bar - Delete me
Column #2 = baz - Delete me
If you now click the "Delete me" link on column #0 ("foo"), it will change to:
Column #0 = bar - Delete me
Column #1 = baz - Delete me
You can see that the value "foo" indeed got spliced out of the array — and the values "bar" and "baz" were shifted down by one position to become the new elements #0 and #1.
Anyway, the fix for this problem is simply "don't do that":
If you're using v-for with a simple array whose elements have no natural key value, you can just omit :key entirely and let Vue decide how to best handle changes to the underlying array. As long as the contents within the v-for loop doesn't contain any form inputs or stateful components or other fancy stuff that doesn't react well to the array being reindexed, it should work just fine.
Conversely, if you do have a natural unique key available for each array element, use it. If you don't, but can create one, consider doing that.
You should not use index as the key with CRUD operations since this will confuse Vue when it comes to deleting. The key should be a unique identifier that relates to the data.
You can create a new formatted array of objects on mount with a key generated from the data within the array (note: I haven't tested the code in a browser if there are any mistakes).
<template>
<div>
<div v-for="col in formattedColumns" :key="col.key">
{{ col.value }}
</div>
</div>
</template>
<script>
export default {
data() {
return {
columns: [['position', '30'], ['position', '60'], ['position', '90']],
formattedColumns: null,
};
},
mounted() {
let columns = [];
for (let i = 0; i < this.columns.length; i++) {
columns.push({
value: this.columns[i],
key: this.columns[i][0] + this.columns[i][1],
});
}
this.formattedColumns = columns;
},
};
</script>
Try this this.$delete(this.columns, index) which is the same as Vue.delete(this.columns, index)
https://v2.vuejs.org/v2/api/index.html#Vue-delete

Vue JS : Getting Unique ID and Value of an array of input at the same time

I am new in Vue and still learning using it. I am learning to use the Element UI for Vue UI. Specifically, I'm trying the Input Number Component, for an array of data. Let say I have my data like this:
dataList = [{
id: 1,
productName: "ABC",
qty: 1
}, {
id: 2,
productName: "DEF",
qty: 2
}];
And the element goes like this:
<div v-for="(item, index) in dataList" v-bind:key="item.id">
<el-input-number v-model="item.qty" #change="handleChange"></el-input-number>
</div>
And for the script goes like this:
<script type="text/javascript">
handleChange = function (value) {
console.log(value); /* I need the item.id also not just the qty value */
};
</script>
from the function handleChange() I only can get the value of input number, but not the item id that I've assign in the element. How can I get both of that (item.id and value)?
I was expecting a js function like this could work, but it didnt:
handleChange = function(item, value) { /* */ }
I've been trying to google for some answer, but the answer always showing only 1 parameter that i can acquire from change event.
Any help would be appreciated, thank you.
You could pass your value ($event) as first parameter and the other parameter as the second one :
<div v-for="(item, index) in dataList" v-bind:key="item.id">
<el-input-number v-model="item.qty" #change="handleChange($event,item.id)"></el-input-number>
</div>
Script :
<script type="text/javascript">
handleChange = function (value,id) {
console.log(value,id);
};
</script>

Vue JS nested loop search not returning results

I'm building a key-command resource and giving VueJS a whirl while doing so. I'm a newbie but am gaining the grasp of things (slowly...).
I want to be able to search in a global search form for key commands I'm defining as actions within sections of commands (see data example below). I would like to search through all the actions to show only those that match the search criteria.
My HTML is below:
<div id="commands">
<input v-model="searchQuery" />
<div class="commands-section" v-for="item in sectionsSearched"
:key="item.id">
<h3>{{ item.section }}</h3>
<div class="commands-row" v-for="command in item.command" :key="command.action">
{{ command.action }}
</div>
</div>
</div>
My main Vue instance looks like this:
import Vue from 'vue/dist/vue.esm'
import { commands } from './data.js'
document.addEventListener('DOMContentLoaded', () => {
const element = document.getElementById("commands")
if (element != null) {
const app = new Vue({
el: element,
data: {
searchQuery: '',
commands: commands
},
computed: {
sectionsSearched() {
var self = this;
return this.commands.filter((c) => {
return c.command.filter((item) => {
console.log(item.action)
return item.action.indexOf(self.searchQuery) > -1;
});
});
},
}
});
}
});
And finally the data structure in data.js
const commands = [
{
section: "first section",
command: [
{ action: '1' },
{ action: '2' },
{ action: '3' },
],
},
{
section: "second section",
command: [
{ action: 'A' },
{ action: 'B' },
{ action: 'C' },
]
},
]
export { commands };
I'm able to output the commands using the console.log(item.action) snippet you see in the computed method called sectionsSearched.
I see no errors in the browser and the data renders correctly.
I cannot however filter by searching in real-time. I'm nearly positive it's a combination of my data structure + the computed method. Can anyone shed some insight as to what I'm doing wrong here?
I'd ideally like to keep the data as is because it's important to be sectioned off.
I'm a Rails guy who is new to this stuff so any and all feedback is welcome.
Thanks!
EDIT
I've tried the proposed solutions below but keep getting undefined in any query I pass. The functionality seems to work in most cases for something like this:
sectionsSearched() {
return this.commands.filter((c) => {
return c.command.filter((item) => {
return item.action.indexOf(this.searchQuery) > -1;
}).length > 0;
});
},
But alas nothing actually comes back. I'm scratching my head hard.
There is a issue in your sectionsSearched as it is returning the array of just commands.
See this one
sectionsSearched() {
return this.commands.reduce((r, e) => {
const command = e.command.filter(item => item.action.indexOf(this.searchQuery) > -1);
const section = e.section;
r.push({
section,
command
});
}, []);
}
const commands = [
{
section: "first section",
command: [
{ action: '1' },
{ action: '2' },
{ action: '3' },
],
},
{
section: "second section",
command: [
{ action: 'A' },
{ action: 'B' },
{ action: 'C' },
]
},
]
const element = document.getElementById("commands")
if (element != null) {
const app = new Vue({
el: element,
data: {
searchQuery: '',
commands: commands
},
computed: {
sectionsSearched() {
var self = this;
return this.commands.filter((c) => {
// the code below return an array, not a boolean
// make this.commands.filter() not work
// return c.command.filter((item) => {
// return item.action.indexOf(self.searchQuery) > -1;
// });
// to find whether there has command action equal to searchQuery
return c.command.find(item => item.action === self.searchQuery);
});
},
}
});
}
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="commands">
<input v-model="searchQuery" />
<div class="commands-section" v-for="item in sectionsSearched"
:key="item.id">
<h3>{{ item.section }}</h3>
<div class="commands-row" v-for="command in item.command" :key="command.action">
{{ command.action }}
</div>
</div>
</div>
Is that work as you wish ?
sectionsSearched() {
return this.commands.filter((c) => {
return c.command.filter((item) => {
return item.action.indexOf(this.searchQuery) > -1;
}).length > 0;
});
},
}
since filter will always return an array(empty or not) which value always is true.

Categories

Resources