How can I use multiple b-form-radio-group avoiding visual interference among them? - javascript

I'm new using Vue and specifically Bootstrap Vue and I'm trying to build a form with multiple radio groups.
My problem is that when I change the value in one of them the others don't change their values (this was checked with Vue DevTools) but visually it looks like none of the values are selected
I don't know what is wrong with my approach
I post here a simplified version of the code looking for some help, thanks in advance:
<template>
<div>
<b-form-group label="Radio group 1" v-slot="{ ariaDescribedby }">
<b-form-radio-group
id="radio-group-1"
v-model="selected1"
:options="options1"
:aria-describedby="ariaDescribedby"
name="radio-options"
></b-form-radio-group>
</b-form-group>
<b-form-group label="Radio Group 2" v-slot="{ ariaDescribedby }">
<b-form-radio-group
id="radio-group-2"
v-model="selected2"
:options="options2"
:aria-describedby="ariaDescribedby"
name="radio-options"
></b-form-radio-group>
</b-form-group>
</div>
</template>
<script>
export default {
data() {
return {
selected1: 'first',
options1: [
{ text: 'First', value: 'first' },
{ text: 'Second', value: 'second' },
],
selected2: 'one',
options2: [
{ text: 'One', value: 'one' },
{ text: 'Two', value: 'two' },
]
}
}
}
</script>

Since both <b-form-radio-group> elements have the same name, "radio-options", visually they are treated like one group. The different v-model would still function correctly but this isn't what you want visually. Give the second group a different name:
<b-form-radio-group
id="radio-group-2"
v-model="selected2"
:options="options2"
:aria-describedby="ariaDescribedby"
name="radio-options2"
></b-form-radio-group>
Here I changed it to radio-options2.

Related

Tooltip disappears after disabling and re-enabling BootstrapVue table column

I have a BootstrapVue table which looks like this;
When you hover your mouse on the First Name column, the tooltip Tooltip for First name will appear. The checkboxes at the top will cause the corresponding table columns to appear/disappear.
Here's the description of the bug I encounter.
I uncheck First Name checkbox. Column First Name disappears. Now, I recheck the First Name checkbox. Column First Name reappears again. This is fine. However, the tooltip no longer works when I hover my mouse on the First Name column.
Here's the complete code in a single HTML file.
<link href="https://unpkg.com/bootstrap#4.4.1/dist/css/bootstrap.min.css" rel="stylesheet"/>
<link href="https://unpkg.com/bootstrap-vue#2.2.2/dist/bootstrap-vue.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.2.2/dist/bootstrap-vue.min.js"></script>
<div id='app'>
<b-checkbox
:disabled="visibleFields.length == 1 && field.visible"
v-for="field in showFields"
:key="field.key"
v-model="field.visible"
inline
>
{{ field.label }}
</b-checkbox>
<b-table :items="items" :fields="visibleFields" bordered>
</b-table>
<b-tooltip target="HeaderFirst" triggers="hover" container="HeaderFirst">
Tooltip for First name<br>
</b-tooltip>
</div>
<script>
new Vue({
el: '#app',
computed: {
visibleFields() {
return this.fields.filter(field => field.visible)
},
showFields() {
return this.fields.filter(field => field.key.includes('first') || field.key.includes('last'))
}
},
data: dataInit,
})
function dataInit() {
let init_data = {};
init_data.fields = [
{ key: 'id', label: 'ID', visible: true },
{ key: 'first', label: 'First Name', visible: true,
thAttr: {
id: "HeaderFirst"
},
},
{ key: 'last', label: 'Last Name', visible: true },
{ key: 'age', label: 'Age', visible: true },
];
init_data.items = [
{ id: 1, first: 'Mike', last: 'Kristensen', age: 16 },
{ id: 2, first: 'Peter', last: 'Madsen', age: 52 },
{ id: 3, first: 'Mads', last: 'Mikkelsen', age: 76 },
{ id: 4, first: 'Mikkel', last: 'Hansen', age: 34 },
];
return init_data;
}
</script>
I am using vue v2.6, BootstrapVue.
The <b-tooltip>'s target must exist in the DOM upon mounting. It does not dynamically attach a new tooltip to newly created elements in the DOM.
The first run of your code shows a tooltip because <b-table> initially contains the #HeaderFirst element. When you uncheck the First Name box, the existing elements in <b-table> are replaced with new elements via the computed property. The elements removed from the DOM include the one that <b-tooltip> initially attached a tooltip to, and no new tooltip is generated after <b-table> was updated.
Solution
One solution is to render <b-tooltip> only when the target element is visible:
Create a computed prop that determines whether the #HeaderFirst field is visible.
Conditionally render <b-tooltip> based on that computed prop.
new Vue({
computed: { 1️⃣
firstNameHeaderVisible() {
return this.fields.find(field => field.thAttr?.id === 'HeaderFirst')?.visible
}
},
⋮
})
2️⃣
<b-tooltip target="HeaderFirst" v-if="firstNameHeaderVisible">
demo
On disabled elements event hover is not working. You need to wrap element by other e.g. div where you set up tooltip and then it should work well

How can I change the value of an item in an array based on multiple conditions?

I am trying to change the value of an item in array, based on matching other items in the array. The array might contain details of the section (non-unique), a unique ID, and a value I wish to change (in this case a 'selected' flag). The goal is to be able to have multiple items which can have their selected flag. Within any single section, only one item could be 'selected' but multiple sections could have an individual item 'selected'. Conceptually, I think this could be thought of in the same way as having multiple groups of radio buttons.
The ultimate aim is to be able to use state to remember the selections made in a component that is created using props. I'm keen to understand not simply copy. I'll get my head around state mutations next, but better to solve this problem first.
So, take an array like:
menuItems: [
{
section: 'National',
id: 'First item',
selected: false
},
{
section: 'National',
id: 'Second item',
selected: false
},
{
section: 'National',
id: 'Third item',
selected: true
},
{
section: 'Local',
id: 'Fourth item',
selected: false
},
{
section: 'Local',
id: 'Fifth item',
selected: false
},
{
section: 'Local',
id: 'Sixth item',
selected: true
}
]
And some search strings like:
searchSection: 'National',
searchId: 'First item'
How would I create a function that could change the selected flag of the item with id: First item to true, the others (second, third item) to false, and don't change anything in the 'Local' section?
I have tried to get my head around using forEach loops to no avail, even though this feels the right approach. Using findIndex for the section seems destined to fail as there are multiple items to be found.
First SO question - so pre-emptive apologies if problems with the way I have asked. I'm using Vue3. All advice appreciated.
Loop through the items testing for the proper section. With the section, if there is an id match, set selected to true, otherwise set selected to false:
methods: {
flag(searchSection, searchId) {
this.menuItems.forEach(item => {
if (item.section === searchSection) {
item.selected = item.id === searchId;
}
});
}
}
Call the function:
this.flag('National', 'First item');

How to check if selected dynamic form input meets some criteria

I'm still relatively new to Angular and the logic of this nested JSON is getting the better of me, so I apologize.
I am looking to create a dynamic form and used this tutorial as a starting point.
I am trying to build out conditional fields that will only display if the user selects a certain option. I would like it to work so that if any of my options has children defined in an array, a subsequent input with those children as options appears. I still have a ton of things to consider, but right now I'm trying to get it so that when a user selects Option 2, Option 2A and Option 2B automatically appear in another dropdown underneath.
How can I create an ngIf* statement that says "if the currently selected option has children, add them to another input"?
Here is my code in a sandbox
I have tried checking that the array length of "children" is longer than zero, and also created a boolean called "hasChildren" and manually assigning a true/false value to check against (though I don't prefer this solution because the finished app will have many nested options and it would be easier to just check if the array is empty or not).
I also tried this solution but must have had a syntax error, because it didn't work for me.
HTML:
<ng-template [ngSwitchCase]="'select'">
<div class="form-group">
<label [for]="input.controlName"> {{input.controlName}}</label>
<select [formControlName]="input.controlName" [name]="input.controlName" [id]="input.controlName"
[required]="input.validators.required">
<option value="">{{input.placeholder}}</option>
<option *ngFor="let option of input.options" [value]="option.value">{{option.optionName}}</option>
</select>
</div>
<div *ngIf="this.input.options.children > 0" class="form-group">
<label [for]="input.options.optionName">{{input.options.optionName}}</label>
<select [formOptionName]="input.options.children.childName" [name]="input.options.children.childName" [id]="input.options.children.childName">
<option *ngFor="let children of input.options.children" [value]="children.value">{{children.childName}}</option>
</select>
</div>
</ng-template>
form-data.ts
export interface FormData {
controlName: string;
controlType: string;
valueType?: string;
currentValue?: string;
placeholder?: string;
options?: Array<{
optionName: string;
value: string;
hasChildren: boolean;
children: Array<{
childName: string;
childValue: string;
}>
}>;
validators?: {
required?: boolean;
minlength?: number;
maxlength?: number;
};
}
mock-form.ts
import { FormData } from './../interface/form-data';
export const MockForm: FormData[] = [
{
controlName: 'Options',
placeholder: 'Choose an option',
controlType: 'select',
options: [{
optionName: 'Option 1',
value: 'option 1',
hasChildren: false,
children: []
},
{
optionName: 'Option 2',
value: 'option 2',
hasChildren: true,
children: [{
childName: 'Option 2A',
childValue: 'option 2a',
},
{
childName: 'Option 2B',
childValue: 'option 2b'
}],
}],
validators: {
required: true
}
}
]
I expect to get an output of a second dropdown containing options "Option 2A" and "Option 2B" when the user selects "Option 2," but I get a parsing error.
Many thanks in advance!
I have modified your code please check:
Your updated code sandbox code
check this demo
I think you are looking for cascading dropdown. The above solution may help you check it.

Returning variables from a Vue.js directive

Vue.js has a built-in directive called v-for which is used to literate over list.
HTML code
<ul id="example-1">
<li v-for="item in items">
{{ item.message }}
</li>
</ul>
Script code
var example1 = new Vue({
el: '#example-1',
data: {
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
})
Here, by using the v-for directive, the elements in items array are returned as variables named item. Here, the variables returned by this v-for directive, can be used in the html DOM. How do I create such a custom directive which returns a variable to the html DOM?
Note: I did search for v-for directives source code in the source code of vuejs, but could not find. Please help me to get this as I am very new to vuejs. Thank you.
Edit:
What I currently have?
There are 3 similar input groups for inputting 'First Name', 'Last Name' and 'Address' respectively. Each input field has label, state, disabled, value, and max properties which are stored in a inputOptions array.
<!-- HMTL -->
<b-input-group :prepend="inputOptions.firstName.label">
<b-form-input
:state="inputOptions.firstName.state"
v-model="inputOptions.firstName.value"
:disabled="inputOptions.firstName.disabled"
:maxlength="inputOptions.firstName.max"
></b-form-input>
</b-input-group>
<b-input-group :prepend="inputOptions.lastName.label">
<b-form-input
:state="inputOptions.lastName.state"
v-model="inputOptions.lastName.value"
:disabled="inputOptions.lastName.disabled"
:maxlength="inputOptions.lastName.max"
></b-form-input>
</b-input-group>
<b-input-group :prepend="inputOptions.address.label">
<b-form-input
:state="inputOptions.address.state"
v-model="inputOptions.address.value"
:disabled="inputOptions.address.disabled"
:maxlength="inputOptions.address.max"
></b-form-input>
</b-input-group>
//Script
inputOptions: {
firstName: {
label: 'First Name',
state: true,
value: 'Foo',
disabled: true,
max: 45
},
lastName: {
label: 'Last Name',
state: true,
value: 'Bar',
disabled: true,
max: 45
},
address: {
label: 'Address',
state: false,
value: 'Foo, Bar.',
disabled: true,
max: 255
},
}
What I needed to achieve
For each input-group field, it is needed to provide the property names one by one. Assume that I have created a vue directive called mydirective and code is simplified as follows.
<!-- HMTL -->
<b-input-group v-mydirective="inputOptions.firstName as myProperty" :prepend="myProperty.label">
<b-form-input
:state="myProperty.state"
v-model="myProperty.value"
:disabled="myProperty.disabled"
:maxlength="myProperty.max"
></b-form-input>
</b-input-group>
<b-input-group v-mydirective="inputOptions.lastName as myProperty" :prepend="myProperty.label">
<b-form-input
:state="myProperty.state"
v-model="myProperty.value"
:disabled="myProperty.disabled"
:maxlength="myProperty.max"
></b-form-input>
</b-input-group>
<b-input-group v-mydirective="inputOptions.address as myProperty" :prepend="myProperty.label">
<b-form-input
:state="myProperty.state"
v-model="myProperty.value"
:disabled="myProperty.disabled"
:maxlength="myProperty.max"
></b-form-input>
</b-input-group>
//Script
inputOptions: {
firstName: {
label: 'First Name',
state: true,
value: 'Foo',
disabled: true,
max: 45
},
lastName: {
label: 'Last Name',
state: true,
value: 'Bar',
disabled: true,
max: 45
},
address: {
label: 'Address',
state: false,
value: 'Foo, Bar.',
disabled: true,
max: 255
},
}
HTML
In your template, iterate over the multiple inputOptions using v-for like:
<div id="app">
<b-input-group v-for="option in inputOptions" :key="option.label" :option="option" />
</div>
SCRIPT
Create custom components for the group, input, and label, like:
Vue.component('b-label', {
props: ['label'],
template: '<div>{{ label }}</div>'
})
Vue.component('b-form-input', {
props: ['state', 'value', 'disabled', 'maxlength'],
template: '<input type="text" :value="value" />'
})
Vue.component('b-input-group', {
props: ['option'],
template:
`<div>
<b-label :label="option.label" />
<b-form-input
:state="option.state"
v-model="option.value"
:disabled="option.disabled"
:maxlength="option.max" />
</div>`
})
FIDDLE
Here is a demo on JSFiddle
This is a basic example of how to use components. Whatever transformations you need to do to the strings can be done in the associated components, via computed properties or methods. You can see a demo by clicking the link where I use a computed property to transform the labels into lowercase. That should be enough to get you going.

AngularJS: Hide divs when dropdown is unselected, and output text

I'm still new to AngularJS, and I've tried looking for the solution to my problem but I can't seem to find one that specifically addresses this. Sorry if this has been asked before! And by new, I mean, I'm still pretty clueless on how much of this works.
I have an array of items that I'm displaying with ng-repeat. Each item has a drop down where they can select Yes or No, or leave it unselected. The data is sorted so that anything that's set to Yes or No moves to the top of the list.
I currently also have a checkbox that allows them to Hide an item, which hides it, and moves it to the end of the array, so that it doesn't clutter them up.
I would like to instead have a button that hides all unselected items (a value of neither Yes nor No), instead of making them hide one at a time.
Second: Any item where they've selected Yes should have their names displayed in Field One; Any items where they've selected No should have their names displayed in Field Two.
Here is my code:
var app = angular.module('List', []);
app.controller('MainController', ['$scope', function ($scope) {
$scope.selected = false;
$scope.pList = [
{
id: '1',
title: 'Apples',
checked: false
},
{
id: '2',
title: 'Oranges',
checked: false
},
{
id: '3',
title: 'Bananas',
checked: false
},
{
id: '3',
title: 'Pears',
checked: false
}
];
$scope.pStatus = [
{
stat: 'Unselected',
color: 'black'
},
{
stat: 'Yes',
color: 'green',
},
{
stat: 'No',
color: 'red'
}
];
}]);
<div class="main" ng-controller="MainController">
<div class="container">
<div class="card" ng-repeat="stuff in pList | orderBy: ['checked', 'selectedpStatus', 'id']:false">
<div ng-hide="stuff.checked">
<h2 class="title">{{ stuff.title }}</h2>
<br /><br /><br />
<div class="status" ng-style="{'color':stuff.pStatus.color}">
<select ng-model="stuff.selectedpStatus" ng-options="item.stat for item in pStatus"></select>
</div>
<p class="normal">Hide <label><input type="checkbox" ng-model="stuff.checked" id="{{ stuff.id }}" /></label></p>
</div>
</div>
<br /><br />
<div class="main">
Field One: :{{ stuff.title }}:
<br />
Field Two: :{{ stuff.title }}
</div>
</div>
Thank you for any help!
There are several questions/clarifications I'd typically want to ask, but here's an answer that should guide you further:
Have a property on the scope that stores whether the filter should be applied or not.
You can then filter the array using a filter in the controller, or defined globally. Or, just hide the elements you don't want shown using an ng-if directive on each element.
The logic for showing the title in field 1 vs 2 is simple; the issue is whether you do this for all items, or if you want to somehow select one to show outside of the repeat.
Here's a basic solution though:
<button type="button" ng-click="toggle()">Hide/Show Unselected</button><br>
Hide all: {{ hideAll }}<br />
<div class="card" ng-repeat="stuff in pList | orderBy: ['checked', 'selectedpStatus', 'id']:false"
ng-if="!hideAll || !stuff.selectedpStatus || stuff.selectedpStatus.stat !== 'Unselected'">
<div ng-hide="stuff.checked">
<h2 class="title">{{ stuff.title }}</h2>
<div class="status" ng-style="{'color':stuff.pStatus.color}">
<select ng-model="stuff.selectedpStatus" ng-options="item.stat for item in pStatus"></select>
</div>
stuff.selectedpStatus: {{ stuff.selectedpStatus }}<br>
Field One: {{ stuff.selectedpStatus && stuff.selectedpStatus.stat === 'Yes' ? stuff.title : '' }}<br />
Field Two: {{ stuff.selectedpStatus && stuff.selectedpStatus.stat === 'No' ? stuff.title : '' }}<br />
</div>
</div>
and
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.selected = false;
$scope.hideAll = false;
$scope.pList = [{
id: '1',
title: 'Apples',
checked: false
}, {
id: '2',
title: 'Oranges',
checked: false
}, {
id: '3',
title: 'Bananas',
checked: false
}, {
id: '3',
title: 'Pears',
checked: false
}];
$scope.pStatus = [{
stat: 'Unselected',
color: 'black'
}, {
stat: 'Yes',
color: 'green',
}, {
stat: 'No',
color: 'red'
}];
$scope.toggle = function() {
$scope.hideAll = !$scope.hideAll;
}
});
In a plunkr: https://plnkr.co/edit/PBr753nznrMwsgAIsTwE?p=preview

Categories

Resources