I Developer this Template and put its code below. When I type a phrase, Google offers me an address, and when I click on it, the map zooms automatically and displays the marker, as in the picture below:
In the image below, the fields (longitude and latitude) are automatically set only when the map is clicked, but I need the values of the fields (longitude) when I search for the address at the same time (as I explained above) and latitude) are updated and the user does not need to necessarily click on the map.
I just started working with vuejs. Please guide me by editing my code. Thanks
<template>
<div no-body class="mb-1 w-100">
<div class="body-bg shadow-none" id="test2">
<div
:class="{
'is-invalid':
$parent.form.errors.has('auto_complete_map') ||
$parent.form.errors.has('location'),
}"
class="d-flex flex-row justify-content-center border border-1 rounded border-dark"
>
<h4 block v-b-toggle.accordion-2 variant="info">Location</h4>
</div>
</div>
<b-collapse
class="navbar-fixed-top"
id="accordion-2"
accordion="my-accordion"
>
<div class="d-flex flex-row gap-2 justify-content-between mt-2">
<div class="col-sm-6">
<label for="latitude" class="form-label"
>{{ $t("latitude")
}}<span class="small text-danger m-1">*</span></label
>
<input
id="latitude"
:value="latitude"
class="form-control bg-transparent"
name="latitude"
placeholder=""
spellcheck="false"
data-ms-editor="true"
:disabled="true"
/>
<has-error :form="form" field="latitude" />
</div>
<div class="col-sm-6">
<label for="longitude" class="form-label">{{
$t("longitude")
}}</label>
<input
id="longitude"
class="form-control bg-transparent"
:value="longitude"
name="longitude"
placeholder=""
spellcheck="false"
data-ms-editor="true"
:disabled="true"
/>
<has-error :form="form" field="longitude" />
</div>
</div>
<label class="form-label mt-2"
>{{ $t("location") }}<span class="small text-danger">*</span></label
>
<GmapAutocomplete
id="auto_complete_map"
ref="location"
v-validate="'required|min:5'"
class="form-control"
name="location"
:value="location"
:placeholder="$t('enter a location')"
aria-required="true"
:disabled="$parent.canEditProperty()"
#place_changed="setPlace"
#keydown.enter.prevent
/>
<has-error :form="form" field="auto_complete_map" />
<GmapMap
ref="map"
class="my-3"
:center="center"
:zoom="zoom"
style="width: 100%; height: 300px"
#click="clickMap"
>
<GmapMarker
v-for="(m, index) in markers"
:key="index"
:position="m.position"
#click="center = m.position"
/>
</GmapMap>
<!-- <div class="d-flex flex-row justify-content-end m-2">
<b-button class="rounded-lg px-4 text-lg" block v-b-toggle.accordion-3 variant="info">next</b-button>
</div> -->
<div class="d-flex flex-row justify-content-end gap-3 m-2 mt-3">
<b-button
class="rounded-lg px-4 col-sm-2 text-white text-lg border border-dark"
block
v-b-toggle.accordion-1
variant="dark"
>previous</b-button
>
<b-button
class="rounded-lg px-6 col-sm-2 text-lg border border-dark"
block
v-b-toggle.accordion-3
variant="light"
>Next Step</b-button
>
</div>
</b-collapse>
</div>
</template>
<script>
import Autocomplete from "vue2-google-maps/dist/components/autocomplete";
export default {
name: "GoogleMap",
props: {
location: { type: String, default: "" },
latitude: { type: String, default: "" },
longitude: { type: String, default: "" },
},
data() {
return {
center: {
lat: 45.508,
lng: -73.587,
},
currentPlace: null,
markers: [],
places: [],
zoom: 12,
};
},
mounted() {
this.geolocate();
},
methods: {
setPlace(place) {
this.markers.splice(0, this.markers.length);
this.places.splice(0, this.places.length);
this.currentPlace = place;
this.updateLocation(place.formatted_address);
this.addMarker();
},
addMarker() {
if (this.currentPlace) {
const marker = {
lat: this.currentPlace.geometry.location.lat(),
lng: this.currentPlace.geometry.location.lng(),
};
this.markers.push({ position: marker });
this.places.push(this.currentPlace);
this.center = marker;
this.zoom = 17;
this.currentPlace = null;
}
},
clickMap(location) {
const marker = location.latLng;
// this.markers.clear()
this.markers.splice(0, this.markers.length);
this.places.splice(0, this.places.length);
this.markers.push({ position: marker });
// this.places.push(this.currentPlace)
// this.center = marker
// this.zoom = 17
this.currentPlace = null;
const geocoder = new google.maps.Geocoder();
geocoder
.geocode({ location: location.latLng })
.then((response) => {
if (response.results[0]) {
this.updateLocation(response.results[0].formatted_address);
this.getLoc(location.latLng);
} else {
window.alert("No results found");
}
})
.catch((e) => window.alert("Geocoder failed due to: " + e));
},
geolocate: function () {
const oldLocation = this.location;
if (oldLocation.length > 0) {
const _this = this;
const geocoder = new google.maps.Geocoder();
geocoder.geocode({ address: oldLocation }, function (results, status) {
if (status == google.maps.GeocoderStatus.OK) {
// const lat = results[0].geometry.location.lat()
// const lng = results[0].geometry.location.lng()
// const placeName = results[0].address_components[0].long_name
if (results.length > 0) {
_this.setPlace(results[0]);
}
}
});
} else {
navigator.geolocation.getCurrentPosition((position) => {
this.center = {
lat: position.coords.latitude,
lng: position.coords.longitude,
};
});
}
},
updateLocation: function (newLocation) {
this.location = newLocation;
this.$emit("eventname", newLocation);
},
getLoc: function (location) {
// this.location = this.markers
this.longitude = location.lng();
this.latitude = location.lat();
this.$emit("getlog", location);
},
},
};
</script>
the passed in variable place in setPlace(place) has properties .geometry.location.lag() and .geometry.location.lng(). These are your latitude and longitude values for the entered address. Use these to set the latitude and longitude variables used by your <input> elements
setPlace(place) {
this.markers.splice(0, this.markers.length);
this.places.splice(0, this.places.length);
this.currentPlace = place;
this.updateLocation(place.formatted_address);
this.addMarker();
this.latitude = place.geometry.location.lat();
this.longitude = place.geometry.location.lng();
},
I do also want to point out that you've created latitude and longitude as props, which aren't meant to be mutated. If you don't have a reason to have them as props put them as data variables instead
Related
I have 3 components like: Parent, First-child and Second-child. And I am iterating First-child in Parent component in array(it is cards), and I want to call Second-child in Parent component with First-child's props(props of one card).
My Parent component looks like this(how I am calling First-child):
``
<CardComponent
v-for="card of cards"
:key="card.urlsId"
:cardImages="card.images"
:cardTitle="card.title"
:cardDescription="card.description"
:mediaRef="card.urlsId"
:dbRef="card.dbId"
:deleteBtn="true"
:imagesWithSlider="true"
/>
And my First child is:
<template>
<div class="cards">
<v-card class="card-container">
<div class="delete-btn">
<v-btn
v-if="deleteBtn"
class="mx-2"
fab
dark
small
#click="$emit('onOpenDeleteModal')"
>
<v-icon dark> mdi-delete </v-icon>
</v-btn>
</div>
<ImageSlider
v-if="imagesWithSlider"
:imagesArray="cardImages"
:arrowBtns="false"
/>
<div class="text-container">
<h3 class="card-title">{{ cardTitle }}</h3>
<p class="card-description">{{ cardDescription }}</p>
</div>
</v-card>
</div>
</template>
<script>
export default {
props: {
cardImages: {
type: Array,
default: null,
},
cardTitle: {
type: String,
default: 'Title',
},
cardDescription: {
type: String,
default: 'Description',
},
deleteBtn: {
type: Boolean,
},
imagesWithSlider: {
type: Boolean,
},
mediaRef: {
type: String,
default: '',
},
dbRef: {
type: String,
default: '',
},
deleteModalOpen: {
type: Boolean,
},
},
emits: ['onOpenDeleteModal', 'onCloseDeleteModal'],
}
</script>
And my Second-child is:
<template>
<v-card class="modal" :loading="newCard.loading ? true : false">
<v-card class="modal-header">
<h3 v-if="addCardModal" class="header-title">New card</h3>
<h3 v-if="deleteModal" class="header-title">Delete</h3>
<v-icon aria-hidden="false" width="100%" #click="$emit('closeModal')"
>mdi-close</v-icon
>
</v-card>
<!-- Delete Modal -->
<div v-if="deleteModal" class="modal-delete">
<h3>Are you really want to delete this card ?</h3>
<div class="modal-delete-btns">
<v-btn #click="$emit('closeModal')">Cancel</v-btn>
<v-btn color="error" #click="$emit('onDeleteCard')">Delete</v-btn>
</div>
</div>
<!-- Add New Card Modal -->
<form
v-if="addCardModal"
class="modal-container"
#submit.prevent="postNewCardToDb"
>
<v-file-input
v-model="newCard.cardImages"
:clearable="false"
multiple
show-size
label="Upload card images"
#change="previewImage"
>
</v-file-input>
<v-file-input
v-model="newCard.cardVideo"
:clearable="false"
show-size
label="Upload video"
>
</v-file-input>
<div v-if="newCard.cardImageUrls.length !== 0" class="preview-image">
<ImageSlider :imagesArray="newCard.cardImageUrls" :arrowBtns="true" />
</div>
<v-text-field
v-model="newCard.cardTitle"
label="Enter card title"
></v-text-field>
<v-text-field
v-model="newCard.cardSnippet"
label="Enter card description"
></v-text-field>
<v-btn type="submit" :loading="newCard.loading ? true : false" block
>POST</v-btn
>
</form>
</v-card>
</template>
<script>
import { v4 as uuidV4, v1 as uuidV1 } from 'uuid'
export default {
/* eslint-disable no-console */
props: {
addCardModal: {
type: Boolean,
},
deleteModal: {
type: Boolean,
},
},
emits: ['closeModal', 'onDeleteCard'],
data() {
return {
newCard: {
loading: false,
cardImages: [],
cardVideo: null,
cardImageUrls: [],
cardTitle: '',
cardSnippet: '',
},
}
},
methods: {
previewImage($event) {
for (const image of event.target.files) {
this.newCard.cardImageUrls.push(URL.createObjectURL(image))
}
},
async getMediaUrlsFromStorage(newCardData) {
const cardMediaRef = uuidV1()
const cardImagesRef = await this.$fire.storage
.ref('/albums_cards/')
.child(cardMediaRef)
const videoRef = await cardImagesRef.child(uuidV4())
if (this.newCard.cardVideo) {
await videoRef.put(this.newCard.cardVideo)
const videoUrl = await videoRef.getDownloadURL()
newCardData.video = videoUrl
}
newCardData.urlsId = cardMediaRef
const promiseArr = this.newCard.cardImages.map(async (image) => {
const imageRef = cardImagesRef.child(uuidV4())
await imageRef.put(image)
const imageUrl = await imageRef.getDownloadURL()
newCardData.images.push(imageUrl)
})
await Promise.all(promiseArr)
},
async postNewCardToDb() {
this.newCard.loading = true
const newCardData = {
urlsId: '',
title: this.newCard.cardTitle,
description: this.newCard.cardSnippet,
video: '',
images: [],
}
await this.getMediaUrlsFromStorage(newCardData)
await this.$fire.database.ref('albums/cards').push(newCardData)
console.log(newCardData)
this.newCard.loading = false
this.newCard.cardTitle = null
this.newCard.cardSnippet = null
this.newCard.cardImages = []
this.newCard.cardImageUrls = []
this.newCard.cardVideo = null
},
},
}
</script>
First-child is a card component and I need to pass props of each card to Second-child without calling it. I cant call Second-child in First-child because of iteration.
I hope I expleined it well
I am using vue#2.6.11 and bootstrap-vue#2.11.0
I am having a component called index.vue to list all customers.
and a child component customerModal.vue to control the v-modal for Edit and Create Cusotmer form.
I am using v-table to list the customers details with button actions
index.vue //customer create btn
<!-- {{-- CREATE CUSTOMER --}} -->
<b-button #click="$root.$emit('bv::show::modal', 'customerModal' , $event.target)" variant="success" title="Create New Customer">
<i class="fas fa-plus-circle" id="action-icon" style="right:3%"></i>
</b-button>
<!-- Modal Customer -->
<customer-modal :customers="customers" modalType="create" #create-customer="customers.push($event)"></customer-modal>
index.vue //customer edit btn
<b-table
show-empty
:filter="filter"
#filtered="on_filtered"
id="customers-table"
:sort-by.sync="sort_by"
:sort-desc.sync="sort_desc"
:items="customers"
:fields="fields"
:per-page="per_page"
:current-page="current_page"
responsive
hover
head-variant="light"
class="text-center mt-4"
>
<template v-slot:cell(actions)="data">
<div class="btn-group" role="group">
<!-- {{-- EDIT CUSTOMER --}} -->
<b-button #click="$root.$emit('bv::show::modal', data.item.first_name.replace(/\s/g, '')+data.item.id, $event.target)" variant="primary" title="Edit Customer">
<i class="fas fa-edit"></i>
</b-button>
<!-- Customer Modal-->
<customer-modal modalType="edit" :selectedCustomer="data.item" :customers="customers" #update-customer="data.item = $event"></customer-modal>
</div>
</template>
index.vue //b-pagination
<b-pagination
class=" m-auto justify-content-center"
pills
:per-page="per_page"
:total-rows="rows"
v-model="current_page"
aria-controls="#customers-table"
>
</b-pagination>
customerModal.vue
<b-modal
:id="this.customer.first_name.replace(/\s/g, '')+this.customer.id || 'customerModal'"
title="Customer Modal"
#hidden="resetModal"
hide-footer
>
The problem
when I go to next page using b-pagination the btn edit does not show the corresponding v-modal. While in the first page all edit btns for each customer are working just fine.
However if I add the create btn to the list in the second page, it will work fine
The issue is that Vue is trying to reuse your modal on page 1, to save how much it has to re-render.
The root of your issue is that inside the modal component, you set this.customer to this.selectedCustomer in the create hook and no where else. Since the create doesn't run again on page 2, it wont update this.customer meaning the ID will still match the rows from page 1.
In the long term it might be a good idea for for you to rethink that logic.
But you should be able to solve the issue by adding :key="data.item.id" to the customer-modal, which forces Vue to re-render the modal when the id updates. So it can't try to reuse the modals across the pages.
<customer-modal modalType="edit" :selectedCustomer="data.item" :customers="customers" #update-customer="data.item = $event" :key="data.item.id"></customer-modal>
Working snippet (based on codepen from this Github Issue.
Vue.use(window.vuelidate.default);
const { required, email, numeric, minLength, maxLength } = window.validators;
let OMDModal = {
props: ["selectedCustomer", "modalType", "customers"],
template: '#customer-modal',
validations: {
customer: {
first_name: {
required,
minLen: minLength(3),
maxLen: maxLength(12)
},
last_name: {
required,
minLen: minLength(3),
maxLen: maxLength(12)
},
email: {
required,
email,
isUnique: function (val) {
if (val === "") return true;
else if (val === this.currentEmail) return true;
return axios.get(`api/validateEmail/${val}`).then((res) => {
return res.data.unique;
});
}
}
}
},
data() {
return {
currentEmail: "",
alert_sec: 0,
alert_duration: 5,
alert_message: "",
alert_color: "",
customer: {
id: "",
first_name: "",
last_name: "",
email: ""
},
customer_default: {
first_name: [],
last_name: [],
email: []
}
};
},
watch: {
"customer.first_name"(newVal, oldVal) {
if (this.modalType === "edit") {
this.customer_default.first_name.push(oldVal);
}
},
"customer.last_name"(newVal, oldVal) {
if (this.modalType === "edit") {
this.customer_default.last_name.push(oldVal);
}
},
"customer.email"(newVal, oldVal) {
if (this.modalType === "edit") {
this.customer_default.email.push(oldVal);
}
}
},
computed: {
ModalID() {
if (this.modalType === "create") {
return "customerModal";
} else {
return this.customer.first_name.replace(/\s/g, "") + this.customer.id;
}
},
now() {
const monthNames = [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec"
];
const today = new Date();
let day = today.getDate().toString();
let month = (today.getMonth() + 1).toString();
let year = today.getFullYear().toString();
return year + "-" + month + "-" + day;
},
first_nameValidate() {
if (this.$v.customer.first_name.$dirty) {
return !this.$v.customer.first_name.$anyError;
} else return null;
},
last_nameValidate() {
if (this.$v.customer.last_name.$dirty) {
return !this.$v.customer.last_name.$anyError;
} else return null;
},
emailValidate() {
if (this.$v.customer.email.$dirty) {
return !this.$v.customer.email.$anyError;
} else return null;
}
},
methods: {
setFirstName(e) {
this.customer.first_name = e;
this.$v.customer.first_name.$touch();
},
setLastName(e) {
this.customer.last_name = e;
this.$v.customer.last_name.$touch();
},
setEmail(e) {
this.customer.email = e;
this.$v.customer.email.$touch();
},
resetModal() {
this.$nextTick(() => {
this.$v.$reset();
});
if (this.modalType === "create") {
this.customer = {
id: "",
first_name: "",
last_name: "",
email: ""
};
}
if (this.alert_color != "success") {
if (this.customer_default.first_name[1] != undefined) {
this.customer.first_name = this.customer_default.first_name[1];
}
if (this.customer_default.last_name[1] != undefined) {
this.customer.last_name = this.customer_default.last_name[1];
}
if (this.customer_default.email[1] != undefined) {
this.customer.email = this.customer_default.email[1];
}
}
},
onSubmit(v, e) {
v.customer.$touch();
if (this.$v.customer.$anyError) {
this.alert_message = "Correct the inputs Please";
this.alert_color = "danger";
this.show_alert();
} else {
this.customer.first_name =
this.customer.first_name.charAt(0).toUpperCase() +
this.customer.first_name.slice(1);
// CREATE CUSTOMER
if (this.modalType === "create") {
this.customer.created_at = this.now;
let last_index = this.customers.length - 1;
let last_customer = this.customers[last_index];
this.customer.id = last_customer.id + 1;
fetch(`api/customers`, {
method: "POST",
body: JSON.stringify(this.customer),
headers: { "content-type": "application/json" }
})
.then((res) => res.json())
.then((res) => {
this.$nextTick(() => {
this.$v.$reset();
});
this.$bvModal.hide(
this.customer.first_name.replace(/\s/g, "") +
this.customer.id || "customerModal"
);
this.$emit("create-customer", this.customer);
this.customer = {
id: "",
first_name: "",
last_name: "",
email: ""
};
})
.catch((e) => console.log(e));
} else {
// UPDATE CUSTOMER
this.customer.updated_at = this.now;
fetch(`api/customers/${this.customer.id}`, {
method: "put",
body: JSON.stringify(this.customer),
headers: {
"content-type": "application/json"
}
})
.then((res) => res.json())
.then((data) => {
this.$nextTick(() => {
this.$v.$reset();
this.$bvModal.hide(
this.customer.first_name.replace(/\s/g, "") +
this.customer.id || "customerModal"
);
});
this.$emit("update-customer", this.selectedCustomer);
this.alert_color = "success";
this.alert_message = "Customer Updated Successfully";
})
.catch((error) => console.log(error));
}
}
},
show_alert() {
this.alert_sec = this.alert_duration;
},
countDown(alert_sec) {
this.alert_sec = alert_sec;
}
},
created() {
if (this.modalType === "edit") {
this.customer = this.selectedCustomer;
this.currentEmail = this.selectedCustomer.email;
}
}
};
window.onload = () => {
new Vue({
el: "#app",
data() {
return {
per_page: 3,
current_page: 1,
fields: [
{ key: "#", sortable: false },
{ key: "id", sortable: true },
{ key: "first_name", label: "Name", sortable: true, class: "w-25" },
{ key: "email", sortable: true },
{ key: "actions", sortable: false }
],
sort_by: "email",
sort_desc: false,
filter: null,
filter_on: [],
customers: [],
customer: {
id: "",
first_name: "",
last_name: "",
email: ""
}
};
},
computed: {
now() {
const monthNames = [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec"
];
const today = new Date();
let day = today.getDate().toString();
let month = (today.getMonth() + 1).toString();
let year = today.getFullYear().toString();
return year + "-" + month + "-" + day;
},
rows() {
return this.customers.length;
}
},
methods: {
fetchCustomers() {
fetch("https://reqres.in/api/users")
.then((res) => res.json())
.then((res) => {
this.customers = res.data;
})
.catch((err) => console.log(err));
},
on_filtered(filtered_items) {
this.rows;
},
openModal(id) {
this.$root.$emit('bv::show::modal', id)
}
},
created() {
this.fetchCustomers();
},
components: {
"omd-modal": OMDModal
}
});
};
<link href="https://unpkg.com/bootstrap#4.5.0/dist/css/bootstrap.min.css" rel="stylesheet" />
<link href="https://unpkg.com/bootstrap-vue#2.15.0/dist/bootstrap-vue.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.15.0/dist/bootstrap-vue.js"></script>
<div id="app">
<div class="card">
<div class="card-body">
<!-- Card Header (Title & Create btn) -->
<div class="row ">
<div class="col-sm-4">
<h4 class="card-title font-weight-bold">
User Management
</h4>
</div>
<div class="col-sm-4 offset-sm-4">
<div class="btn-toolbar float-right">
<!-- {{-- CREATE CUSTOMER --}} -->
<b-button #click="$root.$emit('bv::show::modal', 'customerModal', $event.target)" variant="success" title="Create New Customer">
CREATE
</b-button>
</div>
</div>
</div>
<!-- Modal Customer Create -->
<omd-modal :customers="customers" modal-type="create" #create-customer="customers.push($event)"></omd-modal>
<!-- Search Bar -->
<div class="row">
<div class="col-12">
<b-input-group class="w-50 m-auto">
<b-form-input placeholder="Search for Customers" v-model="filter" class="rounded-pill searchbar">
</b-form-input>
</b-input-group>
</div>
</div>
<!-- Customer List (Card Content) -->
<div class="row ">
<div class="col">
<b-table ref="table" :key="customer.id" show-empty :filter="filter" #filtered="on_filtered" id="customers-table" :sort-by.sync="sort_by" :sort-desc.sync="sort_desc" :items="customers" :fields="fields" :per-page="per_page" :current-page="current_page" responsive hover head-variant="light" class="text-center mt-4">
<template v-slot:cell(#)="data">
{{data.index+1}}
</template>
<template v-slot:cell(first_name)="data">
{{data.item.first_name}} {{data.item.last_name}}
</template>
<template v-slot:cell(actions)="data">
<div class="btn-group" role="group">
<!-- {{-- EDIT CUSTOMER --}} -->
<b-button #click="$root.$emit('bv::show::modal', data.item.first_name.replace(/\s/g, '')+data.item.id)" variant="primary" title="Edit Customer">
Edit
</b-button>
</div>
<!--Edit Customer Modal-->
<omd-modal modal-type="edit" :selected-customer="data.item" :customers="customers" #update-customer="data.item = $event" :key="data.item.id"></omd-modal>
</template>
</b-table>
</div>
</div>
<hr>
<!-- {{-- Card Footer --}} -->
<div class="row">
<div class="col-sm-4">
<small class="text-muted"> Total Customers {{rows}} </small>
</div>
<div class="col-sm-4">
<b-pagination class=" m-auto justify-content-center" pills :per-page="per_page" :total-rows="rows" v-model="current_page" aria-controls="#customers-table">
</b-pagination>
</div>
</div>
</div>
</div>
</div>
<template id="customer-modal">
<b-modal :id="customer.first_name.replace(/\s/g, '')+customer.id || 'customerModal'" title="Customer Modal" #hidden="resetModal" hide-footer>
<b-alert id="alert" style="width: 100%;" :show="alert_sec" dismissible :variant="alert_color" #dismissed="alert_sec=0" #dismiss-count-down="countDown">
{{alert_message}}
</b-alert>
<b-form #submit.prevent="onSubmit($v, $event)">
<!-- first_name -->
<b-form-group id="first-name-group" label="First Name" label-for="first-name">
<b-form-input id="first-name" type="text" :value="customer.first_name" :state="first_nameValidate" #change="setFirstName($event)" required></b-form-input>
<b-form-invalid-feedback :state="first_nameValidate">First Name is required and must be 3-5 character long.</b-form-invalid-feedback>
</b-form-group>
<!-- Last_name -->
<b-form-group id="last-name-group" label="Last Name" label-for="last-name">
<b-form-input id="last_name" :value="customer.last_name" :state="last_nameValidate" #change="setLastName($event)" type="text" required>
</b-form-input>
<b-form-invalid-feedback :state="last_nameValidate">Last Name is required and must be 3-5 character long.</b-form-invalid-feedback>
</b-form-group>
<!-- Email -->
<b-form-group id="email-group" label="Email" label-for="email">
<b-form-input id="email" :state="emailValidate" :value="customer.email" #change="setEmail($event)" type="email" required>
</b-form-input>
<b-form-invalid-feedback :state="emailValidate">Invalid Email.</b-form-invalid-feedback>
</b-form-group>
<hr style="margin-top: 2rem; margin-bottom: 1rem;">
<div class="row justify-content-center">
<b-button variant="success" class="mr-1" type="submit">Save Changes</b-button>
<b-button variant="danger" class="ml-1" #click="$bvModal.hide(customer.first_name.replace(/\s/g, '')+customer.id || 'customerModal')">Cancel</b-button>
</div>
</b-form>
</b-modal>
</template>
<script src="https://unpkg.com/vuelidate#0.7.5/dist/validators.min.js"></script>
<script src="https://unpkg.com/vuelidate#0.7.5/dist/vuelidate.min.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
I'm new in vuejs. I don't see how to use a "computed" value in a table of the ui-element librairy. Here is how I tried ..
<template>
<div class="row">
<div class="col-md-12">
<h4 class="title">Commandes en cours</h4>
</div>
<!--<div v-if="$can('manage-order')">You can manage order.</div>-->
<div class="col-12">
<card title="">
<div>
<div class="col-12 d-flex justify-content-center justify-content-sm-between flex-wrap">
<el-select
class="select-default mb-3"
style="width: 200px"
v-model="pagination.perPage"
placeholder="Per page">
<el-option
class="select-default"
v-for="item in pagination.perPageOptions"
:key="item"
:label="item"
:value="item">
</el-option>
</el-select>
<el-input type="search"
class="mb-3"
style="width: 200px"
placeholder="Search records"
v-model="searchQuery"
aria-controls="datatables"/>
</div>
<div class="col-sm-12">
<el-table stripe
style="width: 100%;"
:data="queriedData"
border>
<el-table-column v-for="column in tableColumns"
:key="column.label"
:min-width="column.minWidth"
:prop="column.prop"
:label="column.label">
</el-table-column>
<el-table-column
:min-width="120"
fixed="right"
label="Actions">
<template slot-scope="props">
<a v-tooltip.top-center="'Like'" class="btn-info btn-simple btn-link"
#click="handleLike(props.$index, props.row)">
<i class="fa fa-heart"></i></a>
<a v-tooltip.top-center="'Edit'" class="btn-warning btn-simple btn-link"
#click="handleEdit(props.$index, props.row)"><i
class="fa fa-edit"></i></a>
<a v-tooltip.top-center="'Delete'" class="btn-danger btn-simple btn-link"
#click="handleDelete(props.$index, props.row)"><i class="fa fa-times"></i></a>
</template>
</el-table-column>
</el-table>
</div>
</div>
<div slot="footer" class="col-12 d-flex justify-content-center justify-content-sm-between flex-wrap">
<div class="">
<p class="card-category">Showing {{from + 1}} to {{to}} of {{total}} entries</p>
</div>
<l-pagination class="pagination-no-border"
v-model="pagination.currentPage"
:per-page="pagination.perPage"
:total="pagination.total">
</l-pagination>
</div>
</card>
</div>
</div>
</template>
<script>
import {Table, TableColumn, Select, Option} from 'element-ui'
import LPagination from 'src/components/Pagination.vue'
import Fuse from 'fuse.js'
export default {
components: {
LPagination,
[Table.name]: Table,
[Select.name]: Select,
[Option.name]: Option,
[TableColumn.name]: TableColumn
},
computed: {
clientName(customer){
return customer.firstname + ' '+ customer.lastname
},
pagedData () {
return this.tableData.slice(this.from, this.to)
},
/***
* Searches through table data and returns a paginated array.
* Note that this should not be used for table with a lot of data as it might be slow!
* Do the search and the pagination on the server and display the data retrieved from server instead.
* #returns {computed.pagedData}
*/
queriedData () {
let result = this.tableData
if (this.searchQuery !== '') {
result = this.fuseSearch.search(this.searchQuery)
this.pagination.total = result.length
}
return result.slice(this.from, this.to)
},
to () {
let highBound = this.from + this.pagination.perPage
if (this.total < highBound) {
highBound = this.total
}
return highBound
},
from () {
return this.pagination.perPage * (this.pagination.currentPage - 1)
},
total () {
this.pagination.total = this.tableData.length
return this.tableData.length
}
},
data () {
return {
pagination: {
perPage: 5,
currentPage: 1,
perPageOptions: [5, 10, 25, 50],
total: 0
},
searchQuery: '',
propsToSearch: ['id_order'],
tableColumns: [
{
prop: 'id_order',
label: 'ID',
minWidth: 200
},
{
prop: "clientName(customer)",
label: 'Client',
minWidth: 200,
}
],
fuseSearch: null,
tableData:[]
}
},
methods: {
handleLike (index, row) {
alert(`Your want to like ${row.name}`)
},
handleEdit (index, row) {
alert(`Your want to edit ${row.name}`)
},
handleDelete (index, row) {
let indexToDelete = this.tableData.findIndex((tableRow) => tableRow.id === row.id)
if (indexToDelete >= 0) {
this.tableData.splice(indexToDelete, 1)
}
}
},
mounted () {
this.fuseSearch = new Fuse(this.tableData, {keys: ['id_order']})
},
created (){
this.$store.dispatch('ps_orders/get_ps_orders').then(
this.tableData = this.$store.getters["ps_orders/orders"])
}
}
</script>
<style>
</style>
My object is like (for a row)
{
"id_order": 4641,
"customer": {
"id_customer": 9008,
"firstname": "Pierre",
"lastname": "dupont"
}
}
In the column "Client" I would like to have "customer.firstname + " " + customer.lastname ... but my computed "method" is not working (I guess it is completly wrong)
Thanks for your help
Here the answer : you can't declare a computed with a parameter, here is how to solve
<el-table-column
label="Client" >
<template slot-scope="scope">
{{ clientName(scope.row.customer) }}
</template>
</el-table-column>
AND
computed: {
clientName(){
return (customer) => customer.firstname + ' '+ customer.lastname
},
I have three components. All of them bind a value.
Below is the blade file with the three components
Blade File:
<div id="app">
<div class="row">
<div class="col-4">
<crud-component :type="'tower'"></crud-component>
</div>
<div class="col-4">
<crud-component :type="'country'"></crud-component>
</div>
<div class="col-4">
<crud-component :type="'department'"></crud-component>
</div>
</div>
</div>
All of these components are dynamic, they only rely on that binded value called :type
The Vue file:
<template>
<div>
<h3>{{capitalize(type)}} <button class="btn btn-primary btn-sm" #click="showAddModal()">Add</button></h3>
<datatable :columns="columns" :sortKey="sortKey" :sortOrders="sortOrders">
<tbody v-if="loading">
<tr>
<td colspan="11"><div class="lds-dual-ring mx-auto"></div></td>
</tr>
</tbody>
<tbody v-else>
<tr v-for="data in retData" :key="data.id">
<td> {{data.id}}</td>
<td> {{data.name}}</td>
<td>
<button class="btn btn-warning btn-sm" #click="showEditModal(data)"> Edit </button>
<button class="btn btn-danger btn-sm" #click="showDeleteModal(data)"> Delete </button>
</td>
</tr>
</tbody>
</datatable>
<!-- MODALS -->
<modal :name="type + '-add-modal'" height="auto">
<div class="row p-3">
<div class="col-12">
<h3>Add {{capitalize(type)}}</h3>
<hr>
<form>
<div class="form-group">
<label for="add-item">{{capitalize(type)}} Name</label>
<input v-validate="'required'" type="text" name="item-name" class="form-control" id="add-item-name" required>
<small class="form-text text-danger">{{errors.first('item-name')}}</small>
</div>
<button type="button" class="float-right btn btn-sm btn-primary" #click="store">Submit</button>
</form>
</div>
</div>
</modal>
<modal :name="type + '-edit-modal'" height="auto">
<div class="row p-3">
<div class="col-12">
<h3>Edit {{capitalize(type)}}</h3>
<hr>
<form>
<div class="form-group">
<label for="edit-item">{{capitalize(type)}} Name</label>
<input v-validate="'required'" name="item-name" type="text" class="form-control" id="edit-item-name" v-model="currentItem.name" required>
<small class="form-text text-danger">{{errors.first('item-name')}}</small>
</div>
<button type="button" class="float-right btn btn-sm btn-primary" #click="store">Submit</button>
</form>
</div>
</div>
</modal>
<modal :name="type + '-delete-modal'" height="auto">
<div class="row p-3">
<div class="col-12">
<h3>Delete {{capitalize(type)}}</h3>
<hr>
<p class="lead">Are you sure you want to delete <strong>{{currentItem.name}}</strong>?</p>
<button type="button" class="float-right btn btn-sm btn-primary" #click="destroy">Submit</button>
</div>
</div>
</modal>
</div>
</template>
<script>
import Datatable from './utilities/DatatableComponent.vue';
import Pagination from './utilities/PaginationComponent.vue';
export default {
components: {
'datatable': Datatable,
'pagination': Pagination,
},
props: ['type'],
mounted() {
this.get(this.type);
},
data() {
let sortOrders = {};
let columns = [
{label: 'ID', name:'id', isSortable: true},
{label: 'NAME', name:'name', isSortable: true},
{label: 'ACTIONS', name:'actions', isSortable: false}
];
columns.forEach(column => {
sortOrders[column.name] = -1;
});
return {
loading: true,
currentItem: '',
isUpdate: false,
sortKey: 'id',
columns: columns,
sortOrders: sortOrders,
tableData: {
draw: 0,
length: 10,
search: '',
column: 'id',
dir: 'desc',
},
pagination: {
lastPage: '',
currentPage: '',
total: '',
lastPageUrl: '',
prevPageUrl: '',
nextPageUrl: '',
from: '',
to: '',
},
retData: []
};
},
methods: {
showEditModal(item) {
this.currentItem = item;
this.$modal.show(this.type + '-edit-modal');
this.isUpdate = true;
},
showAddModal() {
this.$modal.show(this.type + '-add-modal');
},
store() {
this.$validator.validate().then(valid => {
if(!valid) {
return;
} else {
if(this.isUpdate == true) {
axios.post('/api/crud/store', {
type: this.type,
item: this.currentItem,
isUpdate: this.isUpdate
}).then(response => {
this.get(this.type);
this.$modal.hide(this.type + '-edit-modal');
this.isUpdate = false;
this.currentItem = '';
});
} else {
axios.post('/api/crud/store', {
type: this.type,
name: $('#add-item-name').val(),
isUpdate: this.isUpdate
}).then(response => {
this.get(this.type);
this.$modal.hide(this.type + '-add-modal');
});
}
}
});
},
showDeleteModal(item) {
this.currentItem = item;
this.$modal.show(this.type + '-delete-modal');
},
destroy() {
axios.delete('/api/crud/delete', {
data: {
type: this.type,
item: this.currentItem
}
}).then(response => {
this.$modal.hide(this.type + '-delete-modal')
this.currentItem = '';
this.get(this.type)
});
},
capitalize(s) {
if (typeof s !== 'string') return ''
return s.charAt(0).toUpperCase() + s.slice(1)
},
get(type) {
axios.interceptors.request.use(config => {
NProgress.start();
this.loading = true;
return config;
});
axios.interceptors.response.use(response => {
NProgress.done();
this.loading = false;
return response;
});
this.tableData.draw++;
axios.post('/api/crud', {
type: type,
tableData: this.tableData
}).then(response => {
let data = response.data;
if(this.tableData.draw == data.draw) {
this.retData = data.data.data
}
console.log('Done');
});
},
}
}
</script>
<style>
.lds-dual-ring {
display: block;
width: 64px;
height: 64px;
}
.lds-dual-ring:after {
content: " ";
display: block;
width: 46px;
height: 46px;
margin: 5px;
border-radius: 50%;
border: 5px solid #000;
border-color: #000 transparent #000 transparent;
animation: lds-dual-ring 1.2s linear infinite;
}
#keyframes lds-dual-ring {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
The problem is, when I'm trying to update, fetch, or do anything with one component.
It runs all of the API calls to fetch their specific data.
I just want to have that one table load when it's fetching data.
As mentioned in my comment above, you are adding interceptors to your Axios instance every time any of your components calls its get method.
The interceptor code runs on every request / response made through Axios, no matter where it comes from. This is why all your components appear to be loading when a request is made.
I suggest you remove the interceptors and change your code to
get(type) {
NProgress.start()
this.loading = true
this.tableData.draw++
axios.post('/api/crud', {
type: type,
tableData: this.tableData
}).then(response => {
NProgress.done()
this.loading = false
let data = response.data;
if(this.tableData.draw == data.draw) {
this.retData = data.data.data
}
})
}
Data needs to be a function.
data () {
return {
...
}
}
You can't simply have a bunch of variables and also a function like you have. You need to refactor your component.
I have a field component that is utilized by both edit or create component. in field component i used Vue-multiselect 2.1.4 plugin to show dropdown with multi-select options here is my code
<template>
<div class="col-md-12">
<div class="alert alert-danger alert-dismissible" v-if="errors.length && displayErrors">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
<h4><i class="icon fa fa-ban"></i> Please correct the following error(s):</h4>
<ul>
<li v-for="error in errors">{{ error }}</li>
</ul>
</div>
<div class="form-group">
<label>Title<span class='red'>*</span></label>
<input type="text" v-model="fields.title" class="form-control">
</div>
<div class="form-group">
<label>Description<span class='red'>*</span></label>
<input type="text" v-model="fields.description" class="form-control">
</div>
<div class="form-group">
<label>Categories<span class='red'>*</span></label>
<multiselect
v-model="fields.category"
:options="categories"
:value="prevGameCategory"
:multiple="true"
:close-on-select="false"
:clear-on-select="false"
:preserve-search="true"
placeholder="Pick some"
label="name"
track-by="id">
</multiselect>
</div>
<div class="form-group">
<label>Game Grade Levels<span class='red'>*</span></label>
<multiselect
v-model="fields.level"
:options="gameLevel"
:value="prevGameLevel"
:multiple="true"
:close-on-select="false"
:clear-on-select="false"
:preserve-search="true"
placeholder="Pick some"
label="name"
track-by="id">
</multiselect>
</div>
</div>
And here is my script code
<script type="text/javascript">
import router from '../../router';
import Multiselect from 'vue-multiselect'
import ClassicEditor from '#ckeditor/ckeditor5-build-classic'
import VueCkeditor from 'vue-ckeditor5'
export default {
props: [
'categories',
'gameLevel'
],
mounted() {
if (this.$route.params.id) {
this.isEdit = true
this.getGameById(this.$route.params.id)
}
},
data () {
return {
data: {},
prevGameLevel: [],
prevGameCategory: [],
baseUrl: window.BreakOut.baseUrl,
isEdit: false,
errors: [],
displayErrors: false,
image: '',
fields: {
title: null,
description: null,
category: [],
},
editors: {
classic: ClassicEditor
}
}
},
methods: {
async getGameById(game_id) {
let urlArr = _.split(window.BreakOut.routes.admin_game_edit, '/', 3)
let end_point = _.join(urlArr, '/')+'/'+game_id
let url = this.baseUrl+'/'+end_point
await axios.get(url).then((response) => {
this.data = response.data
this.fields.title = this.data.title
this.fields.description = this.data.description
if (_.isArray(this.data.game_category)) {
if (this.data.game_category.length > 0) {
_.forEach(this.data.game_category, (value, index) => {
this.prevGameCategory.push(_.pick(value.category, ['id', 'name']))
})
}
}
if (_.isArray(this.data.game_grade_level)) {
if (this.data.game_grade_level.length > 0) {
_.forEach(this.data.game_grade_level, (value, index) => {
this.prevGameLevel.push(_.pick(value.grade_level, ['id', 'name']))
})
}
}
// here i have get previous selected objects
console.log(this.prevGameLevel)
console.log(this.prevGameCategory)
}).catch((error) => {
this.$awn.alert(error)
})
},
}
}
In my code what am missing i almost follow plugin doc but the selected items were not displayed
You should not use both v-model and :value simultaneously. You can do:
<multiselect
v-model="fields.category"
:options="categories"
:multiple="true"
:close-on-select="false"
:clear-on-select="false"
:preserve-search="true"
placeholder="Pick some"
label="name"
track-by="id">
</multiselect>
and set this.fields value at the end of getting data function:
await axios.get(url).then((response) => {
this.data = response.data
this.fields.title = this.data.title
this.fields.description = this.data.description
if (_.isArray(this.data.game_category)) {
if (this.data.game_category.length > 0) {
_.forEach(this.data.game_category, (value, index) => {
this.prevGameCategory.push(_.pick(value.category, ['id', 'name']))
})
}
}
if (_.isArray(this.data.game_grade_level)) {
if (this.data.game_grade_level.length > 0) {
_.forEach(this.data.game_grade_level, (value, index) => {
this.prevGameLevel.push(_.pick(value.grade_level, ['id', 'name']))
})
}
}
// here i have get previous selected objects
console.log(this.prevGameLevel)
console.log(this.prevGameCategory)
this.fields = {
...this.fields,
category: this.prevGameCategory,
level: this.prevGameLevel
}
}).catch((error) => {
this.$awn.alert(error)
})