I am trying to use the vue-select component for a dropdown list. So far I have written.
<template>
<div>
<v-select label="name" key="id" :v-model="selected" :reduce="data => data.id" :options="items" #input="update()" />
</div>
</template>
<script>
export default {
props: {
initial: {
type: [String, Number],
default: 0,
},
api_call: {
type: String,
required: true,
},
},
data(){
return {
value: this.initial,
items: [],
}
},
computed: {
selected: {
get() {
return this.value;
},
set(val) {
return this.value = val;
}
},
},
methods:{
update() {
console.log('selected', this.selected, this.value);
this.$emit('input', this.selected);
},
getData: function(){
axios.get('/api/' + this.api_call)
.then(function (response) {
this.items = response.data;
}.bind(this));
},
},
created(){
this.getData();
}
}
The dropdown list populates as intended and selecting an Item inserts it in the input filed. The two problems I have are
Neither the value nor the selected variables change when something is selected.
I am also passing in an initial value which I would like to be selected as the default in the list.
Remove the binding sign : from v-model directive
<v-select label="name" key="id" v-model="selected" :reduce="data => data.id" :options="items" #input="update()" />
and init your value like :
data(vm){//vm refers to this
return {
value: vm.initial,
items: [],
}
},
or :
data(){
return {
value: null,
items: [],
}
},
mounted(){
this.value=this.initial
}
Related
I have a component that uses Multiselect from #vueform/multiselect. The problem is when the user selects an option and the tag is created, if enter is pressed it will deselect that option. How am I able to control that? I tried adding #keydown.enter.prevent="deselect" but it doesn't work.
Here is how my component looks:
<template>
<Multiselect
v-model="propValue"
:options="this.options"
:placeholder=this.$filters.translate(this.placeholder)
:closeOnSelect="false"
:searchable="this.searchable"
:noResultsText="$filters.translate(this.labels.noResult)"
ref="multiselect"
#change="change"
#click="click"
#deselect="remove"
:mode="this.mode"
:hideSelected='false'
:max="this.limit">
<template v-slot:caret>
<img class="caretMultiselect" :src="filterIcon" alt="Filter">
</template>
<template v-slot:beforelist v-if="showWarning">
<span class="beforeListContainer">
<p class="beforeListWarningMessage">{{this.warningMessage}}</p>
</span>
</template>
</Multiselect>
</template>
<script>
import Multiselect from '#vueform/multiselect';
import filter from '#/assets/filter.svg';
import { nextTick } from 'vue';
export default {
components: {
Multiselect,
},
props: {
disabled: Boolean,
placeholder: String,
mode: {
type: String,
default: 'single'
},
showWarning: {
type: Boolean,
default: false
},
options: Object,
value: Array,
warningMessage: String,
limit: Number
},
emits: ['change', 'reachedMax', 'removedTag'],
data(){
return {
filterIcon: filter,
labels: {
noResult: 'No matching records'
},
propValue : this.value,
searchable: true
}
},
methods: {
change(value) {
this.propValue = value;
this.$emit('change', value);
if (this.propValue.length == this.limit) {
nextTick(() => {
this.$refs["multiselect"].close();
})
return;
}
this.searchable = true;
},
click(val) {
if (this.propValue.length == this.limit) {
this.searchable = false;
for (let idx in this.options) {
if (this.options[idx].label == val.target.ariaLabel && !this.propValue.includes(this.options[idx].value)) {
this.$emit('reachedMax');
}
}
}
},
remove() {
this.$emit('removedTag');
}
},
watch: {
options(current) {
if (current.length !== 0) {
for (let idx in this.propValue) {
let option = this.propValue[idx];
if (!current.some(e => e.value === option)) {
let index = this.propValue.indexOf(option);
index > -1 ? this.propValue.splice(index, 1) : '';
this.$emit('change', this.propValue);
}
}
}
}
}
}
</script>
Thanks in advance, appreciate the help.
I have the following vue component
<template>
<div class="box"
:data-target="dropAreaClass"
:class="{ 'js-draggable': isDraggable }"
:id="id"
:draggable="isDraggable"
#dragstart="dragStart"
#dragend="dragEnd">
{{ id }}
</div>
</template>
<script>
export default {
name: 'ActionBox',
props: {
dropAreaClass: {
default: 'js-droppable--any',
type: String,
},
id: {
default: null,
type: String,
required: true,
},
isDraggable: {
default: true,
type: Boolean,
},
},
data: () => ({
dropAreas: null,
}),
mounted() {
this.dropAreas = document.querySelectorAll(`.${this.dropAreaClass}`);
},
methods: {
dragEnd(event) {
this.dropAreas.forEach(dropArea => {
dropArea.classList.remove('drop');
});
event.currentTarget.classList.remove('dragging');
},
dragStart(event) {
this.dropAreas.forEach(dropArea => {
dropArea.classList.add('drop');
});
event.currentTarget.classList.add('dragging');
event.dataTransfer.setData('text', event.currentTarget.id);
},
},
};
</script>
This is a simple div which I can drag a drop into multiple columns in the parent component - once it is dropped in one of the target columns, the following function is fired to move the component to the column it is dropped in:
drop(event) {
const droppedElement = document.getElementById(event.dataTransfer.getData('text'));
if (event.currentTarget.classList.contains(droppedElement.dataset.target)) {
event.currentTarget.prepend(droppedElement);
event.currentTarget.classList.remove('drop');
}
},
This all works fine, however, once it is dropped, I can no longer drag the component to another column as it seems to have lost all it's event bindings. Is there a way to keep the events after dropping?
Might be a simple solution, but I'm currently not seeing it. I have an object that describes several configurations.
Object looks like this:
export const fieldSelectionDefault = {
cohort: {
currency_key: null,
salary_key: null,
timeframe_key: null
},
school: {
currency_key: null,
salary_key: null,
timeframe_key: null,
response_count_key: null,
},
}
export const cohortListFieldDefault = {
field_student: { ...fieldSelectionDefault },
field_alum_1: { ...fieldSelectionDefault },
field_alum_2: { ...fieldSelectionDefault },
field_alum_3: { ...fieldSelectionDefault },
}
Now, I have a parent component where I have a form. This form will list each field_* to have a <CohortFieldConfig /> component where we can input the values of the fieldSelectionDefault.
In the parent form, I add them like this:
<h5>Student</h5>
<CohortFieldConfig
:key="'settings.field_student'"
:disabled="settings.active_entities.student"
:selection-fields="settings.field_student"
#update-fields="(val) => test(val, 'stu')"
/>
<h5>Alumnus 1</h5>
<CohortFieldConfig
:key="'settings.field_alum_1'"
:disabled="settings.active_entities.alum_1"
:selection-fields="settings.field_alum_1"
#update-fields="(val) => test(val, 'alum')"
/>
CohortFieldConfig looks like this (example of one inputs, removed js imports):
<template>
<div>
<a-form-item label="Currency input">
<a-input
:disabled="!disabled"
placeholder="Select a currency form key"
v-model="objSelectionFields.cohort.currency_key"
/>
</a-form-item>
<FieldSelector
#select="val => (objSelectionFields.cohort.currency_key = val)"
:user="user"
:disabled="!disabled"
/>
</div>
</template>
<script>
export default {
name: 'CohortFieldConfig',
components: { FieldSelector },
props: {
selectionFields: {
type: [Object, null],
default: () => {
return { ...fieldSelectionDefault }
},
},
disabled: {
type: Boolean,
default: () => false,
},
},
data: function() {
return {
fieldSelectionDefault,
objSelectionFields: { ...this.selectionFields },
}
},
watch: {
objSelectionFields: {
handler(){
this.$emit('update-fields', this.objSelectionFields)
},
deep: true
}
},
methods: {
update() {
// not really used atm
this.$emit('update-fields', this.objSelectionFields)
},
},
}
</script>
When you type in the input, BOTH are updated at the same time. For student & alum_1.
The update-fields event is fired for both (same) components
Whats the reason? I've tried setting different key, doesn't work.
UPDATE
As pointed out in the comments, the issue was I was giving the same object. To correct this, I make a (deep) copy of the object as so:
export const cohortListFieldDefault = {
field_student: JSON.parse(JSON.stringify(fieldSelectionDefault)),
field_alum_1: JSON.parse(JSON.stringify(fieldSelectionDefault)),
field_alum_2: JSON.parse(JSON.stringify(fieldSelectionDefault)),
field_alum_3: JSON.parse(JSON.stringify(fieldSelectionDefault)),
}
With the following component, I am getting an Error: [vuex] do not mutate vuex store state outside mutation handlers. error:
<template>
<div>
<v-data-table
:headers="headers"
:items="items"
:search="search"
:key="tableKey"
:pagination.sync="pagination"
disable-initial-sort
rowKey
>
<template slot="items" slot-scope="props">
<tr #click="clicked(props.item)" :class="{'secondary': props.item[rowKey]===selectedCode}">
<td v-for="header in headers" :key="header.value">
<BaseTableColumn
:item="props.item"
:index="header.value"
:format="header.format"
/>
</td>
</tr>
</template>
</v-data-table>
</div>
</template>
<script>
export default {
name: 'BaseTable',
props: {
headers: Array,
items: Array,
search: String,
tableKey: String,
rowKey: String,
},
data: () => ({
pagination: {
rowsPerPage: 10,
totalItems: -1,
},
selectedCode: -1,
}),
components: {
BaseTableColumn: () => import('#/components/base/BaseTableColumn'),
},
methods: {
clicked(row) {
window.scrollTo(0, 0);
this.selectedCode = row[this.rowKey];
this.$set(row, 'selected', true);
this.$emit('rowClick', row);
},
highlightFirst(items) {
this.selectedCode = this.items[0][this.rowKey];
this.$set(this.items[0], 'selected', true);
},
},
updated() {
if (this.selectedCode === -1 && (typeof this.items === 'object') && this.items.length > 0) {
this.highlightFirst(this.items);
}
},
};
</script>
For reference, here is headers.js:
const headers = [
{
text: 'Tenant Code',
value: 'code',
},
{
text: 'Tenant ID',
value: 'name',
},
];
export default headers;
and BaseTableColumn.vue:
<script>
export default {
name: 'BaseTableColumn',
props: {
format: Function,
item: Object,
index: String,
},
methods: {
getText() {
return this.item[this.index];
},
},
render(createElement) {
if (this.$props.format) {
return this.$props.format(this.item, this.index, createElement);
}
return createElement('div', this.getText());
},
};
</script>
The issue happens here:
this.$set(this.items[0], 'selected', true);
However, if I follow the docs like so:
<template>
<div>
<v-data-table
:headers="headers"
:items="tableRows"
:search="search"
:key="tableKey"
:pagination.sync="pagination"
disable-initial-sort
rowKey
>
...
</template>
<script>
export default {
name: 'BaseTable',
props: {
headers: Array,
items: Array,
search: String,
tableKey: String,
rowKey: String,
},
...
computed: {
tableRows() {
const rows = [...this.items];
return rows;
},
},
...
methods: {
...
highlightFirst(items) {
this.selectedCode = this.items[0][this.rowKey];
this.$set(this.tableRows[0], 'selected', true);
},
},
updated() {
if (this.selectedCode === -1 && (typeof this.tableRows === 'object') && this.tableRows.length > 0) {
this.highlightFirst(this.tableRows);
}
},
};
</script>
I still get the errors, specifically in the updated() hook and the highlightFirst() method, even though I'm not referencing or mutating a prop. What else do I need to change to get rid of this error?
The way I eventually solved this problem was to emit an event and use the row value in the parent component:
clicked(row) {
window.scrollTo(0, 0);
this.selectedCode = row[this.rowKey];
this.$emit('rowClick', row);
},
However, to #Jesper's point above, since then, I have been using Object.assign() in cases like this where I need to break the link to Vuex.
I am stuck at this very point: exporting filter function and using it in vuex store. No problem'till here. Now am trying to put #click event on divs. And when I click, for example. Audi the filter needs to show just "audi" And if I click "audi" again then it needs remove it from the filter.
Here is the sandbox: https://codesandbox.io/s/filtering-bzphi
filter.js
export const carFilter = car => allcars => {
if (car.length > 0) {
if (allcars.name.includes(car)) {
return true;
} else {
return false;
}
} else {
return true;
}
};
Store
export const store = new Vuex.Store({
state: {
cars: [
{ name: "AUDI" },
{ name: "BMW" },
{ name: "MERCEDES" },
{ name: "HONDA" },
{ name: "TOYOTA" }
],
carBrand: []
},
mutations: {
updateCarsFilter(state, carBrand) {
state.carBrand = carBrand;
}
},
getters: {
filteredCars: state => {
return state.cars.filter(carFilter(state.carBrand));
}
}
});
and App.js
<template>
<div id="app">
<div class="boxes" :key="index" v-for="(item, index) in cars">{{item.name}}</div>
<List/>
</div>
</template>
<script>
import List from "./List.vue";
export default {
name: "App",
components: {
List
},
computed: {
selectBrand: {
set(val) {
this.$store.commit("updateCarsFilter", val);
},
get() {
return this.$store.state.carBrand;
}
},
cars() {
return this.$store.getters.filteredCars;
}
}
};
</script>
I also created a sandbox for this. You can check it for better understanding. https://codesandbox.io/s/filtering-bzphi
In the store.js
changed the carBrand default to ''
added Mutation clearFilter
added Getter isActiveFilter
update
remove carBrand from state
replaced by selectedCars that is an array
removed mutation about carBrand
added mutation addCarSelection removeCarSelection
filteredCars return selectedCars array if contains cars, otherwise cars state
added isSelectedCar to check if a car is in the selection
carFilter function from filter.js is no longer needed.
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
cars: [
{ name: "AUDI" },
{ name: "BMW" },
{ name: "MERCEDES" },
{ name: "HONDA" },
{ name: "TOYOTA" }
],
selectedCars: []
},
mutations: {
addCarSelection(state, car) {
state.selectedCars.push(car);
},
removeCarSelection(state, car) {
state.selectedCars = state.selectedCars.filter(r => r.name !== car.name);
}
},
getters: {
filteredCars: state => {
if (state.selectedCars.length !== 0) {
// There's selected cars, return filtered
return state.selectedCars;
} else {
return state.cars;
}
},
isSelectedCar: state => car => {
return state.selectedCars.some(r => r.name === car.name);
}
}
});
In the App.vue
added method filterCars (moved from computed property searchText)
added method clearFilter
update
removed filterCars and 'clearFilter' method and mapped new mutation and getters from store
methods: {
addCarSelection(car) {
this.$store.commit("addCarSelection", car);
},
removeCarSelection(car) {
this.$store.commit("removeCarSelection", car);
},
isSelectedCar(car) {
return this.$store.getters.isSelectedCar(car)
},
}
added isFilterActive() computed property
update
removed isFilterActive() and searchText from computed property
computed: {
cars() {
return this.$store.getters.filteredCars;
},
},
update
Changed the Template code to manage #click event to add car or remove car from selection
boxes always show cars available, if isSelectedCar toggle between add or remove function.
List show selected cars if presents otherwise the full car catalog.
<template>
<div id="app">
<div class="boxes" :key="index" v-for="(item, index) in cars">
<div
v-if="!isSelectedCar(item)"
style="cursor:pointer"
#click="addCarSelection(item)"
>{{item.name}}</div>
<div v-else style="cursor:pointer;" #click="removeCarSelection(item)">
{{item.name}}
<small>[x]</small>
</div>
</div>
<List/>
</div>
</template>
Updated version is available in this sandbox
https://codesandbox.io/s/filtering-3ej7d