In
https://codesandbox.io/s/rmh2n?file=/src/components/NestedDraggable.vue
, in:
<template>
<draggable class="dragArea" tag="ul" :list="tasks" :group="{ name: 'g1' }">
<li v-for="el in tasks" :key="el.name">
<generic-item :taskItem="el"> </generic-item>
<p>{{ el.name }}</p>
<nested-draggable :tasks="el.tasks" />
</li>
</draggable>
</template>
why isn't generic-item rendering el?
GenericItem:
<template>
<div v-if="taskItemLocal['itemSectionCategoryId']">
<p>
Section: {{taskItemLocal['itemSectionCategoryName']}}
</p>
</div>
<div v-else-if="taskItemLocal['itemSectionCategoryItemId']">
<p>
Item: {{taskItemLocal['itemSectionCategoryItemName']}}
</p>
</div>
<div v-else>
Not set
</div>
</template>
<script>
export default {
name: "generic-item",
props: {
taskItem: {
required: true,
type: Object,
default() {
return {};
},
},
},
components: {},
watch: {
taskItem: {
deep: true,
handler(val) {
this.taskItemLocal = Object.assign({}, val);
},
},
},
computed: {
getTaskItemLocal: {
get() {
if (!this.taskItemLocal) {
return Object.assign({}, this.taskItem);
}
return Object.values(this.taskItemLocal);
},
set(value) {
this.taskItemLocal = value;
},
},
},
data() {
return {
taskItemLocal: {
type: Object,
default: {},
},
};
},
};
</script>
<style scoped></style>
It looks like it isn't computing taskItemLocal. This component will have to modify props (they can't be used directly in the template). How can I "directly" copy props to a local variable and use it in the template "on the first run"?
Related
I have two child components I have to pass dynamically props from first child to parent and from parent to second child.
Parent
<script>
data: () => ({
model: {}
}),
methods: {
changeData(payload) {
this.model.personalData = {...payload}
}
}
</script>
<template>
<first-child #changeData="(payload) => changeData(payload)"/>
<second-child :enter-object="model" />
</template>
Child one
<script>
data: () => ({
model: {}
}),
methods: {
changeData() {
this.$emit("changeData", this.model);
}
}
</script>
<template>
<v-text-field v-model="model.name" #input="changeData()">
<v-text-field v-model="model.email" #input="changeData()">
</template>
Child two
<script>
props: {
enterObject: {
type: Object,
required: false,
default: () => ({})
}
},
data: () => ({
model: {}
}),
watch: {
enterObject: {
immediate: true,
handler() {
Object.assign(this.model.personalData, this.enterObject.personalData);
}
}
</script>
<template>
<div>
<div v-if="model.personalData.name || model.personalData.email">
<span class="mr-3">{{ model.personalData.name }}</span>
<span>{{ model.personalData.email }}</span>
</div>
<div v-else>
No data
</div>
</div>
</template>
I get data in parent component with no problem, but this data doesn't pass to second child, why I have always "No data" ?
I tested your code and found a few things:
You need to create "personalData" inside the model in "childTwo".
<template>
<div>
// I changed the validation for personalData
<div v-if="model.personalData">
<span class="mr-3">{{ model.personalData.name }}</span>
<span>{{ model.personalData.email }}</span>
</div>
<div v-else>No data</div>
</div>
</template>
export default {
props: {
enterObject: {
type: Object,
required: false,
default: () => ({})
}
},
data: () => ({
model: {
personalData: {}
}
}),
watch: {
enterObject: {
deep: true,
handler() {
// Add a validation in the handler, you can use Object assign inside the validation.
if(this.enterObject) {
Object.assign(this.model.personalData, this.enterObject.personalData)
}
}
}
}
It's worked for me.I hope it helps you.
You have to assign the value of the object using this.$set for more about object reactivity click here
your Parent component should be like this:-
here is the working example
<template>
<div>
<first-child #change-data="(payload) => changeData(payload)" />
<second-child :enter-object="model" />
</div>
</template>
<script>
import FirstChild from "./FirstChild";
import SecondChild from "./SecondChild";
export default {
data: () => ({
model: {},
compKey: 0,
}),
components: {
FirstChild,
SecondChild,
},
methods: {
changeData(payload) {
this.$set(this.model, "test", payload);
//this.model.test = payload;
},
},
};
</script>
I have the following code:
import './menu-item';
import ItemImage from "./item-image";
Vue.component('quest-card', {
props: {
title: String,
isFree: Boolean,
points: Number,
item: String,
level: Number,
},
components: {
ItemImage
},
methods: {
showFree: function() {
return !!+this.isFree;
},
},
template: `
<section class="quest-reward bg-dark">
<div class="rewardLevel">{{ level }}</div>
<div v-if="!showFree()" class="bg-success freeReward">FREE</div>
<div class="rewardItem">
<item-image name="item" heb-name="test" rarity="epic"></item-image>
</div>
</section>
`,
})
My component are used in a legacy HTML/JQuery website using selector (for this one - <quest-card ...> and it's not rendered with the item-image component:
Item image component looks like this:
const ItemImage = Vue.component('ItemImage', {
props: ['name','heb-name', {
rarity: {
default: 'normal',
type: String,
}
}],
computed: {
glowClass: () => {
return `glow-${this.rarity}`;
}
},
template: `
<img v-bind:src="'images/items/' + item + '.png'" v-bind:class="glowClass"/>
`,
})
export default ItemImage;
the export default of the Vue component seem wrong, but not sure what I have to do so they can play together. Any idea?
You had problems in defining the props
const ItemImage = Vue.component('ItemImage', {
props: {name: String, 'heb-name': String, item: [String, Number], rarity: {
default: 'normal',
type: String,
}},
computed: {
glowClass: () => {
return `glow-${this.rarity}`;
}
},
template: `
<img v-bind:src="'images/items/' + item + '.png'" v-bind:class="glowClass"/>
`,
})
Vue.component('quest-card', {
props: {
title: String,
isFree: Boolean,
points: Number,
item: String,
level: Number,
},
components: {
ItemImage
},
methods: {
showFree: function() {
return !!+this.isFree;
},
},
template: `
<section class="quest-reward bg-dark">
<div class="rewardLevel">{{ level }}</div>
<div v-if="!showFree()" class="bg-success freeReward">FREE</div>
<div class="rewardItem">
<item-image name="item" heb-name="test" rarity="epic"></item-image>
</div>
</section>
`,
})
new Vue({
el: '#app',
})
<div id="app"><quest-card /></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
I have a Vue app that can either randomize a title and subtitle OR manually edit these two values through a custom input component. When a user decides to edit, their input should then display on save those results on the parent component.
I have the randomizer and child component emitting the updated headings working, but having a troubled time updating the parents and state to display the custom input title and subtitle on save and getting a "undefined" error for both title and subtitle when I placed console logs in updateTitleAndSubtitle() in the actions section of the store.
The objective of this code challenging is to return the new values to the store and be able to display the custom inputs while having the randomizer handy whenever a user decides to use that instead.
Any direction on what I'm doing wrong or missing would be much appreciated. I've been reading article after article around Vuex and Vue2 for 3 days now with 2 months of experience using Vue.
Custom Input Child Component:
<template>
<div>
<label for="title">Edit Title: </label>
<input
type="text"
id="title"
:updateTitle="updateTitle"
v-model="inputTitle"
/>
<label for="title">Edit Subtitle: </label>
<input
type="text"
id="subtitle" :updateSubtitle="updateSubtitle"
v-model="inputSubtitle"
/>
</div>
</template>
<script>
export default {
name: 'CustomInput',
props: {
title: String,
subtitle: String,
},
computed: {
updateTitle() {
console.log('updateTitle: ', this.title);
return this.title;
},
updateSubtitle() {
console.log('updateSubtitle: ', this.subtitle);
return this.subtitle;
},
inputTitle: {
get() {
console.log('set title: ', this.title);
return this.title;
},
set(title) {
console.log('set title: ', title);
this.$emit('input', title);
},
},
inputSubtitle: {
get() {
return this.subtitle;
},
set(subtitle) {
console.log('set subtitle: ', subtitle);
this.$emit('input', subtitle);
},
},
},
};
</script>
Parent component:
<template>
<main class="home-page page">
<div v-if="!editMode" class="display-information">
<div class="title">
<span class="bold">Title: </span>{{title}}
</div>
<div class="subtitle">
<span class="bold">Subtitle: </span>{{subtitle}}
</div>
<div class="controls">
<button id="randomize-button" class="control-button" #click="randomizeTitleAndSubtitle">
Randomize
</button>
<button id="edit-button" class="control-button" #click="onEdit">Edit</button>
</div>
</div>
<div v-else class="edit-controls">
<CustomInput
:title="title"
:subtitle="subtitle"
#update="v => onSave(v)"
/>
<div class="controls">
<button id="cancel-button" class="control-button" #click="onCancel">Cancel</button>
<button id="save-button" class="control-button" #click="onSave">Save</button>
</div>
</div>
</main>
</template>
<script>
// # is an alias to /src
import CustomInput from '#/components/CustomInput.vue';
import { mapState, mapActions } from 'vuex';
export default {
name: 'Home',
components: {
CustomInput,
},
data() {
return {
editMode: false,
};
},
computed: {
...mapState(['title', 'subtitle']),
},
methods: {
...mapActions(['randomizeHeadings', 'updateHeadings']),
onEdit() {
this.editMode = true;
},
onCancel() {
this.editMode = false;
},
onSave(v) {
this.editMode = false;
this.title = v.title;
this.subtitle = v.subtitle;
this.updateTitleAndSubtitle(v);
},
},
mounted() {
this.randomizeHeadings();
},
};
Vuex Store:
import randomWords from 'random-words';
export default new Vuex.Store({
state: {
title: '',
subtitle: '',
},
mutations: {
UPDATE_TITLE(state, value) {
state.title = value;
},
UPDATE_SUBTITLE(state, value) {
state.subtitle = value;
},
},
actions: {
randomizeTitle({ commit }) {
const newTitle = randomWords();
commit('UPDATE_TITLE', newTitle);
},
randomizeSubtitle({ commit }) {
const newSubtitle = randomWords();
commit('UPDATE_SUBTITLE', newSubtitle);
},
randomizeTitleAndSubtitle({ dispatch }) {
dispatch('randomizeTitle');
dispatch('randomizeSubtitle');
},
updateTitleAndSubtitle({ commit }) {
const payload = {
title: this.title || null,
subtitle: this.subtitle || null,
};
commit('UPDATE_TITLE', payload);
commit('UPDATE_SUBTITLE', payload);
},
},
modules: {
},
});
I tested your code in my local development environment and find out that you need a lot of changes in your codes to work better. Here is the new vuex store code:
vuex store:
export default new Vuex.Store({
state: {
title: '',
subtitle: '',
},
mutations: {
UPDATE_TITLE(state, value) {
state.title = value;
},
UPDATE_SUBTITLE(state, value) {
state.subtitle = value;
},
},
actions: {
randomizeTitle({ commit }) {
const newTitle = randomWords();
commit('UPDATE_TITLE', newTitle);
},
randomizeSubtitle({ commit }) {
const newSubtitle = randomWords();
commit('UPDATE_SUBTITLE', newSubtitle);
},
randomizeTitleAndSubtitle({ dispatch }) {
dispatch('randomizeTitle');
dispatch('randomizeSubtitle');
},
updateTitleAndSubtitle({ commit }, inputUser) {
/* I changed the structure of this action to work correctly */
console.log(inputUser);
commit('UPDATE_TITLE', inputUser.title);
commit('UPDATE_SUBTITLE', inputUser.subtitle);
},
},
modules: {
},
});
Also here is the new Parent component code:
Parent component:
<template>
<main class="home-page page">
<div v-if="!editMode" class="display-information">
<div class="title">
<span class="bold">Title: </span>{{title}}
</div>
<div class="subtitle">
<span class="bold">Subtitle: </span>{{subtitle}}
</div>
<div class="controls">
<button id="randomize-button" class="control-button" #click="randomizeTitleAndSubtitle">
Randomize
</button>
<button id="edit-button" class="control-button" #click="onEdit">Edit</button>
</div>
</div>
<div v-else class="edit-controls">
<CustomInput
:title="title"
:subtitle="subtitle"
#titleEvent = "myFuncTitle"
#subTitleEvent = "myFuncSubTitle"
/>
<!--
I removed this part from your component.
#update="v => onSave(v)"
and also added custom events (titleEvent and subTitleEvent) to the component
-->
<div class="controls">
<button id="cancel-button" class="control-button" #click="onCancel">Cancel</button>
<button id="save-button" class="control-button" #click="onSave">Save</button>
</div>
</div>
</main>
</template>
<script>
// # is an alias to /src
import CustomInput from '../components/CustomInput.vue';
import { mapActions } from 'vuex';
export default {
name: 'Parent',
components: {
CustomInput,
},
data() {
return {
editMode: false,
/* defining new data for handling "cancel" button functionality */
temporaryTitle: "",
temporarySubTitle: ""
};
},
computed: {
/* defining setter and getter for each computed value separately */
title: {
// getter
get: function () {
return this.$store.state.title;
},
// setter
set: function (newValue) {
this.$store.commit('UPDATE_TITLE', newValue);
}
},
subtitle: {
// getter
get: function () {
return this.$store.state.subtitle;
},
// setter
set: function (newValue) {
this.$store.commit('UPDATE_SUBTITLE', newValue);
}
},
},
methods: {
/* changing the name of actions according to the names defined in "store" */
...mapActions(['randomizeTitleAndSubtitle', 'updateTitleAndSubtitle']),
onEdit() {
this.editMode = true;
this.temporaryTitle = this.$store.state.title;
this.temporarySubTitle = this.$store.state.subtitle;
},
onCancel() {
this.editMode = false;
this.$store.commit('UPDATE_TITLE', this.temporaryTitle);
this.$store.commit('UPDATE_SUBTITLE', this.temporarySubTitle);
},
myFuncTitle(event) {
console.log(event);
/* we could not set values to "computed" properties, if we had not defined "set: function ..." for them above. */
this.title = event;
},
myFuncSubTitle(event) {
this.subtitle = event;
},
onSave(v) {
this.editMode = false;
console.log(v); /* "v" is not related to your data. notice the console */
// this.title = v.title;
// this.subtitle = v.subtitle;
const payload = {
title: this.title,
subtitle: this.subtitle,
};
this.updateTitleAndSubtitle(payload);
},
},
created() {
this.randomizeTitleAndSubtitle();
},
};
</script>
And finally here is the code of new Custom Input component:
Custom Input:
<template>
<div>
<label for="title">Edit Title: </label>
<input
type="text"
id="title"
v-model="inputTitle"
#input="$emit('titleEvent', $event.target.value)"
/>
<!-- emitting event like above code for each input -->
<label for="title">Edit Subtitle: </label>
<input
type="text"
id="subtitle"
v-model="inputSubtitle"
#input="$emit('subTitleEvent', $event.target.value)"
/>
</div>
</template>
<script>
export default {
name: 'CustomInput',
props: {
title: String,
subtitle: String,
},
computed: {
inputTitle: {
get() {
console.log('set title: ', this.title);
return this.title;
},
set(title) {
console.log('set title: ', title);
},
},
inputSubtitle: {
get() {
return this.subtitle;
},
set(subtitle) {
console.log('set subtitle: ', subtitle);
},
},
},
};
</script>
<style scoped>
</style>
I tried to comment some changes to the codes, but the main changes are related to changing the name of mapActions actions according to the names defined in "store" and also provide a setter for computed properties.
I suggest that you read more in vue and vuex documentations, especially the page that is related to custom events and computed setters and vuex actions, if you have problems with my codes.
I have a checkbox component in Vue:
<template>
<div class="checkbox">
<input class="checkbox-input" name="input" type="checkbox" v-model="checkbox">
</div>
</template>
<script>
export default {
data(){
return {
checkbox: false
};
},
};
</script>
So in the parent component I want to control these checkbox. So here is my parent component:
<div class="card">
<div class="card-header">
<CheckBox />
</div>
<div class="card-body" v-for="item in cart" :key="item.product.id">
<div class="checkbox-area">
<CheckBox />
</div>
</div>
So checkbox in card-body can be added when user clicks to add. So if a user clicks 3 times, 3 checkbox are being added inside of card-body. What I am trying to achieve is, as you see in card-header there is another checkbox, and when this checkbox is clicked, I want to check all the checkboxes inside card-body, and when it is unchecked in card-header, I want to unchcecked everything inside card-body.
So do you have any idea how to achieve this?
Thanks...
You can try like this :
Vue.component('checkbox', {
template: `
<div class="checkbox">
<input class="checkbox-input" name="input" type="checkbox" #change="getCheck" v-model="value">
</div>
`,
props: {
checked: {
type: Boolean,
default: false
}
},
data(){
return {
value: this.checked
};
},
methods: {
getCheck() {
this.$emit("set-checked", this.value)
}
},
watch: {
checked(){
this.value = this.checked
}
}
})
new Vue({
el: '#demo',
data(){
return {
all: false,
cart: [
{id: 1, check: false},
{id: 2, check: false},
{id: 3, check: true},
{id: 4, check: false}
]
};
},
watch: {
cart() {
this.cart.find(c => c.check === false) ? this.all = false : this.all = true
}
},
methods: {
checkAll(val) {
this.all = val
this.cart = this.cart.map(c => {
c.check = val
return c
})
},
checkItem(id) {
this.cart = this.cart.map(c => {
if(c.id === id) {
c.check = !c.check
}
return c
})
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<div class="card">
<div class="card-header">
<p>All</p>
<checkbox :checked="all" #set-checked="checkAll" />
</div>
<br/>
<div class="card-body" v-for="item in cart" :key="item.id">
<div class="checkbox-area">
<checkbox :checked="item.check" #set-checked="checkItem(item.id)" />
</div>
</div>
</div>
</div>
First of all you need to add some props to your Component. than a wachter to emit a sync when the Value of the checkbox changes.
<template>
<div class="checkbox">
<input class="checkbox-input" name="input" type="checkbox" v-model="value">
</div>
</template>
<script>
export default {
props: {
checked: {
type: Boolean,
default: false
}
}
data(){
return {
value: this.checked
};
},
watch: {
value(){
this.$emit("update:checked", this.value)
}
}
};
</script>
on the cart you need to watch for theses changes an than you can check/uncheck all the items.
<template>
<div class="card">
<div class="card-header">
<CheckBox :checked.sync="global"/>
</div>
<div class="card-body" v-for="item in cart" :key="item.product.id">
<div class="checkbox-area">
<CheckBox :checked.sync="item"/>
</div>
</div>
</div>
</template>
<script>
export default {
data(){
return {
global: false,
cart: [
false,
false,
false
]
};
},
watch: {
global(){
for (let i = 0; i < this.cart.length; i++) {
this.chart[i] = this.global
}
}
}
};
</script>
I have not tested this code, but this should work...
Using checkbox input as custom components can be a little tricky see if this code can help you:
code sandbox
Vue.component('checkbox', {
template: `
<label>
<input type="checkbox" :value="value" v-model="deltaValue" />
{{ label }}
</label>
`,
name: "checkbox",
model: {
prop: 'modelValue',
event: 'update:modelValue'
},
props: {
label: String,
value: [Object, Boolean, Array],
modelValue: {
type: [Array, Boolean],
default: () => [],
},
},
computed: {
deltaValue: {
get() {
return this.modelValue;
},
set(value) {
this.$emit("update:modelValue", value);
},
},
},
});
new Vue({
el: '#app',
data() {
return {
areAllSelected: false,
checkedItems: [],
cart: [
{ id: 1, name: "tablet"},
{ id: 2, name: "phone"},
{ id: 3, name: "PC" },
],
};
},
watch: {
areAllSelected(areAllSelected) {
if (areAllSelected) {
this.checkedItems = [...this.cart];
return;
}
this.checkedItems = [];
},
checkedItems(items){
if (items.length === this.cart.length) {
this.areAllSelected = true;
return;
}
if (items.length === 0) {
this.areAllSelected = false;
}
}
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class="card">
{{ checkedItems }}
<div class="card-header">
<checkbox label="check all" v-model="areAllSelected" />
</div>
<hr />
<div class="card-body" v-for="product in cart" :key="product.id">
<checkbox
:label="product.name"
:value="product"
v-model="checkedItems"
/>
</div>
</div>
</div>
I have been trying to show my modal but for some reason it keeps saying that the property isn't defined even though I have declared it in the Data()
I feel like I am missing something critical to my understanding on how this all works ...
The property is defined as false on load and should turn to true on click of the button.
<template>
<div class="product-item">
<h3>{{product.name}}</h3>
<p>{{product.tagline}}</p>
<img class="product-image" :src="product.image_url">
<p>PH: {{product.ph}}</p>
<button class="show-modal" #click="showModal = true">Show a tip</button>
<modal v-if="showModal" #close="showModal = false"></modal>
</div>
</template>
<script>
import Modal from "#/components/Modal.vue";
export default {
components: {
Modal
},
Data() {
showModal: false
},
props: {
product: {
type: Object
}
},
methods: {},
computed: {},
mounted() {}
};
</script>
You data Object should be returned via a function like :
data(){
return{
showModal: false
}
}
data should be in lowercase .
A component’s data option must be a function, so that each instance can maintain an independent copy of the returned data object:
data: function () {
return {
...
},
}
import Modal from "#/components/Modal.vue";
export default {
components: {
Modal
},
data: function () {
return {
showModal: false
},
}
props: {
product: {
type: Object
}
},
methods: {},
computed: {},
mounted() {}
};
<template>
<div class="product-item">
<h3>{{product.name}}</h3>
<p>{{product.tagline}}</p>
<img class="product-image" :src="product.image_url">
<p>PH: {{product.ph}}</p>
<button class="show-modal" #click="showModal = true">Show a tip</button>
<modal v-if="showModal" #close="showModal = false"></modal>
</div>
</template>