Create dynamic Google Map Markers with SVGs - vue.js

I am trying to create Google Map Markers with dynamic information in it. For that feature I am using a Vuejs-Component with a SVG as template and add a "text"-Tag.
Example:
<template>
<svg width="40px" height="56px" viewBox="0 0 40 56" version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<text>{{ text }}</text>
</svg>
</template>
<script>
export default {
name: "DynamicGMapMarker",
props: {
text: {
type: String,
required: true
}
}
}
</script>
Then I am creating the SVG via a helper function and create a base64 data url:
import Vue from 'vue'
import DynamicGMapMarker from './DynamicGMapMarker'
const DynamicGMapMarkerConstructor = Vue.extend(DynamicGMapMarker)
export const getDynamicMarkerIcon = (text) => {
const iconComponent = new DynamicGMapMarkerConstructor({
propsData: {
text
}
});
iconComponent.$mount();
const iconDom = iconComponent.$el;
const iconString = new XMLSerializer().serializeToString(iconDom);
return 'data:image/svg+xml;charset=UTF-8;base64,' + btoa(iconString);
}
Now, I can use the getDynamicMarkerIcon() function. This is working in vue.js, but not in nuxt.js. In nuxt.js we need to add a XML-Serializer from npm, since nuxt can not use the Browser's XMLSerializer. For that I am using teclone/xml-serializer.
Now, the Problem seems to be that Nuxt can not create a DOM, because of SSR. When I am trying to use this method:
<template>
<div id="app">
<GmapMap
:center="center"
:zoom="15"
:options="options"
id="map"
>
<GmapCluster :zoomOnClick="true">
<GmapMarker
:key="index"
v-for="(marker, index) in markers"
:position="marker.coordinates"
:clickable="true"
:draggable="false"
:title="marker.name"
:icon="getMarker(marker)"
/>
</GmapCluster>
</GmapMap>
</div>
</template>
<script>
import GmapCluster from 'vue2-google-maps/dist/components/cluster'
import {getMarkerIcon} from "./MapIconUtil"
export default {
name: 'App',
components: {
GmapCluster
},
methods: {
getMarker(marker) {
return getDynamicMarkerIcon(marker.text);
}
},
data() {
return {
options: {
zoomControl: false,
mapTypeControl: false,
scaleControl: false,
streetViewControl: false,
rotateControl: false,
fullscreenControl: false,
disableDefaultUi: true,
clickableIcons: false
},
center: {
lat: 11,
lng: 11
},
"markers": [
{
coordinates: {
name: "Test",
lat: 11.0001,
lng: 11.0001,
text: "101"
}
}
]
}
}
}
</script>
I get an error that iconComponent.$el is undefined. I can not mount the SVG inside a DOM.
Are there any possibilities to tell Nuxt, that this component should run on browser side only?
I have already tried to wrap GmapMarker in <client-only>-Tag. That did not work. Can I use something like a "virtual DOM"? Any other ideas how to make it work?

I did it. You can selectively change the "mode" in which nuxt will render a specific url. Here you can see what configurations are possible.
You have to create a server middleware and tell nuxt if a path should be rendered in spa. Otherwise just default to your mode specified in your nuxt.config.js.
Check this out for an example.

Related

Wiris MathType does not parse MathML on Vue Component reload

I am working on a Vue JS app which has MathType plugin and the plugin works fine when the formula is entered and it is displayed in the correct format. But when the component is destroyed and created again and the previously entered data is passed into the component using v-model, the MathML code is intact (as observed in Vue inspector) but only the raw text content is shown in the editor and the equations are not parsed to the image format. If I reload the entire page with the v-model containing formulae, MathType parses it and displays the correct images. Is there a way to trigger this parsing for dynamically/programatically added text in CKEditor 4? Here is my code:
<template>
<div>
<input v-bind:name="fieldName" type="hidden" v-model="content" />
<ckeditor v-bind:editor-url="editorUrl" ref="editor" v-bind:config="editorConfig" v-model="content"></ckeditor>
</div>
</template>
<script>
import CKEditor from 'ckeditor4-vue';
export default {
props: {
currentContent: {
type: String,
default: null,
},
fieldName: {
type: String,
default: 'content',
},
toolbarType: {
type: String,
default: 'Full',
},
},
data: function () {
return {
editorConfig: {
embed_provider: '//ckeditor.iframe.ly/api/oembed?url={url}&callback={callback}',
mathJaxLib: '//cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.4/MathJax.js?config=TeX-AMS_HTML',
protectedSource: [/<u[^>]><\/u>/g],
toolbar_Exam: [
['Cut', 'Copy', 'Paste', 'PasteFromText', 'PasteFromWord', 'Undo', 'Redo'],
['Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript'],
['Outdent', 'Indent', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock'],
['Table', 'HorizontalRule', 'Smiley', 'SpecialChar'],
['Styles', 'Format', 'Font', 'FontSize', 'TextColor', 'BGColor'],
//['oembed', 'MediaEmbed'],
['ckeditor_wiris_formulaEditor', 'ckeditor_wiris_formulaEditorChemistry'],
],
toolbar_Full: null,
toolbar: this.toolbarType,
allowedContent: true,
extraAllowedContent: 'math[xmlns]; maction; maligngroup; malignmark; menclose; merror; mfenced; mfrac; mglyph; mi;' +
'mlabeledtr; mlogdiv; mmultiscripts; mn; mo; mover; mpadded; mphantom; mroot; mrow; ms; mscarries; mscarry;' +
'msgroup; mstack; msline; mspace; msqrt; msrow; mstack; mstyle; msub; msup; msubsup; mtable; mtd; mtext; mtr;' +
'munder; munderover; semantics; annotation; annotation-xml;',
},
editorUrl: siteUrl+'/vendor/ckeditor/ckeditor.js',
};
},
components: {
'ckeditor': CKEditor.component,
},
};
</script>
I have fixed this problem by using the Generic Wiris MathType Plugin https://www.npmjs.com/package/#wiris/mathtype-generic and importing the wirisplugin-generic.js script and using the WirisPlugin.Parser.initParse on the content variable in the component in the mounted function.
mounted() {
this.content = WirisPlugin.Parser.initParse(this.content);
}
And this has resolved the issue.

set default map centering with props in vue

I'm using vue to make an arc gis driven application, because the map's center can changed based on user location, I am setting the default location to some props then passing them into my map component to be called in the map components mounted() hook for rendering.
I think i'm pretty close to doing this correctly but when I log my props in my map component in the console it says they're undefined.
I'm not sure if my app code is at fault or my child component code?
as you can see in my app.vue I am even trying to pass in plain integers to test the values and they are still undefined.
app.vue
<template>
<div id="app">
<web-map v-bind:centerX="-118" v-bind:centerY="34" />
<div class="center">
<b-button class="btn-block" #click="getLocation" variant="primary">My Location</b-button>
</div>
</div>
</template>
<script>
import WebMap from './components/webmap.vue';
export default {
name: 'App',
components: { WebMap },
data(){
return{
lat: -118,
long: 34
}
},
};
</script>
webmap.vue
<template>
<div></div>
</template>
<script>
import { loadModules } from 'esri-loader';
export default {
name: 'web-map',
props:['centerX, centerY'],
data: function(){
return{
X: this.centerX,
Y: this.centerY
}
},
mounted() {
console.log(this.X,this.Y)
// lazy load the required ArcGIS API for JavaScript modules and CSS
loadModules(['esri/Map', 'esri/views/MapView'], { css: true })
.then(([ArcGISMap, MapView]) => {
const map = new ArcGISMap({
basemap: 'topo-vector'
});
this.view = new MapView({
container: this.$el,
map: map,
center: [this.X,this.Y], ///USE PROPS HERE FOR NEW CENTER
zoom: 8
});
});
},
beforeDestroy() {
if (this.view) {
// destroy the map view
this.view.container = null;
}
}
};
</script>
There is a typo. Change:
props:['centerX, centerY'],
to:
props:['centerX', 'centerY'],

Highmaps(HighCharts) loads same map when drilling down

I'm faced with a problem while implementing Highmaps.
In my vue project with Webpack, I followed each step explained https://github.com/highcharts/highcharts-vue to use Highmaps.
Here's my main.js file
//highchart
import HighchartsVue from 'highcharts-vue'
import Highcharts from 'highcharts'
import WorldMap from '#highcharts/map-collection/custom/world.geo.json'
import USMap from '#highcharts/map-collection/countries/us/us-all.geo.json'
import mapInit from 'highcharts/modules/map.js'
import DrillDown from 'highcharts/modules/drilldown.js'
mapInit(Highcharts);
DrillDown(Highcharts);
Highcharts.maps['world'] = WorldMap;
Highcharts.maps['us'] = USMap;
And, here's my vue file
<template>
<div>
<mymap :mapOptions="mapOptions.options"></mymap>
</div>
</template>
<script>
export default{
data(){
return {
mapOptions: {
options: {
chart: {
map: 'us',
events: {
drilldown: function(e){
var chart = this;
this.addSeriesAsDrilldown(e.point, {
data: 'world',
dataLabels: {
enabled: true,
format: '{point.name}'
}
})
},
drillup: function(){
}
}
},
series: [{
data: data,
joinBy: ['postal-code', 'code'],
dataLabels: {
enabled:true,
color: '#FFFFFF',
format: '{point.name}'
}
}],
}
}
}
},
}
</script>
Because the chart has 'chart': 'us' option, it renders US map first. Then, when I click any state (just for a test) it switches US map to world map.
It renders world map. However, it also render US map too. That is, it renders both two maps. How can I fix it??

How to fix 'Cannot find module' in a vuejs module with npm link

I've created a vuejs library with some components that could be used in many project.
In that library, I've got a component which can load svg files to be used inline in html (svg-icon).
It work great.
But in this same library, I've got another component which use svg-icon with a svg image stored in the library.
An import point, I'd like to use this library (node module) with an npm link
Which is the good way to give the path of the svg image, or where to store it?
I've tried a lot of different paths, but none of them is working...
This is my svg-icon component:
<template>
<component :is="component"></component>
</template>
<script>
export default {
name: 'SvgIcon',
props: {
icon: {
type: String,
required: true
}
},
data () {
return {
component: null
}
},
watch: {
icon: () => {
this.load()
}
},
computed: {
loader () {
return () => import(this.icon)
}
},
methods: {
load () {
this.loader().then(() => {
this.component = () => this.loader()
})
}
},
mounted () {
this.load()
}
}
</script>
And this is the component which use svg-icon (the svg image is in the same folder actually) :
<template>
<svg-icon icon="~my-module/components/media/no-image.svg"></svg-icon>
<svg-icon icon="./no-image.svg"></svg-icon>
</template>
<script>
import SvgIcon from '../svg-icon/SvgIcon'
export default {
components: {
SvgIcon
}
}
</script>
I always got this errors:
Cannot find module '~my-module/components/media/no-image.svg'
Cannot find module './no-image.svg'
Which is the good path in that situation? Or should I put the svg file somewhere else? (I'd like to keep it in the library)
I've created a CodeSandbox here
SvgIcon.vue
<template>
<span v-html="icon"></span>
</template>
<script>
export default {
name: "SvgIcon",
props: {
icon: {
type: String,
required: true
}
}
};
</script>
HelloWorld.vue
//Usage
<template>
<svg-icon :icon="AlertIcon"></svg-icon>
</template>
<script>
import AlertIcon from "../assets/alert.svg";
import SvgIcon from "./SvgIcon";
export default {
data() {
return { AlertIcon };
},
components: {
SvgIcon
}
};
</script>
I've made some changes to your components.
You need to import the image and pass it to your component because dynamic import causes complications when it comes to the absolute paths.
I've removed some unnecessary fields from your SvgIcon code.
Hope this helps.

window is not defined Nuxt for vue-slick

Hello I am having an issue with Nuxts ssr. I a trying to add 'vue-slick' to my web app and no matter what I do it continues to show "window is not defined".
As you can see I have tried multiple ways to allow vue-slick to be loaded on client side. Using plugins didn't help, using process.client in my component did not work as well.
Components/Carousel/Carousel.vue
<template>
<div class="carousel">
<Slick ref="slick" :options="slickOptions">
<a href="http://placehold.it/320x120">
<img src="http://placehold.it/320x120" alt="">
</a>
...
<a href="http://placehold.it/420x220">
<img src="http://placehold.it/420x220" alt="">
</a>
</Slick>
</div>
</template>
<script>
if (process.client) {
require('vue-slick')
}
import Slick from 'vue-slick'
export default {
components: {
Slick
},
data() {
return {
slickOptions: {
slidesToShow: 4
},
isMounted: false
}
},
methods: {
}
}
</script>
nuxt.config.js
plugins: [
{ src: '~/plugins/vue-slick', ssr: false }
],
plugins/vue-slick
import Vue from 'vue'
import VueSlick from 'vue-slick'
Vue.use(VueSlick);
Thanks for any help you can give!
So this is due to nuxt trying to render the slick component on the server side, even though you have set ssr: false in nuxt.config.
I have had this issue in other nuxt plugins and these steps should fix it.
in nuxt.config.js add this to your build object:
build: {
extend(config, ctx) {
if (ctx.isServer) {
config.externals = [
nodeExternals({
whitelist: [/^vue-slick/]
})
]
}
}
}
and in the page where you are trying to serve it you have to not mount the component until the page is fully mounted. So in your Components/Carousel/Carousel.vue set it up like this:
<template>
<div class="carousel">
<component
:is="slickComp"
ref="slick"
:options="slickOptions"
>
<a href="http://placehold.it/320x120">
<img src="http://placehold.it/320x120" alt="">
</a>
...
<a href="http://placehold.it/420x220">
<img src="http://placehold.it/420x220" alt="">
</a>
</component>
</div>
</template>
Then in your script section import your component and declare it like this:
<script>
export default {
data: () => ({
'slickComp': '',
}),
components: {
Slick: () => import('vue-slick')
},
mounted: function () {
this.$nextTick(function () {
this.slickComp = 'Slick'
})
},
}
</script>
Bascially this means the component isn't declared until the mounted function is called which is after all the server side rendering. And that should do it. Good luck.
I found another simple solution.
simply install "vue-slick" package to your nuxt project.
$ yarn add vue-slick
then component markup like below.
<template>
<component :is="slick" :options="slickOptions">
<div>Test1</div>
<div>Test2</div>
<div>Test3</div>
</component>
</template>
Finally set data and computed properties like this.
data() {
return {
slickOptions: {
slidesToShow: 1,
slidesToScroll: 1,
infinite: true,
centerMode: true,
},
};
},
computed: {
slick() {
return () => {
if (process.client) {
return import("vue-slick");
}
};
},
},
This solution prevents slick component importing globally as a plugin in nuxt config.