Vue2 : v-show and changind data dynamically - vue.js

I have this code that works but outputs an error
this.hideButton is not a function
here a part of the code
<template>
<div>
<v-select
#open="openSelect"
#search="applySearch"
>
<b-button variant="none" class="selectAllButton"
v-on:click="clickSelectAll" v-show="hiddenBtn">Select All</b-button>
</div>
</template>
export default {
data() {
return {
hiddenBtn: false
}
},
methods: {
applySearch(search, loading) {
if (search.length > 0 && search.length < 3) {
this.hideBtn();
return;
}
this.showBtn();
this.retrieveEntities(search, loading)
},
showBtn() {
this.hiddenBtn = true;
},
hideBtn(){
this.hiddenBtn = false;
}
}
I think this is the wrong way to update my hiddenBtn property to show and hide the button, but It works even if I get an error, so I don't understand what happens

you are calling this.hideBtn() which is not a function
applySearch(search, loading) {
if (search.length > 0 && search.length < 3) {
this.hideBtn(); // <-- you are calling this.hideBtn() which isn't a function. just remove this and try
return;
}
this.showBtn();
this.retrieveEntities(search, loading)
}

This code should work properly, maybe you have somewhere else in code something that is trying to execute this.hideButton() while your method's name is this.hideBtn().

Related

Vue styling with a method

I'm trying to style a card with a method called needsApprove
<v-card #click="displayAmendments(article)" :style="needsApprove(article)? 'background: black;' : ''">
<h5 class="text-center p-2"
v-text="article.num + '. ' + article.title">
</h5>
</v-card>
Tried compured property:
needsApprove(article) {
article.amendments.forEach(amendment => {
if(amendment.approved == 0) {
return true
}
else {
return false
}
})
},
Tried method:
needsApprove(article) {
article.amendments.forEach(amendment => {
if(amendment.approved == 0) {
return true
}
else {
return false
}
})
},
It doesn't seem to work, it does return true although the styling doesn't seem to work, is this kind of thing possible? What am i doing wrong?
Try out a computed property with parameter like :
needsApprove() {
return articel=>article.amendments.some(am=>am.approved==0)
}
Boussadjra Brahim answered how to fix it but did not explained why it does not work.
forEach returns nothing and is used only to make certain action with every element of collection. To return something you can also use 'every' instead of 'some' to make sure that every element of collection satisfies the condition.

Prevent Vue Multiple Select to Store an Empty Array

I want this select multiple to pre-select one option, and not be able to deselect all options.
Whenever the last selected option is deselected it should be reselected. In other words when the user tries to deselect the last selected option it should visually not be deselected.
<template>
<b-select
if="Object.keys(doc).length !== 0 /* wait until firebase has loaded */"
:options="computedOptions"
v-model="model"
multiple
#input="onChange"
/>
</template>
<script>
//import Vue from 'vue'
import { fb } from "../fbconf";
export default {
name: "MyMultiSelect",
props: {
doc: Object, // firestore document
},
data() {
return {
options: []
};
},
firestore() {
var options = fb.db.collection("options");
return {
options: options
};
},
computed: {
computedOptions: function() {
return this.options.map(function(option) {
return {
text: option.name,
value: option.id
};
});
},
// to make sure mySelectedOptions is an array, before this.doc is loaded
// I use the following custom model
// because not using 'get' below causes a warning:
// [Vue warn]: <select multiple v-model="localValue"> expects an Array value for its binding, but got Undefined
model: {
get: function() {
if (!this.doc.hasOwnProperty('mySelectedOptions')) return []; // empty array before this.doc is loaded
else return this.doc['mySelectedOptions'];
},
set: function(newValue) {
// here I can prevent the empty array from being stored
// but visually the user can deselect all options, which is bad UX
//if (Array.isArray(newValue) && newValue.length > 0) this.doc['mySelectedOptions'] = newValue;
}
},
},
methods: {
onChange: function(newValue){
// I can manually store the array as I want here
// but I cannot in any way prevent the user from deselecting all options
if (Array.isArray(newValue) && newValue.length > 0) this.doc['mySelectedOptions'] = newValue;
else {
// none of these reselects the last selected option
var oldValue = this.doc['mySelectedOptions'];
this.doc['mySelectedOptions'] = this.doc['mySelectedOptions'];
//this.$forceUpdate();
//this.$emit("change", newValue);
//Vue.set(this.doc, 'mySelectedOptions', this.doc['mySelectedOptions']);
}
}
}
};
</script>
You could add watcher and when length becomes 0 just add previous value.
watch: {
model(val, oldVal) {
if(val.length == 0 && oldVal.length > 0) {
// take only one item in case there's clear button or etc.
this.model = [oldval[0]];
}
}
}

Vue Js pass all context when wrapping components with functional components

I am creating some custom components based on Element UI.
I have two issues at the moment:
- Pass all the context down from the wrapper to the component;
- When I click on the select element in the following snippet the event does not trigger the change of currentValue. I tried also with #onchange="setValue" :value="currentValue", but nothing changed.
Obviously if I use Select and Option as they come with Element UI, they do work as supposed.
The reason why I need to wrap the components is that I need to add some default classes and brand them with some custom CSS.
---CustomSelect.js
import Vue from 'vue';
import { Select } from 'element-ui';
import classnames from 'classnames';
import 'element-theme-chalk/src/select.scss';
import './select.scss';
export default Vue.component('ExampleSelect', {
functional: true,
render(h, context) {
console.log('ExampleSelect context', context);
function wrappedComponent() {
return Select;
}
function getExtendedClassName() {
return classnames('example-select', {
[context.props.classNames]: context.props.classNames
});
}
return h(
wrappedComponent(),
{
class: getExtendedClassName(),
parent: context.parent && Object.keys(context.parent).length > 0 && context.parent,
data: context.data && Object.keys(context.data).length > 0 && context.data,
props: context.props && Object.keys(context.props).length > 0 && context.props,
injections:
context.injections && Object.keys(context.injections).length > 0 && context.injections,
listeners:
context.listeners && Object.keys(context.listeners).length > 0 ? context.listeners : {}
},
context.children && Object.keys(context.children).length > 0 && context.children
);
}
});
---CustomOption.js
import Vue from 'vue';
import { Option as ExampleOption } from 'element-ui';
import classnames from 'classnames';
import 'element-theme-chalk/src/option.scss';
import './option.scss';
export default Vue.component('ExampleOption', {
functional: true,
render(h, context) {
console.log('ExampleSelect option', context);
function wrappedComponent() {
return ExampleOption;
}
function getExtendedClassName() {
return classnames('example-option', {
[context.props.classNames]: context.props.classNames
});
}
return h(
wrappedComponent(),
{
class: getExtendedClassName(),
parent: context.parent && Object.keys(context.parent).length > 0 && context.parent,
data: context.data && Object.keys(context.data).length > 0 && context.data,
props: context.props && Object.keys(context.props).length > 0 && context.props,
injections:
context.injections && Object.keys(context.injections).length > 0 && context.injections,
listeners:
context.listeners && Object.keys(context.listeners).length > 0 ? context.listeners : {}
},
context.children && Object.keys(context.children).length > 0 && context.children
);
}
});
Thank you in advance for your help.
I solved the issue.
So it looks like the names of the properties in the data object
https://v2.vuejs.org/v2/guide/render-function.html#The-Data-Object-In-Depth
Are different from the names of the properties in context:
https://v2.vuejs.org/v2/guide/render-function.html#Functional-Components
Maybe a suggestion for the future is to make them match, or create an utility that maps them allowing to pass them all at once like that.
This is useful in the context of hocs where you want to delegate the main functionality to the received component and you just want to change a few details and make them default.
Therefore, this is the correct return statement:
return h(
wrappedComponent(),
{
class: getExtendedClassName(),
name: 'ExampleInput',
componentName: 'ExampleInput',
props: context.props,
slots: context.slots(),
scopedSlots: context.scopedSlots,
data: context.data,
parent: context.parent,
on: context.listeners,
inject: context.injections,
},
context.children
);

Vue / Vuex : paste event triggered before input binded value is updated

I have a simple form in a component :
<form v-on:submit.prevent="submitSearch">
<input v-model="objId" #paste="submitSearch">
<button>Submit</button>
</form>
and
var searchForm = {
methods : {
submitSearch() {
store.dispatch('submitSearch')
}
},
computed : {
objId: {
get () {
return ...
},
set (id) {
store.commit('objId', id)
}
}
},
...
};
It works well when typing and submitting, however when pasting a value submitSearch is called just before objId is updated so it doesn't. Is there a consise and vue-friendly way to handle this?
One way you could do it is have a local variable isPaste and set it to true, when the paste event is triggered. Then also register an input event which will trigger after the paste event and check if isPaste is true. If it is, then submit and set isPaste to false again.
<input v-model="objId" #paste="paste" #input="input">
data(): {
return {
isPaste: false
}
},
methods: {
paste() {
this.isPaste = true;
},
input() {
if (this.isPaste) {
store.dispatch('submitSearch');
isPaste = false;
}
}
}
Solved it using nextTick() :
submitSearch() {
Vue.nextTick().then(function () {
store.dispatch('submitSearch')
})
}
Not sure if it's the recommended way but it works well and avoid extra variables.

Vue js : _this.$emit is not a function

I have created a Vue component call imageUpload and pass property as v-model
<image-upload v-model="form.image"></image-upload>
and within imgeUpload component
I have this code
<input type="file" accept="images/*" class="file-input" #change="upload">
upload:(e)=>{
const files = e.target.files;
if(files && files.length > 0){
console.log(files[0])
this.$emit('input',files[0])
}
}
and I received
Uncaught TypeError: _this.$emit is not a function
Thanks
Do not define your method with a fat arrow. Use:
upload: function(e){
const files = e.target.files;
if(files && files.length > 0){
console.log(files[0])
this.$emit('input',files[0])
}
}
When you define your method with a fat arrow, you capture the lexical scope, which means this will be pointing to the containing scope (often window, or undefined), and not Vue.
This error surfaces if $emit is not on the current context/reference of this, perhaps when you're in the then or catch methods of a promise. In that case, capture a reference to this outside of the promise to then use so the call to $emit is successful.
<script type="text/javascript">
var Actions = Vue.component('action-history-component', {
template: '#action-history-component',
props: ['accrual'],
methods: {
deleteAction: function(accrualActionId) {
var self = this;
axios.post('/graphql',
{
query:
"mutation($accrualId: ID!, $accrualActionId: String!) { deleteAccrualAction(accrualId: $accrualId, accrualActionId: $accrualActionId) { accrualId accrualRate name startingDate lastModified hourlyRate isHeart isArchived minHours maxHours rows { rowId currentAccrual accrualDate hoursUsed actions { actionDate amount note dateCreated } } actions {accrualActionId accrualAction actionDate amount note dateCreated }} }",
variables: {
accrualId: this.accrual.accrualId,
accrualActionId: accrualActionId
}
}).then(function(res) {
if (res.data.errors) {
console.log(res);
alert('errors');
} else {
self.$emit('accrualUpdated', res.data.data.deleteAccrualAction);
}
}).catch(function(err) {
console.log(err);
});
}
}
});
You can write the method in short using upload(e) { instead of upload:(e)=>{ to make this point to the component.
Here is the full example
watch: {
upload(e) {
const files = e.target.files;
if(files && files.length > 0) {
console.log(files[0]);
this.$emit('input',files[0]);
}
}
}