Friday, March 27, 2015

Adopting <google-map-marker> Children in Polymer.dart


Up tonight, I would like to muck around with custom, Google Map-like, Polymer.dart elements. Thanks to the custom_element_apigen, I can pull the JavaScript <google-map> element into Dart code. It also pulls in <google-map-marker> and other, related elements. I can even individually use them in custom-built Polymer elements. But what about together?

Specifically, what if I wanted to mark up my <x-map> Polymer.dart element (powered by <google-map>) with <x-meetup> marker elements (powered by <google-map-marker>)? That is, I would the following to result in a map with the venerable B'more on Rails meetup displayed:
      <x-map latitude="39.283" longitude="-76.616">
        <x-meetup
           name="B'more on Rails"
           url="http://bmoreonrails.org"
           latitude="39.2810662"
           longitude="-76.5808788"></x-meetup>
      </x-map>
I think this is going to be tricky for two reasons. First, the <google-map> element is rendering in the shadow DOM of <x-map>:



Similarly, the <google-map-marker> element in <x-meetup> would reside in the shadow DOM of <x-meetup>. How on earth do I get the <google-map> and <google-map-marker> on the same document fragment, let alone working together?

The second thing that makes this tricky is that <x-meetup> resides in the light DOM of the document and is projected into <x-map>. The problem there is that, to the best of my knowledge, there is no easy way to get distributed nodes in Polymer.dart.

It turns out that I do not have to worry about distributed nodes in this case. Instead, once the container <x-map> element is attached to the document, I can iterate over the children:
@HtmlImport('x-map.html')
library x_map;

import 'package:polymer/polymer.dart';
import '../google_map.dart';
import 'x_meetup.dart';

@CustomTag('x-map')
class XMap extends PolymerElement {
  // ...
  XMap.created(): super.created();

  attached(){
    children.forEach((el){
       // Add markers from each child the map...
    });
  }
}
I am unsure how best to grab the <google-map-marker> from the children. It seems wrong to root through a child's shadow DOM. So instead, I assume the element has a googleMapMarker property that exposes this element (likely as part of a mixin):
@CustomTag('x-map')
class XMap extends PolymerElement {
  // ...
  XMap.created(): super.created();

  Element get map => $['google-map'];

  attached(){
    children.forEach((el){
      var marker = el.googleMapMarker.clone(true);
      map.append(marker);
    });
  }
}
Once I have the marker, I clone it so that it can be added to the <x-map> shadow root.

The map getter finds the <google-map> via dollar sign lookup of the google-map ID:
<polymer-element name="x-map">
  <template>
    <h1>x map</h1>
    <google-map
       id="google-map"
       latitude="{{latitude}}"
       longitude="{{longitude}}"></google-map>
  </template>
</polymer-element>
As for the <x-meetup> element that has a <google-map-marker> element, I define the attributes and the googleMapMarker getter in x_meetup.dart as:
@HtmlImport('x-meetup.html')
library x_map;

import 'package:polymer/polymer.dart';
import '../google_map.dart';

@CustomTag('x-meetup')
class XMeetup extends PolymerElement {
  @published String name;
  @published String url;
  @published float latitude = 0.0;
  @published float longitude = 0.0;
  @published Element map;

  XMeetup.created(): super.created();

  Element get googleMapMarker => $['marker'];
}
Finally the x-meetup.html template builds the <google-map-marker> element as:
<polymer-element name="x-meetup">
  <template>
    <google-map-marker
       id="marker"
       map="{{map}}"
       latitude="{{latitude}}"
       longitude="{{longitude}}"
       title="{{name}}"
       labelContent="{{name}}"
       labelClass="labels">
      <p>This is... <a href="{{url}}">{{name}}</a></p>
    </google-map-marker>
  </template>
</polymer-element>
The class looks this element up via the marker ID, returns it back to the <x-map> class, which clones it and inserts it in the <x-map> element's <google-map>. The end result is a marker showing where B'more on Rails meets:



This might be somewhat contrived, but it does have the advantage of taking what could be consistent data for a bunch of meetups:
        <x-meetup
           name="B'more on Rails"
           url="http://bmoreonrails.org"
           latitude="39.2810662"
           longitude="-76.5808788"></x-meetup>
And presenting it in a consistent way on the map. For instance, thanks to the <p> tag in the <x-meetup> template:
<polymer-element name="x-meetup">
  <template>
    <google-map-marker
       id="marker"
       map="{{map}}"
       latitude="{{latitude}}"
       longitude="{{longitude}}"
       title="{{name}}"
       labelContent="{{name}}"
       labelClass="labels">
      <p>This is... <a href="{{url}}">{{name}}</a></p>
    </google-map-marker>
  </template>
</polymer-element>
These meetup markers get a nifty little info window:



There may be better ways to go about accomplishing this, but in the end, a fairly minimal amount of code produced some nice markers on a custom map. That is a nice win.

Day #11

2 comments:

  1. Just shows assumptions can get you in trouble, but oh well. I never knew you could access child elements without going through the <content> tag. It never occurred to me to check if you could. Thanks for simplifying my life for me :-)

    ReplyDelete
  2. Its nice.You can check mine too. Its regarding google maptags. Maptags shortens your address to your favourite word and makes sharing address as easy as sharing a word. Visit at mapta.gs/maptags/700110

    ReplyDelete