Utilities
We have three main utilities googleMapsApiInitializer
, pluginComponentBuilder
, and getGoogleMapsAPI
.
googleMapsApiInitializer
This function load the Google Maps API into your page creating the <script></script>
tag in the head of your page to load the Maps script from https://maps.google.cn
or https://maps.googleapis.com
depending on your preference and the options you passed into the plugin.
googleMapsApiInitializer
API
(options: ILoadPluginOptions): void => {
/**
* Allow options to be an object.
* This is to support more esoteric means of loading Google Maps,
* such as Google for business
* https://developers.google.com/maps/documentation/javascript/get-api-key#premium-auth
*/
if (Array.isArray(options) || typeof options !== 'object') {
throw new Error('options should be an object');
}
// Do nothing if run from server-side
if (typeof document === 'undefined') {
return;
}
const finalOptions = { ...options };
const { libraries } = finalOptions;
finalOptions.callback = 'GoogleMapsCallback';
// libraries
if (Array.isArray(libraries)) {
finalOptions.libraries = libraries.join(',');
}
if (!isApiSetUp) {
isApiSetUp = true;
const googleMapScript = document.createElement('script');
googleMapScript.setAttribute('type', 'text/javascript');
googleMapScript.innerHTML = `
((g) => {
var h,
a,
k,
p = 'The Google Maps JavaScript API',
c = 'google',
l = 'importLibrary',
q = '__ib__',
m = document,
b = window;
b = b[c] || (b[c] = {});
var d = b.maps || (b.maps = {}),
r = new Set(),
e = new URLSearchParams(),
u = () =>
h ||
(h = new Promise(async (f, n) => {
await (a = m.createElement('script'));
e.set('libraries', [...r] + '');
for (k in g)
e.set(
k.replace(/[A-Z]/g, (t) => '_' + t[0].toLowerCase()),
g[k]
);
e.set('callback', c + '.maps.' + q);
a.src = \`https://maps.\${c}apis.com/maps/api/js?\` + e;
d[q] = f;
a.onerror = () => (h = n(Error(p + ' could not load.')));
a.nonce = m.querySelector('script[nonce]')?.nonce || '';
m.head.append(a);
}));
d[l]
? console.warn(p + ' only loads once. Ignoring:', g)
: (d[l] = (f, ...n) => r.add(f) && u().then(() => d[l](f, ...n)));
})(${JSON.stringify(finalOptions)});`;
document.head.appendChild(googleMapScript);
} else {
window.console.info('You already started the loading of google maps');
}
};
pluginComponentBuilder
This function helps you to build your own components if it does not exist in the plugin, you can pass a set of options and the function will return a new vue component to be used in your application.
pluginComponentBuilder
API
/**
* A helper to build your own component for the plugin
*
* @param {Object} providedOptions
* @param {Object} providedOptions.mappedProps - Definitions of props
* @param {Object} providedOptions.mappedProps.PROP.type - Value type
* @param {Boolean} providedOptions.mappedProps.PROP.twoWay
* - Whether the prop has a corresponding PROP_changed
* event
* @param {Boolean} providedOptions.mappedProps.PROP.noBind
* - If true, do not apply the default bindProps / bindEvents.
* However it will still be added to the list of component props
* @param {Object} providedOptions.props - Regular Vue-style props.
* Note: must be in the Object form because it will be
* merged with the `mappedProps`
*
* @param {Object} providedOptions.events - Google Maps API events
* that are not bound to a corresponding prop
* @param {String} providedOptions.name - e.g. `polyline`
* @param {Function} providedOptions.ctr - constructor, e.g.
* `google.maps.Polyline`. However, since this is not
* generally available during library load, this becomes
* a function instead, e.g. () => google.maps.Polyline
* which will be called only after the API has been loaded
*
* default: () => String
*
* @param {Function} providedOptions.ctrArgs -
* If the constructor in `ctr` needs to be called with
* arguments other than a single `options` object, e.g. for
* GroundOverlay, we call `new GroundOverlay(url, bounds, options)`
* then pass in a function that returns the argument list as an array
*
* default: (MappedProps, OtherVueProps) => Array
*
* Otherwise, the constructor will be called with an `options` object,
* with property and values merged from:
*
* 1. the `options` property, if any
* 2. a `map` property with the Google Maps
* 3. all the properties passed to the component in `mappedProps`
* @param {Function} providedOptions.beforeCreate -
* Hook to modify the options passed to the initializer
*
* default: (Object) => Any
*
* @param {Function} providedOptions.afterCreate -
* Hook called when
*
* default: (options.ctr, Object) => Any
*
* @returns {Object} A component object that should be exported by default from a Vue component
*/
export function pluginComponentBuilder(
providedOptions: IGmapVueElementOptions
): ComponentOptions {
const {
mappedProps,
name,
ctr,
ctrArgs,
events,
beforeCreate,
afterCreate,
props,
...rest
} = providedOptions;
const promiseName = `$${name}Promise`;
const instanceName = `$${name}Object`;
_assert(!(props instanceof Array), '`props` should be an object, not Array');
return {
props: {
...props,
},
async setup() {},
render() {
return '';
},
provide() {
const promise = useMapPromise()
?.then((map) => {
if (!map) {
throw new Error('the map instance was not created');
}
// Infowindow needs this to be immediately available
this.$map = map;
// Initialize the maps with the given options
const options = {
map,
...getPropsValuesWithoutOptionsProp(props, this),
...this.options,
};
// don't use delete keyword in order to create a more predictable code for the engine
if (beforeCreate) {
const result = beforeCreate.bind(this)(options);
if (result instanceof Promise) {
return result.then(() => ({ options }));
}
}
return { options };
})
.then(({ options }: { options: { [key: string]: any } }) => {
const ConstructorObject = ctr();
// https://stackoverflow.com/questions/1606797/use-of-apply-with-new-operator-is-this-possible
this[instanceName] = ctrArgs
? new (Function.prototype.bind.call(
ConstructorObject,
null,
...ctrArgs(
options,
getPropsValuesWithoutOptionsProp(props || {}, this)
)
))()
: new ConstructorObject(options);
bindProps(mappedProps, this[instanceName], this);
bindEvents(events, this[instanceName], this);
if (afterCreate) {
afterCreate.bind(this)(this[instanceName]);
}
return this[instanceName];
});
this[promiseName] = promise;
return { [promiseName]: promise };
},
destroyed() {
// Note: not all Google Maps components support maps
if (this[instanceName] && this[instanceName].setMap) {
this[instanceName].setMap(null);
}
},
...rest,
};
}
Below you can see the interface of the options object that it receives
export interface IVueProp {
type?:
| StringConstructor
| NumberConstructor
| BooleanConstructor
| ArrayConstructor
| ObjectConstructor
| DateConstructor
| FunctionConstructor
| SymbolConstructor;
required?: boolean;
default?: () => undefined;
validator?: () => boolean;
}
export interface IGmapVueElementOptions {
mappedProps: Omit<SinglePluginComponentConfig, 'events'>;
props: { [key: string]: IVueProp };
events: string[];
name: string;
ctr: () => any;
ctrArgs: (
options: { [key: string]: any },
props: { [key: string]: IVueProp }
) => any[];
beforeCreate: (options: { [key: string]: any }) => any;
afterCreate: (mapElementInstance: { [key: string]: any }) => any;
}
You can check the SinglePluginComponentConfig
type here
getGoogleMapsAPI
This functions helps you to get the global google
object if it is available on your page.
getGoogleMapsAPI
API
/**
* This function helps you to get the Google Maps API
* when its ready on the window object
* @function
*/
function getGoogleMapsAPI(): false | GlobalGoogleObject {
return globalThis.GoogleMapsApi.isReady && globalThis.google;
}
Check the GlobalGoogleObject
type here