Modify elements with checkbox checked created by iteration - vue.js

I have an array of objects in a state, the object has an id, a title and a url, I go through this array and show a div with an input checkbox and an image that corresponds to the checkbox, when I click on a checkbox using v- model I save the id in an array, if it is checked I save it, if it is not, I remove it from the array. I want that when it is checked or the checkbox id exists in the array, modify the image, if it is not, return to its original form, this with each checkbox and its corresponding image, how can I do it?
I loop through the store array and display a div with a checkbox and the image
<div v-for="item in store.items" :key="store.id">
<input
type="checkbox"
:value="item.title"
v-model="store.checkeds"
:id="item.id"
>
<img
:src="item.url"
:alt="item.title"
width="30"
height="30"
>
</div>
store
state: () => ({
checkeds: [],
items: [
{
"id": "1",
"url": "../assets/imagen.svg",
"title": "imagen"
},
{
"id": "2",
"url": "../assets/imagen2.svg",
"title": "imagen2"
},
{
"id": "3",
"url": "../assets/imagen3.svg",
"title": "imagen3"
},
{
"id": "4",
"url": "../assets/imagen4.svg",
"title": "imagen4"
},
{
"id": "5",
"url": "../assets/imagen5.svg",
"title": "imagen5"
},
{
"id": "6",
"url": "../assets/imagen6.svg",
"title": "imagen6"
},
]
})

I write a simple sample about how to manage array-style models.
<template>
<div>
<div
v-for="(item, i) in filtersStore.items" :key="item.id"
class="col-6 col-sm-4 col-lg-3 mb-3 d-flex align-items-start mb-2"
>
<input
type="checkbox"
:id="item.id"
class="mt-2"
v-model="filtersStore.checked[i]"
#change="checkedChanged"
>
<img
:src="item.urlEdited || item.url"
:alt="item.title"
:style="item.style"
>
</div>
</div>
</template>
<script>
export default {
name: 'BaseCheckBoxTest',
data() {
return {
filtersStore: {
items: [
{
id: 1,
title: 'Check 1',
url: 'https://picsum.photos/id/0/5616/3744',
style: {
width: '100px',
height: '100px',
},
},
{
id: 2,
title: 'Check 2',
url: 'https://picsum.photos/seed/picsum/200/300',
style: {
width: '100px',
height: '100px',
},
},
{
id: 3,
title: 'Check 3',
url: 'https://picsum.photos/id/1/5616/3744',
style: {
width: '100px',
height: '100px',
},
},
{
id: 4,
title: 'Check 4',
url: 'https://picsum.photos/id/1023/3955/2094',
style: {
width: '100px',
height: '100px',
},
},
],
checked: [],
},
};
},
methods: {
checkedChanged() {
this.filtersStore.checked.forEach((isChecked, index) => {
if (isChecked) {
// Apply your action here
this.filtersStore.items[index].urlEdited = this.filtersStore.items[index].url + '?grayscale';
this.filtersStore.items[index].style = {
width: '150px',
height: '150px',
};
} else {
// Remove your action here
this.filtersStore.items[index].urlEdited = undefined;
this.filtersStore.items[index].style = {
width: '100px',
height: '100px',
};
}
});
},
},
};
</script>
I store the status of checkboxes in an array (v-model="filtersStore.checked[i]"). This array provides us with the selected items. Just loop over the status array and apply your filter or action.
If you provide more information about the state and actions, I can improve it in a more practical way.
UPDATE: if a checkbox gets checked, the image style will change.

Related

Group transition with dynamic list as a whole

I have a data_sets. I need to take out a certain dataset (data_source in the snipped), filter it (itemList in the snippet) and display it, with any simple group transition, preferably list transition in Vue's official guide
This is a snippet that I created on https://sfc.vuejs.org/ to play around.
<template>
<div>
<ul>
<transition-group name="list" mode="out-in">
<li v-for="item in itemList" :key="item.id">
{{item.content}}
</li>
</transition-group>
</ul>
</div>
<button #click="changeDatasource(0)">
Dataset 1
</button>
<button #click="changeDatasource(1)">
Dataset 2
</button>
<button #click="changeDatasource(2)">
Dataset 3
</button>
</template>
<script>
export default {
data() {
return {
data_sets: [
[{
id: 1,
content: "Frog",
active: 1,
},
{
id: 2,
content: "Elephant",
active: 0,
},
{
id: 3,
content: "Racoon",
active: 1,
},
{
id: 8,
content: "Cheetah",
active: 1,
},
],
[{
id: 4,
content: "Rooster",
active: 1,
},
{
id: 5,
content: "Cow",
active: 1,
},
{
id: 6,
content: "Cat",
active: 1,
},
{
id: 7,
content: "Dog",
active: 0,
},
],
[{
id: 10,
content: "Lion",
active: 1,
},
{
id: 11,
content: "Shark",
active: 1,
},
{
id: 12,
content: "Bee",
active: 0,
},
{
id: 13,
content: "Cockroaches",
active: 0,
},
],
],
data_source: [],
}
},
computed: {
itemList: {
get() {
return this.data_source.filter((item) => item.active)
},
set(newValue) {
//this.itemList = newValue; <--
}
}
},
methods: {
changeDatasource: function(id) {
//this.itemList = [] <--
this.data_source = this.data_sets[id];
}
}
}
</script>
<style scoped>
.list-enter-active,
.list-leave-active {
transition: all 0.5s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateX(30px);
}
</style>
When clicking on buttons to choose a data set, you can see the transition is not okay: old items still present when the new ones come in. The expected order should be: old items disappears, then new items come in.
It's very likely because itemList is replaced as a whole at a time. So I've tried to:
Empty itemList = [],
Continue to make the changes. (the commented part in the snippet)
RangeError: Maximum call stack size exceeded
Guess it bounds to some kind of infinite loop, at this point I'm completely pointless.
I've also messed around with transition mode, it isn't better.
My question is there any way to apply transition to list as a whole like this?
Solved it by working around with setTimeout and setInterval and changed from computed propery to watch.
Content shifting is there but a little CSS and JavaScript manipulation will fix it. Not the best solution but by now that's what I can think of.
Result snippet
<template>
<div>
<ul>
<transition-group name="list">
<li v-for="item in itemList" :key="item.id">
{{item.content}}
</li>
</transition-group>
</ul>
</div>
<button #click="changeDatasource(0)">
Dataset 1
</button>
<button #click="changeDatasource(1)">
Dataset 2
</button>
<button #click="changeDatasource(2)">
Dataset 3
</button>
</template>
<script>
export default {
data() {
return {
data_sets: [
[{
id: 1,
content: "Frog",
active: 1,
},
{
id: 2,
content: "Elephant",
active: 0,
},
{
id: 3,
content: "Racoon",
active: 1,
},
{
id: 8,
content: "Cheetah",
active: 1,
},
],
[{
id: 4,
content: "Rooster",
active: 1,
},
{
id: 5,
content: "Cow",
active: 1,
},
{
id: 6,
content: "Cat",
active: 1,
},
{
id: 7,
content: "Dog",
active: 0,
},
],
[{
id: 10,
content: "Lion",
active: 1,
},
{
id: 11,
content: "Shark",
active: 1,
},
{
id: 12,
content: "Bee",
active: 0,
},
{
id: 13,
content: "Cockroaches",
active: 0,
},
],
],
data_source: [],
itemList: [],
}
},
methods: {
changeDatasource: function(id) {
//this.itemList = [] <--
this.data_source = this.data_sets[id];
}
},
watch: {
data_source: function(newValue, oldValue) {
let initialLength = this.itemList.length;
if (initialLength > 0) {
this.itemList = [];
}
let dataLength = newValue.length;
let interval = 200;
let i = 0;
let timer = setInterval(() => {
setTimeout(() => {
if (typeof newValue[i - 1] != 'undefined') {
this.itemList.push(newValue[i - 1])
}
}, interval)
if (i === dataLength - 1) {
clearInterval(timer);
}
i++;
}, interval)
}
}
}
</script>
<style scoped>
.list-enter-active,
.list-leave-active {
transition: all 0.5s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateX(30px);
}
</style>

Data is not display in correct order after update

I try to display an array of object sort by date.
When it's mount there is no problem but when i try to add new data, it is not shown in the correct order. I'm sorry but I cannot be more clear ...
Here is my code :
<template>
<div class="container">
<div class="row">
<div class="col" style="position: relative; height: 50vh">
<canvas id="graphique"></canvas>
</div>
<div>
<h1>Finds</h1>
<div v-for="(find, index) in finds" v-bind:key="index">
<input v-model="find.date" />
<input v-model="find.value" />
<input v-model="find.label" />
</div>
<input v-model="dateToAdd" />
<input v-model="valueToAdd" />
<input v-model="labelToAdd" />
<button #click="addFind">Add cost</button>
<p v-if="Chart != null">{{ Chart.data.datasets[0].data }}</p>
</div>
</div>
</div>
</template>
<script>
import Chart from "chart.js/auto";
export default {
name: "PlanetChart",
data() {
return {
Chart: null,
valueToAdd: 20,
labelToAdd: "A",
dateToAdd: "12/01/1900",
finds: [
{ date: "12/01/2020", label: "1", value: 12 },
{ date: "12/01/2021", label: "2", value: 2 },
{ date: "12/01/2000", label: "0", value: 1 },
{ date: "12/01/2023", label: "3", value: 12 },
],
};
},
mounted() {
const ctx = document.getElementById("graphique");
this.finds.sort(function (a, b) {
return new Date(a.date) - new Date(b.date);
});
this.Chart = new Chart(ctx, {
type: "line",
data: {
datasets: [
{
label: "My dataset",
fill: false,
lineTension: 0.1,
backgroundColor: "rgba(75,192,192,0.4)",
borderColor: "rgba(75,192,192,1)",
borderCapStyle: "butt",
borderDash: [],
borderDashOffset: 0.0,
borderJoinStyle: "miter",
pointBorderColor: "rgba(75,192,192,1)",
pointBackgroundColor: "#fff",
pointBorderWidth: 1,
pointHoverRadius: 5,
pointHoverBackgroundColor: "rgba(75,192,192,1)",
pointHoverBorderColor: "rgba(220,220,220,1)",
pointHoverBorderWidth: 2,
pointRadius: 5,
pointHitRadius: 10,
data: this.finds,
},
],
},
options: {
parsing: {
xAxisKey: "date",
yAxisKey: "value",
},
responsive: true,
maintainAspectRatio: false,
},
});
},
methods: {
addFind: function () {
if (this.valueToAdd > 0 && this.labelToAdd != "") {
this.finds.push({
date: this.dateToAdd,
value: this.valueToAdd,
label: this.labelToAdd,
});
this.finds.sort(function (a, b) {
return new Date(a.date) - new Date(b.date);
});
this.Chart.data.datasets[0].data = this.finds;
console.log(this.Chart.data.datasets[0].data);
this.Chart.update();
this.valueToAdd = 0;
this.dateToAdd = "";
this.labelToAdd = "";
}
},
},
};
</script>
Here the result I have :
The point at the extrem right must be the first because his date is before the others points.
So it seems to be a rendering probleme and not a sort probleme.
If you need a js.fiddle let me know.
Thanks for your help !
EDIT : The only solution found is to create a function to create the graph and inside the addFind function do this :
this.Chart.destroy();
this.Chart = this.createChart(this.finds)
This is a working example with you data.
Here is the template code, I used a computed property to sort the array of data instead of sorting the actual data.
Please review the dependencies of the Chart.js package, as time series chart require external date adapters as mentioned here.
Also, there seems to be a Chart.js wrapper for Vue, perhaps you should try it.
<template>
<div class="container">
<div class="row">
<div class="col" style="position: relative; height: 50vh">
<canvas id="graphique"></canvas>
</div>
<div>
<h1>Finds</h1>
<div v-for="(find, index) in finds" v-bind:key="index">
<input v-model="find.date" />
<input v-model="find.value" />
<input v-model="find.label" />
</div>
<input v-model="dateToAdd" />
<input v-model="valueToAdd" />
<input v-model="labelToAdd" />
<button #click="addFind">Add cost</button>
<p v-if="Chart">{{ Chart.data }}</p>
</div>
</div>
</div>
</template>
<script>
import Chart from "chart.js/auto";
import "chartjs-adapter-moment";
export default {
name: "PlanetChart",
data() {
return {
Chart: null,
valueToAdd: 20,
labelToAdd: "A",
dateToAdd: "12/01/1900",
finds: [
{ date: "12/01/2020", label: "1", value: 12 },
{ date: "12/01/2021", label: "2", value: 2 },
{ date: "12/01/2000", label: "0", value: 1 },
{ date: "12/01/2023", label: "3", value: 12 },
],
};
},
mounted() {
const ctx = document.getElementById("graphique");
this.Chart = new Chart(ctx, {
type: "line",
data: {
datasets: [
{
label: "US Dates",
backgroundColor: "#fff",
borderWidth: 1,
pointBorderWidth: 1,
borderColor: "#f00",
data: this.transformedData,
fill: false,
borderColor: "red",
},
],
},
options: {
scales: {
x: {
type: "time",
time: {
unit: "month",
},
},
},
},
});
},
computed: {
transformedData() {
const d = this.finds.map((i) => {
return { x: new Date(i.date), y: i.value };
});
d.sort(function (a, b) {
return a.x - b.x;
});
return d;
},
},
methods: {
addFind: function () {
if (this.valueToAdd > 0 && this.labelToAdd !== "") {
this.finds.push({
date: this.dateToAdd,
value: this.valueToAdd,
label: this.labelToAdd,
});
this.finds.sort(function (a, b) {
return new Date(a.date) - new Date(b.date);
});
this.Chart.data.datasets[0].data = this.transformedData;
console.log(this.Chart.data.datasets[0].data);
this.Chart.update();
this.valueToAdd = 0;
this.dateToAdd = "";
this.labelToAdd = "";
}
},
},
};
</script>
So without using a computed method is it possible to do it simply by using the
import "chartjs-adapter-moment";
as Juand David mentioned. And don't forget to convert the string in Date.
Here is my solution :
<template>
<div class="container">
<div class="row">
<div class="col">
<canvas id="graphique"></canvas>
</div>
</div>
<div class="row">
<div class="col-xxs">
<h1>Mes catégories</h1>
<div v-for="(cat, index) in category" v-bind:key="index">
{{ cat.label }}
</div>
</div>
<div class="col">
<h1>Mes dépenses</h1>
<div v-for="(find, index) in finds" v-bind:key="index">
<input v-model="find.date" readonly />
<input v-model="find.value" readonly />
<input v-model="find.label" readonly />
</div>
<div>
<input v-model="dateToAdd" />
<input v-model="valueToAdd" />
<select v-model="labelToAdd">
<option
v-for="(cat, index) in category"
:value="cat.label"
v-bind:key="index"
>
{{ cat.label }}
</option>
</select>
<button #click="addFind">Add cost</button>
</div>
</div>
</div>
</div>
</template>
<script>
import Chart from "chart.js/auto";
import "chartjs-adapter-moment";
export default {
name: "PlanetChart",
data() {
return {
ctx: null,
Chart: null,
valueToAdd: null,
labelToAdd: null,
dateToAdd: null,
category: [
{ id: "salary", label: "Salaire" },
{ id: "fun", label: "Loisir" },
],
finds: [
{ date: new Date("12/01/2020"), label: "Dépense 1", value: 12 },
{ date: new Date("12/01/2021"), label: "Salariée", value: 2 },
{ date: new Date("12/01/2000"), label: "Ciné", value: 1 },
{ date: new Date("12/01/2023"), label: "Restaurant", value: 12 },
],
};
},
mounted() {
this.ctx = document.getElementById("graphique");
this.finds.sort(function (a, b) {
return a.date - b.date;
});
this.Chart = this.createChart(this.finds);
},
methods: {
createChart: function (dataToAdd) {
var toRet = new Chart(this.ctx, {
type: "line",
data: {
datasets: [
{
label: "My dataset",
fill: false,
lineTension: 0.1,
backgroundColor: "rgba(75,192,192,0.4)",
borderColor: "rgba(75,192,192,1)",
borderCapStyle: "butt",
borderDash: [],
borderDashOffset: 0.0,
borderJoinStyle: "miter",
pointBorderColor: "rgba(75,192,192,1)",
pointBackgroundColor: "#fff",
pointBorderWidth: 1,
pointHoverRadius: 5,
pointHoverBackgroundColor: "rgba(75,192,192,1)",
pointHoverBorderColor: "rgba(220,220,220,1)",
pointHoverBorderWidth: 2,
pointRadius: 5,
pointHitRadius: 10,
data: dataToAdd,
},
],
},
options: {
parsing: {
xAxisKey: "date",
yAxisKey: "value",
},
scales: {
x: {
type: "time",
time: {
unit: "month",
},
},
},
responsive: true,
maintainAspectRatio: false,
},
});
return toRet;
},
addCategory: function () {},
addFind: function () {
if (this.valueToAdd !== null && this.labelToAdd !== null) {
this.finds.push( { date: new Date("12/01/1990"), label: "Dépense nouvelle", value: 5 });
this.finds.sort(function (a, b) {
return new Date(a.date) - new Date(b.date);
});
this.Chart.data.datasets[0].data = this.finds;
// console.log(this.Chart.data.datasets);
this.Chart.update();
// this.Chart = this.createChart(this.finds);
this.valueToAdd = 0;
this.dateToAdd = "";
this.labelToAdd = "";
}
},
},
};
</script>

How to make multiple search filters work with a for loop in Vue Bootstrap?

I have a table with some filters in each column but when I type anything all the items disappear. The console does not return any error.
Here’s the code:
<template>
<div>
<b-table
id="the-table"
:per-page="perPage"
:current-page="currentPage"
:items="filteredItems"
:fields="fields"
>
<template slot="top-row" slot-scope="{ fields }">
<td v-for="field in fields" :key="field.key">
<input v-model="filters[field.key]" :placeholder="field.label" />
</td>
</template>
</b-table>
<b-pagination v-model="currentPage" :total-rows="rows" :per-page="perPage" aria-controls="the-table"></b-pagination>
</div>
</template>
<script>
import axios from "axios";
export default {
data() {
return {
filters: {
option: "",
style: "",
stock: "",
},
items: [],
fields: [
{ key: "options", label: "Options", sortable: true },
{ key: "style", label: "Style", sortable: true },
{ key: "stock", label: "Stock", sortable: true },
{ key: "type", label: "Type", sortable: true },
{ key: "class", label: "Class.", sortable: true },
],
perPage: 15,
currentPage: 1,
};
},
created() {
this.getTable();
},
methods: {
getTable() {
axios
.get("./data/options.json")
.then((res) => (this.items = res.data))
.catch((error) => console.log(error));
},
},
computed: {
rows() {
return this.items.length;
},
filteredItems() {
return this.items.filter((d) => {
return Object.keys(this.filters).every((f) => {
return this.filters[f].length < 1 || this.filters[f].includes(d[f]);
});
});
},
},
};
</script>
I am trying to filter an array of objects like this:
[{"option": "ABEVH160", "style": "American", "stock": "ABEV3", "type": "CALL", "class": "ITM"},
{"option": "BBAS230", "style": "European", "stock": "BBAS3", "type": "PUT", "class": "ATM"},
{"option": "BBDC180", "style": "American", "stock": "BBDC4", "type": "CALL", "class": "OTM"}]
My goal is to be able to filter each of the columns individually:
Does anyone know what I’m missing?
The problem is with the condition:
return this.filters[f].length < 1 || this.filters[f].includes(d[f]);
d[f] is the full value and this.filters[f] is the search string. Obviously this does not work since it checks if the full word is contained in a substring. Simply invert the condition:
return this.filters[f].length < 1 || d[f].includes(this.filters[f]);
You can see it working here.
What you want seems to be something like DataTable Filter | Filter Row in PrimeVue. I tried to find something similar in the documentation of bootstrap-vue (documentation), but couldn´t find anything like that.
Maybe you are in a stage where you can still implement PrimeVue in your project. I´m using Filter Row from PrimeVue by myself and can recommend it.

How to change the width of b-table th

I want to expand the rows of the table as in screenshot.How do I change the width of the th?
Is there any way how to add class to th
I want to
logTableField: [
{ key: "LogDate", label: "Tarih",thClass: 'bg-white text-dark' },
{ key: "LogUser", label: "Analist" },
{ key: "Description", label: " İşlem Açıklaması" },
{ key: "LogText", label: "Kullanıcı Yorumu" },
],
<b-tab title="Kayıt Logları" v-if="this.Logs != null">
<b-table
id="logTable"
striped
hover
:items="Logs"
:fields="logTableField"
per-page="10"
:current-page="currentPageLog"
>
<template slot="Description" slot-scope="row">
<div v-html="row.item.Description"></div>
</template>
</b-table>
<b-pagination
v-model="currentPageLog"
:total-rows="Logs.length"
per-page="10"
aria-controls="my-table"
></b-pagination>
</b-tab>
Check bootstrap-vue documentation for detailed styling on tables.
EDIT:
Please use lowercase variables.
Read the docs which are listed above
Why are your summed up widths not 100%?
If you in fact want to use classes, look up the answer from Stefanos_Apk which is perfect if there is more than one style attribute in my opinion
https://codesandbox.io/s/nervous-chandrasekhar-d5e2o?file=/src/components/Table.vue
logTableField: [
{
key: "logDate",
label: "Tarih",
thStyle: { width: "10%" },
},
{ key: "logUser", label: "Analist", thStyle: { width: "20%" } },
{
key: "description",
label: " İşlem Açıklaması",
thStyle: { width: "20%" },
},
{
key: "logText",
label: "Kullanıcı Yorumu",
thStyle: { width: "50%" },
},
],
You can set the thClass property inside of your field object. But tdClass just accepts a string or an array, not an object. So you can only reference to a css class.
logTableField: [
{ key: "LogDate", label: "Tarih", thClass: 'nameOfTheClass' },
{ key: "LogUser", label: "Analist", thClass: 'nameOfTheClas1' },
{ key: "Description", label: " İşlem Açıklaması", thClass: 'nameOfTheClass2' },
{ key: "LogText", label: "Kullanıcı Yorumu", thClass: 'nameOfTheClass3' },
]
and in your CSS:
.nameOfTheClass {
width: 10%
},
.nameOfTheClass1 {
width: 15%
}
.nameOfTheClass2 {
width: 15%
}
.nameOfTheClass3 {
width: 50%
}

Two-Way Binding in Component Scope

Suppose I'm trying to make a simple questionnaire, where the user answers a list of questions.
new Vue(
{
el: "#app",
data:
{
questions:
[
{
id: 1,
name: "What is your favorite color?",
selectedId: 2,
choices:
[
{ id: 1, name: "red" },
{ id: 2, name: "green" },
{ id: 3, name: "blue" },
]
},
...
]
}
});
How do I go about making a question component with two-way binding. That is, if the user swaps their favorite color from green to red, by clicking on the respective input, the selectedId will automatically update. I'm not very clear on how v-model works within a component. Does it only have access to the components data? Also, I don't understand the difference between props/data.
There are lots of ways you can approach this, here's my attempt:
let id = 0;
Vue.component('question', {
template: '#question',
props: ['question'],
data() {
return {
radioGroup: `question-${id++}`,
};
},
methods: {
onChange(choice) {
this.question.selectedId = choice.id;
},
isChoiceSelected(choice) {
return this.question.selectedId === choice.id;
},
},
});
new Vue({
el: '#app',
data: {
questions: [
{
title: 'What is your favorite color?',
selectedId: null,
choices: [
{ id: 1, text: 'Red' },
{ id: 2, text: 'Green' },
{ id: 3, text: 'Blue' },
],
},
{
title: 'What is your favorite food?',
selectedId: null,
choices: [
{ id: 1, text: 'Beans' },
{ id: 2, text: 'Pizza' },
{ id: 3, text: 'Something else' },
],
},
],
},
});
.question {
margin: 20px 0;
}
<script src="https://rawgit.com/yyx990803/vue/master/dist/vue.js"></script>
<div id="app">
<question v-for="question of questions" :question="question"></question>
</div>
<template id="question">
<div class="question">
<div>{{ question.title }}</div>
<div v-for="choice of question.choices">
<input type="radio" :name="radioGroup" :checked="isChoiceSelected(choice)" #change="onChange(choice)"> {{ choice.text }}
</div>
<div>selectedId: {{ question.selectedId }}</div>
</div>
</template>