Vue do not save clicked element style - vue.js

I am generating small list of items. After click on every single item it should change style. Only one item can be selected. If you click on another item first item reverse to default value.
I have follow code:
<div class="LngList">
<div class="lng" v-for="item in languages">
<button :class="[ isLangSelected ? 'ui inverted basic button' : 'ui inverted red basic button' ]" #click=LangSelect(item.lang)>{{item.lang}}</button>
</div>
</div>
My method:
data: function (){
return {
isLangSelected: false,
mycode: "",
languages: [
{lang:'D'},
{lang:'C#'},
{lang:'Python'}
],
selectedLanguage: ""
}
},
methods: {
LangSelect(lang)
{
this.selectedLanguage = lang;
if(this.selectedLanguage.length != "")
{
this.isLangSelected = !this.isLangSelected;
}
}
}
But when I am clicking outside the button I am losing selected style.
I did small gif to show the problem:

This is possible of course with buttons, but why don't you use a radio input instead? Having only one item selected, that's what they are done for.
new Vue({
el: '#app',
data() {
return {
languages: [{
lang: 'D'
},
{
lang: 'C#'
},
{
lang: 'Python'
}
],
selectedLanguage: ''
};
},
computed: {
isLangSelected() {
return this.selectedLanguage !== '';
}
}
});
input[type=radio] {
visibility: hidden;
width: 0;
}
input[type=radio]:checked + span {
color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="app">
<label v-for="lang in languages">
<input type="radio" name="languages"
v-model="selectedLanguage" :value="lang.lang">
<span>{{lang.lang}}</span>
</label>
<div v-if="isLangSelected">
Selected language is: {{ selectedLanguage }}
</div>
</div>

Related

#click bound to each item within v-for loop executed many times when clicking

I have a #click bound to individual items within a v-for loop. In the resulting render, I should have one #click for each item, so clicking one item should trigger the function bound to the item once.
Yet it triggers it as many times as there are items. Why?
<ul>
<li :key="option.value" v-for="option in options">
<QuizCheckbox
#click.native="handleClick(option.value)"
>
{{ option.value }}
</QuizCheckbox>
</li>
</ul>
...
methods: {
handleClick(val) {
console.log(val);
},
EDIT:
If I replaced ... with a simple element, then clicking that doesn't trigger the problem. So it's the <QuizCheckbox> component who's the culprit. However, nothing in it seems to indicate what could cause the problem. Here's the content of QuizCheckbox.vue:
<template>
<div :class="['quiz-checkbox', {'quiz-checkbox--checked': shouldBeChecked}]">
<div :class="['form-checkbox']">
<label class="form-checkbox__label">
<slot/>
<input
:checked="shouldBeChecked"
:value="value"
#change="updateInput"
class="form-checkbox__input"
type="checkbox"
/>
<span class="form-checkbox__checkmark"></span>
</label>
</div>
</div>
</template>
<script>
export default {
model: {
prop: 'modelValue',
event: 'change'
},
props: {
value: {
type: String,
},
modelValue: {
type: [Boolean, Array],
default: false
}
},
computed: {
shouldBeChecked() {
if (this.modelValue instanceof Array) {
return this.modelValue.includes(this.value);
}
return this.modelValue;
}
},
created() {
if (!this.$slots.default) {
console.error('QuizCheckbox: requires label to be provided in the slot');
}
},
methods: {
updateInput(event) {
const isChecked = event.target.checked;
if (this.modelValue instanceof Array) {
const newValue = [...this.modelValue];
if (isChecked) {
newValue.push(this.value);
} else {
newValue.splice(newValue.indexOf(this.value), 1);
}
this.$emit('change', newValue);
} else {
this.$emit('change', isChecked);
}
}
}
};
</script>
The code you post seems fine. Although simplified here is the code you post running:
new Vue({
el: 'ul',
data: {
options: [
{
value: 'option1',
},
{
value: 'option2',
}
]
},
methods: {
handleClick(val) {
console.log(val);
},
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<ul>
<li :key="option.value" v-for="option in options">
<div
#click="handleClick(option.value)"
>
{{ option.value }}
</div>
</li>
</ul>
The problem must be elsewhere.

Vue-MultiSelect Checkbox binding

The data properties of the multi-select component does not update on change. Check-boxes doesn't update on the front-end.
Expected Behavior: The check-boxes should get ticked, when clicked.
Link to code: https://jsfiddle.net/bzqd19nt/3/
<div id="app">
<multiselect
select-Label=""
selected-Label=""
deselect-Label=""
v-model="value"
:options="options"
:multiple="true"
track-by="library"
:custom-label="customLabel"
:close-on-select="false"
#select=onSelect($event)
#remove=onRemove($event)
>
<span class="checkbox-label" slot="option" slot-scope="scope" #click.self="select(scope.option)">
{{ scope.option.library }}
<input class="test" type="checkbox" v-model="scope.option.checked" #focus.prevent/>
</span>
</multiselect>
<pre>{{ value }}</pre>
</div>
new Vue({
components: {
Multiselect: window.VueMultiselect.default
},
data: {
value: [],
options: [
{ language: 'JavaScript', library: 'Vue.js', checked: false },
{ language: 'JavaScript', library: 'Vue-Multiselect', checked: false },
{ language: 'JavaScript', library: 'Vuelidate', checked: false }
]
},
methods: {
customLabel(option) {
return `${option.library} - ${option.language}`;
},
onSelect(option) {
console.log('Added');
option.checked = true;
console.log(`${option.library} Clicked!! ${option.checked}`);
},
onRemove(option) {
console.log('Removed');
option.checked = false;
console.log(`${option.library} Removed!! ${option.checked}`);
}
}
}).$mount('#app');
In your code you call onSelect and try to change the option argument of this function inside the function:
option.checked = true;
This affects only the local variable option (the function argument). And doesn't affect objects in options array in the data of the Vue instance, the objects bound with checkboxes. That's why nothing happens when you click on an option in the list.
To fix it find the appropriate element in options array and change it:
let index = this.options.findIndex(item => item.library==option.library);
this.options[index].checked = true;
Here is the code snippet with fix:
new Vue({
components: {
Multiselect: window.VueMultiselect.default
},
data: {
value: [],
options: [
{ language: 'JavaScript', library: 'Vue.js', checked: false },
{ language: 'JavaScript', library: 'Vue-Multiselect', checked: false },
{ language: 'JavaScript', library: 'Vuelidate', checked: false }
]
},
methods: {
customLabel (option) {
return `${option.library} - ${option.language}`
},
onSelect (option) {
console.log("Added");
let index = this.options.findIndex(item => item.library==option.library);
this.options[index].checked = true;
console.log(option.library + " Clicked!! " + option.checked);
},
onRemove (option) {
console.log("Removed");
let index = this.options.findIndex(item => item.library==option.library);
this.options[index].checked = false;
console.log(option.library + " Removed!! " + option.checked);
}
}
}).$mount('#app')
* {
font-family: 'Lato', 'Avenir', sans-serif;
}
.checkbox-label {
display: block;
}
.test {
position: absolute;
right: 1vw;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<link href="https://unpkg.com/vue-multiselect#2.0.2/dist/vue-multiselect.min.css" rel="stylesheet"/>
<script src="https://unpkg.com/vue-multiselect#2.0.3/dist/vue-multiselect.min.js"></script>
<div id="app">
<multiselect
select-Label=""
selected-Label=""
deselect-Label=""
v-model="value"
:options="options"
:multiple="true"
track-by="library"
:custom-label="customLabel"
:close-on-select="false"
#select=onSelect($event)
#remove=onRemove($event)
>
<span class="checkbox-label" slot="option" slot-scope="scope" #click.self="select(scope.option)">
{{ scope.option.library }}
<input class="test" type="checkbox" v-model="scope.option.checked" #focus.prevent/>
</span>
</multiselect>
<pre>{{ value }}</pre>
</div>

Vue: How to switch between displaying input and label with v-if

I need to be able to switch between an input field and a label. When the button "Add Location" is clicked (which create a new div), the input field must be visible. But when the div "Expandable" is maximized it must be hidden and the label visible instead!
The input field should only be visible right after the mentioned button is clicked, else the label has to take its place. What is the best way to achieve this? I was thinking about using some sort of toggle since I am using that in other places.
The label and the input field is placed in the div class "switch".
You can also see the code in this jsFiddle!
Html
<div id="lotsOfDivs">
<addingdivs></addingdivs>
</div>
Vue
var gate = 0;
Vue.component('addingdivs', {
template: `
<div>
<div id="header">
<button class="addDiv" type="button" #click="createDiv">ADD LOCATION</button>
</div>
<div class="parent" v-for="div in divs" :style=" div.height ? { 'height': div.height }: null">
<div class="big" v-if="div.expanded" :key="'expanded' + div.id">
<div class="switch">
<input type="text" v-if="inputFieldInfo">
<label class="propertyLabel" v-else>
<div class="firstChild">
<button class="done" #click="increaseLimit">INCREASE</button>
</div>
<div class="secondChild">
<button class="done" #click="expand(div)">EXPAND</button>
</div>
</div>
<div class="small" v-else :key="'collapsed' + div.id">
<button class="done" #click="expand(div)">EXPAND</button>
</div>
</div>
</div>
`,
data: function() {
return {
gate: gate,
height: "",
count: 0,
locationsArr: ["one", "two", "three"],
divs: [],
InputFieldInfo: false
}
},
methods: {
expand: function(div) {
if (div.expanded) {
div.expanded = false
this.height = ''
} else {
div.expanded = true
this.height = '7vh'
}
},
createDiv: function() {
if (this.count <= gate) { // Here you can decide how many divs that will be generated
// this.count++;
this.divs.push({
id: this.count,
expanded: true,
inputFieldInfo: true,
height: '',
});
this.count++
}},
increaseLimit: function() {
// Here you can increase the number of divs that it's possible to generate
gate++;
}
}
});
new Vue({
el: '#lotsOfDivs',
});
The template had a few compilation errors:
The <label> needs a closing tag (and text content to be useful)
The <div class="big"> needs a closing tag
The v-if was bound to inputFieldInfo, but that variable was declared as InputFieldInfo (note the uppercase I), but based on your behavior description, this field should be unique per location container, so a single data property like this wouldn't work (if I understood your description correctly).
Each location container should have a variable to contain the location name (e.g., locationName) and another variable to contain the show/hide Boolean for the <input> and <label> (i.e., inputFieldInfo):
createDiv: function() {
this.divs.push({
// ...
inputFieldInfo: true,
locationName: ''
});
}
Then, we could bind div.inputFieldInfo and div.locationName to the <input>. We bind to v-model so that the user's text is automatically reflected to the div.locationName variable:
<input v-if="div.inputFieldInfo" v-model="div.locationName">
The <label>'s content should be div.locationName so that it contains the text from the <input> when shown:
<label class="propertyLabel" v-else>{{div.locationName}}</label>
To switch the <input> with the <label> when the expand-button is clicked, we update expand() to set div.inputFieldInfo to false but only when div.locationName is not empty (this gives the user a chance to revisit/re-expand the container to fill in the location later if needed):
expand: function(div) {
if (div.expanded) {
div.expanded = false
if (div.locationName) {
div.inputFieldInfo = false
}
// ...
updated jsfiddle
You had some missing closing tags and an error with InputFieldInfo, it should have a lowercase i.
var gate = 0;
Vue.component('addingdivs', {
template: `
<div>
<div id="header">
<button class="addDiv" type="button" #click="createDiv">ADD LOCATION</button>
</div>
<div class="parent" v-for="div in divs" :style=" div.height ? { 'height': div.height }: null">
<div class="big" v-if="div.expanded" :key="'expanded' + div.id">
<div class="switch">
<input type="text" v-if="inputFieldInfo">
<label class="propertyLabel" v-else>Label</label>
<div class="firstChild">
<button class="done" #click="increaseLimit">INCREASE</button>
</div>
<div class="secondChild">
<button class="done" #click="expand(div)">EXPAND</button>
</div>
</div>
</div>
<div class="small" v-else :key="'collapsed' + div.id">
<button class="done" #click="expand(div)">EXPAND</button>
</div>
</div>
</div>
`,
data: function() {
return {
gate: gate,
height: "",
count: 0,
locationsArr: ["one", "two", "three"],
divs: [],
inputFieldInfo: true
}
},
methods: {
expand: function(div) {
this.inputFieldInfo = false
if (div.expanded) {
div.expanded = false
this.height = ''
} else {
div.expanded = true
this.height = '7vh'
}
},
createDiv: function() {
this.inputFieldInfo = true
if (this.count <= gate) { // Here you can decide how many divs that will be generated
// this.count++;
this.divs.push({
id: this.count,
expanded: true,
inputFieldInfo: true,
height: '',
});
this.count++
}
},
increaseLimit: function() {
// Here you can increase the number of divs that it's possible to generate
gate++;
}
}
});
new Vue({
el: '#lotsOfDivs',
});
You just basically toggle the inputFieldInfo data, whenever each button is pressed.
You can do that by using toggle variable like this
Vue.component('addingdivs', {
template: `
<div>
<div>
<input type="text" v-if="takeinput">
<label v-if="!takeinput">
<button #click="toggleInput()">
</div>
</div>
`,
data: function() {
return {
takeinput:true,
}
},
methods: {
toggleInput: function(){
let vm = this;
vm.takeinput = ( vm.takeinput == true) ? false : true
}
}
});
new Vue({
el: '#lotsOfDivs',
});
In this example, we are just toggeling value of takeinput on click , so according the value either label or input will be showed.
This is very basic exmpale. But you can extend it as your need

Vue JS pass a value inside to a CSS property

I'm developing an exams portal application.
and this is my code.
<div class="" v-for="areas in knowledgeAreas">
{{areas.area}}<div class="progress" style="margin-top:10px;">
<div class="progress-bar" style="width:50%">{{areas.correctlyMarkedAnswers}} out of {{areas.numberOfQuestion}}</div>
</div>
</div>
and
data(){
return{
knowledgeAreas :[
{ area: 'Math',numberOfQuestion:10,correctlyMarkedAnswers:3,correctAnswersPercentage:12 },
{ area: 'IQ',numberOfQuestion:10,correctlyMarkedAnswers:5,correctAnswersPercentage:5 },
{ area: 'GK',numberOfQuestion:10,correctlyMarkedAnswers:8,correctAnswersPercentage:3 }
]
}
}
What I want is to dynamically pass the correctAnswersPercentage property's value to the CSS width property.
I created getProgress method then pass the value of correctAnswersPercentage during iteration.
Please check the snippet below:
new Vue({
el: "#app",
data: {
knowledgeAreas :[
{ area: 'Math',numberOfQuestion:10,correctlyMarkedAnswers:3,correctAnswersPercentage:50 },
{ area: 'IQ',numberOfQuestion:10,correctlyMarkedAnswers:5,correctAnswersPercentage:60 },
{ area: 'GK',numberOfQuestion:10,correctlyMarkedAnswers:8,correctAnswersPercentage:70 }
]
},
methods: {
getProgress: function(width) {
return {
'width': width + '%',
'background-color': 'yellow'
};
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.13/dist/vue.js"></script>
<div id="app">
<div class="" v-for="areas in knowledgeAreas">
{{areas.area}}<div class="progress" style="margin-top:10px;">
<div class="progress-bar" v-bind:style="getProgress(areas.correctAnswersPercentage)">{{areas.correctlyMarkedAnswers}} out of {{areas.numberOfQuestion}}</div>
</div>
</div>
</div>

How do you activate a class for individual elements within an array? [Vue.js]

I want to activate a class for each input individually. I have two inputs bound to the same v-model and class. I have a method that checks for something to be true, and if true enables the bound class. Currently it enables the class on all inputs. (The end goal is to search multiple inputs for an element within an array and if it matches, the class activates only for that element)
<input v-model="highlightTest" id="1" v-bind:class="{ active: active }" v-on:keyup="Highlighting"></input>
<input v-model="highlightTest" id="2" v-bind:class="{ active: active }" v-on:keyup="Highlighting"></input>
Highlighting: function() {
if (this.highlightTest != '') {
this.active = true;
}
else {
this.active = false;
}
How about this:
<template>
<input v-for="(hi,index) of highlights" v-model="highlights[]" v-bind:class="{ active: highlights[index] }" v-on:keyup="highlighting(index)"></input>
</template>
<script>
export default{
data() {
return {
highlights: []
};
},
created() {
this.$http.get('some/api').then(res => {
// map: convert 0,1 to false,true
this.highlights = res.json().map(h => h==1);
});
},
methods: {
highlighting(index) {
if (this.highlights[index]) {
// this.highlights[index] = false won't let vue detect the data change(thus no view change)
this.highlights.splice(index, 1, false);
} else {
this.highlights.splice(index, 1, true);
}
}
}
}
</script>
Here's one way to do it (sorry for the delay btw)
HTML:
<div id="app">
<p :class="{'active': activateWord(word)}" v-for="word in words">#{{ word }}</p>
<input type="text" v-model="inputText">
</div>
CSS:
.active {
color: red;
}
JS:
const app = new Vue({
el: '#app',
data: {
inputText: '',
words: [
'foo',
'bar'
]
},
methods: {
activateWord(word) {
return this.inputText === word
},
},
})
here's a fiddle