In this code lab, we will create a fully working Google Maps app using elements in Polymer's Google Web Components collection. The app will be responsive and will include driving directions and transit mode. Along the way, you'll also learn about Polymer's data-binding features and iron ajax element.
Fire up a code editor, start a new project and make sure your folder structure should look like this
IngAtmsCodeLab/
data/ <!-- will hold the data for our application -->
images/ <!-- will hold the images for our application -->
src/ <!-- will hold the elements for our application -->
bower.json <!-- Bower metadata files used for managing dependencies -->
index.html <!-- our application main entry point -->
Install Polymer and web components polyfill
$ bower install --save Polymer/polymer#1.11.3
$ bower install --save webcomponentsjs#0.7.24
Also install polymer-cli
as a global Node
package
$ npm install -g polymer-cli
Start with a minimal HTML5 document
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>ING ATMs</title>
</head>
<body>
</body>
</html>
then add the reference to the webcomponents-lite.min.js
polyfill inside the head
tag
ing-atms
elementInside the src
folder create an html file named ing-atms.html
. Use the following element template:
<link rel="import" href="../bower_components/polymer/polymer.html">
<dom-module id="ing-atms">
<template>
<style>
</style>
<h1>Hello World from Polymer!</h1>
</template>
<script>
Polymer({
is: 'ing-atms'
});
</script>
</dom-module>
ing-atms
elementTo employ <ing-atms>
, you need to:
index.html
.body
.From command line run (insde the application folder
$ polymer serve -o
If everything went well you shall see a nice greeting
The Google Web Components collection provides the <google-map>
element for declaratively rendering a Google Map. To use it, you first need to install it using Bower.
$ bower install --save GoogleWebComponents/google-map
The download may take a few seconds. You can verify that <google-map>
(and any dependencies) was installed by checking that bower_components/google-map/
was created and populated.
To use the map element, you'll need to obtain an API key for the Google Maps API. Visit https://developers.google.com/maps/documentation/javascript/get-api-key and copy your key. We'll use it in the next step.
To employ <google-map>
, you need to:
HTML Import
to load it inside ing-atms.html
.
<link rel="import" href="../bower_components/google-map/google-map.html">
ing-atms.html
's template (replace the h1
tag).
<google-map api-key="YOUR_KEY" disable-default-ui zoom="15">
</google-map>
If you run the app right now, nothing will display. In order for the map to properly display itself, you need to set its container (in this case, <ing-atms>
) to have a fixed height.
For this we are going to use a component iron-flex-layout
$ bower install --save PolymerElements/iron-flex-layout
HTML Import
to load it inside ing-atms.html
.
<link rel="import" href="../bower_components/iron-flex-layout/iron-flex-layout.html">
:host
pseudoselector inside the style
tag
:host {
@apply --layout-fit;
}
If you haven't already done so, refresh the page. At this point, you should see a map that takes up the entire viewport.
The application will be useful if the map will be centered to our location.
$ bower install --save ebidel/geo-location
To employ <geo-location>
, you need to:
HTML Import
to load it inside ing-atms.html
.
<link rel="import" href="../bower_components/geo-location/geo-location.html">
ing-atms.html
's template.
<geo-location watch-pos high-accuracy latitude="[[_twoWayBinding('lat')]]" longitude="[[_twoWayBinding('lng')]]"></geo-location>
The geo-location
component will provide the latitude
and longitude
every time our position changes. We used two way binding to bind them to our lat
and lng
properties.
Update the google-map
to use the new properties
<google-map
api-key="YOUR_KEY"
latitude="[[_oneWayBinding('lat')]]" longitude="[[_oneWayBinding('lng')]]"
disable-default-ui zoom="15">
</google-map>
Refreshing the page will center the map to our position (a permission to use the location will be asked).
Will be nice if our location will be clearly marked on the map by a pin
google-map-marker
The google-map-marker
comes with the google-map
component and it is used to display a marker on the map, but first we have to import the component.
<link rel="import" href="../bower_components/google-map/google-map-marker.html">
In order to use it we have to declare it inside the google-map
component and use the same lat
and lng
properties for its position
<google-map
api-key="YOUR_KEY"
latitude="[[_oneWayBinding('lat')]]" longitude="[[_oneWayBinding('lng')]]"
disable-default-ui zoom="15">
<google-map-marker
latitude="[[_oneWayBinding('lat')]]"
longitude="[[_oneWayBinding('lng')]]">
</google-map-marker>
</google-map>
At this point we have a default marker showing our position but we can change the icon and even add some content that shows up when we click the marker.
<h2>You are here</h2>
images
folderand update the google-map-marker
by adding the attribute icon="images/my-location.png"
Download the locations.json inside the data
folder of your project
locations.json
fileTo read the file we need to do an AJAX request and in Polymer there is the iron-ajax
component for that
iron-ajax
element
$ bower bower install --save PolymerElements/iron-ajax
iron-ajax
element
<iron-ajax auto url="../data/locations.json" on-response="handleResponse"></iron-ajax>
where
auto
automatically performs the Ajax request.url
the URL target of the request.on-response
is the callback being called when the request finishes with successThe response will contain information in a format different than the one we need. It is an array of cities
, each containing an array of locations
What we need is just an array of locations
and in order to obtain that we will going to use the Array.reduce
function.
Polymer({
is: 'ing-atms',
handleResponse: function (evt, xhr) {
this.locations = xhr.response.city.reduce(function (locations, city) {
return locations.concat(city.location);
}, []);
}
});
Now that we have the locations prepared let's show them on the map
For this we are going to use another Polymer construct, the dom-repeat
template
Just after the location marker add
<template is="dom-repeat" items="[[_oneWayBinding('locations')]]" as="atm">
<google-map-marker
latitude="[[_oneWayBinding('atm.map.latitude')]]"
longitude="[[_oneWayBinding('atm.map.longitude')]]">
</google-map-marker>
</template>
filter
function of the dom-repeat
tempplateHint:
make sure that atm type is not ATM non-ING
You can use:
The <google-map-directions>
element was installed alongside <google-map>
. It provides driving direction information using the Google Maps API.
To employ <google-map-directions>
:
ing-atms.html
.
<link rel="import" href="../bower_components/google-map/google-map-directions.html">
Declare <google-map-directions>
as a sibling of <google-map>
.
<google-map-directions
api-key="YOUR_KEY">
</google-map-directions>
<google-map
api-key="YOUR_KEY"
latitude="[[_oneWayBinding('lat')]]" longitude="[[_oneWayBinding('lng')]]"
disable-default-ui zoom="15">
...
</google-map>
<google-map-directions>
fetches directions, but it isn't that useful by itself. You need to connect the results from <google-map-directions>
to <google-map>
so the directions render on the map.
Both elements expose a .map
property that allow users to access/set an underlying Map
object (used by the Google Maps JavaScript API). To get these two elements talking, set them to use the same Map
object.
api-key
a property of the ing-atms
component and the bind it to both the google-map
and google-map-directions
componentgoogle-map
and google-map-directions
to the map
propertygoogle-map-directions
elementThe start address will be our current location. Create a computed property named startAddress
Polymer({
is: 'ing-atms',
properties: {
...
startAddress: {
type: Object,
computed: 'computeStartAddress(lat, lng)'
}
},
...
computeStartAddress: function (lat, lng) {
return {lat: lat, lng: lng};
}
});
The end address will be the ATM we want to go to location's
In order to compute the end address we need to add the following attributes on the ATM's google-map-marker
, click-events on-google-map-marker-click="computeEndAddress"
where
click-events
when present, marker *click events are automatically registered.on-google-map-marker-click
callback function for the marker's click event.and
computeEndAddress: function (event) {
this.endAddress = {lat: parseFloat(event.target.latitude), lng: parseFloat(event.target.longitude)};
},
startAddress
and endAddress
properties to the google-map-directions
elementgoogle-map-directions
adds by creating a renderOptions
property
renderOptions: {
type: Object,
value: function () {
return {
suppressMarkers: true
};
}
}
$ bower install --save PolymerElements/iron-icons
$ bower install --save PolymerElements/paper-tabs
In ing-atms.html, add the following HTML Imports:
<link rel="import" href="../bower_components/paper-tabs/paper-tabs.html">
<link rel="import" href="../bower_components/paper-tabs/paper-tab.html">
<link rel="import" href="../bower_components/iron-icons/maps-icons.html">
<link rel="import" href="../bower_components/iron-icon/iron-icon.html">
Note: maps-icons
is required to load Polymer's map iconset. You'll use it later to render icons for the travel modes.
We'll use <paper-tabs>
to allow users to select their travel type. Each transit mode will also use an icon from the maps
icon set instead of the default one. To use one of the other icon sets, the icon
attribute takes the form: <iconset>:<name>
, where <iconset>
is the name of the icon set (e.g. "maps") and <name>
is the name of the icon from that set.
As an example, the following code places the directions-car
icon from the maps
icon set:
<iron-icon icon="maps:directions-car" item-icon></iron-icon>
The <google-map-directions>
element contains a travelMode
property for specifying the type of directions to render. It has four possible values: "DRIVING", "WALKING", "BICYCLING", and "TRANSIT".
<google-map>
element, add a <paper-tabs>
with the travel mode options.<iron-icon>
and <span>
containing helper text.
<!-- selected="0" selects the first item in the tab list.
Change it to another index if you want a different default. -->
<paper-tabs selected="0">
<paper-tab label="DRIVING">
<iron-icon icon="maps:directions-car"></iron-icon>
<span>DRIVING</span>
</paper-tab>
<paper-tab label="WALKING">
<iron-icon icon="maps:directions-walk"></iron-icon>
<span>WALKING</span>
</paper-tab>
<paper-tab label="BICYCLING">
<iron-icon icon="maps:directions-bike"></iron-icon>
<span>BICYCLING</span>
</paper-tab>
<paper-tab label="TRANSIT">
<iron-icon icon="maps:directions-transit"></iron-icon>
<span>TRANSIT</span>
</paper-tab>
</paper-tabs>
<paper-tabs>
is styled by default. It has a yellow material design ink effect and selection bar indication the selected tab. We can make it nicer!
Polymer uses CSS custom properties to expose styling hooks for internal parts of a component's Shady DOM. For example, paper-tabs exposes --paper-tabs-selection-bar-color
to colorize the tab's selection bar.
<dom-module id="ing-atms">
<template>
<style>
:host {
@apply --layout-fit;
}
paper-tabs {
@apply --layout-fixed-left;
--paper-tabs-selection-bar-color: #4271F4;
background: #ffffff;
}
paper-tab {
--paper-tab-ink: #BBDEFB;
}
paper-tab.iron-selected {
background: rgb(66, 113, 244);
color: #ffffff;
}
paper-tab iron-icon {
margin-right: 10px;
}
</style>
...
</dom-module>
This bit of code will colorize the selection bar and ink ripple effect a nice material design blue.
The last thing you need to do is data-bind the travelMode
property of <google-map-directions>
to <paper-tabs>
's .selected
property. The tabs updates this property whenever a new child item is selected. By default, its set to the index of the selected item. We need to override this in order to bind to the string value of each direction type. The attr-for-selected
property does just that.
Bind the direction's travelMode
property to the tab's selected
property:
<google-map-directions
map="[[_oneWayBinding('map')]]" api-key="[[_oneWayBinding('apiKey')]]"
start-address="[[_oneWayBinding('startAddress')]]" end-address="[[_oneWayBinding('endAddress')]]"
travel-mode="[[_oneWayBinding('travelMode')]]" renderer-options="[[_oneWayBinding('renderOptions')]]">
</google-map-directions>
...
<paper-tabs attr-for-selected="label" selected="[[_twoWayBinding('travelMode')]]">
<paper-tab label="DRIVING">
<iron-icon icon="maps:directions-car"></iron-icon>
<span>DRIVING</span>
</paper-tab>
<paper-tab label="WALKING">
<iron-icon icon="maps:directions-walk"></iron-icon>
<span>WALKING</span>
</paper-tab>
<paper-tab label="BICYCLING">
<iron-icon icon="maps:directions-bike"></iron-icon>
<span>BICYCLING</span>
</paper-tab>
<paper-tab label="TRANSIT">
<iron-icon icon="maps:directions-transit"></iron-icon>
<span>TRANSIT</span>
</paper-tab>
</paper-tabs>
The map should automatically update to show different forms of travel:
You built an entire maps application complete with driving directions, ajax call and showing custom pins, and kept it under 150 lines of code
Think about that for a second. This just goes to show you the power of web components. They're reusable and composable. Zest in Polymer's data-binding features and you can create an entire app using nothing but declarative markup.