How to integrate leaflet-search plugin to my vue2-leaflet project? - javascript

I’m new to Vue and I’m developing a map application with vue2-leaflet. I would like to add a search box to my application to locate markers on my map, I found this leaflet plugin leaflet-search which is just the functionality I’m looking for, but I don’t know how to integrate it into my vue cli project.
After installing the plugin, how do I import it into my vue component? and, where in my <script> should I add the code?
This is what the component where I want to add the search box looks like:
<template>
<body>
<l-map class="map" ref="map" :min-zoom="minZoom" :crs="crs">
<l-tile-layer :url="url"></l-tile-layer>
<l-grid-layer class="grid" :tile-component="tileComponent"></l-grid-layer>
<l-marker v-for="(newCoords, i) in InvertedCoords" :key="i" :lat-lng="newCoords">
<div v-if="stars[i].status === 'ALLY'">
<l-icon ></l-icon>
</div>
<div v-else>
<l-icon></l-icon>
</div>
<l-popup class="popup">
<em class="popup-bold">Name: </em>{{ stars[i].name }}<br />
<em class="popup-bold">Longitud: </em>{{ stars[i].lng }}<br />
<em class="popup-bold">Latitud: </em>{{ stars[i].lat }}<br />
</l-popup>
</l-marker>
</l-map>
</body>
</template>
<script>
import L from "leaflet";
import { CRS } from "leaflet";
import GridTemplate from './GridTemplate.vue';
import { eventBus } from '../main.js'
import {
LMap,
LTileLayer,
LMarker,
LPopup,
LPolyline,
LIcon,
} from "vue2-leaflet";
export default {
name: "Map",
components: {
LMap,
LTileLayer,
LMarker,
LImageOverlay,
LPopup,
LIcon,
},
props: {
msg: {
type: String
}
},
data() {
return {
url: "",
bounds: [ [-2600, -2700], [1000, 3000] ],
minZoom: 0,
crs: L.CRS.Simple,
stars: [],
messageList: [],
tileComponent: GridTemplate
};
},
computed: {
InvertedCoords() {
var newArraw = [];
for (let i = 0; i < this.stars.length; i++) {
newArraw[i] = {
id: i + 2,
lat: this.stars[i].lat * -1,
lng: this.stars[i].lng * -1
};
}
return newArraw;
console.log(newArraw);
}
},
watch: {
msg: function() {
this.messageList.push(this.msg);
}
},
mounted() {
this.$refs.map.mapObject.setView([552, 40], 1);
this.$http.get("url")
.then(response => {
return response.json();
})
.then(data => {
const resultArray = [];
for (let key in data) {
resultArray.push(data[key]);
}
this.stars = resultArray;
});
methods: {
inverted() {
for (let i = 0; i < this.newArraw.length; i++) {
console.log(this.newArraw[i]);
return this.newArraw[i];
}
},
updateStars(text) {
this.$http.get("url")
.then(response => {
return response.json();
})
.then(data => {
const resultArray = [];
for (let key in data) {
resultArray.push(data[key]);
}
this.stars = resultArray;
});
},
StarsData() {
eventBus.$emit('i-got-clicked', this.stars)
},
}
};
</script>
<style scoped>
</style>

I don't know your Plugin but I have used gesearch.
Here is what I did to register the leaflet plugin.
import { OpenStreetMapProvider } from "leaflet-geosearch";
import "leaflet-geosearch/assets/css/leaflet.css";
import { GeoSearchControl } from "leaflet-geosearch";
Then in mounted() I can register it to leaflet. You can look at the docs of how to register controls.
mounted() {
const map = this.$refs.map.mapObject;
const searchControl = new GeoSearchControl({
provider,
// ... some more options
});
map.addControl(searchControl);
}
Perhaps this helps you.

Related

How to fix parse error during Vue app deployment?

I try to deploy Vue app for uploading on GitHub Pages, but got such parse error:
95: </script>
^
96: <template>
97: <Navbar
error during build:
Error: Parse error #:95:10
at parse$b (file:///C:/Users/User/vContact/vContact/node_modules/vite/dist/node/chunks/dep-a713b95d.js:33668:355)
at Object.transform (file:///C:/Users/User/vContact/vContact/node_modules/vite/dist/node/chunks/dep-a713b95d.js:42856:27)
My code:
<script >
import { RouterLink, RouterView } from "vue-router";
import Navbar from "./components/Navbar.vue";
import Contacts from "./components/Contacts.vue";
import Modal from "./components/Modal.vue";
import AddButton from "./components/AddButton.vue";
export default{
components: {
Navbar,
Contacts,
Modal,
AddButton
},
data(){
return {
currentId: 1,
modalOpen: false,
contacts: [],
edit: false,
editContact: {},
search: ''
}
},
created(){
this.getContacts()
},
computed: {
// filterContacts(){
// return this.search ? this.contacts.filter(contact =>
// contact.fullName.toLowerCase().includes(this.search.toLowerCase())) :
this.contacts;
// },
filterContacts(){
return this.search ?
this.contacts.filter(contact => {
for(let key in contact){
if(String(contact[key]).toLowerCase().includes(this.search.toLowerCase())){
return contact;
}
}
})
: this.contacts;
},
// filterContactsCategory(){
// return this.search ? this.contacts.filter(contact =>
contact.category.toLowerCase().includes(this.search.toLowerCase())) : this.contacts;
// }
},
methods: {
openModal(){
this.modalOpen = true
},
closeModal(){
this.modalOpen = false
this.edit = false
},
addContact(item){
this.contacts.push(item)
this.modalOpen = false
},
deleteContact(id){
let index = this.contacts.findIndex(contact => contact.id == id)
this.contacts.splice(index, 1)
},
changeContact(id){
this.edit = this.modalOpen = true
let pickedContact = this.contacts.find(contact => contact.id == id)
this.editContact = pickedContact
},
editedContact(contactEdited){
this.contacts.forEach(contact => {
if(contact.id == contactEdited.id) {
contact.fullName = contactEdited.fullName
contact.phoneNumber = contactEdited.phoneNumber
contact.email = contactEdited.email
contact.category = contactEdited.category
}
})
},
getContacts(){
const localContacts = localStorage.contacts
if(localContacts){
this.contacts = JSON.parse(localContacts)
}
}
},
watch: {
contacts: {
handler(newContacts){
localStorage.contacts = JSON.stringify(this.contacts)
},
deep: true
}
}
}
</script>
<template>
<Navbar
#searchValue="search = $event"
/>
<Contacts
:contacts="filterContacts"
#delContact="deleteContact"
#changeContact="changeContact"
:search="search"
/>
<Modal
:edit="edit"
:editContact="editContact"
#addContact="addContact"
:currentId="currentId"
v-show="modalOpen"
#closeModal="closeModal"
#editedContact="editedContact"
/>
<AddButton
#openModal="openModal"
/>
<RouterView />
</template>
did you put
import vue from '#vitejs/plugin-vue';
in your vite.config.js file, after (of course) installing it via npm?
Expanding on Stephan Franks answer, I had the exact same error as OP, and putting this in vite.config.ts did the trick for me.
import { defineConfig } from 'vite'
import vue from '#vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()], // <-- This is where the magic happens
})
For more information, please have a look at the documentation for #vitejs/plugin-vue.

how do i use vuex mutation payload object properly

I have 2 inputs in which i provide value to search whether its name of the company, position (1st input) or location (2nd input). It works with one argument provided into foundJobs mutation and then into action. But when payload has an object everything is undefined and array is empty. What am i doing wrong?
component:
<script setup>
import IconSearch from "../Icons/icon-search.vue";
import IconLocation from "../Icons/icon-location.vue";
import { ref } from "vue";
import { useStore } from "vuex";
const store = useStore();
const nameFilter = ref("");
const locationFilter = ref("");
</script>
<template>
<div class="header-filter">
<div class="header-filter__search">
<IconSearch />
<input
type="text"
placeholder="Filter by title, companies, expertise…"
ref="nameFilter"
/>
</div>
<div class="header-filter__location">
<IconLocation />
<input
type="text"
placeholder="Filter by location…"
ref="locationFilter"
/>
</div>
<div class="header-filter__fulltime">
<input type="checkbox" />
<p>Full Time Only</p>
<button
type="button"
#click="
store.dispatch('foundJobs', {
nameFilter: nameFilter.value,
locationFilter: locationFilter.value,
})
"
>
Search
</button>
</div>
</div>
</template>
vuex: (not working)
import { createStore } from "vuex";
const store = createStore({
state() {
return {
jobs: [],
filteredJobs: [],
};
},
mutations: {
setJobs(state, jobs) {
state.jobs = jobs;
},
foundJobs(state, { nameInputValue, locationInputValue }) {
let copiedJobsArr = [...state.jobs];
if (nameInputValue !== "") {
copiedJobsArr = copiedJobsArr.filter(
(job) =>
job.company === nameInputValue || job.position === nameInputValue
);
}
if (locationInputValue !== "") {
copiedJobsArr = copiedJobsArr.filter(
(job) => job.location === locationInputValue
);
}
console.log(locationInputValue); // undefined
state.filteredJobs = copiedJobsArr;
console.log(state.filteredJobs); //empty array
},
},
actions: {
foundJobs(context, { nameInputValue, locationInputValue }) {
context.commit("foundJobs", { nameInputValue, locationInputValue });
},
loadJobs(context) {
return fetch("./data.json")
.then((response) => {
return response.json();
})
.then((data) => {
const transformedData = data.map((job) => {
return {
id: job.id,
company: job.company,
logo: job.logo,
logoBackground: job.logoBackground,
position: job.position,
postedAt: job.postedAt,
contract: job.contract,
location: job.location,
website: job.website,
apply: job.apply,
description: job.description,
reqContent: job.requirements.content,
reqItems: job.requirements.items,
roleContent: job.role.content,
roleItems: job.role.items,
};
});
context.commit("setJobs", transformedData);
});
},
},
getters: {
jobs(state) {
return state.jobs;
},
filteredJobOffers(state) {
return state.filteredJobs;
},
},
});
export default store;
vuex (working) - here i also provide one argument into action assigned to a button (in a component file)
import { createStore } from "vuex";
const store = createStore({
state() {
return {
jobs: [],
filteredJobs: [],
};
},
mutations: {
setJobs(state, jobs) {
state.jobs = jobs;
},
foundJobs(state, nameInputValue) {
let copiedJobsArr = [...state.jobs];
if (nameInputValue !== "") {
copiedJobsArr = copiedJobsArr.filter(
(job) =>
job.company === nameInputValue || job.position === nameInputValue
);
}
console.log(nameInputValue);
state.filteredJobs = copiedJobsArr;
console.log(state.filteredJobs);
},
},
actions: {
foundJobs(context, nameInputValue) {
context.commit("foundJobs", nameInputValue);
},
loadJobs(context) {
return fetch("./data.json")
.then((response) => {
return response.json();
})
.then((data) => {
const transformedData = data.map((job) => {
return {
id: job.id,
company: job.company,
logo: job.logo,
logoBackground: job.logoBackground,
position: job.position,
postedAt: job.postedAt,
contract: job.contract,
location: job.location,
website: job.website,
apply: job.apply,
description: job.description,
reqContent: job.requirements.content,
reqItems: job.requirements.items,
roleContent: job.role.content,
roleItems: job.role.items,
};
});
context.commit("setJobs", transformedData);
});
},
},
getters: {
jobs(state) {
return state.jobs;
},
filteredJobOffers(state) {
return state.filteredJobs;
},
},
});
export default store;
store.dispatch('foundJobs', {
nameFilter: nameFilter.value,
locationFilter: locationFilter.value,
})
You are sending data like this and trying to get on the wrong way
foundJobs(state, { nameInputValue, locationInputValue })
you can receive data this way:
foundJobs(state, { nameFilter, locationFilter})

The right way to draw a Map when data is ready

I need to render a map using Mapbox only when data is ready.
I have the following code in my Vuex store:
/store/index.js
import Vue from "vue";
import Vuex from "vuex";
import _ from "lodash";
import { backendCaller } from "src/core/speakers/backend";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
// Activity
activity: [],
geoIps: [],
},
mutations: {
// Activity
setActivity: (state, value) => {
state.activity = value;
},
setGeoIp: (state, value) => {
state.geoIps.push(value);
},
},
actions: {
// Activity
async FETCH_ACTIVITY({ commit, state }, force = false) {
if (!state.activity.length || force) {
await backendCaller.get("activity").then((response) => {
commit("setActivity", response.data.data);
});
}
},
async FETCH_GEO_IPS({ commit, getters }) {
const geoIpsPromises = getters.activityIps.map(async (activityIp) => {
return await Vue.prototype.$axios
.get(
`http://api.ipstack.com/${activityIp}?access_key=${process.env.IP_STACK_API_KEY}`
)
.then((response) => {
return response.data;
});
});
geoIpsPromises.map((geoIp) => {
return geoIp.then((result) => {
commit("setGeoIp", result);
});
});
},
},
getters: {
activityIps: (state) => {
return _.uniq(state.activity.map((activityRow) => activityRow.ip));
},
},
strict: process.env.DEV,
});
In my App.vue I fetch all APIs requests using an async created method.
App.vue:
<template>
<div id="app">
<router-view />
</div>
</template>
<script>
export default {
name: "App",
async created() {
await this.$store.dispatch("FETCH_ACTIVITY");
await this.$store.dispatch("FETCH_GEO_IPS");
},
};
</script>
In my Dashboard component I have a conditional rendering to draw the maps component only when geoIps.length > 0
Dashboard.vue:
<template>
<div v-if="geoIps.length > 0">
<maps-geo-ips-card />
</div>
</template>
<script>
import mapsGeoIpsCard from "components/cards/mapsGeoIpsCard";
export default {
name: "dashboard",
components: {
mapsGeoIpsCard,
},
computed: {
activity() {
return this.$store.state.activity;
},
activityIps() {
return this.$store.getters.activityIps;
},
geoIps() {
return this.$store.state.geoIps;
},
};
</script>
Then I load the Maps component.
<template>
<q-card class="bg-primary APP__card APP__card-highlight">
<q-card-section class="no-padding no-margin">
<div id="map"></div>
</q-card-section>
</q-card>
</template>
<script>
import "mapbox-gl/dist/mapbox-gl.css";
import mapboxgl from "mapbox-gl/dist/mapbox-gl";
export default {
name: "maps-geo-ips-card",
computed: {
geoIps() {
return this.$store.state.geoIps;
},
},
created() {
mapboxgl.accessToken = process.env.MAPBOX_API_KEY;
},
mounted() {
const mapbox = new mapboxgl.Map({
container: "map",
center: [0, 15],
zoom: 1,
});
this.geoIps.map((geoIp) =>
new mapboxgl.Marker()
.setLngLat([geoIp.longitude, geoIp.latitude])
.addTo(mapbox)
);
},
};
</script>
<style>
#map {
height: 500px;
width: 100%;
border-radius: 25px;
overflow: hidden;
}
</style>
The problem is that when the function resolves the first IP address, the map is drawn showing only one address and not all the others like this:
What is the best way to only draw the map when my FETCH_GEO_IPS function has finished?
Thanks in advance
I think the answer lies in this bit of code:
geoIpsPromises.map((geoIp) => {
return geoIp.then((result) => {
commit("setGeoIp", result);
});
});
Your map function loops through every element of the array and commits each IP one by one. So when the first one is committed, your v-if="geoIps.length > 0" is true.
A workaround would be to set a flag only when the IPs are set.
This is a proposed solution:
import Vue from "vue";
import Vuex from "vuex";
import _ from "lodash";
import { backendCaller } from "src/core/speakers/backend";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
// Activity
activity: [],
geoIps: [],
isReady: false
},
mutations: {
// Activity
setActivity: (state, value) => {
state.activity = value;
},
setGeoIp: (state, value) => {
state.geoIps.push(value);
},
setIsReady: (state, value) => {
state.isReady = value;
}
},
actions: {
// Activity
async FETCH_ACTIVITY({ commit, state }, force = false) {
if (!state.activity.length || force) {
await backendCaller.get("activity").then((response) => {
commit("setActivity", response.data.data);
});
}
},
async FETCH_GEO_IPS({ commit, getters }) {
let tofetch = getters.activityIps.length; // get the number of fetch to do
const geoIpsPromises = getters.activityIps.map(async (activityIp) => {
return await Vue.prototype.$axios
.get(
`http://api.ipstack.com/${activityIp}?access_key=${process.env.IP_STACK_API_KEY}`
)
.then((response) => {
return response.data;
});
});
geoIpsPromises.map((geoIp) => {
return geoIp.then((result) => {
commit("setGeoIp", result);
toFetch -= 1; // decrement after each commit
if (toFetch === 0) {
commit("setIsReady", true); // all commits are done
}
});
});
},
},
getters: {
activityIps: (state) => {
return _.uniq(state.activity.map((activityRow) => activityRow.ip));
},
},
strict: process.env.DEV,
});
And in your view:
<template>
<div v-if="isReady">
<maps-geo-ips-card />
</div>
</template>
<script>
import mapsGeoIpsCard from "components/cards/mapsGeoIpsCard";
export default {
name: "dashboard",
components: {
mapsGeoIpsCard,
},
computed: {
activity() {
return this.$store.state.activity;
},
activityIps() {
return this.$store.getters.activityIps;
},
isReady() {
return this.$store.state.isReady;
},
};
</script>

Use data from axios get as prop for another Vue file

I have some code which resolves the ip address of a computer to a lat / long, like so
ip_resolve.vue
<script>
const axios = require('axios').default;
const ipRegex = /ip=(\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3})$/gmi
export default {
name: 'ip',
props: {
ip: String,
lat: String,
long: String
},
mounted () {
axios.get('https://www.cloudflare.com/cdn-cgi/trace')
.then(response => (
this.ip = ipRegex.exec(response.data)[1]
)
.then(
axios.get('https://cors-anywhere.herokuapp.com/http://api.ipstack.com/'+this.ip+'?access_key=<key>')
.then( response => (
this.lat = response.data.latitude,
this.long = response.data.longitude
)
)
)
)
}
}
</script>
I want to "return" the lat / long to App.Vue, where it will pass Lat/Long as props to "Weather.js"
App.Vue
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<ip></ip>
<Weather lat={{lat}} long={{long}} />
</div>
</template>
<script>
import Weather from './components/Weather.vue'
import ip from './components/ip_resolve.vue'
export default {
name: 'App',
components: {
Weather,
ip
}
}
</script>
I've read a little bit about $emit, but I am unfamiliar with the design paradigm and don't know how to implement it. Can someone offer me some best practices here?
Thanks,
In your ip_resolve.vue use this to emit the event after getting the data from axios:
this.$emit('response', {
lat: response.data.latitude,
long: response.data.longitude
}
And then in your App.vue:
<ip #response="onResponse"></ip>
<Weather :lat="lat" :long="long" />
and inside <script> in App.vue:
export default {
name: 'App',
data() {
return {
lat: 0,
long: 0
}
},
components: {
Weather,
ip
},
methods: {
onResponse($event) {
this.lat = $event.lat
this.long = $event.long
}
}
}
You're nearly there.
This is how you can emit the data on your ip component:
<script>
const axios = require('axios').default;
const ipRegex = /ip=(\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3})$/gmi
export default {
name: 'ip',
props : {
ip: String
},
mounted () {
axios.get('https://www.cloudflare.com/cdn-cgi/trace')
.then(function(response) {
this.ip = ipRegex.exec(response.data)[1]
return axios.get('https://cors-anywhere.herokuapp.com/http://api.ipstack.com/'+this.ip+'?access_key=<key>')
})
.then(function(response) {
this.$emit('change', {
lat : response.data.latitude,
long : response.data.longitude
})
})
}
}
</script>
And then how you receive and push it to your weather component:
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<ip #update="updateCoords"></ip>
<weather :lat="lat" :long="long" />
</div>
</template>
<script>
import Weather from './components/Weather.vue'
import ip from './components/ip_resolve.vue'
export default {
name: 'App',
components: {
Weather,
ip
},
data : () => ({
lat : null,
long : null,
}),
methods : {
updateCoords (coords) {
this.lat = coords.lat
this.long = coords.long
}
}
}
</script>

Trouble with drag-and-drop sortable list using ReactJs and react-dnd

Using ReactJs and react-dnd
I want a user to be able to sort the form fields (a.k.a. properties)
I set up the code almost identical to the source code for the Cards in the simple sort demo. There are no console warnings or errors, and I can't figure out why this won't work. I can neither drag nor drop anything.
What it looks like:
Code:
App.js
import EditForm from './Forms/EditForm.js';
var id = $('#form').data('id');
var source = `/api/forms/${id}?include=type,properties.type`;
React.render(
<EditForm source={source} />,
document.getElementById('form')
);
EditForm.js
import React from 'react/addons';
import update from 'react/lib/update';
import Property from './Property.js';
var EditForm = React.createClass({
mixins: [ React.addons.LinkedStateMixin ],
getInitialState: function() {
return {
id: null,
name: null,
slug: null,
properties: []
}
},
componentDidMount: function() {
this.getFormFromServer();
},
getFormFromServer: function () {
$.get(this.props.source, (result) => {
if (this.isMounted()) {
this.setState({
id: result.id,
name: result.name,
slug: result.slug,
properties: result.properties.data
});
}
});
},
moveProperty: function(id, afterId) {
const { properties } = this.state;
const property = properties.filter(p => p.id === id)[0];
const afterProperty = properties.filter(p => p.id === afterId)[0];
const propertyIndex = properties.indexOf(property);
const afterIndex = properties.indexOf(afterProperty);
this.setState(update(this.state, {
properties: {
$splice: [
[propertyIndex, 1],
[afterIndex, 0, property]
]
}
}));
},
render: function() {
const { properties } = this.state;
var propertiesList = properties.map((property, i) => {
return (
<Property
key={property.id}
id={property.id}
type={property.type.name}
name={property.name}
moveProperty={this.moveProperty} />
);
});
return (
<div>
<h1>Form</h1>
<form>
<div className="form-group">
<label>Name:</label>
<input type="text" name="name" valueLink={this.linkState('name')} className="form-control" />
</div>
<div className="form-group">
<label>Properties:</label>
<div className="list-group properties-list">
{propertiesList}
</div>
</div>
</form>
</div>
);
}
});
export default EditForm;
Property.js
import React, { PropTypes } from 'react/addons';
import { DragDropMixin } from 'react-dnd';
import ItemTypes from './ItemTypes';
const dragSource = {
beginDrag(component) {
return {
item: {
id: component.props.id
}
};
}
};
const dropTarget = {
over(component, item) {
component.props.moveProperty(item.id, component.props.id);
}
};
var Property = React.createClass({
mixins: [ React.addons.LinkedStateMixin, DragDropMixin ],
propTypes: {
id: PropTypes.any.isRequired,
type: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
moveProperty: PropTypes.func.isRequired
},
statics: {
configureDragDrop(register) {
register(ItemTypes.PROPERTY, {
dragSource,
dropTarget
});
}
},
render: function () {
const { type } = this.props;
const { name } = this.props;
const { isDragging } = this.getDragState(ItemTypes.PROPERTY);
const opacity = isDragging ? 0 : 1;
return (
<a className="list-group-item"
{...this.dragSourceFor(ItemTypes.PROPERTY)}
{...this.dropTargetFor(ItemTypes.PROPERTY)}>
{type}: {name}
</a>
);
}
});
export default Property;
ItemTypes.js
module.exports = {
PROPERTY: 'property'
};
If anybody could help I would greatly appreciate it. It's kind of sad how much time I've actually spent trying to figure this out.
Reference links:
My code on github
Demo example
Demo source on github
After spending over a day trying to get the drag and drop working I fixed it with one single line of code.
import React from 'react/addons';
How it compiled and rendered at all without that, I don't even know.

Categories