我已经有一段时间没有写关于Alpine.js的博客了,我认为用它来整合Google Maps的例子将是一个很好的方式来继续我适应这个框架的道路。我想这将是相当简单的,但在建立一些演示的过程中,我遇到了一些有趣的问题,帮助我对Alpine有了更多的了解。让我们来看看。
我的数据
我所有的例子都将使用同一组数据--靠近我所在地的几家Spirit万圣节商店。我去了他们的网站,打开了开发工具,复制了七家商店的位置。我调整了一个离另一个地点超级近的数据,并添加了一个代表商店是否一天24小时营业的值。Spirit商店并不这样做,但我后来有了一个计划:
[
{
"lat": 30.175776,
"lng": -92.077008,
"allday":true
},
{
"lat": 30.173529,
"lng": -92.078997,
"allday":true
},
{
"lat": 30.394324,
"lng": -91.086551,
"allday":false
},
{
"lat": 30.176162,
"lng": -93.219241,
"allday":false
},
{
"lat": 31.269887,
"lng": -92.46034,
"allday":false
},
{
"lat": 29.61629,
"lng": -90.757389,
"allday":false
},
{
"lat": 30.486217,
"lng": -90.45677,
"allday":false
}
]
从简单的开始--静态的
对于我的第一个例子,我决定使用谷歌地图的静态地图API。这是我多年来最喜欢的一个API,因为它不是一个API,只是一个URL。在你不需要动态地图的情况下,静态地图API简单得不得了,只需要你制作一个带有位置信息的URL。我首先建立了一个新的Alpine应用程序,将每个商店的位置与地图一起呈现。我显示的是经纬度值,你可能永远不会这样做,因为 "普通人 "并不真正关心。相反,我会显示一个街道地址:
<div x-data="app">
<template x-for="store in stores">
<p>
Store location: <span x-text="store.lat"></span>,<span x-text="store.lng"></span><br/>
<img :src="getImageUrl(store.lat,store.lng)">
</p>
</template>
</div>
对于每个商店,我都会调用一个方法,getImageUrl ,它将返回我的图像所需的静态地图API URL。这是我的Alpine应用程序的全部内容,只是JSON的大小被修剪了一下:
const MAP_KEY = 'AIzaSyC3hC35ehz1oAfUll7q7qzUlPa27Gz5g5g';
document.addEventListener('alpine:init', () => {
Alpine.data('app', () => ({
stores:getStores(),
getImageUrl(lat,lng) {
return `https://maps.googleapis.com/maps/api/staticmap?center=${lat},${lng}&zoom=15&size=400x400&maptype=roadmap&markers=color:blue%7C${lat},${lng}&key=${MAP_KEY}`
}
}))
});
/*
imagine this was an API call..
*/
function getStores() {
return [
// list of stores here
]
}
作为一个例子,第一个商店的URL是。
https://maps.googleapis.com/maps/api/staticmap?
center=30.175776,-92.077008&zoom=15&size=400x400&maptype=roadmap
&markers=color:blue%7C30.175776,-92.077008
&key=AIzaSyC3hC35ehz1oAfUll7q7qzUlPa27Gz5g5g
你可以看到它的打击。
请注意,我在地图的中心位置添加了一个标记*,*这样我所指出的东西就非常明显。标记可以有不同的颜色和标签,但我现在要保持简单。下面是一个显示完整应用的CodePen。
请看Raymond Camden (@cfjedimaster) 在CodePen上的PenAlpine + Google Maps 1。
一个动态地图
对于我的第二个例子,我想做一个适当的动态Google地图,但仅仅是这样,没有别的。通常情况下,如果我的唯一目标是渲染地图而不是其他,我就不会使用Alpine.js。谷歌地图的JavaScript库并不难用,如果我不需要Alpine,那么就没有理由加载它。
但是在做这个演示的过程中,我遇到了一些有趣的问题。首先,谷歌地图和Alpine都是异步加载的。如果在我的下一个版本中,我想添加与地图集成的Alpine功能,我需要一种方法来处理这个问题。我在网上搜索了一下,发现了这个有用的资源。从第三方脚本中调用Alpine.js方法。这篇博客描述了一种方法,通过这种方法,你可以从你的谷歌地图代码中发射一个自定义事件,而Alpine会监听到这个事件。他们的代码没有立即为我工作,我不得不对它进行一些调整,但让我们看看。
首先,这是你如何添加谷歌地图的JavaScript SDK(为清晰起见,增加了一些换行):
<script
src="https://maps.googleapis.com/maps/api/js?
key=AIzaSyC3hC35ehz1oAfUll7q7qzUlPa27Gz5g5g
&callback=initMap&v=weekly"
defer
></script>
注意这里有两件重要的事情--首先是我的钥匙(我把它锁定在几个域中,所以它在这里是安全的),更重要的是,callback 函数。当谷歌地图准备好时,这将被调用。
这是我的initMap ,取自谷歌地图的文档,并稍作修改:
// earlier in the code:
let map;
// The location of Uluru
const uluru = { lat: -25.344, lng: 131.031 };
function initMap() {
// The map, centered at Uluru
map = new google.maps.Map(document.getElementById("map"), {
zoom: 4,
center: uluru,
});
window.dispatchEvent(new Event('map-loaded'));
}
注意到我是如何调度一个自定义事件的吗?回到Alpine这边,我可以像这样为它添加一个应用范围的监听器。
<div x-data="app" @map-loaded.window="doMapStuff()">
<div id="map"></div>
</div>
现在让我们回到Alpine:
document.addEventListener('alpine:init', () => {
Alpine.data('app', () => ({
init() {
if("google" in window) this.doMapStuff();
},
doMapStuff() {
console.log('do map stuff');
// The marker, positioned at Uluru
const marker = new google.maps.Marker({
position: uluru,
map: map,
});
}
}))
});
首先,谷歌地图有可能在Alpine之前加载,所以在init ,我检查它,如果它在那里,我就做我的地图工作。那个函数,doMapStuff ,是由Alpine事件监听器调用的同一个函数。因此,无论如何--我都被覆盖了。在这种情况下,我只是在地图上添加一个标记。你可以看到下面的完整演示。
带有阿尔卑斯山数据的地图
好了,让我们把第一个例子中的地图数据与第二个例子中的动态版本结合起来。首先,我对HTML做了一些修改,加入了一个新的控件,让用户可以过滤到一天24小时营业的商店:
<div x-data="app" @map-loaded.window="doMapStuff()">
<h2>Stores</h2>
<p>
<label><input type="checkbox" x-model="alldayfilter"> Filter to 24 Hour Stores</label>
</p>
<div id="map"></div>
</div>
在JavaScript中,我首先修改了doMapStuff ,以便为每个商店呈现一个标记:
doMapStuff() {
console.log('do map stuff');
this.stores.forEach(store => {
store.marker = new google.maps.Marker({
position: { lat: store.lat, lng: store.lng },
map: map,
});
});
}
请注意,我正在创建一个标记对象并将其存储在我的商店数据中。为什么?这使我能够在复选框被改变时切换可见性。为了支持这个功能,我使用了一个观察者:
this.$watch('alldayfilter', val => {
this.stores.forEach(store => {
if(!val) store.marker.setVisible(true);
else {
if(!store.allday) store.marker.setVisible(false);
}
});
});
当alldayfilter 变化时,我要么将每个标记设置为可见,要么有条件地隐藏那些全天未打开的标记。
再一次,这里是CodePen。