Sorting multiple columns based on headers in Vue.js - vue.js

I want to apply sorting on firstname, lastname and email but i did not undestand How We sort table in ascending and descnding order by clicking on column name in vuejs. I am tried lot of concepts on stackoverflow and other resources but they didn't seem to work . This is very silly question but I am stopped on this from many days.I'm new to Vue.js and any help would be appreciated. Image
<template>
<div class="h-full flex flex-col">
<ListFilter
ref="filter"
:class="{ 'mb-8': hasFilters || search }"
:filter="filter"
:has-filters="hasFilters"
:has-quick-filters="hasQuickFilters"
:query="query"
:search="search"
:search-placeholder="searchPlaceholder"
:filter-classes="filterClasses"
:initial-values="initialFilter"
:apply-filter="values => applyFilter(filterMapper(values))"
:apply-search="applySearch"
:clear-search="clearSearch"
>
<template #filters="props">
<slot
name="filters"
:reset="props.reset"
:filter="filter"
/>
</template>
<template #quickfilter>
<slot
name="quickfilter"
:apply-filter="applyFilter"
/>
</template>
<template #loader>
<loader
:loading="loading"
:backdrop="true"
/>
</template>
</ListFilter>
<!-- <div>
</div> -->
<vuetable
ref="vuetable"
:row-class="rowClass"
:api-mode="false"
:fields="columns"
:data-manager="dataManager"
:css="css.table"
:track-by="trackBy"
:sort-order="innerSortOrder"
:detail-row-component="detailRow"
:detail-row-options="detailOptions"
:no-data-template="noDataTemplate"
pagination-path="pagination"
#vuetable:row-clicked="handleRowClicked"
#vuetable:cell-clicked="props => $emit('cell-clicked', props)"
#vuetable:pagination-data="onPaginationData"
>
<template #empty-result>
<slot name="empty-result" />
</template>
<template #actions="props">
<div v-if="hasActions">
<ListActions :record="props.rowData">
<slot
name="actions"
:record="props.rowData"
/>
</ListActions>
</div>
</template>
<template #inline-actions="props">
<div v-if="hasInlineActions">
<InlineActions :record="props.rowData">
<slot
name="inline-actions"
:record="props.rowData"
/>
</InlineActions>
</div>
</template>
<template #detail-toggler="props">
<div v-if="hasDetailRow">
<DetailToggler
:row-data="props.rowData"
:row-index="props.rowIndex"
>
<template #default="detailProps">
<slot
name="detail-toggler"
:record="props.rowData"
:is-open="detailProps.isOpen"
:toggle-row="detailProps.toggleRow"
/>
</template>
</DetailToggler>
</div>
</template>
</vuetable>
<div
v-if="!infinityScroll"
class="pt-2"
>
<vuetable-pagination
ref="pagination"
:css="css.pagination"
#vuetable-pagination:change-page="onChangePage"
/>
</div>
</div>
</template>
<script>
import { Vuetable, VuetablePagination } from 'vue3-vuetable';
import AuthMixin from '#/components/auth/AuthMixin';
import ModalNavigation from '#/mixins/ModalNavigation';
// import Loader from '#/components/ui/Loader';
import { throttle } from 'lodash-es';
import ListFilter from '#/components/auth/list/ListFilter';
import NotifyMixin from '#/mixins/NotifyMixin';
const css = {
table: {
tableClass: 'table-auto w-full table',
tableBodyClass: '',
tableHeaderClass: 'px-4 py-2',
tableWrapper: 'overflow-x-auto flex-1',
loadingClass: 'loading',
ascendingIcon: 'blue chevron up icon',
descendingIcon: 'blue chevron down icon',
ascendingClass: 'sorted-asc',
descendingClass: 'sorted-desc',
sortableIcon: 'grey sort icon',
handleIcon: 'grey sidebar icon',
detailRowClass: 'bg-blue-100',
},
pagination: {
wrapperClass: 'flex justify-center py-4',
activeClass: 'active',
disabledClass: '',
pageClass: 'btn-paging',
linkClass: 'btn-paging',
paginationClass: '',
paginationInfoClass: 'pagination-info',
dropdownClass: '',
icons: {
first: '',
prev: '',
next: '',
last: '',
},
},
};
export default {
components: {
// Loader,
Vuetable,
VuetablePagination,
ListFilter,
},
mixins: [NotifyMixin, AuthMixin, ModalNavigation],
props: {
css: {
type: Object,
default() {
return css;
},
},
},
data() {
return {
// loading: false,
query: '',
// filter: this.filterMapper(this.initialFilter),
sort: undefined,
showFilters: false,
loading: false,
scrollContainer: null,
innerSortOrder: this.sortOrder || [],
data: [],
columns: [
{
name: 'first_name',
title: 'First Name',
class: 'w-1/4',
},
{
name: 'last_name',
title: 'Last Name',
class: 'w-1/4',
},
{
name: 'email',
title: 'Email',
class: 'w-1/4',
},
{
name: 'phone_number',
title: 'Phone Number',
class: 'w-1/4',
},
{
name: 'communities',
title: 'Communities',
class: 'w-1/4',
},
{
label: 'communities',
field: 'Communities',
class: 'w-1/4',
formatter(value) {
// console.log('ROW', value);
let communities = [];
value.community_has_leasing_agents.forEach(function (row) {
communities.push('<span class="tag">' + row.community_id + '</span>');
});
// console.log('communities=>', communities);
return communities.join('');
},
},
],
};
},
computed: {
search() {
return true;
},
searchPlaceholder() {
return 'Search by name, community, email';
},
hasFilters() {
// return !!this.$slots.filters;
return true;
},
hasQuickFilters() {
// return !!this.$slots['quickfilter'];
return true;
},
hasActions() {
// return !!this.$slots.actions;
return true;
},
hasInlineActions() {
// return !!this.$slots['inline-actions'];
return true;
},
hasDetailRow() {
// return this.detailRow !== "";
return true;
},
hasClickRowListener() {
// return this.$attrs['row-clicked'];
return true;
},
nonClickableRow() {
return (
this.onRowClick === 'none' &&
!this.hasClickRowListener &&
!(this.$route.params?.passFlowTo && this.$route.params?.passFlowTo !== this.$route.name)
);
},
detailOptions() {
return {
...this.detailRowOptions,
vuetable: this.$refs.vuetable,
};
},
},
async mounted() {
console.log('== Leasing Agents Index ==');
console.log(this.profile);
await this.getLeasingAgentsFromAPI();
},
created() {
if (!this.community) {
this.notifyError('please select a community to continue, then refresh the browser');
}
},
methods: {
async getLeasingAgentsFromAPI() {
console.log('LEASING::getLeasingAgentsFromAPI()');
this.loading = true;
return await this.$calendarDataProvider
.get('calendarGetLeasingAgents', {
customer_id: this.profile.customerId,
})
.then(res => {
console.log('LEASING::getLeasingAgentsFromAPI() result:');
console.log(res);
this.data = res;
return res;
// return this.getEvents(res);
})
.catch(err => console.log(err.message))
.finally(() => {
this.loading = false;
});
},
rowClass(dataItem) {
const classes = ['table-row', 'row'];
if (this.nonClickableRow) {
classes.push('table-row-nonclickable');
}
if (this.hasDetailRow && this.$refs.vuetable?.isVisibleDetailRow(dataItem[this.trackBy])) {
classes.push('table-row-detail-open');
}
return classes.join(' ');
},
toggleFilters() {
this.showFilters = !this.showFilters;
},
applySearch(value) {
this.query = value;
},
clearSearch() {
this.query = '';
},
applyFilter(values) {
this.filter = values;
this.$refs.vuetable?.changePage(1);
this.$refs.vuetable?.resetData();
},
doSearch: throttle(
function () {
if (this.query.length > 2 || this.query.length === 0) {
this.reload();
}
},
500,
{ trailing: true }
),
onPaginationData(paginationData) {
if (!this.infinityScroll) {
this.$refs.pagination.setPaginationData(paginationData);
}
},
onChangePage(page) {
this.$refs.vuetable.changePage(page);
},
reload() {
this.$refs.vuetable.resetData();
this.$refs.vuetable.refresh();
},
getSort({ sortField, direction }) {
// sortField: 'type&bundle.name', - sort by two fields
const fields = sortField.split('&');
if (fields.length > 1) {
return fields.map(item => {
const [fieldName, dir = direction] = item.split('|');
return `${fieldName},${dir}`;
});
}
return `${sortField},${direction}`;
},
async dataManager(/*sortOrder = [], pagination = { current_page: 1 }*/) {
// allow static data
// if (this.data) {
// return {
// data: this.data,
// };
// }
const data = await this.getLeasingAgentsFromAPI();
return {
data: data,
};
// const prevData = this.$refs.vuetable?.tableData ?? [];
// const { pageSize: size, query, filter, sort: oldSort } = this;
// const sort = sortOrder[0] ? this.getSort(sortOrder[0]) : undefined;
// this.loading = true;
// return this[this.dataProvider]
// .getList(this.resource, {
// page: pagination.current_page - 1,
// size,
// sort,
// query,
// ...this.requestParams,
// ...filter,
// })
// .then(this.responseMapper)
// .then(({ content: nextData, totalElements }) => {
// const newPagination = this.$refs.vuetable?.makePagination(totalElements, this.pageSize, pagination.current_page);
// this.sort = sort;
// this.innerSortOrder = sortOrder;
// return {
// pagination: newPagination,
// data: this.infinityScroll && oldSort === sort ? [...prevData, ...nextData] : nextData,
// };
// })
// .catch(error => {
// this.notifyError(error.message);
// return {
// pagination: this.$refs.vuetable?.tablePagination,
// data: this.$refs.vuetable?.tableData,
// };
// })
// .finally(() => (this.loading = false));
},
handleRowClicked({ data, ...rest }) {
if (this.$route.params?.passFlowTo && this.$route.params?.passFlowTo !== this.$route.name) {
this.$router.push({
name: this.$route.params?.passFlowTo,
params: {
...this.$route.params,
[this.routeIdParam]: data[this.trackBy],
},
});
} else {
switch (this.onRowClick) {
case 'edit':
this.$router.replace({ path: `${this.basePath}/${data[this.trackBy]}` });
break;
case 'details':
this.$router.replace({ path: `${this.basePath}/${data[this.trackBy]}/details` });
break;
default:
this.$emit('row-clicked', { data, ...rest });
}
}
},
handleScrollContainer(e) {
if (this.$refs.vuetable.tablePagination && e.target.scrollHeight - e.target.scrollTop - e.target.clientHeight <= 2) {
this.onChangePage('next');
}
},
},
};
</script>
<style scoped>
.tag {
display: inline-flex;
align-items: center;
overflow: hidden;
margin-right: 0.25rem;
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
border-radius: 0.0625rem;
--bg-opacity: 1;
background-color: #eaf6ff;
background-color: rgba(234, 246, 255, var(--bg-opacity));
--text-opacity: 1;
color: #105d91;
color: rgba(16, 93, 145, var(--text-opacity));
font-family: 'FrankNew', sans-serif;
font-weight: 500;
}
</style>
I'm attaching my index.vue file and image.

Related

Vuejs auto suggest input box leading to extra space in the html

Auto suggest uses extra spaces in the html, because of the reason the next elements like H1, H2 and paragraph are going down when I start typing in the suggestion box
App.vue
<template>
<div id="app">
<Autocomplete
:items="[
'Apple',
'Banana',
'Orange',
'Mango',
'Pear',
'Peach',
'Grape',
'Tangerine',
'Pineapple',
]"
/>
<p>p1</p>
<h1>h1</h1>
<h1>h2</h1>
</div>
</template>
<script>
import Autocomplete from "./components/AutoComplete";
import AutoCompleteSearch from "./directives/auto-complete-search.js";
import axios from "axios";
export default {
name: "App",
directives: {
AutoCompleteSearch,
},
components: {
Autocomplete,
},
data() {
return {
data: [],
};
},
methods: {
async getUser() {
await axios
.get("https://jsonplaceholder.typicode.com/users")
.then((res) => (this.data = res.data))
.catch((err) => console.log(err));
},
},
mounted() {
this.data = [{ name: "Elie" }, { name: "John" }];
},
};
</script>
<style>
#app {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
AutocompletSearch.js -- directive
import Vue from "vue";
/**
* Add the list attribute to the bound input field.
*
* #param element
* #param binding
* #returns {HTMLElement}
*/
const addAttribute = (element, binding) => {
const { id } = binding.value;
element.setAttribute("list", id);
return element;
};
/**
* The search data for auto-completions
*
* #param binding
* #returns {Object|Array}
*/
const searchData = (binding) => {
const { data } = binding.value;
return data;
};
/**
* Construct the datalist HTMLElement
*
* #param element
* #param binding
* #returns {HTMLDataListElement}
*/
const constructDataList = (element, binding) => {
const datalist = document.createElement("datalist");
datalist.id = binding.value.id;
const data = searchData(binding);
data.forEach((item) => {
const { autoCompleteValue } = binding.value;
const option = document.createElement("option");
option.value = item[autoCompleteValue];
datalist.appendChild(option);
});
return datalist;
};
/**
* #param el the element where to bind the directive
* #param binding {id & autoCompleteValue & data}
*/
const init = (el, binding) => {
const element = addAttribute(el, binding);
const wrapper = element.parentNode;
const datalist = constructDataList(element, binding);
wrapper.appendChild(datalist);
};
export default Vue.directive("auto-complete-search", {
update(el, binding, vnode) {
const vm = vnode.context;
const bindingData = binding.value;
const componentName = vm.$options.name;
const customCallbackName = bindingData.callbackName;
console.log("component updated");
// call the function to fetch auto complete dat
vm.$options.methods.getUser();
if (!vm.$options.methods.getUser && customCallbackName === undefined)
return console.error(`the getUser() is neither defined or overriden in ${componentName} component, on
the auto-complete-search directive`);
if (binding.value.apply) {
init(el, binding);
}
if (!binding.value.apply) {
el.removeAttribute("list");
}
}
});
AutoComplete.vue
<template>
<div class="autocomplete">
<input
type="text"
#input="onChange"
v-model="search"
#keydown.down="onArrowDown"
#keydown.up="onArrowUp"
#keydown.enter="onEnter"
/>
<ul id="autocomplete-results" v-show="isOpen" class="autocomplete-results">
<li class="loading" v-if="isLoading">Loading results...</li>
<li
v-else
v-for="(result, i) in results"
:key="i"
#click="setResult(result)"
class="autocomplete-result"
:class="{ 'is-active': i === arrowCounter }"
>
{{ result }}
</li>
</ul>
</div>
</template>
<script>
export default {
name: "AutoComplete",
props: {
items: {
type: Array,
required: false,
default: () => [],
},
isAsync: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
isOpen: false,
results: [],
search: "",
isLoading: false,
arrowCounter: -1,
};
},
watch: {
items: function (value, oldValue) {
if (value.length !== oldValue.length) {
this.results = value;
this.isLoading = false;
}
},
},
mounted() {
document.addEventListener("click", this.handleClickOutside);
},
destroyed() {
document.removeEventListener("click", this.handleClickOutside);
},
methods: {
setResult(result) {
this.search = result;
this.isOpen = false;
},
filterResults() {
this.results = this.items.filter((item) => {
return item.toLowerCase().indexOf(this.search.toLowerCase()) > -1;
});
},
onChange() {
this.$emit("input", this.search);
if (this.isAsync) {
this.isLoading = true;
} else {
this.filterResults();
this.isOpen = true;
}
},
handleClickOutside(event) {
if (!this.$el.contains(event.target)) {
this.isOpen = false;
this.arrowCounter = -1;
}
},
onArrowDown() {
if (this.arrowCounter < this.results.length) {
this.arrowCounter = this.arrowCounter + 1;
}
},
onArrowUp() {
if (this.arrowCounter > 0) {
this.arrowCounter = this.arrowCounter - 1;
}
},
onEnter() {
this.search = this.results[this.arrowCounter];
this.isOpen = false;
this.arrowCounter = -1;
},
},
};
</script>
<style>
.autocomplete {
position: relative;
}
.autocomplete-results {
padding: 0;
margin: 0;
border: 1px solid #eeeeee;
height: 120px;
overflow: auto;
}
.autocomplete-result {
list-style: none;
text-align: left;
padding: 4px 2px;
cursor: pointer;
}
.autocomplete-result.is-active,
.autocomplete-result:hover {
background-color: #4AAE9B;
color: white;
}
</style>
Please help me out this, auto suggestion can show like popup-modal instead of using extra spaces in the html. like
Here is my code link - https://codesandbox.io/s/vuejs-autocomplete-input-forked-fh31tr
After checking out your codesandbox just add these 3 css properties to your autocomplete class:
position: absolute;
width: 100%;
background: white;

How to remove line over the stroke line in area chart - Apexchart?

How to remove line over the stroke line in area chart - Apexchart. the image
Please help me to fix this!
Here is my code so far
<template>
<div id="chart">
<apexchart
ref="pricesRef"
type="area"
height="150"
:options="options"
:series="series"
></apexchart>
<!-- <button #click="updateSeries">click</button> -->
</div>
</template>
<script>
import { axios } from 'boot/axios';
// import { dates, prices } from 'src/services/area-chart';
import { date } from 'quasar';
function formatNumber2(number, tofix) {
const val = (number / 1).toFixed(tofix).replace(',', ' ');
return val.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
let sharePrice = [];
const dataSharePrice = [];
export default {
name: 'AreaChart',
data() {
return {
timeInterval: 0,
dataSharePrice: [],
series: [
{
name: 'Share Price',
data: [
// { x: dates[0], y: prices[1] },
// { x: 1602392287529, y: 0.05 },
],
date: [],
},
],
options: {
chart: {
type: 'area',
zoom: {
enabled: false,
},
toolbar: {
show: false,
},
sparkline: {
enabled: true,
},
},
tooltip: {
custom({ series, w, dataPointIndex }) {
const unixTime = w.config.series[0].data[dataPointIndex].x;
const timeStamp = new Date(unixTime * 1000);
const hour = date.formatDate(timeStamp, 'DD MMMM');
return (
`
<div class="arrow_box_tooltip q-pa-md">
<span class="text-h6">
Date: ${hour}</span>
<br />
<span class="text-h6">
Share Price: $${formatNumber2(
series[0][dataPointIndex],
0,
)} USD</span>
</div>
`
);
},
},
noData: {
text: 'Loading...',
},
dataLabels: {
enabled: false,
},
stroke: {
curve: 'smooth',
lineCap: 'butt',
colors: undefined,
width: 2,
},
title: {
text: '',
align: 'left',
},
grid: {
show: false,
},
xaxis: {
type: 'datetime',
},
yaxis: {
opposite: true,
},
legend: {
horizontalAlign: 'left',
},
},
};
},
computed: {},
methods: {
getData() {
axios
.get(
'apiurl',
)
.then((response) => {
// console.log(response.data.prices);
sharePrice = response.data.prices;
})
.catch((err) => {
console.log(err);
});
},
updateSeriesData() {
for (const price of sharePrice) {
const unixTime = price[0];
if (Object.keys(price).length > 0) {
dataSharePrice.push({
x: unixTime,
y: price[1],
});
} else {
dataSharePrice.push({});
console.log('err');
}
// console.log(price[0], price[1]);
}
// console.log(dataSharePrice);
this.series[0].data = dataSharePrice;
this.$refs.pricesRef.updateSeries([
{
data: dataSharePrice,
},
]);
// console.log(this.$refs);
},
// timer() {},
},
mounted() {},
async created() {
await this.getData();
setTimeout(() => {
this.updateSeriesData();
// console.log('done');
}, 3000);
/* ; */
// this.timer();
},
beforeDestroy() {
// clearInterval(this.timer);
},
};
</script>
<style scoped>
/* #chart {
background-color: #18212f;
opacity: 1;
background-size: 7px 7px;
background-image: repeating-linear-gradient(
45deg,
#111726 0,
#111726 0.7000000000000001px,
#18212f 0,
#18212f 50%
);
} */
</style>
You can specify each stroke width separately by passing array
stroke: {
curve: 'smooth',
lineCap: 'butt',
colors: undefined,
width: [2,0],
},

Handle interaction between vue fields

I have prepared a functional code example in JSFiddle of VUE field interaction.
https://jsfiddle.net/JLLMNCHR/2a9ex5zu/6/
I have a custom autocomplete component that works properly, a normal input field, and a 'Load' button which objetive is to load the value entered in the normal input in the autocomplete field.
This 'load' button is not working.
HTML:
<div id="app">
<p>Selected: {{test1}}</p>
<br>
<div>
<label>Test1:</label>
<keep-alive>
<autocomplete v-model="test1" v-bind:key="1" :items="theItems">
</autocomplete>
</keep-alive>
</div>
<br>
<label>Display this in 'test1':</label>
<input type="text" v-model=anotherField>
<button type="button" v-on:click="loadField()">Load</button>
<br>
<br>
<button type="button" v-on:click="displayVals()">Display vals</button>
</div>
<script type="text/x-template" id="autocomplete">
<div class="autocomplete">
<input type="text" #input="onChange" v-model="search"
#keyup.down="onArrowDown" #keyup.up="onArrowUp" #keyup.enter="onEnter" />
<ul id="autocomplete-results" v-show="isOpen" class="autocomplete-results">
<li class="loading" v-if="isLoading">
Loading results...
</li>
<li v-else v-for="(result, i) in results" :key="i" #click="setResult(result)"
class="autocomplete-result" :class="{'is-active':i === arrowCounter}">
{{ result }}
</li>
</ul>
</div>
</script>
VUE.JS:
const Autocomplete = {
name: "autocomplete",
template: "#autocomplete",
props: {
items: {
type: Array,
required: false,
default: () => []
},
isAsync: {
type: Boolean,
required: false,
default: false
}
},
data() {
return {
isOpen: false,
results: [],
search: "",
isLoading: false,
arrowCounter: 0
};
},
methods: {
onChange() {
// Let's warn the parent that a change was made
this.$emit("input", this.search);
// Is the data given by an outside ajax request?
if (this.isAsync) {
this.isLoading = true;
} else {
// Let's search our flat array
this.filterResults();
this.isOpen = true;
}
},
filterResults() {
// first uncapitalize all the things
this.results = this.items.filter(item => {
return item.toLowerCase().indexOf(this.search.toLowerCase()) > -1;
});
},
setResult(result) {
this.search = result;
this.$emit("input", this.search);
this.isOpen = false;
},
onArrowDown(evt) {
if (this.arrowCounter < this.results.length) {
this.arrowCounter = this.arrowCounter + 1;
}
},
onArrowUp() {
if (this.arrowCounter > 0) {
this.arrowCounter = this.arrowCounter - 1;
}
},
onEnter() {
this.search = this.results[this.arrowCounter];
this.isOpen = false;
this.arrowCounter = -1;
},
handleClickOutside(evt) {
if (!this.$el.contains(evt.target)) {
this.isOpen = false;
this.arrowCounter = -1;
}
}
},
watch: {
items: function(val, oldValue) {
// actually compare them
if (val.length !== oldValue.length) {
this.results = val;
this.isLoading = false;
}
}
},
mounted() {
document.addEventListener("click", this.handleClickOutside);
},
destroyed() {
document.removeEventListener("click", this.handleClickOutside);
}
};
new Vue({
el: "#app",
name: "app",
components: {
autocomplete: Autocomplete
},
methods: {
displayVals() {
alert("test1=" + this.test1);
},
loadField() {
this.test1=this.anotherField;
}
},
data: {
test1: '',
anotherField: '',
theItems: [ 'Apple', 'Banana', 'Orange', 'Mango', 'Pear', 'Peach', 'Grape', 'Tangerine', 'Pineapple']
}
});
Any help will be appreciated.
See this new fiddle where it is fixed.
When you use v-model on a custom component you need to add a property named value and watch it for changes, so it can update the local property this.search.

set data of parent component in child

In parent component, send a variable as prop to child(DirectionsRenderer)
In child there is function(preparePoints function in DirectionsRenderer.js) set this;
but couldnt access 'this' reference inside that function
Parent:
<template>
<div>
<div>
<h2>Start</h2>
<label>
<gmap-autocomplete #place_changed="setStartPlace"></gmap-autocomplete>
<button #click="addStartMarker">Add</button>
</label>
<br />
</div>
<div>
<h2>End</h2>
<label>
<gmap-autocomplete #place_changed="setEndPlace"></gmap-autocomplete>
<button #click="addEndMarker">Add</button>
</label>
<br />
</div>
<br />
<gmap-map ref="xyz" :center="center" :zoom="7" style="width:100%; height: 400px;">
<gmap-marker
:key="index"
v-for="(m, index) in markers"
:position="m.position"
#click="center=m.position"
></gmap-marker>
<DirectionsRenderer
:v-model="pointList" //this is what I want filled by child component
travelMode="DRIVING"
:origin="origin"
:destination="destionation"
/>
{{pointList}}
</gmap-map>
</div>
</template>
<script>
import DirectionsRenderer from "./DirectionsRenderer.js";
export default {
components: {
DirectionsRenderer,
},
name: "GoogleMap",
data() {
return {
center: { lat: 41.85, lng: -87.65 },
pointList: [],
markers: [],
places: [],
path: [],
currentPlace: null,
startPoint: {},
endPoint: {},
};
},
computed: {
origin() {
if (!this.startPoint) return null;
return this.startPoint;
},
destionation() {
if (!this.endPoint) return null;
return this.endPoint;
},
},
mounted() {
this.geolocate();
},
methods: {
getPoints() {
return this.pointList;
},
setStartPlace(place) {
this.currentPlace = place;
},
setEndPlace(place) {
this.currentPlace = place;
},
addStartMarker() {
if (this.currentPlace) {
const marker = {
lat: this.currentPlace.geometry.location.lat(),
lng: this.currentPlace.geometry.location.lng(),
};
this.startPoint = marker;
this.markers[0] = { position: marker };
this.places.push(this.currentPlace);
this.center = marker;
this.currentPlace = null;
}
},
addEndMarker() {
if (this.currentPlace) {
const marker = {
lat: this.currentPlace.geometry.location.lat(),
lng: this.currentPlace.geometry.location.lng(),
};
this.endPoint = marker;
this.markers[1] = { position: marker };
this.places.push(this.currentPlace);
this.center = marker;
this.currentPlace = null;
}
},
geolocate: function () {
navigator.geolocation.getCurrentPosition((position) => {
this.center = {
lat: position.coords.latitude,
lng: position.coords.longitude,
};
});
},
},
};
</script>
Child(DirectionsRenderer.js):
export default MapElementFactory({
name: "directionsRenderer",
ctr() {
return window.google.maps.DirectionsRenderer;
},
events: [],
mappedProps: {},
props: {
pointList: { type: Array },
origin: { type: Object },
destination: { type: Object },
travelMode: { type: String }
},
methods: {
preparePoints: (array) => {
var result = []
array.forEach(element => {
result.push({ lat: element.lat(), lng: element.lng() })
});
debugger;
this.pointList = result;//**Throws exception cant read 'pointList' of undefined
}
},
afterCreate(directionsRenderer) {
let directionsService = new window.google.maps.DirectionsService();
this.$watch(
() => [this.origin, this.destination, this.travelMode],
() => {
let { origin, destination, travelMode } = this;
if (!origin || !destination || !travelMode) return;
var self = this;
directionsService.route(
{
origin,
destination,
travelMode
},
(response, status) => {
self.preparePoints(response.routes[0].overview_path);
}
);
}
);
},
});
How to retrieve data from child properly?
There is array in child all I need to do set 'pointList' in child and use it in parent.
You cannot use events.
The problem is, that you do not have a child / parent relationship, but a deeper hierarchy. You can use provide/inject for this.
In your parent, use
export default {
... all your old code ...
provide() {
return {
// Notice that this is a lambda => this gets bound to the this in provide, which is the vue instance
pointsCallback: (points) => this.pointList = points
}
}
}
In your child, use this:
export default {
inject: ['pointsCallback'],
// Your other code ...
// { {
// Somewhere after self.preparePoints(response.routes[0].overview_path);
this.pointsCallback(theNewPointsListYouComputed);
// This will call the pointsCallback you defined in your parent.
// } } and so on
}

Vuejs2: Vue Tables 2 - Multiple Custom filters

I'm trying to use a Vue table 2 filter to filter data by date, unfortunately it is not wroking and I am not able to find the reason. Has anyone tried such multiple filters with Vue table 2?
I went through the documentation but cannot find a solution.
https://matanya.gitbook.io/vue-tables-2/custom-filters
Html code to filter the data by date
<div class="col-md-4">
<div class="form-group">
<label for="sel1">Start Date:</label>
<input type="text" class="form-control" #keyup="applyFilterSearchText(searchText)" v-model="searchText" placeholder="End date" />
</div>
</div>
import { Event } from "vue-tables-2";
import axios from "axios";
export default {
title: "HelloWorld",
props: {
msg: String
},
data() {
return {
letters: ["Filled", "Unfilled", "Expired"],
selectedLetter: "",
searchText: "",
columns: ["refNumber", "vacancyTitle", "sector", "startDate", "endDate", "vacancyStatus"],
//data: getdetails(),
options: {
headings: {
refNumber: "Ref",
vacancyTitle: "Title",
sector: "Sector",
startDate: "Start",
endDate: "End",
vacancyStatus: "Status"
},
customFilters: [
{
name: "alphabet",
callback: function(row, query) {
return row.vacancyStatus == query;
}
},
{
name: "datefilter",
callback: function(row, query) {
return row.startDate == query;
}
}
],
// filterAlgorithm: {
// textsearch(row, query) {
// return (row.title).includes(query);
// }
// },
sortable: ["startDate", "vacancyTitle","endDate"],
sortIcon: {
base: "fa",
is: "fa-sort",
up: "fa-sort-asc",
down: "fa-sort-desc"
},
texts: {
filter: "Search by text:"
}
},
tableData:[],
};
},
methods: {
applyFilter(event) {
this.selectedLetter = event.target.value;
Event.$emit("vue-tables.filter::alphabet", this.selectedLetter);
},
applyFilterSearchText() {
console.log(this.searchText,"heiiiiiiiiiiii");
Event.$emit("vue-tables.filter::datefilter", this.searchText);
},
getdetails(){
axios.get("https://localhost:44399/api/Vacancy")
.then((res) => {
console.log(res.data,"ressssssss");
this.tableData = res.data;
})
.catch(function(error) {
console.log("Error: ", error);
});
}
},
mounted() {
this.getdetails();
}
};
A potential solution to that is to sort the data before using it in your data-table. Start by creating a computed of your data but with all your potential filters in it and create data variables with "parameters" (sort direction, sort column...)
export default {
title: "HelloWorld",
props: {
msg: String
},
data () {
return {
yourData: [],
sortBy: 'name',
sortDir: 'asc',
filterSearch: ''
}
},
computed: {
filteredData () {
if (filterSearch != '') {
let _this = this;
return this.sortedData.filter(item => {
return item.name.toLowerCase().includes(_this.filterSearch.toLowerCase());
})
} else {
return this.sortedData;
}
},
sortedData() {
return this.yourData.sort((a, b) => {
let modifier = 1;
if (this.sortBy == "order") {
if (this.sortDir === 'asc') {
return a[this.sortBy] - b[this.sortBy]
} else if (this.sortDir === 'desc') {
return b[this.sortBy] - a[this.sortBy]
}
} else {
if (this.sortDir === 'desc') modifier = -1;
if (a[this.sortBy] < b[this.sortBy]) return -1 * modifier;
if (a[this.sortBy] > b[this.sortBy]) return modifier;
return 0;
}
});
}
}
}
With that you just have to replace the props value you use to pass data to your VueTables