I have a Vue component to display a list of data. It should receive the data and sortBy as props and render correctly. However, I have an extra button in the component that I want to update sortBy and re-render the new list but I don't know how to assign new data to computed property sortedData. Thanks a lot if I can have some advices.
<template>
<div>
<template v-for="(item, index) in sortedData" :key="index">
{{ item.name }}
</template>
<button #click.prevent="sortWith('color')">sort Color</button>
</div>
</template>
<script>
export default {
props: {
data: {
type: Array,
default: []
},
sortBy: {
type: String,
default: 'name'
},
},
methods: {
sort(array, sortBy) {
return array.sort(function (a, b) {
return b[sortBy] - a[sortBy]
})
},
sortWith(sortBy) {
// Need to sort the list and re-render new order
}
}
computed: {
sortedData() {
return this.sort(this.data, this.sortBy)
}
}
}
</script>
Try like following snippet:
const app = Vue.createApp({
data() {
return {
items: [{name: 'aaa', color: 'red'}, {name: 'ccc', color: 'purple'}, {name: 'fff', color: 'yellow'}, {name: 'bbb', color: 'blue'}, {name: 'eee', color: 'green'}],
sortingBy: 'name',
sortingOrder: false
}
},
methods: {
setSorting(item) {
this.sortingBy = item
this.sortingOrder = !this.sortingOrder
}
}
})
app.component('child', {
template: `
<div>
<template v-for="(item, index) in sortedData" :key="index">
<div>{{ item.name }} - {{ item.color }}</div>
</template>
<div class="sort">
<p>Sort By (child) :</p>
<div v-for="(title, i) in Object.keys(data[0])" :key="i" class="fields">
{{ title }}
</div>
</div>
</div>
`,
props: {
data: {
type: Array,
default: []
},
sortBy: {
type: String,
default: 'name'
},
sortOr: {
type: String,
default: false
},
},
data() {
return {
sortField: this.sortBy,
sortOrder: this.sortOr
}
},
methods: {
sort(array, sortBy) {
return array.sort((a, b) => {
let x = a[sortBy].toLowerCase()
let y = b[sortBy].toLowerCase()
if (this.sortOrder) {
return x > y ? -1 : 1;
} else {
return x > y ? 1 : -1;
}
return 0
})
},
sorting(item) {
this.sortField = item
this.sortOrder = !this.sortOrder
}
},
computed: {
sortedData() {
return this.sort(this.data, this.sortField)
}
},
watch: {
sortBy: function (val) {
this.sortField = val
},
sortOr: function (val) {
this.sortOrder = val
},
}
})
app.mount('#demo')
.sort {
display: flex;
align-items: center;
}
.fields {
margin-left: 1em;
}
<script src="https://unpkg.com/vue#3.2.29/dist/vue.global.prod.js"></script>
<div id="demo">
<div class="sort">
<p>Sort By (parent) :</p>
<div v-for="(title, i) in Object.keys(items[0])" :key="i" class="fields">
{{ title }}
</div>
</div>
<child :data="items" :sort-by="sortingBy" :sort-or="sortingOrder"></child>
</div>
Related
Hi I'm new to vue and I'm trying to complete one task. I have dynamic component toggles which I render with v-for. Can you suggest how can I pass an extra click to only one button (button 'border-left')? Desirable illustrative examples
<script>
import Vue from "vue";
import BorderLeftComonent from "./BorderLeftComonent.vue";
import TextBalloonComponent from "./TextBalloonComponent.vue";
import DashedComponent from "./DashedComponent.vue";
export default Vue.extend({
data() {
return {
component: "button[0].name",
color: "",
buttons: [
{
label: "A",
isActive: false,
type: "border-left",
name: "BorderLeftComonent",
},
{
label: "A",
isActive: false,
type: "text-balloon",
name: "TextBalloonComponent"
},
{
label: "A",
isActive: false,
type: "dashed",
name: "DashedComponent"
},
],
};
},
methods: {
toggleShowPopup() {
this.isOpen = !this.isOpen;
},
activeBtn(event, index) {
this.buttons[index].isActive = !this.buttons[index].isActive;
}
},
computed: {
currentComponent() {
return this.component;
},
cssVars() {
return {
'--border-left': this.color,
}
}
},
</script>
template is presented here
<template>
<div id="btn-box">
<button
v-for="(button, index) in buttons"
:key="index"
:class="button.isActive ? 'on' : 'off'"
#click="component = button.name, activeBtn($event, index)">
<div :class="`btn btn-${button.type}`">{{ button.label }}</div>
</button>
</div>
</template>
the method i need to pass to only one button
toggleShowPopup() {
this.isOpen = !this.isOpen;
}
You need conditional event binding. Try this:
<button
v-for="(button, index) in buttons"
:key="index"
:class="button.isActive ? 'on' : 'off'"
#click="component = button.name, activeBtn($event, index), button.type === 'border-left' && toggleShowPopup()">
<div :class="`btn btn-${button.type}`">{{ button.label }}</div>
</button>
This way might be a little more cleaned-up:
//import BorderLeftComonent from "./BorderLeftComonent.vue";
//import TextBalloonComponent from "./TextBalloonComponent.vue";
//import DashedComponent from "./DashedComponent.vue";
/*export default */new Vue/*.extend*/({
el: '#container',
template: `<div>
<div id="btn-box">
<button
v-for="(button, index) in buttons"
:key="index"
:class="button.isActive ? 'on' : 'off'"
:data-index="index"
#click="button.method">
<div :class="${"`"}btn btn-${"$"}{button.type}${"`"}">{{ button.label }}</div>
</button>
</div>
</div>`,
data() {
return {
component: "button[0].name",
color: "",
buttons: [
{
label: "A",
isActive: false,
type: "border-left",
name: "BorderLeftComonent",
method: this.toggleShowPopup,
},
{
label: "A",
isActive: false,
type: "text-balloon",
name: "TextBalloonComponent",
method: this.handleClick,
},
{
label: "A",
isActive: false,
type: "dashed",
name: "DashedComponent",
method: this.handleClick,
},
],
};
},
methods: {
toggleShowPopup(event) {
this.isOpen = !this.isOpen;
this.handleClick(event)
},
activeBtn(event) {
index = event.currentTarget.dataset.index
this.buttons[index].isActive = !this.buttons[index].isActive;
},
handleClick(event) {
index = event.currentTarget.dataset.index
button = this.buttons[index]
this.component = button.name;
this.activeBtn(event)
},
},
computed: {
currentComponent() {
return this.component;
},
cssVars() {
return {
'--border-left': this.color,
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="container"></div>
I have got an issue I can't get my head around, I have tried to reproduce the error below, however the below code works just fine!
So below is what is supposed to happen.
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<middleman :items="items" :selected="selected" #update="update"></middleman>
</div>
<script type="text/x-template" id="checkbox">
<label :for="$vnode.key">
{{ value }}
<input type="checkbox"
:value="value"
:id="$vnode.key"
#change="$emit('update', $event)"
:checked="isSelected(value)">
</label>
</script>
<script type="text/x-template" id="middleman">
<div>
<checkbox v-for="(item, index) in filtered"
:selected="selected"
:value="item.value"
#update="$emit('update', $event)"
:key="`checkbox-${index}`"></checkbox>
</div>
</script>
<script>
Vue.component('checkbox', {
props: ['value', 'selected'],
template: '#checkbox',
methods: {
isSelected(value) {
return this.selected.indexOf(value) > -1;
}
}
})
Vue.component('middleman', {
props: ['items', 'selected'],
template: '#middleman',
computed: {
filtered() {
return [...this.items].sort((a, b) => this.selected.includes(b.value) - this.selected.includes(a.value))
}
}
})
new Vue({
el: '#app',
data: {
items: [
{value: 'BMW', count: 1},
{value: 'AUDI', count: 1},
{value: 'VAUXHALL', count: 1},
{value: 'FIAT', count: 1},
{value: 'HONDA', count: 1},
{value: 'LANDROVER', count: 1},
],
selected: ['AUDI', 'HONDA']
},
methods: {
update(event) {
if (event.target.checked) {
this.selected.push(event.target.value);
return;
}
const index = this.selected.indexOf(event.target.value);
this.selected.splice(index, 1);
}
}
});
</script>
<style>
label {
display: block;
}
</style>
The above is pretty much what I have got. There is a root component, a middleman, and then the checkbox...
However mine is doing this:
Here is the code, very similar but using single file components:
// Root.vue
<template>
<div class="app-search">
<div class="app-search__facets">
<facet v-for="(facet, key) in facets"
#update="updateFacet(...$event, key)"
:facet="facet"
:label="key"
:data="items[key]"
:key="key"/>
</div>
</div>
</template>
<script>
import facet from '../Facets/Facet';
export default {
components: {
facet
},
data: function () {
return {
facets: {
manufacturer: [{"control": "checkbox", "type": "value", "size": 20, "selected": []}]
},
items: {
manufacturer: [
{value: 'BMW', count: 1},
{value: 'AUDI', count: 1},
{value: 'VAUXHALL', count: 1},
{value: 'FIAT', count: 1},
{value: 'HONDA', count: 1},
{value: 'LANDROVER', count: 1}
]
}
}
},
methods: {
updateFacet(action, value, facet) {
switch (action) {
case 'add':
this.facets[facet].selected.push(value);
break;
case 'remove':
let index = this.facets[facet].selected.indexOf(value);
this.facets[facet].selected.splice(index, 1);
break;
}
},
}
}
</script>
// Facet.vue
<template>
<div class="app-search__facet">
<p class="app-search__facet--title">{{ label }} ({{ selected }})</p>
<component :is="facet.control"
v-for="(value, index) in filter"
:data="value"
:value="facet.selected"
:key="`facet-${label}-${index}`"
#value="update($event)" />
</div>
</template>
<script>
import '#/prototypes/toPascalCase'
export default {
props: {
data: {
required: true,
type: Array
},
facet: {
required: true,
type: Object
},
label: {
required: true,
type: String
}
}
computed: {
selected() {
return this.facet.selected.length
},
filter() {
const selected = this.facet.selected;
return [...this.data].sort((a, b) => selected.includes(b.value) - selected.includes(a.value));
}
},
methods: {
component(control) {
return () => import(`./Controls/${control.toPascalCase()}`)
},
update(event) {
switch(this.facet.control) {
case 'checkbox':
const value = event.target.value;
if (event.target.checked && this.facet.selected.indexOf(value) === -1) {
this.$emit('update', [ 'add', value ])
break;
}
this.$emit('update', [ 'remove', value ])
break;
default:
console.log('Can\'t handle this facet');
break;
}
},
},
}
</script>
// Checkbox.vue
<template>
<label :for="$vnode.key" class="app-search__checkbox">
{{ data.value }} ({{ data.count }})
<input type="checkbox"
:id="$vnode.key"
:value="data.value"
#change="$emit('value', $event)"
:checked="isSelected">
</label>
</template>
<script>
export default {
props: {
data: {
required: true,
type: Object
},
value: {
required: true
}
},
computed: {
isSelected() {
return this.value.indexOf(this.data.value) > -1;
}
}
}
</script>
Anyone got any idea what is going on here?
The problem is caused by the combination of using v-for index to create a :key and sorting items after each select/deselect action. Do not use index in key - use something unique for each item, for example item.value
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.14/vue.js"></script>
<div id="app">
<middleman :items="items" :selected="selected" #update="update"></middleman>
</div>
<script type="text/x-template" id="checkbox">
<label :for="$vnode.key">
{{ value }}
<input type="checkbox"
:value="value"
:id="$vnode.key"
#change="$emit('update', $event)"
:checked="isSelected(value)">
</label>
</script>
<script type="text/x-template" id="middleman">
<div>
<checkbox v-for="(item, index) in filtered"
:selected="selected"
:value="item.value"
#update="$emit('update', $event)"
:key="item.value"></checkbox>
</div>
</script>
<script>
Vue.component('checkbox', {
props: ['value', 'selected'],
template: '#checkbox',
methods: {
isSelected(value) {
return this.selected.indexOf(value) > -1;
}
}
})
Vue.component('middleman', {
props: ['items', 'selected'],
template: '#middleman',
computed: {
filtered() {
return [...this.items].sort((a, b) => this.selected.includes(b.value) - this.selected.includes(a.value))
}
}
})
new Vue({
el: '#app',
data: {
items: [
{value: 'BMW', count: 1},
{value: 'AUDI', count: 1},
{value: 'VAUXHALL', count: 1},
{value: 'FIAT', count: 1},
{value: 'HONDA', count: 1},
{value: 'LANDROVER', count: 1},
],
selected: ['AUDI', 'HONDA']
},
methods: {
update(event) {
if (event.target.checked) {
this.selected.push(event.target.value);
return;
}
const index = this.selected.indexOf(event.target.value);
this.selected.splice(index, 1);
}
}
});
</script>
<style>
label {
display: block;
}
</style>
I have a table where I need show data from another associated table. Everything works well but I receive this warning when I pass some filter:
prop depto_modules.length not exist in the row, please confirm wether the prop is right, this may cause unpredictable filter result
This warning does not affect the operation but I would like to remove this.
My table is Modules and the associated table is depto_modules(DeptoModules).
My list.vue :
<template>
<div class="">
<alert
:show-alert="alertShowErrors"
:closable="alertClosable"
:type="alertType"
:title="alertTitle"
:description="alertDescription"
:show-icon="alertShowIcon"
/>
<box box_type="solid" :title="title" :collapse="collapse" class="box-title">
<el-row>
<el-col :span="2">
<el-tooltip class="item" effect="dark" content="Novo" placement="top-start">
<el-button plain type="primary" size="mini" #click="handleInsert()">
<i class="fas fa-plus" /><span v-if="device!=='mobile'"> Novo</span>
</el-button>
</el-tooltip>
</el-col>
<el-col :xs="11" :sm="11" :md="11" :lg="11" :offset="1">
<el-input v-model="filters[0].value" size="mini" placeholder="Search" prefix-icon="el-icon-search" />
</el-col>
<el-col :span="3" :offset="1">
<el-tooltip class="item" effect="dark" content="Update Table" placement="top-start">
<el-button plain size="mini" #click="handleRefresh()">
<i class="fas fa-sync" /><span v-if="device!=='mobile'"> Atualizar</span>
</el-button>
</el-tooltip>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<data-tables
v-loading="listLoading"
:data="data"
:filters="filters"
:total="totalRows"
:pagination-props="pageSizes"
:current-page.sync="currentPage"
:page-size="pageSize"
#expand-change="handleExpand"
#query-change="list"
#selection-change="handleSelectionChange"
#size-change="handleSizeChange"
>
<el-table-column align="left" min-width="35" width="70">
<template slot-scope="scope">
<el-tooltip class="item" effect="dark" content="Edit" placement="top-start">
<el-button size="mini" type="primary" icon="el-icon-edit" plain circle #click="handleEdit(scope.$index, scope.row)" />
</el-tooltip>
<el-tooltip class="item" effect="dark" content="Remove" placement="top-start">
<el-button size="mini" type="danger" icon="el-icon-delete" plain circle #click="handleDelete(scope.$index, scope.row)" />
</el-tooltip>
</template>
</el-table-column>
<el-table-column
v-for="(column, index) in columns"
:key="column.prop"
:item="column"
:index="index"
:prop="column.prop"
:label="column.label"
:type="column.type"
:resizable="column.resizable"
:min-width="column.width"
:align="column.align"
:header-align="column.headerAlign"
:formatter="cellValueRenderer"
sortable="custom"
/>
</data-tables>
</el-col>
</el-row>
</box>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import Box from '#/components/Share/box'
import Alert from '#/components/Alert'
import { iframeResize } from '#/utils/resize_iframe'
import { getStatusCode } from '#/utils/status-response'
import { numberFormat } from '#/utils/cells-format'
import Modules from '#/api/plan/modules'
export default {
name: 'Modules',
components: {
Box,
Alert
},
props: {
title: {
type: String,
default: 'Modules List'
},
collapse: {
type: Boolean,
default: false
},
filterField: {
type: String,
default: null
},
filterValue: {
type: (String, Number),
default: null
}
},
data() {
return {
data: [],
columns: this.loadColumns(),
filters: this.loadFilter(),
selectedRow: [],
pageSize: 10,
perPage: 10,
pageSizeExpand: 10,
pageSizes: {
background: true,
pageSizes: [10, 20, 30, 40, 50, 100, 150, 200, 250, 300]
},
pageSizesExpand: {
background: true,
pageSizes: [10, 20, 30, 40, 50, 100, 150, 200, 250, 300]
},
currentPage: 1,
currentPageExpand: 1,
totalRows: 0,
totalRowsExpand: 0,
listLoading: true,
expandLoading: false,
alertType: 'warning',
alertShowErrors: false,
alertClosable: false,
alertShowIcon: false,
alertTitle: null,
alertDescription: ''
}
},
computed: {
...mapGetters([
'device'
])
},
created() {},
mounted() {
this.getFilter()
this.resetAlert()
this.list()
},
updated() {
this.$nextTick(() => {
iframeResize()
})
},
methods: {
cellValueRenderer(row, column, cellValue) {
var value = cellValue
if (column.type === 'money') {
value = numberFormat(cellValue, 'money')
}
if (column.type === 'decimal') {
value = numberFormat(cellValue, 'decimal')
}
if (column.type === 'date') {
value = numberFormat(cellValue, 'date')
}
if (column.type === 'percent') {
value = numberFormat(cellValue, 'percent')
}
if (typeof row[column.property] === 'boolean') {
value = 'Inativo'
if (cellValue) {
value = 'Ativo'
}
}
return value
},
loadColumns() {
var columns = [
{ prop: 'id', label: 'ID', resizable: true, align: 'left', headerAlign: 'left', width: '30' },
{ prop: 'name', label: 'Name', resizable: true, align: 'left', headerAlign: 'left', width: '30' },
{ prop: 'path', label: 'Path', resizable: true, align: 'left', headerAlign: 'left', width: '30' },
{ prop: 'depto_modules.length', label: 'Deptos', resizable: true, align: 'left', headerAlign: 'left', width: '30' },
{ prop: 'status', label: 'Status', resizable: true, align: 'left', headerAlign: 'left', width: '30' }
]
if (this.filterField) {
for (var i = 0; i < columns.length; i++) {
if (columns[i].prop === this.filterField) {
columns.splice(i, 1)
}
}
}
return columns
},
loadFilter() {
var columns = this.loadColumns()
var filterItems = []
columns.forEach(function(value) {
filterItems.push(value.prop)
})
var filter = [{ prop: filterItems, value: '' }]
return filter
},
getFilter() {
if (this.getStore('FilterPageSizeModules')) {
this.pageSize = this.getStore('FilterPageSizeModules')
} else {
this.pageSize = 10
}
},
setFilter() {
this.setStore('FilterPageSizeModules', this.perPage)
},
setParentField() {
var params = []
if (this.filterField) {
params = { 'parent_id': this.filterValue }
}
return params
},
handleRefresh() {
this.list()
},
handleInsert() {
this.$router.push({ name: 'Modules_Add', query: this.setParentField() })
},
handleEdit(index, row) {
if (this.filterField) {
this.setParentField()
}
this.setStore('setModulesId', row.id)
this.$router.push({ name: 'Modules_Edit' })
},
handleDelete(index, row) {
this.setStore('setModulesId', row.id)
this.delete(row.id)
},
handleSizeChange(val) {
this.perPage = val
this.setFilter()
this.$nextTick(() => {
iframeResize()
})
},
handleCurrentChange(val) {
this.currentPage = val
},
handleSelectionChange(val) {
this.selectedRow = val
},
handleExpand(row, expandedRows) {
},
setStore(field, value) {
this.$session.set(field, value)
},
getStore(field) {
return this.$session.get(field)
},
showAlert(message, title = '') {
this.alertShowErrors = true
this.alertClosable = true
this.alertTitle = title
this.alertDescription = message
},
resetAlert() {
this.alertShowErrors = false
this.alertClosable = false
this.alertTitle = ''
this.alertDescription = ''
},
formatData(data) {
return data
},
async list() {
this.resetAlert()
this.listLoading = true
var tables = { table: 'UsersModules, DeptoModules' }
var select = {}
var order = {}
var query = { ...tables, ...select, ...order }
if (this.filterField && this.filterValue) {
query = { ...query, filter: this.filterField + ':' + this.filterValue }
}
Modules.list(query)
.then(response => {
this.resetAlert()
var dados = response.data
this.data = this.formatData(dados.data)
this.totalRows = dados.count
this.listLoading = false
})
.catch(error => {
var err = getStatusCode(error)
this.showAlert(err)
this.listLoading = false
})
}, ...
The warning:
My out:
Someone has some idea about how can I remove this warning?
Try to change the name of property:
From: depto_modules.length
To: depto_modules_length
It's seems that el-table-column think the depto_modules.length is a sub-object.
I have a couple of challenges that I am trying to overcome in my 1 week of learning Vue.js. Please note that this example is in reality wrapped around a parent component called <question> which isn't that interesting, so I have kept my code simplified for this post.
How can I set certain items to be default checked on load?
Edit — I figured 1 out. Just had to do [ "Chicken", "Turkey", "Beef", "Fish", "Pork" ]
How can I uncheck certain items like if I select Vegan, meat options should be unchecked?
How do I have an Exclude and Include checkbox alongside my options?
Checkbox
<div id="questionnaire">
<checkbox v-model="form.meats" id="8a" option="Chicken"></checkbox>
<checkbox v-model="form.meats" id="8b" option="Turkey"></checkbox>
<checkbox v-model="form.meats" id="8c" option="Beef"></checkbox>
<checkbox v-model="form.meats" id="8d" option="Pork"></checkbox>
<checkbox v-model="form.meats" id="8e" option="Fish"></checkbox>
<checkbox v-model="form.meats" id="8f" option="Vegetarian Only"></checkbox>
<checkbox v-model="form.meats" id="8g" option="Vegan Only"></checkbox>
{{ form.meats }}
</div>
Vue.component('checkbox')
Vue.component('checkbox', {
template: `
<div>
<input type="checkbox" :id="id" :value="option" v-model="checked" #change="update">
<label :for="id">
{{ option }}
<slot></slot>
</label>
</div>
`,
data() {
return {
checkedProxy: false
}
},
computed: {
checked: {
get() {
return this.value
},
set(option) {
this.checkedProxy = option
}
}
},
methods: {
update: function(e) {
this.$emit('input', this.checkedProxy)
}
},
props: {
value: null,
option: null,
id: {
type: String,
required: true
}
}
});
new Vue({
el: "#questionnaire",
data: {
form: {
meats: [],
}
}
})
I think these are what you want.
<div id="questionnaire">
<check-group :chks="chks" v-model="form.meats"></check-group>
{{ form.meats }}
</div>
const groups = {
'1': {
tag: 'meats',
exclusiveGroups: [2]
},
'2': {
tag: 'vegan',
exclusiveGroups: [1]
}
}
const chks = {
'Chicken': {
groupIds: [1]
},
'Turkey': {
groupIds: [1]
},
'Beef': {
groupIds: [1]
},
'Pork': {
groupIds: [1]
},
'Fish': {
groupIds: [1]
},
'Vegetarian Only': {
groupIds: [2]
},
'Vegan Only': {
groupIds: [2]
}
}
Vue.component('checkbox', {
template: `
<div>
<label>
<input type="checkbox" ref="chk" :value="val" v-model="value" #change="update($event)">
{{ txt }}
<slot></slot>
</label>
<input type="checkbox" :checked="value.indexOf(val)<0" #change="reverseSelection($event)">
</div>
`,
data () {
return {
val: this.optValue || this.optText,
txt: this.optText || this.optValue
}
},
methods: {
update (e) {
this.$emit('input', this.value, e.target.value, e.target.checked)
},
reverseSelection () {
var e = document.createEvent("MouseEvents");
e.initEvent("click", true, true);
this.$refs.chk.dispatchEvent(e);
}
},
props: ['value','optValue','optText']
});
Vue.component('check-group',{
template: `
<div>
<checkbox v-for="item in chks" :opt-value="item.value" :opt-text="item.text" #input="update" v-model="value"></checkbox>
</div>
`,
props: {
value: {
required: true,
type: Array
},
chks: {
required: true,
type: Array
}
},
methods: {
update (val,curVal,checked) {
if(checked){//only checkbox be checked need to judge mutually-exclusive
chks[curVal].groupIds.forEach(id=>{//iterate all group of this checkbox
groups[id].exclusiveGroups.forEach(eid=>{//iterate all exclusiveGroups of this group
for(let i=0;i<val.length;i++){
let p = chks[val[i]].groupIds.indexOf(eid)
if(p>=0){//if this checkbox's group in exclusiveGroups then remove this item from val Array
val.splice(p,1)
i--
}
}
})
})
}
this.$emit('input',val)
},
}
})
new Vue({
el: "#questionnaire",
data: {
chks: Object.keys(chks).map(key=>({value: key,groupIds: chks[key]})),
form: {
meats: ['Chicken']
}
}
})
if you want to let vegan and vegetarian can't be both selected at once,
you can modify defining of groups and chks like this:
const groups = {
'1': {
tag: 'meats',
exclusiveGroups: [2] //means that when the checkbox of this group be checked,the checkbox whose group-index equals 2 where be unchecked
},
'2': {
tag: 'vegan',
exclusiveGroups: [1,3]
},
'3': {
tag: 'Vegetarian Only',
exclusiveGroups: [1,2]
}
}
const chks = {
'Chicken': {
groupIds: [1]
},
'Turkey': {
groupIds: [1]
},
'Beef': {
groupIds: [1]
},
'Pork': {
groupIds: [1]
},
'Fish': {
groupIds: [1]
},
'Vegetarian Only': {
groupIds: [3]
},
'Vegan Only': {
groupIds: [2]
}
}
I'm passing some dynamic data from a parent component to a child component using props .. So I would like to know how I can add myColor prop to total value and show it an render the result in a final value.
I've already update the post with the parent component (shapes) and the child component (colors)
I'm using Vue 2 and webpack.
//parent component
<v-layout row wrap primary-title v-for="shape in shapes" :key="shape.id">
<v-layout column>
<v-flex >
<v-subheader>{{shape.name}} {{shape.price}}€ {{selectedShape.price}}</v-subheader>
</v-flex>
</v-layout>
</v-layout>
<my-colors :myShape="selectedShape.price"></my-colors>
<script>
import Colors from './Colors.vue';
export default {
components: {
Colors
},
data() {
return {
selectedShape: {},
shapes: [{
id: 1,
name: "Square",
price: 4,
href: "../../static/square.jpg"
}, {
id: 2,
name: "Circle",
price: 6,
href: "../../static/circle.jpg"
}]
}
},
computed: {
totalShape: function() {
var totalShape = 0;
for (var i = 0; i < this.shapes.length; i++) {
if (this.shapes[i].selected) {
totalShape += this.shapes[i].price;
}
}
return totalShape;
}
},
methods: {
getSelectedShape() {
return this.selectedShape;
},
}
}
</script>
//child component
<v-layout>
<v-layout>
<v-flex >
<h3 >Total price:</h3>
</v-flex>
</v-layout>
<v-layout>
<v-flex
<v-subheader> {{total}} {{myShape}} €</v-subheader>
</v-flex>
</v-layout>
</v-layout>
<script>
export default {
props: ['myShape'],
data: () => ({
checked1: '',
showCart: false,
colors: [{
id: 1,
name: "white",
price: 2,
checked: '',
}, {
id: 2,
name: "black",
price: 2.0,
checked: '',
}, {
id: 3,
name: "Grey",
price: 2.25,
checked: '',
}, {
id: 4,
name: "Blue",
price: 1.6,
checked: '',
}, {
id: 5,
name: "Red",
price: 2.5,
checked: '',
}, {
id: 6,
name: "Yellow",
price: 2.75,
checked: '',
}],
}),
computed: {
total: function() {
var total = 0;
for (var i = 0; i < this.colors.length; i++) {
if (this.colors[i].checked) {
total += this.colors[i].price;
}
}
return total;
},
},
}
</script>
I do not understand your needs from this script, but be aware of one way data flow in Vue. So, you can send data from parent component to child component in which its will be accessible through props, but not from child component to parent. Use Vuex if you need two-way data flow between components.
var child = {
template: '#child',
props: ['fromParent']
}
Vue.component('parent', {
template: '#parent',
components: {
child: child
},
props: ['fromInstance']
})
new Vue({
el: '#app',
data: {
instanceData: {
text: 'Original value'
}
},
created () {
var self = this
setTimeout(_ => self.instanceData.text = 'Changed value', 3000)
}
})
<script src="https://unpkg.com/vue#2.4.2/dist/vue.min.js"></script>
<div id="app">
<parent :from-instance="this.instanceData"></parent>
</div>
<template id="parent">
<div>
<child :from-parent="this.fromInstance"></child>
</div>
</template>
<template id="child">
<p>{{this.fromParent.text}}</p>
</template>
Example with SELECT:
var child = {
template: '#child',
props: ['selected']
}
Vue.component('parent', {
template: '#parent',
components: {
child: child
},
props: ['options'],
data () {
return {
parentCar: 'none'
}
},
methods: {
update (e) {
this.parentCar = e.target.value
}
}
})
new Vue({
el: '#app',
data: {
items: {
audi: 'Audi',
bmw: 'BMW',
mercedes: 'Mercedes',
}
}
})
<script src="https://unpkg.com/vue#2.4.2/dist/vue.min.js"></script>
<div id="app">
<parent :options="this.items"></parent>
</div>
<template id="parent">
<div>
<select #change="update">
<option value="none" selected>Car</option>
<option v-for="(value, key) in options" :value="key">
{{ value }}
</option>
</select>
<child :selected="this.parentCar"></child>
</div>
</template>
<template id="child">
<p>{{ selected }}</p>
</template>
Example with checked / unchecked checkbox:
var child = {
template: '#child',
props: ['checked']
}
Vue.component('parent', {
template: '#parent',
components: {
child: child
},
data () {
return {
checkbox: false
}
}
})
new Vue({
el: '#app'
})
<script src="https://unpkg.com/vue#2.4.2/dist/vue.min.js"></script>
<div id="app">
<parent></parent>
</div>
<template id="parent">
<div>
<input type="checkbox" v-model="checkbox">
<child :checked="this.checkbox"></child>
</div>
</template>
<template id="child">
<p>{{ checked }}</p>
</template>