Vue watch multiple bindings change within a dropdown update - javascript

I require a lookup dropdown where I should be able to watch for multiple data changes. Currently I have this in my html:
<input list="allUsernames" type="text" v-model="selectedUser">
<datalist id="allUsernames">
<option v-for="(user, index) in allUsers"
:id="user.USERID"
:value="user.USERNAME"
:email="user.EMAIL">
</option>
</datalist>
My script data tag looks like this:
data(){
return{
allUsers: [],
selectedUserID: '',
selectedUserEmail: '',
selectedUser: '',
}
}
allUsers gets filled by an SQL call containing USERID, USERNAME and EMAIL.
I want a watch to be able to get the :id and the :email part of the option tag, but right now I can only seem to retrieve the value by default:
watch: {
selectedUser(val, oldval)
{
console.log('this only returns the :value binding:' + val);
//how do I get :id and :email values?
}
},
I want to set selectedUserID and selectedUserEmail based on the dropdown option selection made, using the vbindings :id and :email (so that I get the user.USERID and user.EMAIL values), how do I do this?

You can do this more cleanly with only the v-model data, and no watch is necessary:
data(){
return{
allUsers: [],
selectedUser: ''
}
}
Only the value binding is necessary on the options:
<option v-for="(user, index) in allUsers" :value="user.USERNAME"></option>
Use a computed to track the full object of the selected user:
computed: {
selected() {
return this.allUsers.find(user => user.USERNAME === this.selectedUser);
}
}
The selected computed refers to the whole object of the selected user, so you can use selected.USERID and selected.EMAIL in both the instance and the template.
Here's a demo:
new Vue({
el: "#app",
data(){
return{
allUsers: [
{ USERID: 1, USERNAME: 'name1', EMAIL: 'email1' },
{ USERID: 2, USERNAME: 'name2', EMAIL: 'email2' },
{ USERID: 3, USERNAME: 'name3', EMAIL: 'email3' },
],
selectedUser: ''
}
},
computed: {
selected() {
return this.allUsers.find(user => user.USERNAME === this.selectedUser);
}
}
});
.display {
margin-top: 30px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input list="allUsers" type="text" v-model="selectedUser">
<datalist id="allUsers">
<option v-for="(user, index) in allUsers" :value="user.USERNAME"></option>
</datalist>
<div v-if="selected" class="display">
<div>ID: {{ selected.USERID }}</div>
<div>EMAIL: {{ selected.EMAIL }}</div>
</div>
</div>
A good key to remember is that a watch is only necessary if you want to perform an async or time-based action when data changes. Otherwise, in most cases, use a computed.

Related

VUE - prop binding for "disabled" not working on select field

I am trying to make a drop down "select" field disabled under certain conditions in my app.
I have successfully done this with buttons already using a prop (disableBtn) that i pass up from the root of my app to the button component I made.
I try to do the EXACT same thing on a select component and it refuses to pass the prop (disableOption) back into the child component, even though its passing back many other binded props that work fine and build the options out in the drop down correctly.
I am logging the values on screen right now and can see they are updating in the main app component, but its not passing that back to the child for some reason.
Where am I off here? My understanding is you store the values you want to change in data() in the app.vue, then create a prop for them in the child component and bind them in the HTML. This has been working fine in all my other use cases.
app.vue
<template>
<div class="container">
<img alt="logo" src="./assets/logo.png">
<Header title="Provider Manager" :custTZ="custTZ" :shippingState="shippingState" :patientID="patientID" :custName="custFullName" :custEmail="custEmail" :custPhone="custPhone" />
<div v-if="providerVisibility" class="providerInfo">
<Button #btn-click="providerPicked" id="first-avail" text="First Available" />
<br />
<Select #dd-select="providerPickedSelect" :disabled="disableOption" :selectName="selectName" :id="id" :eligibleProviders="eligibleProviders" :labelFor="labelFor" :labelText="labelText" />
{{ disableOption }}
</div>
<div v-if="providerSelected" >
<hr>
<br />
<h2>Provider: {{ chosenProvider }} </h2>
<br />
</div>
<div v-if="providerSelected" >
<BookingSlots #selectSlot="removeUnselected" :slots="slots" />
<br />
<Button #btn-click="bookMeeting" text="Confirm Request" />
</div>
</div>
</template>
<script>
import { ZOHO } from "./assets/ZohoEmbededAppSDK.min.js";
import Header from './components/Header'
import Button from './components/Button'
import BookingSlots from './components/BookingSlots'
import Select from './components/Select'
const axios = require('axios');
export default {
name: 'App',
components: {
Header,
Button,
BookingSlots,
Select
},
data() {
return{
slots: [],
providerVisibility: true,
providerSelected: false,
currentProvider: 'None Assigned',
chosenProvider: '',
custFullName: '',
custEmail: '',
custPhone: '',
shippingState: '',
patientID: '',
custTZ: '',
providerZID: '',
eligibleProviders: [],
disableBtn: false,
disableOption: true,
}
},
methods: {
removeUnselected(id) {
console.log('id', id)
this.slots = this.slots.filter((slot) => slot.id === id)
},
providerPicked(id) {
console.log("id" + id)
console.log("currentProvider",this.currentProvider)
//Toggle front end visibility
this.providerSelected = true;
this.providerVisibility = false;
if(id === "first-avail"){
// hit booking engine, get name of first available
console.log("FIRST AVAIL")
this.chosenProvider = "Need to Hit Booking App";
}
if(id === "current-provider"){
// hit zoho values and get contact assigned provider
console.log("CURRENT PROVIDER")
this.chosenProvider = this.currentProvider;
}
},
providerPickedSelect(id, selectValue) {
if(this.id === "provider-select"){
// Get values from our DB for the provider selected
console.log("Provider-Select")
this.providerSelected = true;
this.providerVisibility = false;
this.chosenProvider = selectValue;
}
},
bookMeeting() {
//Book the meeting
console.log("book meeting called")
}
},
created() {
//Hit zoho and get customer info back
ZOHO.embeddedApp.on("PageLoad",(data) =>
{
console.log(data);
//Custom Business logic goes here
let entity = data.Entity;
let recordID = data.EntityId[0];
ZOHO.CRM.API.getRecord({Entity:entity,RecordID:recordID})
.then((data) => {
console.log(data.data[0])
// Set values scraped from CRM Contact profile
if(data.data[0].provider !== null && data.data[0].provider !== "None Assigned" ){
this.currentProvider = data.data[0].provider.name;
this.providerZID = data.data[0].provider.id;
}else{
//need to disable button if no doc assigned
this.disableBtn = true;
}
this.custEmail = data.data[0].Email;
this.custFullName = data.data[0].Full_Name;
this.custPhone = data.data[0].Phone;
this.patientID = data.data[0].Patient_ID;
this.shippingState = data.data[0].Mailing_State;
this.custTZ = data.data[0].GTM;
// getEligibleProviders(this.shippingState);
var data = JSON.stringify({
"state":this.shippingState,
});
axios(config)
.then((res) => {
console.log(res.data)
//this.eligibleProviders = res.data;
if(this.eligibleProviders && !this.eligibleProviders.length){
console.log("empty array")
this.eligibleProviders = [{
first_name: "None Avilable in Svc. State",
last_name: ""
}
];
this.disableOption = true;
}else{
console.log("full array")
}
console.log(this.eligibleProviders)
})
.catch((e) => {
console.log(e);
});
});
});
ZOHO.embeddedApp.init();
this.slots = [
{
id: 1,
text: 'Time Slot 1',
providerFname: 'James',
providerLname: "Appleton"
},
{
id: 2,
text: 'Time Slot 2',
providerFname: 'James',
providerLname: "Johnson"
}
];
this.selectName = "provider-select";
this.id = "provider-select";
this.labelFor = "provider-select";
this.labelText = "Choose a Provider: ";
}
}
</script>
select.vue
<template>
<br />
<label :for="labelFor">{{ labelText }} {{ disableOption }}</label>
<select v-on:change="onSelect($event, id)" class="select" :name="selectName" :id="id" :disabled="disableOption" >
<option :value="'none'" selected disabled hidden >Select One</option>
<option :key="provider.id" v-for="provider in eligibleProviders" :value="provider.first_name + ' ' + provider.last_name" >{{ provider.first_name +" "+ provider.last_name }}</option>
</select>
<br /><br />
</template>
<script>
export default {
name: 'Select',
props: {
selectValue: String,
selectName: String,
id: String,
labelFor: String,
labelText: String,
eligibleProviders: Array,
disableOption: Boolean,
},
methods: {
onSelect($event, id) {
console.log($event.target.value)
this.$emit('dd-select', id, $event.target.value);
}
},
emits: ['dd-select']
}
</script>
button.vue
<template>
<button #click="onClick(id)" class="btn" :id="id" :disabled="disableBtn" >{{ text }}</button>
</template>
<script>
export default {
name: 'Button',
props: {
text: String,
id: String,
disableBtn: Boolean,
},
methods: {
onClick(id) {
this.$emit('btn-click', id);
}
}
}
</script>
in select.vue, the props says it wants "disableOption", but you're passing disabled="disableOption"
so you can try updating app.vue with:
<Select
#dd-select="providerPickedSelect"
:disable-option="disableOption"
:select-name="selectName"
:id="id"
:eligible-providers="eligibleProviders"
:label-for="labelFor"
:label-text="labelText"
/>

v-for not re-rendering array vue js

I have a SPA where I show array of pokemon using v-for, with the option to filter those lists by type or generation. I have a button that clears the filters (sets the type to '' and generation to generation 1), but the v-for loop doesn't re-render the array after the filters are cleared. I've logged the function that returns the array of pokemon to confirm it's working, but Vue JS doesn't render the results. I'm not sure how to proceed.
<div class="pokemon"
v-for="pokemon in filteredPokemon"
:key="pokemon.id">
<h2>{{ pokemon.name }}</h2>
</div>
<script>
import Pokemon from '../pokeData'
export default{
props: ['searchFilters'],
data(){
return{
allPokemon: [],
}
},
created(){
this.allPokemon = Pokemon.getPokemon('gen1');
},
computed: {
filteredPokemon: function(){
if(this.searchFilters.type){
if(this.searchFilters.type === ''){
return this.allPokemon
}
return this.allPokemon.filter(pokemon => {
if(pokemon.types.length === 2){
if(pokemon.types[0].type.name == this.searchFilters.type || pokemon.types[1].type.name == this.searchFilters.type){
return true
}
}
else if(pokemon.types[0].type.name == this.searchFilters.type){
return true
}
})
}
return this.allPokemon
}
},
watch:{
'searchFilters.generation': function(generation){
this.allPokemon = Pokemon.getPokemon(generation)
}
}
}
}
</script>
farincz is right, you are changing the attributes of allPokemon with the function call to getPokemon and Vue.JS can't find the change (documentation), therefore it's a caveat and you would need to handle this in a different way because Vue doesn't support the way you want it.
I would filter all pokemons with a filter method with a computed value and bind the filter value to a data property:
HTML:
<template>
<div>
<textarea v-model="text" name="filter" cols="30" rows="2"></textarea>
<div class="pokemon" v-for="pokemon in filteredPokemon" :key="pokemon.id">
<h2>{{ pokemon.name }}</h2>
</div>
</div>
</template>
JS file:
new Vue({
el: "#app",
data(){
return{
text: '',
pokemons: [
{gen: 'gen1', name: 'psyduck', id: '1'},
{gen: 'gen1', name: 'charizard', id: '2'},
{gen: 'gen1', name: 'pikachu', id: '3'},
{gen: 'gen2', name: 'togapi', id: '4'}
]
}
},
computed: {
filteredPokemon() {
if(this.text === '') return this.pokemons
return this.pokemons.filter(x=>x.gen === this.text)
}
}
})
here's the jsfiddle to play around.

How to get First Letter of First & Last name: Vue.js?

Suppose, My Name is "Aerofoil Todo Kite" I want AK
I got a code from Stackoverflow. I hope it will work. But my question is, I am printing data from Array of Objects with v-for loop.
How do I pass the Name to compute that?
I think, Computed Property don't accept Parameter.
Then what will be the process??
Method can do. But It is calling for many times!!!
data:
tableData: [
{ customer: 'EE Fashion'},
{ customer: 'Tom Hangs Ron'}
}]
methods: {
nameOfCompany(fullName) {
console.log(fullName);
return "HL";
}
}
Code of Mine:
<template slot-scope="scope">
<p style="margin-top: 5px;"><b>{{ nameOfCompany(scope.row.customer) }}</b></p>
</template>
Here is the problem:
{{ nameOfCompany(scope.row.customer) }}
This function is calling for many times!!!!
What will be the approach to do that?
You may write a Customer component so you only compute the company's name once:
It takes a name, and in data, computes the associated companyName.
const mytable = {
props: ['rows'],
template: `
<table>
<tr v-for="row in rows">
<slot :row="row"></slot>
</tr>
</table>
`
}
const mycustomer = {
props: ['name'],
data () {
return {
companyName: this.name.split(' ').map(x => x[0].toUpperCase()).join('')
}
},
template: `
<td>{{ name }} - <abbr>{{ companyName }}</abbr></td>
`
}
let vm = new Vue({
el:'#el',
components: { mytable, mycustomer },
template: `
<mytable :rows="['grod zi', 'tu rok']">
<template v-slot:default="{ row: user }">
<mycustomer :name="user"/>
</template>
</mytable>
`
});
abbr {
color: blue;
}
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="el"></div>
You can use either filters or computed.
Filters: Vue.js allows you to define filters that can be used to apply common text formatting. Filters are usable in two places: mustache
interpolations and v-bind expressions (the latter supported in
2.1.0+). Filters should be appended to the end of the JavaScript expression, denoted by the “pipe” symbol: doc
new Vue({
el: "#app",
data: {
tableData: [
{ customer: 'EE Fashion', company_name: "FOO BAR" },
{ customer: 'Tom Hangs Ron', company_name: "BAZ FOO BAR"},
{ customer: 'Jerry', company_name: "Lorem Ipsum Dorsum Zaren" }
],
},
filters: {
short_hand (company_name) {
// You can put your logic here...
let words = company_name.split(" ")
let short_hand = words[0][0] + words[words.length-1][0]
return short_hand // <-- The return value as Per logic
}
},
computed: {
getTableData () {
return this.tableData.map(data => {
let words = data.company_name.split(" ")
let short_hand = words[0][0] + words[words.length-1][0]
return { short_hand, ...data }
})
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
USING FILTER: <br>
<div
v-for="(data, i) in tableData"
:key="'using-filter-'+i"
>
{{ data.company_name | short_hand }}
</div>
<hr> USING COMPUTED: <br>
<div
v-for="(data, i) in getTableData"
:key="'using-computed-'+i"
>
{{ data.short_hand }}
</div>
</div>

Laravel can't get the values from Vue-multiselect

I am using Vue-multiselect with Laravel.
I am using the multiselect component in my form to let the user select multiple countries. The component works fine but when I submit the form and I dd() it, it shows [object Object].
I can't get the value of the multiselect component. I have found similar questions but none of them worked for me.
Here is my code:
The ExampleComponent.vue file:
<template slot-scope="{ option }">
<div>
<label class="typo__label">Restricted country</label>
<multiselect
v-model="internalValue"
tag-placeholder="Add restricted country"
placeholder="Search or add a country"
label="name"
name="selectedcountries[]"
:options="options"
:multiple="true"
track-by="name"
:taggable="true"
#tag="addTag"
>
</multiselect>
<pre class="language-json"><code>{{ internalValue }}</code></pre>
</div>
</template>
<script>
import Multiselect from 'vue-multiselect'
// register globally
Vue.component('multiselect', Multiselect)
export default {
components: {
Multiselect
},
props: ['value'],
data () {
return {
internalValue: this.value,
options: [
{ name: 'Hungary' },
{ name: 'USA' },
{ name: 'China' }
]
}
},
watch: {
internalValue(v){
this.$emit('input', v);
}
},
methods: {
addTag (newTag) {
const tag = {
name: newTag,
code: newTag.substring(0, 2) + Math.floor((Math.random() * 10000000))
}
this.options.push(tag)
this.value.push(tag)
}
},
}
</script>
Here is my register form:
<div id="select">
<example-component v-model="selectedValue"></example-component>
<input type="hidden" name="countriespost" :value="selectedValue">
</div>
<script>
const select = new Vue({
el: '#select',
data: {
selectedValue: null
},
});
</script>
When I submit the form, the countriespost shows me me this: [object Object] instead of the actual value.
It's because you are providing an array of objects as options property:
options: [
{ name: 'Hungary' },
{ name: 'USA' },
{ name: 'China' }
]
so the value emited on input is an object.
Try to change the options to following:
options: [ 'Hungary', 'USA', 'China' ]
If you pass an array of objects to the :options prop of the multiselect component, you should submit the form with javascript so you can extract the object ids or whatever you need on the backend and then send them through.
Add a method like this:
submit: function() {
let data = {
objectIds: _.map(this.selectedOptions, option => option.id), //lodash library used here
// whatever other data you need
}
axios.post('/form-submit-url', data).then(r => {
console.log(r);
});
}
Then trigger it with a #click.stop event on your submit button.

Vue Custom Select Component with Object value

I'm trying to implement custom select component with Vuejs 2. As stated in the documentation that i should not modify value props directly and suggested to use event to pass the selected data to parent component. I'm having issue when the option value is an object and got [Object object] instead.
here's my select component template:
<div :class="inputLength">
<select :id="id"
:value="value"
#change="setValue($event.target.value)"
:multiple="multiple"
class="selectpicker">
<option value="">Nothing selected.</option>
<option :selected="option == value" v-for="option in options"
:value="option">
{{ option[label] }}
</option>
</select>
<span v-if="error.any()" class="help-block" v-text="error.all()"></span>
</div>
and here's the script part:
export default {
props: {
value: {
default() {
return ''
}
},
options: {
type: Array,
require: true
},
...
},
methods: {
setValue(val) {
this.error.clear();
this.$emit('input', val);
}
}
}
and here's the parent component
<input-select-horizontal
v-model="form.category"
:label-class="{'col-md-4': true}"
input-length="col-md-8"
:options="categories.all()"
label="name"
:error="form.errors.get('category_id')">
<span slot="label">Category <span class="required" aria-required="true">*</span></span>
the options:
[
{
id: 1,
name: 'Category 1',
description: 'desc 1'
},
{
id: 2,
name: 'Category 2',
description: 'desc 2'
},
...
]
I'm expecting the
form.category = {
id: 1,
name: "Category 1",
description: "desc 1"
}
but got [Object object]
did i miss something?
Your problem lies here:
<option v-for="option in options" :value="option">
{{ option[label] }}
</option>
You're taking a whole object and assigning it to the value attribute of the option element. This won't work, because the value attribute has to be a string. So the object is converted to [Object object].
You should try using :value="option.id", the ID value should get through to the parent component normally and you can use it to find the right category.
As mzgajner mentioned, you can't bind an object because it will convert it to a string.
What you can do however, is to convert your object to a base64 string in the options component, and then decode it again in the select component.
For example:
Component CustomOption
<template>
<option v-bind="{ ...$attrs, value: innerValue }" v-on="$listeners">
<slot>
{{ label || $attrs.value }}
</slot>
</option>
</template>
<script>
export default {
props: {
label: [String, Number, Boolean],
},
computed: {
innerValue() {
return btoa(JSON.stringify(this.$attrs.value));
},
},
};
</script>
Component CustomSelect
<template>
<select
:value="innerValue"
v-bind="$attrs"
v-on="{
...$listeners,
input: onInput,
}"
>
<slot></slot>
</select>
</template>
<script>
export default {
props: {
value: null
},
computed: {
innerValue() {
return btoa(JSON.stringify(this.value));
},
},
methods: {
onInput(e) {
let value = JSON.parse(atob(e.target.value));
this.$emit('input', value);
},
},
};
</script>
https://www.npmjs.com/package/stf-vue-select
<stf-select v-model="value" style="width: 300px; margin: 0 auto">
<div slot="label">Input address</div>
<div slot="value">
<div v-if="value">
<span>{{value.address}} (<small>{{value.text}}</small>)</span>
</div>
</div>
<section class="options delivery_order__options">
<stf-select-option
v-for="item of list" :key="item.id"
:value="item"
:class="{'stf-select-option_selected': item.id === (value && value.id)}"
>
<span>{{item.text}} (<small>{{item.address}}</small>)</span>
</stf-select-option>
</section>
</stf-select>

Categories