How to target array index position correctly in Vuejs? - javascript

HelloWorld.vue
<template>
<div>
<div v-for="(piza, index) in pizas" :key="piza.pname">
{{ piza.pname }}
<List
:content="matchpizza"
:pname="piza.pname"
:qname="quantitys[index] ? quantitys[index].qname : ''"
/>
</div>
</div>
</template>
<script>
import List from "./List.vue";
export default {
name: "HelloWorld",
components: {
List,
},
data() {
return {
pizas: [
{
pname: "chicken pizza",
},
{
pname: "chicken pizza spl",
},
{
pname: "mutton pizza",
},
{
pname: "plain pizza",
},
],
quantitys: [
{
qname: "one",
},
{
qname: "two",
},
{
qname: "three",
},
{
qname: "four",
},
{
qname: "five",
},
],
matchpizza: [
{
matchnum: "1",
availability: "later",
pname: "plain pizza",
qname: "five",
},
{
matchnum: "2",
availability: "buy now",
pname: "chicken pizza",
qname: "one",
},
{
matchnum: "3",
availability: "buy now",
pname: "plain pizza",
qname: "five",
},
{
matchnum: "4",
availability: "buy now",
pname: "chicken pizza spl",
qname: "five",
},
{
matchnum: "5",
availability: "later",
pname: "mutton pizza",
qname: "five",
},
],
};
},
};
</script>
List.vue
<template>
<div>
<div v-if="matchpizza.length > 0">
<div
v-for="match in matchpizza"
:key="match.matchnum"
:class="{
green: match.availability === 'buy now',
red: match.availability === 'later',
}"
>
<div v-for="quantity in quantitys" :key="quantity.qname">
{{ quantity.qname }}
</div>
<div class="next-data-two">{{ match.availability }}</div>
</div>
</div>
<div v-else class="next-data"><p>No availability</p></div>
</div>
</template>
<script>
export default {
components: {},
props: {
content: {
type: Array,
required: true,
},
pname: {
type: String,
required: true,
},
qname: {
type: String,
required: true,
},
},
data: function () {
return {};
},
methods: {},
computed: {
quantitys() {
return this.content.filter((a) => a.qname === this.qname);
},
matchpizza() {
return this.content.filter((a) => a.pname === this.pname);
},
},
};
</script>
<style scoped>
.next-data {
display: flex;
flex-direction: row-reverse;
margin-top: -2%;
margin-right: 40%;
color: blue;
}
.next-data-two {
display: flex;
flex-direction: row-reverse;
margin-top: -1%;
margin-right: 42%;
color: red;
margin-bottom: 1%;
}
</style>
See my working code here:-
https://codesandbox.io/s/data-display-tcr8oj?file=/src/components/HelloWorld.vue
From the helloworld.vue you can see this line :qname="quantitys[index] ? quantitys[index].qname : ''"
Where as per the above code, from the quantitys array. it is not pointing to the particular index value correctly?.
According to my output from codesandbox:-
For chicken pizza it showing as , chicken pizza --> one --> later
But correct path is, chicken pizza --> later --> five
Similarly for others too, having same issue....
Is it because of index position wrong or issue with logic??

As you mentioned your required pathe is chicken pizza --> later --> five.
This path doesnot have to do anything with the quantitys array. You could simply display the details using the content prop itself.
Working Fiddle https://codesandbox.io/s/data-display-forked-kjxuyv

Related

How to use same JavaScript program multiple times on same website

I am working on my Portfolio project and for that I have made a International country code selector program for my phone number field. but the problem I'm facing right now is that I have two forms in my homepage one is Consultation Form and another one is Contact Form, and I want to use the same JS program for both Forms.
I don't want to rewrite the same type of program or functions again and again, because that negatively effects the performance of my website. but in the other hand I don't know how can I reuse the same code for two forms. It conflicts when I try to reuse the function.
I have a working dropdown menu for Contact Form, also I want to implement the same logic to Consultation Form by using the same program.
Please help me to figure out this problem.
// Cache the elements
const ccDiv = document.querySelector(".contact-frm-cc");
const ccButton = document.querySelector(".cc-telcode");
const ccContainer = document.querySelector(".cc-container");
const ccSearchInput = document.querySelector(".cc-search-box");
const ccList = document.querySelector(".cc-data-list");
var selectedCountry = ""; // saved by "code"
// Add event listeners to the button, input, and list
// We use a process called "event delegation" on the list
// to catch events from its children as they "bubble up" the DOM
// https://dmitripavlutin.com/javascript-event-delegation/
ccButton.addEventListener("click", handleButton);
ccSearchInput.addEventListener("input", handleInput);
ccList.addEventListener("click", handleListClick);
document.addEventListener("click", handleDocumentClick);
// All of the data held as objects within an array
const data = [
{ name: "Afganistan", code: "93", flag: "afg" },
{ name: "Albania", code: "355", flag: "alb" },
{ name: "Algeria", code: "213", flag: "dza" },
{ name: "American Samoa", code: "1-684", flag: "asm" },
{ name: "Andorra", code: "376", flag: "and" },
{ name: "Angola", code: "244", flag: "ago" },
{ name: "Anguilla", code: "1-264", flag: "aia" },
{ name: "Antarctica", code: "672", flag: "ata" },
{ name: "Antigua and Barbuda", code: "1-268", flag: "atg" },
{ name: "Argentina", code: "54", flag: "arg" },
{ name: "Armenia", code: "374", flag: "arm" },
{ name: "Aruba", code: "297", flag: "abw" },
{ name: "Australia", code: "61", flag: "aus" },
{ name: "Austria", code: "43", flag: "aut" },
{ name: "Azerbaijan", code: "994", flag: "aze" },
{ name: "Bahamas", code: "1-242", flag: "bhs" },
{ name: "Bahrain", code: "973", flag: "bhr" },
{ name: "Bangladesh", code: "880", flag: "bgd" },
{ name: "Barbados", code: "1-246", flag: "brb" },
{ name: "Belarus", code: "375", flag: "blr" },
{ name: "Belgium", code: "32", flag: "bel" },
{ name: "Belize", code: "501", flag: "blz" },
{ name: "Benin", code: "229", flag: "ben" },
{ name: "Bermuda", code: "1-441", flag: "bmu" },
{ name: "Bhutan", code: "975", flag: "btn" },
{ name: "Bolivia", code: "591", flag: "bol" },
{ name: "Bosnia and Herzegovina", code: "387", flag: "bih" },
{ name: "Botswana", code: "267", flag: "bwa" },
{ name: "Brazil", code: "55", flag: "bra" },
{ name: "British Indian Ocean Territory", code: "246", flag: "iot" },
{ name: "British Virgin Islands", code: "1-284", flag: "vgb" },
{ name: "Brunei", code: "673", flag: "brn" },
{ name: "Bulgaria", code: "359", flag: "bgr" },
{ name: "Burkina Faso", code: "226", flag: "bfa" },
{ name: "Burundi", code: "257", flag: "bdi" },
{ name: "Cambodia", code: "855", flag: "khm" },
{ name: "Cameroon", code: "237", flag: "cmr" },
{ name: "Canada", code: "1", flag: "can" },
{ name: "Cape Verde", code: "238", flag: "cpv" },
{ name: "Cayman Islands", code: "1-345", flag: "cym" },
{ name: "Central African Republic", code: "236", flag: "caf" },
{ name: "Chad", code: "235", flag: "tcd" },
{ name: "Chile", code: "56", flag: "chl" },
{ name: "China", code: "86", flag: "chn" },
{ name: "Christmas Island", code: "61", flag: "cxr" },
{ name: "Cocos Islands", code: "61", flag: "cck" },
{ name: "Colombia", code: "57", flag: "col" },
{ name: "Comoros", code: "269", flag: "com" },
{ name: "Cook Islands", code: "682", flag: "cok" },
{ name: "Costa Rica", code: "506", flag: "cri" },
{ name: "Croatia", code: "385", flag: "hrv" },
//------- MORE COUNTRIES TO ADD LATER
];
// Handles the document click - it checks to see if the clicked
// part of the document has a parent element which is either
// `null` or is the HTML element, and then closes the container
// if it's open
function handleDocumentClick(e) {
const { parentElement } = e.target;
document.addEventListener("click", (event) => {
if (!ccDiv.contains(event.target)) {
ccContainer.classList.remove("show-cc-list");
}
});
}
// Filters the data based on the characters
// at the start of the provided name
function filterData(data, value) {
return data.filter((obj) => {
return (
obj.name.toLowerCase().startsWith(value.toLowerCase()) ||
obj.code.toLowerCase().startsWith(value.toLowerCase())
);
});
}
// Create a series of list items based on the
// data passed to it
function createListHtml(data) {
return data.map((obj) => {
const { name, code, flag } = obj;
let isSelected = "";
if (obj.code == selectedCountry) isSelected = "selected-country";
return `
<li class="cc-list-items ${isSelected}" data-name="${name}" data-code="${code}" data-flag="${flag}">
<div class="flag-icon flag-icon-${flag}"></div>
<div class="name">${name} (+${code})</div>
</li>
`;
}).join("");
}
// Toggle the container on/off
function handleButton() {
ccContainer.classList.toggle("show-cc-list");
ccList.innerHTML = createListHtml(data);
}
// No data available list item
function createNoDataHtml() {
return '<li class="nodata">No data available</li>';
}
// When the input is changed filter the data
// according to the current value, and then
// create some list items using that filtered data
function handleInput(e) {
const { value } = e.target;
if (value) {
const filtered = filterData(data, value);
if (filtered.length) {
ccList.innerHTML = createListHtml(filtered);
} else {
ccList.innerHTML = createNoDataHtml();
}
} else {
ccList.innerHTML = createListHtml(data);
}
}
// Create some button HTML
function createButtonHtml(code, flag) {
return `
<div class="flag-icon flag-icon-${flag}"></div>
<option class="cc-code" value="+${code}">+${code}</option>
`;
}
// When an item is clicked, grab the relevant data
// attributes, create the new button HTML, and then
// close the container
function handleListClick(e) {
const item = e.target.closest("li") || e.target;
if (item.classList.contains("cc-list-items")) {
const { code, flag } = item.dataset;
selectedCountry = item.dataset.code;
ccButton.innerHTML = createButtonHtml(code, flag);
ccContainer.classList.remove("show-cc-list");
}
}
.cc-telcode {
margin-bottom: 1em;
display: flex;
justify-content: center;
width: 100%;
}
.cc-telcode div.cc-code,
.cc-list-items div.name {
margin-left: 0.25em;
}
.cc-container {
display: none;
width: 300px;
position: absolute;
}
.show-cc-list {
display: block;
z-index: +999;
}
.cc-data-list {
max-height: 100px;
list-style: none;
margin: 1em 0 0 0;
padding: 0;
overflow-y: scroll;
border: 1px soldi darkgray;
}
.cc-list-items {
display: flex;
padding: 0.25em;
border: 1px solid lightgray;
}
.cc-list-items:hover {
cursor: pointer;
background-color: lightyellow;
}
.selected-country {
cursor: pointer;
background-color: rgb(73, 118, 241);
}
.contact-frm-cc {
width:100px;
}
<link rel="stylesheet" href="https://amitdutta.co.in/flag/css/flag-icon.css">
<!------------- Working Dropdown Menu ------------>
<div class="contact-frm-cc">
<button class="cc-telcode">Tel code</button>
<section class="cc-container">
<input type="text" class="cc-search-box" placeholder="Search for country" />
<ul class="cc-data-list">
</ul>
</section>
</div>
<!------------- Not-Working Dropdown Menu ------------>
<!--
<div class="consult-frm-cc">
<button class="cc-telcode">Tel code</button>
<section class="cc-container">
<input type="text" class="cc-search-box" placeholder="Search for country" />
<ul class="cc-data-list">
</ul>
</section>
</div>
-->
You can create a function to register your form logic, and just pass your root form element.
here is a simple modification based on your code
<!-- https://codepen.io/DlmaK/pen/dyjXYrm -->

Show alert on input change in vue js

How can i display a confirmation alert only if i want to change radio button on previous step? So if i confirm my action all steps below should be removed.
I've binded a #change directive to the radio button with a method implementing the expected confirmation alert, but it appears on each change i make.
Here is my fiddle
Thanks for your advices in advance
new Vue({
el: "#app",
data() {
return {
answer: ["1"],
stepsData: [
{
id: "1",
yes_section: "2",
no_section: "4",
name: "Step 1",
},
{
id: "2",
yes_section: "5",
no_section: "1",
name: "Step 2",
},
{
id: "3",
yes_section: "2",
no_section: "4",
name: "Step 3",
},
{
id: "4",
yes_section: "2",
no_section: "4",
name: "Step 4",
},
{
id: "5",
yes_section: "2",
no_section: "4",
name: "Step 5",
},
],
};
},
computed: {
quation() {
return this.answer.map((answer) => {
return this.stepsData.find((step) => step.id === answer);
});
},
},
methods: {
pushAnswer(answer) {
this.answer.push(answer);
},
confirmPopup() {
alert('Are you sure?')
},
},
});
.step {
background: #ccc;
padding: 20px;
margin-bottom: 15px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="(step, id) in quation" :key="id" class="step">
<div v-html="step.name"></div>
<div>
<input
type="radio"
:id="step.UF_NO_SECTION"
:name="step.ID"
:value="step.yes_section"
#click="pushAnswer(step.yes_section)"
#change="confirmPopup"
/>
<label
:for="step.UF_NO_SECTION"
class="legal-aid__step-btn button _outline _no"
>
YES {{ step.yes_section }}
</label>
</div>
<div class="legal-aid__step-action_no">
<input
type="radio"
:id="step.UF_NO_SECTION"
:name="step.ID"
:value="step.no_section"
#click="pushAnswer(step.no_section)"
#change="confirmPopup"
/>
<label
:for="step.UF_NO_SECTION"
class="legal-aid__step-btn button _outline _no"
>
NO {{ step.no_section }}
</label>
</div>
</div>
</div>
I would add a property on every stepData that is called isSelected which will be initialized to false and once it was clicked, it will be changed to true.
So you data will look like this:
stepsData: [
{
id: "1",
yes_section: "2",
no_section: "4",
name: "Step 1",
isSelected: false
},
....
]
You will need to change pushAnswer to set isSelected to `true:
#click="pushAnswer(step)"
pushAnswer(answer) {
answer.isSelected = true;
this.answer.push(answer.yes_section);
},
And confirmPopup function will check if the answer is already selcted or not:
#change="confirmPopup(step)"
confirmPopup(step) {
if (step.isSelected) {
alert('Are you sure?')
}
},
Of course you can change anything to your liking, but this is the basic idea

React setting multiple states dynamically

Im learning React and currently playing with passing data around.
I have info for 5 cards stored in state like this:
state = {
cards: [
{ id: 1, name: "p1", value: "Prize 1", imageSrc: "/path/to/image", bgColor: { background: "#FF6384", border: "4px solid #FF6384" } },
{ id: 2, name: "p2", value: "Prize 2", imageSrc: "/path/to/image", bgColor: { background: "#4BC0C0", border: "4px solid #4BC0C0" } },
{ id: 3, name: "p3", value: "Prize 3", imageSrc: "/path/to/image", bgColor: { background: "#FFCE56", border: "4px solid #FFCE56" }},
{ id: 4, name: "p4", value: "Prize 4", imageSrc: "/path/to/image", bgColor: { background: "#67bd42", border: "4px solid #67bd42" } },
{ id: 5, name: "p5", value: "Prize 5", imageSrc: "/path/to/image", bgColor: { background: "#c931f7", border: "4px solid #c931f7" }}
],
flipped: null,
};
I am displaying them with an iteration, like this:
<div className="prize-cards-inner">
{this.state.cards.map((card) => {
return (
<section key={card.id} className="prize-card-container">
<div
className={`prize-card ${
this.state.flipped === card ? "flipped" : ""
}`}
onClick={() => this.clickHandler(card)}
>
<div className="front">
<div className="card-name-div" style={ card.bgColor }>
{card.value}
</div>
<div className="prize-image-div" >
<img className="prize-image" src={card.imageSrc} alt="test" />
</div>
<div className="slot-name-div" style={card.bgColor}>
<p> {card.name}</p>
</div>
</div>
<div className="back">Prize</div>
</div>
</section>
);
})}
</div>
and handling card clicks with this:
clickHandler = (card) => {
if (this.state.flipped) {
return;
}
this.setState({
flipped: card,
***Set another state for all other cards (how do I say "set the state of all cards that aren't this card?")***
});
};
I would like to set the state for all other cards aside from the clicked card at the same time I set the state for the clicked card.
I have no clue how to do what I want, so I have marked the part I am unsure about with asterisks. Thanks in advance for your time.
I think you are looking for this:
clickHandler(card) {
if (this.state.flipped) {
return;
}
this.setState((state) => ({
flipped: card,
cards: state.cards.map((c) => (
// If the card we are mapping is not the flipped card add someOtherState to it
c === card ? c : { ...c, someOtherState: 'some value'};
)),
});
};
I'd recommend you start using functional components with React hooks right away. They provide a much nicer developer experience

How to arrow toggle up and down by clicked index? Vue

How do I rotate just that arrow icon based on the clicked item?
new Vue({
el: "#app",
data() {
return {
isToggled: false,
items: [{
id: 1,
name: "Test1"
},
{
id: 2,
name: "Test2"
},
{
id: 3,
name: "Test3"
},
{
id: 4,
name: "Test4"
},
]
}
},
methods: {
arrowToggle() {
this.isToggled = !this.isToggled;
},
getItems() {
return this.items;
}
},
mounted() {
this.getItems();
}
});
i {
border: solid black;
border-width: 0 3px 3px 0;
display: inline-block;
padding: 3px;
}
.down {
transform: rotate(45deg);
}
.up {
transform: rotate(-155deg);
}
.accordion {
display: flex;
background: lightblue;
align-items: center;
width: 100%;
width: 1000px;
justify-content: space-between;
height: 30px;
padding: 0 20px;
}
.arrow {
transform: rotate(-135deg);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app" style="display: flex; justify-content: center; align-items: center;">
<div v-for="(item, index) in items" :key="index">
<div class="accordion" #click="arrowToggle()">
<p> {{ item.name }}</p>
<i :class="{ 'down': item.isToggled }" class="arrow"> </i>
</div>
</div>
</div>
Based on the clicked element do I want my arrow to rotate?
If i have 10 items and click on 2 items i want the icon to rotate there.
Failing to bind id to the clicked item and to bind that class to rotate the item
One thing is very important, I cannot set the isOpen parameter in my json ITEMS which is false which everyone recommends to me. I get it from a database and I don't have a condition for it.
You will have to toggle at individual item level. Note that I have used isToggled per item. Here is full code at: https://jsfiddle.net/kdj62myg/
Even if you get your items from DB, you can iterate through array and add a key named isToggled to each item.
HTML
<div id="app" style="display: flex; justify-content: center; align-items: center;">
<div v-for="(item, index) in items" :key="index">
<div class="accordion" #click="arrowToggle(item)">
<p> {{ item.name }}</p>
<i :class="{ 'down': item.isToggled, 'up': !item.isToggled }"> </i>
</div>
</div>
</div>
Vue
new Vue({
el: "#app",
data() {
return {
isToggled: false,
items: [{
id: 1,
name: "Test1",
isToggled: false
},
{
id: 2,
name: "Test2",
isToggled: false
},
{
id: 3,
name: "Test3",
isToggled: false
},
{
id: 4,
name: "Test4",
isToggled: false
},
]
}
},
methods: {
arrowToggle(item) {
return item.isToggled = !item.isToggled;
},
getItems() {
return this.items;
}
},
mounted() {
this.getItems();
}
});
You have to map your items and attach a custom data on it to solve your problem.
Items data should be like this
items: [{
id: 1,
name: "Test1",
isToggled: false
},
{
id: 2,
name: "Test2",
isToggled: false
},
{
id: 3,
name: "Test3",
isToggled: false
},
{
id: 4,
name: "Test4",
isToggled: false
},
]
and your toogle function should look like this.
arrowToggle(item) {
return item.isToggled = !item.isToggled;
},
Now, after you fetched the items from the server. You have to map it to attach a isToggled data on every item you have. like this.
getItems() {
axios.get('api/for/items')
.then(({data}) => {
this.items = data.map(item => ({
return {
name:item.name,
id:item.id,
isToggled:false
}
}))
});
}
The above arrowToggle function breaks vue reactivity (google vue reactivity for docs). According to the docs, changing an object property directly will break reactivity. To keep reactivity, the function should change to:
arrowToggle(item) {
this.$set(this.item, 'isToggled', item.isToggled = !item.isToggled)
return item.isToggled;
},

Bootstrap-Vue: Implementing Role Permissions as Multiple arrays of b-form-checkbox displayed as columns in b-table. Not working

The Problem
I am trying to create a page to manage permissions by role as demonstrated in the following image:
As currently implemented, a click in any box results in all of the boxes in that column becoming checked.
Ex.: a click in "Create Users" of "Admin" will look as follows:
Likewise, a check in any of the other columns would result in all of the check boxes for that column being checked. In either case, clearing any checkbox also clears all the checkboxes in that column.
I'm not sure what's going on, but note that if I reverse the comments in the table and run with the other checkboxes, the behavior is such that all columns under "Admin" get checked regardless of which column I checked a box.
Here's the relevant template html and script for the component as well as a the relevant script from the vuex store. Thanks in advance for any help!!
import {
store
} from "../store/store";
export default {
data() {
return {
items: this.$store.state.permissions,
roles: this.$store.state.roles,
adminRolePermissions: this.$store.state.roles[this.$store.state.roles.map(function(permission) {
return permission.name;
}).indexOf('Admin')].rolePermissions,
salesRolePermissions: this.$store.state.roles[this.$store.state.roles.map(function(permission) {
return permission.name;
}).indexOf('Sales')].rolePermissions,
maintRolePermissions: this.$store.state.roles[this.$store.state.roles.map(function(permission) {
return permission.name;
}).indexOf('Maintenance')].rolePermissions,
accouRolePermissions: this.$store.state.roles[this.$store.state.roles.map(function(permission) {
return permission.name;
}).indexOf('Accounting')].rolePermissions,
fields: [{
key: "name",
label: "Permission",
class: "text-right"
},
{
key: "admin",
label: "Admin",
class: "text-center"
},
{
key: "sales",
label: "Sales",
class: "text-center"
},
{
key: "maint",
label: "Maintenance",
class: "text-center"
},
{
key: "account",
label: "Accounting",
class: "text-center"
},
]
};
},
<b-table responsive :items="items" :fields="fields" head-variant="dark">
<template slot="admin" slot-scope="row">
<b-form-checkbox id="admin" v-model="adminRolePermissions" value="row.item.id"/>
<!-- <b-form-checkbox v-model="adminRolePermissions" v-bind:id="row.item.id"/> -->
</template>
<template slot="sales" slot-scope="row">
<b-form-checkbox id="sales" v-model="salesRolePermissions" value="row.item.id"/>
<!-- <b-form-checkbox v-model="salesRolePermissions" v-bind:id="row.item.id"/> -->
</template>
<template slot="maint" slot-scope="row">
<b-form-checkbox id="maint" v-model="maintRolePermissions" value="row.item.id"/>
<!-- <b-form-checkbox v-model="salesRolePermissions" v-bind:id="row.item.id"/> -->
</template>
<template slot="account" slot-scope="row">
<b-form-checkbox id="accou" v-model="accouRolePermissions" value="row.item.id"/>
<!-- <b-form-checkbox v-model="accouRolePermissions" v-bind:id="row.item.id"/> -->
</template>
</b-table>
The store (vuex):
permissions: [{
id: "u1",
name: "View Users",
grouping: "Users"
},
{
id: "u2",
name: "Create Users",
grouping: "Users"
},
{
id: "u3",
name: "Remove Users",
grouping: "Users"
},
{
id: "u4",
name: "Modify Users",
grouping: "Users"
},
{
id: "u5",
name: "Assign Users To Roles",
grouping: "Users"
},
{
id: "r1",
name: "Create Roles",
grouping: "Roles"
},
{
id: "r2",
name: "Modify Roles",
grouping: "Roles"
},
{
id: "a1",
name: "View Assets",
grouping: "Assets"
},
{
id: "a2",
name: "Create Asset",
grouping: "Assets"
},
{
id: "a3",
name: "Update Asset Info",
grouping: "Assets"
},
{
id: "a4",
name: "Locate Assets",
grouping: "Assets"
},
{
id: "a5",
name: "Change Asset Availability",
grouping: "Assets"
},
{
id: "m1",
name: "View Asset Maintenance Records",
grouping: "Maintenance"
},
{
id: "m2",
name: "Change Asset Maintenance Records",
grouping: "Maintenance"
},
{
id: "c1",
name: "View Customer",
grouping: "Customers"
},
{
id: "c2",
name: "Create Customer",
grouping: "Customers"
},
{
id: "c3",
name: "Modify Customer Info",
grouping: "Customers"
},
{
id: "b1",
name: "Create Booking",
grouping: "Booking"
},
{
id: "b2",
name: "Update Booking",
grouping: "Booking"
},
{
id: "b3",
name: "Remove Booking",
grouping: "Booking"
},
{
id: "f1",
name: "View Invoices",
grouping: "Accounting"
},
{
id: "f2",
name: "Create Invoice",
grouping: "Accounting"
},
{
id: "f3",
name: "Update Invoice",
grouping: "Accounting"
},
{
id: "f4",
name: "Pay Invoice",
grouping: "Acounting"
},
{
id: "f5",
name: "Update Customer Status",
grouping: "Accounting"
}
],
roles: [{
name: "Admin",
rolePermissions: ["u1", "u2", "u3", "u4", "u5", "r1", "r2", "a1", "a2", "a3", "a4", "a5", "m1", "m2", "c1", "c2", "c3", "b1", "b2", "b3", "f1", "f2", "f3", "f4", "f5"]
},
{
name: "Sales",
rolePermissions: ["a1", "a2", "a3", "a4", "a5", "c1", "c2", "c3", "b1", "b2", "b3", "m1"]
},
{
name: "Maintenance",
rolePermissions: ["a1", "a5", "m1", "m2"]
},
{
name: "Accounting",
rolePermissions: ["c1", "f1", "f2", "f3", "f4", "f5"]
}
],
You need to use b-form-checkbox-group to bind v-model as an array. I'm not sure how to do it inside v-table, but there is a workaround with methods
<b-form-checkbox id="admin" v-model="adminRolePermissions"
#input="onInputAdminRoles($event, row.item.id)"
:checked="adminRolePermissions.includes(row.item.id)" value="row.item.id"/>
and defined custom method:
methods: {
onInputAdminRoles (isCheck, roleId) {
if (isCheck) {
this.adminRolePermissions = this.adminRolePermissions.concat([roleId])
} else {
this.adminRolePermissions = this.adminRolePermissions.filter (item => item !== roleId)
}
}
}
Combining ittus's suggestion regarding b-form-checkbox-group with several attempts and re-reads of the bootstrap-vue docs I come up with the following:
<script>
export default {
data() {
return {
permissions: [{
id: "a1",
name: "View Assets"
},
{
id: "a2",
name: "Add Asset"
},
{
id: "a3",
name: "Modifiy Assets"
}
],
roles: [{
name: "Admin",
permissions: ["a1", "a2", "a3"]
},
{
name: "Sales",
permissions: ["a1"]
},
{
name: "Maintenance",
permissions: ["a1"]
},
{
name: "Accounting",
permissions: ["a1"]
},
]
};
},
};
</script>
<style>
.headerRow {
padding: .75rem;
background-color: #000000;
color: #ffffff;
font-weight: bold;
vertical-align: bottom;
border-bottom: 2px solid #dee2e6;
}
.bodyRow {
padding: .75rem;
border-top: 1px solid #dee2e6;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.2/vue.js"></script>
<template>
<div>
<b-row>
<b-col>
<b-container>
<div id='permissionsTable'>
<b-row class='headerRow'>
<b-col cols='3'>Permissions</b-col>
<b-col v-for="role in roles" v-bind:key="role.name">{{role.name}}</b-col>
</b-row>
<b-row v-for="permission in permissions" v-bind:key="permission.name" class="bodyRow">
<b-col cols='3'>{{permission.name}}</b-col>
<b-col v-for="(role, index) in roles" v-bind:key="role.name">
<b-form-checkbox-group v-bind:id="role.name" v-bind:name="role.name + 'Permissions'" v-model="roles[index].permissions" >
<b-form-checkbox v-bind:value="permission.id"/>
</b-form-checkbox-group>
</b-col>
</b-row>
</div>
</b-container>
</b-col>
</b-row>
</div>
</template>
Explanation ?
As both ittus and the bootstrap docs specify, we must wrap <b-form-checkbox> in a <b-form-checkbox-group> and then use v-model to link the group with an array. After playing around with it for a little while (about 10 hrs), I finally got it through my skull that I just needed to then bind the value attribute of the <b-form-checkbox> to the permission.id variable obtained from the v-for in the row definition above.
I'd love any additional comments on why or how this works. I guess I'm surprised that the <b-form-checkbox> has visibility to variables from outside the <b-form-checkbox-group>
Here is a quote of the relevant bootstrap-vue documentation found here
Value(s)
By default, value will be true when checked and
false when unchecked. You can customize the checked and
unchecked values by specifying the value and unchecked-value
properties.
v-model binds to the checked property. When you have multiple
checkboxes that bind to a single data state variable, you must provide
an array reference [] to your v-model!
Note that when v-model is bound to multiple checkboxes (i.e an array
ref), the unchecked-value is not used. Only the value(s) of the
checked chcekboxes will be returned in the v-model bound array. You
should provide unique values for each checkbox's value prop.
Multiple checkboxes and accessibility
When binding multiple checkboxes together, you should set the name prop to the
same value for all s in the group individually or via the name > prop of . This will inform users of assitive
technologies that the checkboxes are related.
Whenever using multple checkboxes, it is recommended that the
be placed in a component to
associate a label with the entire group of checkboxes.
Try setting a key on your checkboxes.
<b-table responsive :items="items" :fields="fields" head-variant="dark">
<template slot="admin" slot-scope="row">
<b-form-checkbox id="admin" :key="row.index" v-model="adminRolePermissions" value="row.item.id"/>
<!-- <b-form-checkbox v-model="adminRolePermissions" v-bind:id="row.item.id"/> -->
</template>
<template slot="sales" slot-scope="row">
<b-form-checkbox id="sales" :key="row.index" v-model="salesRolePermissions" value="row.item.id"/>
<!-- <b-form-checkbox v-model="salesRolePermissions" v-bind:id="row.item.id"/> -->
</template>
<template slot="maint" slot-scope="row">
<b-form-checkbox id="maint" :key="row.index" v-model="maintRolePermissions" value="row.item.id"/>
<!-- <b-form-checkbox v-model="salesRolePermissions" v-bind:id="row.item.id"/> -->
</template>
<template slot="account" slot-scope="row">
<b-form-checkbox id="accou" :key="row.index" v-model="accouRolePermissions" value="row.item.id"/>
<!-- <b-form-checkbox v-model="accouRolePermissions" v-bind:id="row.item.id"/> -->
</template>
</b-table>

Categories