Tuesday, March 24, 2015

Ugly Google Maps API from Polymer.dart


Up tonight, I would like to mess about with the Google Maps API a little.

While I await the final copy edits on Patterns in Polymer, I need to keep my mind engaged with Polymer. I will have to run through changes at least one more time and would like this stuff as fresh as possible. In other words, this seems an opportune time to work through topics about which folks inquired but did not seem an obvious fit in the book. I will code in the language in which the question was originally asked.

So, to explore the Google Maps API, I will be coding in Dart. The question is a little old -- I think I got it late last year. Today, I believe the answer for how to approach this begins and ends with the google-map web component. The question then becomes, how easy is it to install with Polymer.dart?

It has at least been done once by the esteemed James Hurford. James' approach hardly seems easy and it has been a while, so it may no longer work. There is one way to find out...

I start not with Dart, but with a Bower install to obtain the <google-map> Polymer element. First, I specify that Bower should install in my application's lib/elements/bower_components directory so that my Dart code can access it. I do this by creating a .bowerrc with:
{
  "directory": "lib/elements/bower_components"
}
Now I can bower install:
$ bower install GoogleWebComponents/google-map --save
...
google-map#0.4.2 lib/elements/bower_components/google-map
└── google-apis#0.4.4

google-apis#0.4.4 lib/elements/bower_components/google-apis
└── core-shared-lib#0.5.5

core-shared-lib#0.5.5 lib/elements/bower_components/core-shared-lib
└── polymer#0.5.5

polymer#0.5.5 lib/elements/bower_components/polymer
├── core-component-page#0.5.5
└── webcomponentsjs#0.5.5

core-component-page#0.5.5 lib/elements/bower_components/core-component-page
├── polymer#0.5.5
└── webcomponentsjs#0.5.5

webcomponentsjs#0.5.5 lib/elements/bower_components/webcomponentsjs
Before creating an element that use <google-map>, let's see if I can simply use it from an application page:
<!doctype html>
<html lang="en">
  <head>
    <title>My Maps</title>
    <link
       rel="import"
       href="packages/my_maps/elements/bower_components/google-map/google-map.html">
    <script type="application/dart">
      export 'package:polymer/init.dart';
    </script>
  </head>
  <body unresolved>
    <google-map
       latitude="37.779"
       longitude="-122.3892"
       minZoom="9"
       maxZoom="11"
       fit></google-map>
  </body>
</html>
After a pub install to grab Polymer.dart, I find that this most obvious approach will not work. I get errors in the console along the lines of:
Uncaught HierarchyRequestError: Failed to execute 'appendChild' on 'Node': Nodes of type 'HTML' may not be inserted inside nodes of type '#document'.

Uncaught TypeError: Cannot read property 'resolveDom' of undefined

Exception: Uncaught Error: NoSuchMethodError: method not found: 'whenPolymerReady'
Receiver: Instance of 'JsObject'
Arguments: [Closure: () => dynamic]
Stack Trace:
#0      JsObject.callMethod (dart:js:230)
#1      _hookJsPolymer (package:polymer/src/loader.dart:82:23)
#2      startPolymer.<anonymous closure> (package:polymer/src/loader.dart:44:19)
...
These errors would seem to be coming from duplicate polymer.js definitions—one from the Polymer.dart application and one that is pulled in from the <google-map> dependencies.

As James found, If I work through each of the following:
  • core-shared-lib/core-shared-lib.html
  • google-apis/google-maps-api.html
  • google-map/google-map-directions.html
  • google-map/google-map-search.html
  • google-map/google-map.html
And I hand-edit the <link> import of Polymer, changing from the JavaScript version to the one supplied by Dart:
<link rel="import" href="../../../../../packages/polymer/polymer.html">
Then I get my map:



That is pretty ugly. There is no way that anyone should be hand-editing web component definitions like that in order to get them to work. I need to find another approach if I am going to recommend using this element.

I do try to lock the JavaScript versions to to the same version used by Polymer.dart via my bower.json file:
{
  "name": "my-maps",
  "dependencies": {
    "google-map": "GoogleWebComponents/google-map#0.4.1",
    "polymer": "Polymer/polymer#0.5.1",
    "webcomponentsjs": "Polymer/webcomponentsjs#0.5.1"
  }
}
But that is of no avail. I still have to hand-edit the files in the list to prevent the error from the duplicate Polymer definitions. Looks like I need to try a different approach tomorrow.


Day #8

4 comments:

  1. I did that a while ago. Things have improved since then. The dart team wrote a library you can use to create a wrapper for JS Polymer Elements. You've used it at least once, it's called Custom Elements APIGen found here https://pub.dartlang.org/packages/custom_element_apigen. That will eliminate 90% of the pain for you, and create a nice wrapper stored in lib.

    ReplyDelete
    Replies
    1. Heh. I was just reading some of those posts a couple of days ago! I kinda assumed (for no particular reason) that it was now old. Looks like I'll be re-familiarizing myself with it tomorrow :)

      Delete
    2. If you look closely at the core and paper elements pub packages, you'll see that they are produced using Custom Elements APIGen. Some JS elements don't wrap well like core-ajax, which is why I assume they created their own version called core-ajax-dart. For most Polymer JS elements it works pretty well in producing a working Polymer Dart wrapper for them.

      Delete
  2. Though having said that, it's not a pure Polymer wrapper, as you can't inherit from elements wrapped this way in Polymer Dart yet. Last time I queried this that is. The wrappers are web components, but inherit from HtmlElement and not PolymerElement. However they still provide working elements that you can use in any Polymer Dart app. Someone will probably correct me on some of this, but that's what I think I know.

    ReplyDelete