<template>
<el-row>
<el-col :span="24">
<el-image
v-for="(card, index) in randomCards"
:key="card.indexOf"
:src="card.url"
:id="(card.id = index)"
fit="fill"
#click="flipCards(index)"
></el-image>
</el-col>
</el-row>
</template>
<script>
import axios from "axios";
export default {
data() {
return {
selectedCardsId: [],
randomCards: null,
data: [],
};
},
computed: {},
methods: {
getResults() {
axios
.get("https://api.thecatapi.com/v1/images/search", {
params: { limit: 36 },
})
.then((response) => {
this.randomCards = response.data
.concat(response.data)
.sort(() => 0.5 - Math.random());
});
},
flipCards(cardIndex) {
this.randomCards[cardIndex].url = "1";
},
},
async mounted() {
await this.getResults();
},
};
</script>
When I load the page and click the image, I see changes in two elements in the array because they have the same value but a different index. I think that's because I pushed the same array. I also tried Array.from() and the spread operator.
In response.data.concat(response.data), you're appending the original objects by reference, so changes to one instance affect the other as they refer to the same data.
Assuming the data items are all shallow, a quick way to clone the data is to map them into new objects:
this.randomCards = response.data.concat(response.data.map(x => ({ ...x })))
demo
Related
Long story short(maybe it is not so short after all): on the same page I want to import and load dynamic components based on the selected module.
I have an object defined in the assets which contains the informations about the components that should be loaded for each module, it looks like this:
export const modules = {
module1: {
calls: {...},
components: [
{
url: 'shared/PreviewItem',
properties: [
{
name: 'width',
value: 'leftComponentWidth'
}
]
},
{
url: 'shared/ResizingDivider',
properties: []
},
{
url: 'forms/FormItem',
properties: [
{
name: 'width',
value: 'rightComponentWidth'
},
{
name: 'item',
value: 'item'
}
]
}
]
},
module2: {...}
}
Then I have my index page:
<template>
<div class="item-content">
<component
:is="component"
v-for="(component, i) in dataComponents"
:key="i"
v-bind="component.propertiesToPass"
#emit-action="emitAction($event)"
/>
</div>
</template>
<script>
export default {
data() {
return {
item: null,
rightComponentWidth: 50,
leftComponentWidth: 50,
dataComponents: []
}
},
created() {
this.importComponents()
},
methods: {
importComponents() {
this.dataComponents = []
modules[this.$route.params.module].components.forEach(
(component) => {
import(`~/components/${component.url}`).then((res) => {
res.default.propertiesToPass = []
component.properties.forEach((prop) => {
res.default.propertiesToPass.push({
[prop.name]: this[prop.value]
})
})
this.dataComponents.push(res.default)
})
}
)
},
emitAction(event) {
this[event.function](event.props)
},
changeComponentsWidth(event) {
this.leftComponentWidth -= event
this.rightComponentWidth = 100 - this.leftComponentWidth
}
}
}
}
</script>
As it is probably easy to understand I have to components and one divider between them that can be dragged to the right or to the left for resize the width of the other two components.
The components are getting loaded and imported correctly, and the props are passed right, so the width of both of the components in the start are 50 50.
The issue is that by doing [prop.name]: this[prop.value] I am setting the props to the value of this[prop.value] variable, and not to the variable itself, so, when I try to resize the components by using the divider, the variables get updated but the props get not.
Then the props are not responsive or reactive, are fixed.
The only way to update the props of the components is to add the following lines to the changeComponentsWidth() method:
this.dataComponents[0].propertiesToPass[0].width = this.leftComponentWidth
this.dataComponents[2].propertiesToPass[0].width = this.rightComponentWidth
But this is not a very dynamic way.
So My question is:
Is it possible to bind the props to the variable itself instead of just passing its value?
Or are there other "dynamic" ways to keep my props "responsive and reactive"?
I am new to Vue.js and would like to understand how v-model works with a checkbox.
I am working with vuetify checkboxes.
My components gets an object as value prop and I would like to display checkboxes according to each key value pair that would look like that
this.value = {property1: true, property2: false}
So here i want to display a checkbox with lable property1 and the checkbox should be checked as the value is true. The second checkbox would be unchecked.
When checking a checkbox I want to send an object with the key and the value in order to save it. I am only able to get the value for now, but what can I do with it ?
If I try to get the key and the value, the issue is that when i uncheck it sends null instead of the key, value pair, how should i manage this ?
<template>
<div class="d-flex flex-wrap justify-space-between">
<div class="d-flex flex-wrap flex-column">
<v-checkbox
class="add-checkbox"
ref="additionalDetails"
v-for="(value, key) in additionalDetails"
type="checkbox"
:key="key"
:value="{key, value}"
v-model="additionalDetails"
:label="key"
><template v-slot:label
><span class="body-2"
>{{
key
}}
</span>
</template></v-checkbox
>
</div>
</div>
</template>
<script>
export default {
name: "additional-details",
props: {
value: Object,
},
components: {},
data: function () {
return {
newAdditionalDetails: [],
};
},
computed: {
additionalDetails: {
get: function () {
return this.value;
},
set: function ({ key, value}) {
let newObject = { ...this.value };
newObject[key] = value;
this.newAdditionalDetails = newObject
},
},
},
methods: {},
beforeMount() {},
};
</script>
v-model should be same type of value you want to track, in this case an object, but actually because of the v-for an array of objects, initialized with the values you expect the checkboxes to hold/emit. The v-checkbox component will also need to be explicitely told what object key-value equals 'truthy' vs 'falsy'. Another tip: since you're also looping over object properties, a third alias in the v-for will give you the index. Something like this will work:
<template>
<div>
<v-checkbox
v-for="(value, key, index) in initValues" :key=index
:label=key
v-model=checkboxes[index]
:value="{ key: key, value: value }"
:false-value="{ key: key, value: false }"
:true-value="{ key: key, value: true }"
#change="log(checkboxes[index])"
></v-checkbox>
</div>
</template>
<script>
export default {
data: () => ({
initValues: {
'hi': true,
'bye': false,
'ok': true
},
checkboxes: []
}),
methods: {
log(newValue) {
console.log(newValue)
}
},
created() {
// initialize array of objects where each object is a key-value pair from initValues
this.checkboxes = Object.entries(this.initValues).map(( [k, v] ) => ({ [k]: v }))
}
};
</script>
It's a bit tricky, and is honestly not how I would've done it. A simpler solution would be to track just the value and create/send the key-value pair as the checkbox input changes, e.g.
<template>
<div>
<v-checkbox
v-for="(value, key, index) in initValues" :key=index
:label="key"
v-model=checkboxes[index]
#change="log(key, checkboxes[index])"
></v-checkbox>
</div>
</template>
<script>
export default {
data: () => ({
initValues: {
'hi': true,
'bye': false,
'ok': true
},
checkboxes: []
}),
methods: {
log(key, newValue) {
console.log({ [key]: newValue })
}
},
created() {
this.checkboxes = Object.values(this.initValues)
}
};
</script>
Instead of writing this much complex logic, You can simply achieve that by binding object property in v-model as per the additionalDetails.
Live Demo :
new Vue({
el: '#app',
vuetify: new Vuetify(),
data () {
return {
additionalDetails: [{
property1: true
}, {
property2: false
}]
}
},
methods: {
getChecboxValues() {
console.log('additionalDetails', JSON.stringify(this.additionalDetails))
}
}
})
<script src="https://unpkg.com/vue#2.x/dist/vue.js"></script>
<script src="https://unpkg.com/vuetify#2.6.8/dist/vuetify.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/vuetify#2.6.8/dist/vuetify.min.css"/>
<link rel="stylesheet" href="https://unpkg.com/#mdi/font#6.x/css/materialdesignicons.min.css"/>
<div id="app">
<v-app id="inspire">
<v-container
class="px-0"
fluid
>
<v-checkbox
v-for="(value, key) in additionalDetails"
:key="key"
:label="Object.keys(value)[0]"
v-model="value[Object.keys(value)[0]]"
></v-checkbox>
<v-btn depressed color="primary" #click="getChecboxValues">
Submit
</v-btn>
</v-container>
</v-app>
</div>
Finally what I did I converted to an array the initail object (that comes in stringified by the way here) and added the checked prop to update in the v-model. I also had to add a method to set the new object (The example code below is a bit different from the original one sorry for that) Somehow the setter of the computed additionalDetails never triggers I have no clue why, if someone has, thanks in advance for sharing toughts.
<template>
<div class="d-flex flex-wrap justify-space-between">
<div class="d-flex flex-wrap flex-column">
<v-checkbox class="add-checkbox" ref="additionalDetails" v-for="(prop, index) in additionalDetails" type="checkbox" :label="prop.label" :key="index" :dataIndex="index" v-model="prop.checked" #change="setAdditionalDetails(prop)"><template v-slot:label><span class="body-2">{{ prop.label }} </span>
</template></v-checkbox>
</div>
</div>
</template>
<script>
export default {
name: "additional-details",
props: {
value: Object,
},
computed: {
additionalDetails: {
get: function() {
if (this.value.JsonAdditionalFields !== null) {
let res = [];
let parsedJsonAdditionalFields = JSON.parse(
this.value.JsonAdditionalFields
);
for (const [key, value] of Object.entries(
parsedJsonAdditionalFields
)) {
res = [
...res,
{
[key]: value,
checked: value,
label: "label"
},
];
}
return res;
} else {
return [];
}
},
},
},
methods: {
setAdditionalDetails(val) {
let newObject = { ...this.value
};
newObject.JsonAdditionalFields = JSON.parse(
newObject.JsonAdditionalFields
);
newObject.JsonAdditionalFields[Object.keys(val)[0]] = val.checked;
this.value.JsonAdditionalFields = JSON.stringify(
newObject.JsonAdditionalFields
);
},
},
watch: {
additionalDetails: {
handler(newVal) {
console.log("additionalDetails new Value:", newVal);
},
deep: true,
},
},
};
</script>
datalist.js
import axios from "axios";
export const datalist = () => {
return axios.get("myapiurl/name...").then((response) => response);
};
HelloWorld.vue
<template>
<div>
<div v-for="item in items" :key="item.DttID">
<router-link
:to="{
name: 'UserWithID',
params: { id: item.DepaD },
query: { DepaD: item.DepaID },
}"
>
<div class="bt-color">{{ item.DepaName }}</div>
</router-link>
</div>
<br /><br /><br />
<User />
</div>
</template>
<script>
import User from "./User.vue";
import { datalist } from "./datalist";
export default {
name: "HelloWorld",
components: {
User,
},
data() {
return {
items: datalist,
};
},
mounted() {
datalist().then((r) => {
this.items = r.data;
});
},
};
</script>
User.vue
<template>
<div>
<div v-for="(item, key) in user" :key="key">
{{ item.Accv }}
</div>
</div>
</template>
<script>
import { datalist } from "./datalist";
export default {
name: "User",
data() {
return {
lists: datalist,
};
},
computed: {
user: function () {
return this.lists.filter((item) => {
if (item.DepaD === this.$route.params.id) {
return item;
}
});
},
},
};
</script>
Error with the code is,
[Vue warn]: Error in render: "TypeError: this.lists.filter is not a function"
TypeError: this.lists.filter is not a function
The above error i am getting in User.vue component in the line number '20'
From the api which is in, datalist.js file, i think i am not fetching data correctly. or in the list filter there is problem in User.vue?
Try to change the following
HelloWorld.vue
data() {
return {
items: [],
};
},
mounted() {
datalist().then((r) => {
this.items = r.data;
});
},
User.vue
data() {
return {
lists: []
};
},
mounted() {
datalist().then((r) => {
this.lists = r.data;
});
},
At least this suppress the error, but i cant tell more based on your snippet since there are network issues :)
Since your datalist function returns a Promise, you need to wait for it to complete. To do this, simply modify your component code as follows:
import { datalist } from "./datalist";
export default {
name: "User",
data() {
return {
// empty array on initialization
lists: [],
};
},
computed: {
user: function() {
return this.lists.filter((item) => {
if (item.DeploymentID === this.$route.params.id) {
return item;
}
});
},
},
// asynchronous function - because internally we are waiting for datalist() to complete
async-mounted() {
this.users = await datalist() // or datalist().then(res => this.users = res) - then async is not needed
}
};
now there will be no errors when initializing the component, since initially lists is an empty array but after executing the request it will turn into what you need.
You may define any functions and import them, but they wont affect until you call them, in this case we have datalist function imported in both HelloWorld and User component, but it did not been called in User component. so your code:
data() {
return {
lists: datalist,
};
},
cause lists to be equal to datalist that is a function, no an array! where .filter() should be used after an array, not a function! that is the reason of error.
thus you should call function datalist and put it's response in lists instead of putting datalist itself in lists
Extra:
it is better to call axios inside the component, in mounted, created or ...
it is not good idea to call an axios command twice, can call it in HelloWorl component and pass it to User component via props
I made a page with two routes one is the home page another is the config where you can decide what should be written to that container, now in the config panel I was able to get the input values, I put them to my state with map actions now I am getting an array with string values in it.
How can I access that array with mapGetters ? I link my code:
<template>
<body>
<div class="container">
<h1 v-show="elementVisible" class="info">{{ message }}</h1>
</div>
</body>
</template>
<script>
import moment from "moment";
import { mapGetters } from "vuex";
export default {
name: "Home",
data() {
return {
// message: this.store.state.message
elementVisible: true
};
},
computed: {
...mapGetters(["message", "sec"]),
...mapGetters({
message: "message",
sec: "sec"
}),
createdDate() {
return moment().format("DD-MM-YYYY ");
},
createdHours() {
return moment().format("HH:mm ");
}
},
mounted() {
this.$store.dispatch("SET_MESSAGE");
this.$store.dispatch("SET_SEC");
setTimeout(() => (this.elementVisible = false), this.sec);
}
};
</script>
so what I have to do is to put to that{{message}} template my message which I received from the config panel and which is in my state right now sitting there as an array of string, for example, ["hello", "how are you"] that's how they are sitting there, so how can I grab the first one('hello') and write it out as a clean string and not as ["hello"] if you know how to grab all of them would be even better.
(RightNow it's just putting that whole array to my template)
Maybe I should something rewrite in my storejs file?
STOREJS:
const state = {
message: [],
// console.log(message);
sec: +[]
// other state
};
const getters = {
message: state => {
// console.log(this.state.message);
return state.message;
},
sec: state => {
return state.sec;
}
// other getters
};
const actions = {
setMessage: ({ commit, state }, inputs) => {
commit(
"SET_MESSAGE",
inputs.map(input => input.message)
);
return state.message;
},
setSec: ({ commit, state }, inputs) => {
commit("SET_TIMEOUT", inputs.map(x => x.sec).map(Number));
console.log(inputs.map(x => x.sec).map(Number));
return state.sec;
}
// other actions
};
const mutations = {
SET_MESSAGE: (state, newValue) => {
state.message = newValue;
},
SET_TIMEOUT: (state, newSecVal) => {
state.sec = newSecVal;
}
// other mutations
};
export default {
state,
getters,
actions,
mutations
};
what my homepage should do is that it writes out that message and there is a sec value which stands for the timeout, after that I want to continue with the second value in that array and when that times out I should want the third to be written out.. and so on.
Thank you!
Hello and welcome to Stack Overflow! Your message Array is being mapped correctly with mapGetters, but you're flattening it as a String when you put it inside the template with {{message}}, since the template interpolation logic covert objects to strings, the same as calling Array.toString in this case. You need to iterate it, i.e. using the v-for directive:
<template>
<body>
<div class="container">
<h1 v-show="elementVisible" class="info">
<span v-for="m of message" :key="m">{{m}}</span>
</h1>
</div>
</body>
</template>
Of course, if you only need the first item, you could show it directly using:
<template>
<body>
<div class="container">
<h1 v-show="elementVisible" class="info">{{message[0] || 'No message'}}</h1>
</div>
</body>
</template>
I have an app where I want to show a list of items. When you click on a single item, you're sent to it's "page", where its info is displayed.
These items have a type.
Beneath the single item info, I want to display all items with the same type, filtered from the list of all items. However I have no idea what to return in my filterItems()-method. Since the axios-calls are done with asyncData() I don't have access to singleitem.type, do I?
HTML:
<template>
<div>
<reusable-component v-for="item in singleitem" :key="item.id" />
<reusable-component v-for="item in filterItems(type)" :key="item.id" />
</div>
</template>
JS:
export default {
data() {
return {
singleitem: [],
allitems: []
}
},
asyncData() {
// Grab single item from ID supplied
return axios.get(`https://url/to/GetItemById${params.id}`)
.then((result) => {
return { singleitem: result.data }
})
// Grab all items
return axios.get('https://url/to/GetAllItems')
.then((result) => {
return { allitems: result.data }
})
},
methods: {
filterItems() {
// Filter items from all items that has same type as the singleitem
return allitems.filter(function(type) {
// Help!
})
}
}
}
I think this is a use case of computed properties [https://v2.vuejs.org/v2/guide/computed.html]
May be something like this:
computed: {
filterItems: function() {
return this.allitems.filter(item => item.type != singleitem.type);
}
}
So whenever the data changed. filterItems get (re)computed.