Load Google Maps one time with multiple components - javascript

I'm using Google API to display a map on my application and I'm using multiple dynamic components which each of them loads one map. Is it possible to load this map only one time in the parent and display it in each child component ?
Here are my components structure (I've removed everything which was important for my problem) :
Parent
<template>
<div>
<div v-for="tab in getTabs" :key="tab.index">
<keep-alive>
<component v-if="selectedTab === tab.index" v-bind:is="selected"/>
</keep-alive>
</div>
</div>
</template>
<script>
import Tab from './Tab';
export default {
name: 'Dispatcher',
components: {
Tab
},
data() {
return {
selected: '',
selectedTab: '',
tabIds: []
}
}
}
</script>
Child
<template>
<div class="row">
<div class="col-md-12" style="height:80vh">
<div class="gmap"></div>
</div>
</div>
</template>
<script>
export default {
name: 'Tab'
}
</script>
index.blade.php
<script src="map.js" type="text/javascript"></script>
<div id="dispatcher-body">
{{ csrf_field() }}
<dispatcher></dispatcher>
</div>
The script map.js in my blade is doing everything to load Google Maps on a div which has gmap class, such as in my child component. The map is loading great but it creates a new map for each child component.
I tried using v-slot but it doesn't work with my map.js and my script says that it doesn't find any gmap class (because I have no child component when I refresh the page).
Thanks in advance,

Maybe you should try vue-google-maps
I strongly recommend to not use vanilla libraries with frameworks such a vue, react or angular because they have their workflow and it creates conflicts between them

Related

Nuxtjs: How to call component inside another component?

I understand paradigm "Page-component" but what if I have a page that renders component, how do I call another component inside this component? Currently nuxtjs does not allow me do it. I can not stick to standart "page-component" scheme as I am bulding cart which calls cart-items.
Say If a cart component which is called by page looks like this, how would it call cart-item component inside it?
<!---- cart component called from index.vue --->
<template>
<div>
<Cart-item></Cart-item> < ---------- This doesn't work.
</div>
</template>
<script>
export default {
props: ['items']
}
</script>
I managed it the standard way:
<template>
<div>
<CartItem></CartItem>
</div>
</template>
<script>
import CartItem from '../components/Cart-item'
export default {
props: ['items']
}
</script>
Since nuxtjs auto-registers all components wonder if there is more graceful way.
EDIT: as promised, here is an example on how to pass some content to a component from another one thanks to slots. This is totally working in any Nuxt page ofc.
NestedContent.vue
<template>
<div>
<p>Here is the NestedContent component and below is a slot passed to ParentWithSlots' component</p>
<hr />
<parent-with-slots>
<!-- <template #default> // this one can be omit since we do use the default slot here -->
<p>This content is inserted into the component ParentWithSlots</p>
<!-- </template> -->
</parent-with-slots>
</div>
</template>
ParentWithSlots.vue
<template>
<div>
<p>xxxxxxxxxxx ParentWithSlots' content before slot xxxxxxxxxxx</p>
<slot>Default content in case none is provided</slot>
<p>xxxxxxxxxxx ParentWithSlots' content after slot xxxxxxxxxxx</p>
</div>
</template>
Here is how it looks
PS: you may also give a try to layouts, it can be useful for overall positioning of some of your components visually.
If your components are in the components directory, you can set components: true in your nuxt.config.js and have access to it pretty much anywhere without any additional step with the <cart-item></cart-item> syntax.
More details here: https://nuxtjs.org/blog/improve-your-developer-experience-with-nuxt-components/

Render external element inside Vue component

I have an external div that I need to render inside my Vue app. I'm trying to use a slot, like but that's a no go as nothing renders.
Any ideas?
Goal is to have HTML like this (Vue mounts on #app):
<div id="app" data-slot-header="#header"></div>
<div id="header">
<h1>Title here</h1>
</div>
Then the Vue component
<template>
<div>
<slot name="header"></slot>
</div>
</template>
You can use a dynamic <component> and refer to your #header element as a template reference.
For example
new Vue({
data: () => ({
headerComponent: {
template: '#header' // refer to template element by selector
}
}),
}).$mount('#app')
#app:before,#header:before{position:absolute;top:0;right:0;color:rgba(1,1,1,.5);font-size:.8rem}#app{border:1px solid #666;position:relative}#app:before{content:'Vue app'}#header{position:relative;opacity:.5}#header:before{content:'Original header'}
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
<div id="app">
<p>Dynamic component rendered here 👇</p>
<component :is="headerComponent"></component>
</div>
<div id="header">
<h1>Title here</h1>
</div>
Slots are mainly used with reusable Vue components so that the parent component can render custom stuff inside designated sections of the child. The root component does not have a parent, so it doesn't make sense to use slots for this.
Why can't you just hard-code the div in the template? Or do you need it to be dynamic; will you be swapping out the header contents in some situations? Please provide more information about what your use-case is, otherwise my answer is "just hard-code it".
Take a look at portal-vue. It allows child components to render templates anywhere in the DOM. This might work for your situation.

What is the Vue.js equivalent of Express-Handlebars sections?

I started a project using Express and Handlebars and then was encouraged to look at Vue.js. I am still at the stage of reading the docs but so far can't understand how to have layouts, partials and sections in Vue.js. I think a partial would be a component, but I'm lost how to have a layout with partials and sections that I can inject content into.
This is what I do using npm express-handlebars in a file called baselayout.hbs:
<!doctype html>
<html lang="en">
<head>
{{> global/headcode }} <!-- partial view with code for the head tag. it has stuff like favicon links --->
{{{_sections.pagemeta}}} <!-- page specific metadata injected here. this would be meta keywords description etc for a page/view --->
</head>
<body>
<div>
{{> global/siteheader }} <!--- partial view for the site's header --->
<div id="base-Container">
<main id="base-Content" role="main">
{{{ body }}} <!--- a page's main body content goes here --->
</main>
</div>
</div>
{{> sitefooter }}
{{{_sections.pagescripts}}} <!-- section for page-specific scripts injected here --->
</body>
</html>
How could I setup something like the above in Vue.js that would also work with server-side rendering? I just need a base layout with header/footer components included but also sections into which page-specific content can go.
For SSR, you should look at Nuxt.js, Vapper, or one of the other SSR Vue frameworks.
That said, yes, you would use components for everything. Generally, you would have one component for your main layout, then one for each view, then individual components for each partial/section that you would then import into your views and/or main layout. So, for example, based on the above code:
Your main app layout:
// AppLayout.vue
<template>
<div id="app-layout">
<site-header />
<router-view />
<site-footer />
</div>
</template>
<script>
import SiteHeader from './components/global/SiteHeader.vue'
import SiteFooter from './components/global/SiteFooter.vue'
export default {
name: 'AppLayout',
components: {
SiteHeader,
SiteFooter
},
meta: {
// metatags and other head content can be modified using vue-meta or similar
}
}
</script>
Example 'partial' component:
// BaseContainer.vue
<template>
<main id="base-container" role="main">
<h1 class="title">{{ content.title }}</h1>
<img :src="image" alt="" />
<base-content v-html="content.body" />
</main>
</template>
<script>
import BaseContent from './components/content/BaseContent.vue'
export default {
name: 'BaseContainer',
components: {
BaseContent
},
props: {
content: {
type: Object,
default() {
return {}
}
},
image: {
type: String,
default: ''
}
}
}
</script>
Example view component:
// MyView.vue
<template>
<div id="my-view">
<base-container :content="content" :image="image" />
</div>
</template>
<script>
import BaseContainer from './components/BaseContainer.vue'
import content from './data/myContent.json'
import image from './assets/myImage.jpg'
export default {
name: 'MyView',
components: {
BaseContainer
},
data() {
return {
content,
image
}
}
}
</script>
You would then use vue-router to specify which view component to load based on the current URL.
You probably need to use components and slots within your components.
Yes you need to create a component for each of your partials. Each component would have a template.
Then you will have a main component that will put all of this together. Using the more granular components you already have (your partials).
Now if the template structure (html for each component) is coming from the server then you can prob use slots https://v2.vuejs.org/v2/guide/components-slots.html which is a way VueJs allow components to receive custom markup when instantiating the components (see the example in the docs)
For the general layout and UI components of your app you may want to look at https://element.eleme.io/#/en-US/component/layout which is a nice alternative to the more popular Vuetify.

In VueJS, how do I use load in a view based on what is populated in my JS?

I am new to Vue.JS and require a little help as I am stuck with some logic.
I have been given a project to building and as I am under NDA I can only show comparative code examples.
In one View (Bars), which is brought into the viewport of the application using the router, I have a simple for loop.
<ul class="bar-listings">
<li v-for="bar in bars">
<router-link to="{bar.barPage}">
<div class="item">
<div class="item-bd">
<h2>{{ bar.barName }}</h2>
</div>
</div>
</router-link>
</li>
</ul>
Then in my JS file conatining all my data, I have this (Only one object for now)
export default [
{
barName: "The Tropicana Bar",
barPage: "views/bars/Tropicana",
}
];
The title displays correctly on Bars so the loop is pulling the data correctly.
However, I will have a .vue file for each bar, which also uses the data from my JS file. See below:
<template>
<div class="content-wrapper">
<h1>{{ getBarByIndex({ bars, index }).barName }}</h1>
</div>
</template>
<script>
import bars from "./../../data/bars";
export default {
data() {
return {
bars: bars
};
},
methods: {
getBarByIndex({ bars = [], index = 0 }) {
return bars[index] || {}
}
}
};
</script>
So what I need to solve is how do I make the <a v-bind:href=""> load the view for this bar?
What I think you can do, is to have a child component inside your component, you can leverage this by using nested routes in your router-view, additionally, you can pass props as an object to your router components, parent or child, whatever you need.
Basically, you will have a main router view, but inside your component, you will have another router-view which renders child components.
See: Nested Routes
See: Passing Props to Route components

How to make a 'Container' component in Vue.js

I'm new to Vue and cannot find a way to implement a React-like 'Wrapper' component with Vue.js, for example, a reusable datagrid component using a 3rd-party some-table component and a pagination component. Intuitively, datagrid will provide the data/props/events that both components need and control the communication between them. In React, this could be done as simply as something like
// inside <Datagrid />
<Table {...someProps} />
<Pagination {...otherProps} />
With Vue, It seems like something like below can only pass props down to children components
// inside Datagrid.vue
<some-table v-bind="$props"></some-table>
I'm not sure if slots could be of help. This wrapper component that I've been struggling for takes all the props/events/slots its children need and pass them down to them so that I could utilize all the functionality that it's children(which probably some 3rd-party components) provide. Moreover, it may also take responsibility for something like data exchange between its children. While datagrid could be a slots wrapper, but what if both table and pagination require a same data prop which I think should reside in datagrid. How to pass this data down to the slots?
// Datagrid.vue
<template>
<div>
<slot name="table"></slot>
<slot name="pagination"></slot>
</div>
</template>
<script>
export default {
name: 'Datagrid',
data() {
return {
data: 'How to share it with table and pagination',
}
},
}
</script>
Some solutions that I could figure out:
Render Functions, but I don't think that complicity is needed in this case
Instead of creating a 'Container' component, simply turn to mixins, but does this mean I have to input <pagination :total="total" :other-props="are-same-for-all-datagrids"></pagination> each time I want a datagrid?
Any examples dealing with such situations in Vue? Thanks in advance!
You want to use slots:
Vue.component('wrapper', { template: '#wrapper' })
new Vue({ el: '#app' })
<!-- wrapper template -->
<script type="text/x-template" id="wrapper">
<div class="wrapper" style="background: beige; padding: 5px;">
This is being wrapped:
<slot></slot>
</div>
</script>
<div id="app">
<wrapper>
<div style="background: aliceblue;">I'm being wrapped!</div>
</wrapper>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.3/vue.js"></script>

Categories