Hi guys and thanks in advance for your time on this.
I'm very new to Vue-js and I was wondering if you could help me understand where I'm being bone-headed.
I am trying to use a toggle-button that will activate-deactivate a certain component.
I have done the database implementation stuff i.e. the change is reflected on the database side and other places just fine.
I clearly don't properly understand either the lifecycle or something else but my 'new' toggle value is not being picked up when read from the database: meaning I switch it from 'on' to 'off' and I see it in the database. When I come back to it the toggle is showing 'on'.
I added the code snippet:
<template>
<div class="asset">
<div class="loading" v-if="loading"></div>
<div class="content" v-else>
<b-row>
<b-col cols="12" style="text-align: right;">
<toggle-button :width="70"
:labels="{checked: 'Active', unchecked: 'Inactive'}"
:value="activeStatus"
#change="handleStatusChange"/>
</b-col>
</b-row>
<h2><span>Asset</span> {{ asset.name }}</h2>
...
</div>
</div>
</template>
<script>
import { ToggleButton } from 'vue-js-toggle-button';
export default {
name: "Asset",
components: {
ToggleButton
},
data() {
return {
assetId: 0,
asset: null,
activeStatus: true,
};
},
methods: {
getActiveStatus() {
this.$http.get(`asset/${this.assetId}/status`)
.then((resp) => {
this.activeStatus = resp.bodyText;
<!-- logging for testing only-->
this.$httpError("Retrieved ActiveStatus: " + this.activeStatus);
})
.catch((resp) => {
this.$httpError("Cannot retrieve active status");
});
},
handleStatusChange(event) {
let newStatus = { activeStatus: event.value };
this.$http.post(`asset/${this.assetId}/status`, newStatus).then(() => {
this.activeStatus = newStatus;
}).catch((resp) => {
this.$httpError('Failed to update activeStatus', resp);
});
},
loadAsset() {
this.loading = true;
this.$http
.get(`asset/${this.assetId}`)
.then((resp) => {
this.asset = resp.body;
})
.catch((resp) => {
this.$httpError("Failed to load asset", resp);
})
.finally(() => {
this.loading = false;
});
},
},
created() {
this.assetId = this.$route.params.id;
this.getActiveStatus();
this.loadAsset();
},
};
</script>
<style scoped>
h2 span {
font-size: 12px;
display: block;
text-transform: uppercase;
}
#dbButton,
#dupButton {
width: 30%;
}
#redspan {
color: red;
}
#copyButton,
#pasteButton {
width: 10%;
}
</style>
Just add one line ":sync=true" to the toggle-button component as an attribute.
In your case, the code looks like this:
<toggle-button :width="70"
:labels="{checked: 'Active', unchecked: 'Inactive'}"
:value="activeStatus"
:sync=true
#change="handleStatusChange"/>
Thanks
Related
I ran into an issue that I know how to solve pretty easily, but I don't really know how to handle this the "vue way" or in a declarative way that is. I usually would just use traditional DOM manipulation in this case and since that is what I am so used to, I'm having a bit of a hard time trying to do this the way it should be done in vue.
The problem is in this array of two products, one product only has one in stock, so if it is added to the cart it shouldn't be able to be added again. Now since we loop over the array, I have an error that can be conditionally shown. In the below code, each item will have an error message on it since the v-if condition will evaluate to true.
What would be good way to handle this declaratively, rather than in the traditional way? Usually I would just pass in $event, get the current target and insertAdjacentHTML. I am confused how this would work declaratively since we need to handle the v-if logic after a request comes back from an api.
<body>
<div id="app">
<div v-for="(product, index) in products" class="product__wrapper" style="position: relative; background: #ccc; margin: 24px;">
<p>{{ product.title }}</p>
<button #click="addToCart(product.id)">add to cart</button>
<div v-if="lastItemIsInCart" class="error">All items are currently in your cart</div>
</div>
</div>
<script type="module">
Vue.createApp({
name: 'test-app',
data() {
return {
products: [],
lastItemIsInCart: null
}
},
mounted() {
fetch('/products')
.then(res => res.json())
.then(data => {
this.products = data.products
})
},
methods: {
addToCart(productId) {
fetch(`/cart/add/${productId}`)
.then(res => res.json())
.then(data => {
// unique to this api
if (data.status !== 200) {
throw new Error(data.description)
}
})
.catch(err => {
console.error(err.message) // all xyz products are in your cart
// set this.lastItemIsInCart to true for a specific product so that v-if doesn't become true for each item in the list
})
}
}
}).mount('#app')
</script>
</body>
Instead of making the variable lastItemIsInCart a simple true/false for all products, I would consider making this an object containing the success/error state for each product fetch.
Something like:
<div id="app">
<div v-for="(product, index) in products" class="product__wrapper" style="position: relative; background: #ccc; margin: 24px;">
<p>{{ product.title }}</p>
<button #click="addToCart(product.id)">add to cart</button>
<div v-if="product.id in productState" class="error">{{ productState[product.id] }}</div>
</div>
</div>
data() {
return {
productState: {},
}
},
methods: {
addToCart(productId) {
setTimeout(() => {
if quantity == 1 {
this.productState[productID] = 'All items are currently in your cart'
}
}, 2000)
}
}
You then have the option to easily extend the functionality by updating the state for a product, which in turn will show if the id is in productState.
(Note: while a simple in tets is fine for a v-if if this is ever more complex it would be worth moving out into a computed property).
As per your comment, if I catch you correctly, you want to show the error only under the product which is declared as "Already Added" by the API.
so, one way would be that after the API response, you can assign your lastItemIsInCart variable with the value of that product's id which is out of stock, and then in the template, you can show the error message only under the product whose id is matched with the lastItemIsInCart.
Here is a demo which will display error for product 2 and 3 but allow product 1 to add.
Vue.createApp({
name: 'test-app',
data() {
return {
lastItemIsInCart: null,
cartItemsCount: 0,
products: [
{
title: 'Product 1',
id: 1,
},
{
title: 'Product 2',
id: 2,
},
{
title: 'Product 3',
id: 3,
}
],
}
},
methods: {
addToCart(productId) {
// Suppose API is returning an error for product 2 and 3 that these items are already added then assign lastItemIsInCart variable with the product's id which is causing the error.
if([2, 3].includes(productId)) {
this.lastItemIsInCart = productId
} else {
this.cartItemsCount++;
}
}
}
}).mount('#app')
.error {
color: red;
}
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="app">
<div id="app">
TOTAL PRODUCTS IN CART - {{ cartItemsCount }}
<div v-for="(product, index) in products" class="product__wrapper" style="position: relative; background: #ccc; margin: 24px;">
<p>{{ product.title }}</p>
<button #click="addToCart(product.id)">add to cart</button>
<div v-if="lastItemIsInCart == product.id" class="error">All items are currently in your cart</div>
</div>
</div>
</div>
I finally got close to what I am looking for, this could be improved because of the setTimeout but here is a good way to do it:
<body>
<div id="app">
<div v-for="(product, index) in products" class="product__wrapper" style="position: relative; background: #ccc; margin: 24px;">
<p>{{ product.title }}</p>
<button #click="addToCart(product.id)">add to cart</button>
<div v-if="addToCartError && addToCartError.id === product.id" class="error">{{ addToCartError.message }}</div>
</div>
</div>
<script type="module">
Vue.createApp({
name: 'test-app',
data() {
return {
products: [],
addToCartError: null,
timeout_id: undefined
}
},
mounted() {
fetch('/products')
.then(res => res.json())
.then(data => {
this.products = data.products
})
},
methods: {
addToCart(productId) {
fetch(`/cart/add/${productId}`)
.then(res => res.json())
.then(data => {
// unique to this api
if (data.status !== 200) {
throw new Error(data.description)
}
})
.catch(err => {
console.error(err.message) // all xyz products are in your cart
this.addToCartError = {
message: err.message,
id: productId
}
if (this.timeout_id) {
clearTimeout(this.timeout_id)
this.timeout_id = undefined
}
this.timeout_id = setTimeout(() => {
this.addToCartError = null
}, 4000)
})
}
}
}).mount('#app')
</script>
</body>
I have created dynamic buttons in vuejs where each button represents a different answer to a question.
My goal is: when I get the answer wrong, the correct option is highlighted in green until the next question is shown.
Is it also possible to change other settings of these "BaseButtons" with CSS? How can I do this?
<template>
<div class="container-botoes">
<BaseButton class="optionsButtons"
v-for="options in optionsAnswers"
:key="options.id" #click="handleAnswer(options)">
{{options.ans}}
</BaseButton>
</div>
</template>
methods:{
handleAnswer(options){
if (options.id === this.correctAnswer){
this.playerHit = true;
}
else {
this.opponentHit = true;
}
this.nextquestion();
},
One option is to create css classes with styles you need and append them to BaseButton component depending on your conditions
Have a look at this one:
HTML block:
<template>
<div class="container-botoes">
<BaseButton
v-for="(options, index) in optionsAnswers"
:key="options.id"
class="optionsButtons"
:class="correctAnsIndex === index ? 'green-button' : 'red-button'"
#click="handleAnswer(options, index)"
>
{{ options.ans }}
</BaseButton>
</div>
</template>
JavaScript block:
<script>
export default {
data() {
return {
correctAnsIndex: null,
}
},
methods: {
handleAnswer(options, index) {
if (options.id === this.correctAnswer) {
this.playerHit = true
this.correctAnsIndex = index
} else {
this.opponentHit = true
this.correctAnsIndex = null
}
this.nextquestion()
},
},
}
</script>
CSS block:
<style>
.red-button {
background: red;
color: white;
font-weight: 700;
}
.green-button {
background: green;
color: white;
font-weight: 700;
}
</style>
Code explanation:
We have passed the index of the loop in the handleAnswer method, where the value of the index will be assigned to the correctAnsIndex variable if options.id === this.correctAnswer and in the else part we will assign null value to the correctAnsIndex variable.
Now, we have applied conditional classes in HTML block, where if the index and correctAnsIndex matches then it would apply green-button class or else it will apple red-button class.
Eventually getting your expected result.
Try this :
Vue.component('basebutton', {
data() {
return {
isCorrect: false
}
},
props: ['answerobj'],
template: `<button :class="{ 'green': isCorrect, 'white': !isCorrect}" #click="handleAnswer(answerobj)">{{ answerobj.answer }}</button>`,
methods: {
handleAnswer(answerobj) {
if (answerobj.correct) {
this.isCorrect = true
} else {
this.isCorrect = false
}
}
}
});
var app = new Vue({
el: '#app',
data: {
list: [{
question: 'Who is the tallest animal ?',
optionsAnswers: [{
answer: 'Elephant',
correct: false
}, {
answer: 'Jirafe',
correct: true
}, {
answer: 'Lion',
correct: false
}, {
answer: 'Zebra',
correct: false
}]
}]
}
});
.green {
background-color: green;
}
.white {
background-color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="(item, index) in list" :key="index">
<p><strong>Question : </strong>{{ item.question }}</p>
<p><strong>Answers :</strong></p>
<BaseButton v-for="(options, i) in item.optionsAnswers" :key="i" :answerobj="options">
</BaseButton>
</div>
</div>
I am using the vue-codemirror package to display css code on a website. The problem is that I don't understand how to update the state I get with vuex
<div>
<codemirror v-model="code" />
<button #click="change">click</button>
{{ $store.state.background }}
</div>
methods: {
change() {
Vue.set(this.$store.state, "background", "#242424");
},
},
data() {
return {
code: dedent`
/* Some example CSS */
body {
margin: ${this.$store.state.background};
padding: 3em 6em;
}
`,
};
},
Vuex
export default new Vuex.Store({
state: {
background: "#000",
},
});
I thought that the problem was reactivity and decided to use Vue.set when clicking on the click button, the value of {{ $store.state.background }} changes, but the code that is inside the data return does not change
You can also see this example in codesandbox
Like #vanblart commented You can create computed property, instead data:
computed: {
code() { return dedent(`
/* Some example CSS */
body {
margin: ${this.$store.state.background};
padding: 3em 6em;
}
`)
}
and in Vuex you can create action/mutation for setting values:
mutations: {
addBkg(state, val) {
state.background = val
},
},
actions: {
setBkg({ commit }, data) {
commit("addBkg", data);
},
},
and in methos dispatch that action:
change() {
this.$store.dispatch("setBkg", "#242424");
},
The issue I am facing here is, I am not able to figure out how can I retain the values in the form on page refresh. Each time I refresh the page all the filled values in the form are gone.
Help me resolve this issue. I was thinking of using localStorage but not sure how I can implement it.
<template>
<v-card class="mb-12">
<v-form :model='user' class="content-padding" ref='pdfInputs'>
<div class="section-header">
User
</div>
<v-container fluid>
<ul>
<li v-for="(input, index) in user.inputs">
<input type="text" v-model="input.one"> - {{ input.one }}
<input type="text" v-model="input.two"> - {{ input.two }}
<button type="button" #click="deleteRow(index)">Delete</button>
</li>
</ul>
</v-container>
</v-form>
</v-card>
</template>
<script>
export default {
data () {
return {
user: {
inputs: []
}
}
}
methods: {
addRow() {
this.user.inputs.push({
one: '',
two: ''
})
},
deleteRow(index) {
this.user.inputs.splice(index,1)
}
}
}
</script>
There is watch functionality in vue
export default {
data () {
return {
user: {
inputs: []
}
}
},
mounted() {
this.user.inputs = JSON.parse(localStorage.getItem('form')) || [];
},
watch: {
user: {
handler: function() {
localStorage.setItem('form', JSON.stringify(this.user.inputs));
},
deep: true
}
},
methods: {
addRow() {
this.user.inputs.push({
one: '',
two: ''
})
},
deleteRow(index) {
this.user.inputs.splice(index,1)
}
}
}
I have a custom input field which i use in my entire project.
I am trying to use clearable props in my input component so that i can clear text with one click.
but setting clearable:"true" is not working.
I am using
<input :class="inputClass" v-bind="$attrs" :clearable="clearable" :value="value" #input="(e) => $emit('input', e.target.value)"/>
and clearable's value is coming via props which i am passing from component i have to use clearable in.
Please guide me..
thanks in Advance !!!!
clearable is not a standard HTML attribute. You're going to have to use a custom component for this, which would be easier to use with v-model.. I have supplied an example of how to accomplish this using events/default value as well as an example that shows how to do this using v-model:
[CodePen Mirror]
Edit: cleaned up the code a little bit...
const customInputNoVmodel = {
template: "#custom-input-no-vmodel",
props: {
clearable: {
type: Boolean,
required: false,
default: false
},
},
methods: {
onInput(e) {
this.$emit("input", e.target.value);
}
},
computed: {
isClearable() {
return this.clearable === true ? "search" : "text";
}
}
};
const vm = new Vue({
el: "#app",
components: {
customInputNoVmodel
},
data: {
value: "Initial Value Without v-model",
cssClasses: "my-class-one"
},
methods: {
handleInput(e) {
this.value = e;
}
}
});
/* *************************** */
const customInputWithVmodel = {
template: "#custom-input-with-vmodel",
props: {
value: {
type: String,
required: false,
},
clearable: {
type: Boolean,
required: false,
default: false
},
},
computed: {
isClearable() {
return this.clearable === true ? "search" : "text";
},
handleOnInput: {
get() {
return this.value;
},
set(val) {
this.$emit('input', val)
}
},
}
};
const vm2 = new Vue({
el: "#app2",
components: {
customInputWithVmodel
},
data: {
text: "Initial Value With v-model",
cssClasses: "my-class-two"
}
})
.my-class-one {
background-color: blue;
color: white;
border: 1px solid red;
width: 200px;
}
.my-class-two {
background-color: black;
color: red;
border: 5px solid darkmagenta;
width: 250px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<div id="app">
<div>
Not using v-model:
<custom-input-no-vmodel
class="my-class-one"
:clearable="true"
:value="value"
#input="handleInput"
></custom-input-no-vmodel>
</div>
</div>
<br/>
<div id="app2">
<div>
With v-model
<custom-input-with-vmodel
:class="cssClasses"
:clearable="true"
v-model="text"
></custom-input-with-vmodel>
</div>
</div>
<!-- SIMULATES COMPONENT ~~WITHOUT~~ V-MODEL -->
<script type="text/x-template" id="custom-input-no-vmodel">
<input
v-bind="$attrs"
:type="isClearable"
#input="onInput"
/>
</script>
<!-- SIMULATES COMPONENT WITH V-MODEL -->
<script type="text/x-template" id="custom-input-with-vmodel">
<input
v-bind="$attrs"
:type="isClearable"
v-model="handleOnInput"
/>
</script>