Saturday, November 23, 2013

Converting JavaScript Polymer to Dart


I still have more questions than I'd like in my JavaScript implementation of a Polymer + Boostrap element. But I think most of those questions can be deferred until later. Tonight I convert my custom Polymer elements into Dart using Polymer.dart.

I am unsure what the code organization ought to be, so I am going to guess tonight and circle back around later to see if any changes are needed. Loosely following Dart Pub guidelines, I create an asset directory to hold my Polymer.dart HTML templates and my bootstrap CSS, a lib directory for my Polymer.dart code, and a web directory for my sample application web page.

Next, I create the usual pubspec.yaml listing only Polymer as a library (non-development) dependency:
name: pricing_panels
dependencies:
  polymer: any
dev_dependencies:
  unittest: any
After a quick pub fetch, I am ready to go. Starting with index.html, I have very similar HTML to the JavaScript version:
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Test</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta content="text/html; charset=UTF-8" http-equiv="content-type">

    <link type="text/css" rel="stylesheet" href="/assets/pricing_panels/bootstrap.min.css">

    <!-- Load component(s) -->
    <link rel="import" href="/assets/pricing_panels/pricing-plans.html">
    <link rel="import" href="/assets/pricing_panels/pricing-plan.html">

    <!-- Load Polymer -->
    <script type="application/dart">export 'package:polymer/init.dart';</script>
    <script src="packages/browser/dart.js"></script>
  </head>
  <body>
    <!-- pricing plan html here -->
  </body>
</html>
The stuff that I placed in the asset directory is, by Pub convention, accessible from the /assets/<package name>/ URL space, which is where the Polymer element definitions and Boostrap CSS are coming from. Also in there is the normal Polymer.dart initialization code. Well, the new normal at least as this has changed slightly since the last time I played with this project.

Taking a closer look at the Polymer element definition, not much needs to change from the JavaScript version:
<polymer-element name="pricing-plan" attributes="name type size">
  <template>
    <div class="col-md-{{size}}">
      <div class="panel panel-{{type}}">
        <div class="panel-heading">
          <h3 class="panel-title">{{name}}</h3>
        </div>
        <div class="panel-body">
          <content></content>
        </div>
      </div>
    </div>
  </template>
  <script type="application/dart" src="/packages/pricing_panels/pricing_plan.dart"></script>
</polymer-element>
In fact, the only thing that has changed is the <script> tag, which, naturally enough, now points to Dart code. Since the Dart code resides in the lib directory, the URL is slightly different than the asset HTML URL.

The Dart code itself looks like:
import 'package:polymer/polymer.dart';
import 'dart:html';

@CustomTag('pricing-plan')
class PricingPlanElement extends PolymerElement {
  @observable String name = 'Plan';
  @observable String type = 'default';
  @observable int size = 4;

  PricingPlanElement.created() : super.created();
}
That is more verbose than the JavaScript version, but only slightly, due to types and code annotations. The equivalent JavaScript was:
    Polymer('pricing-plan', {
      name: 'Plan',
      type: 'default',
      size: 1
    });
Once I have similar definitions for the other custom element in this package (the <pricing-plans> container), this actually works. Almost.

Interestingly, the CSS styles from the main document are not being applied:



To get the Boostrap CSS into the shadow DOM elements, I have to tell the Dart to allow "author" (from the main page) styles:
import 'package:polymer/polymer.dart';
import 'dart:html';

@CustomTag('pricing-plan')
class PricingPlanElement extends PolymerElement {
  @observable String name = 'Plan';
  @observable String type = 'default';
  @observable int size = 4;

  PricingPlanElement.created() : super.created() {
    shadowRoot.applyAuthorStyles = true;
  }
}
With that, I have my Bootstrap panels:



What is interesting is that I did not need to tell my JavaScript version to do the same thing. I am unsure if this is due to different Chrome versions (31 for Dart, 32 for JavaScript) or the embedded Dart VM in the former. The “author styles” thing seems to be a real thing, so I would expect that it is needed in the JavaScript version as well. Ah well, grist for another day.

For now, I have made the successful transition with a minimum of pain. Up tomorrow, I will explore testing of Polymer in Dart. Hopefully thanks to Dart's excellent testing and my hard-earned knowledge of Polymer testing, this will go a little smoother than when I was earning that knowledge.


Day #944

6 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. I get

    Exception: The null object does not have a setter 'applyAuthorStyles='.

    NoSuchMethodError : method not found: 'applyAuthorStyles='
    Receiver: null
    Arguments: [true]

    When I try assigning true to it in created. In enteredView() method It does exist, and I can assign true to it, and seems to work.

    @override
    void enteredView(){
    shadowRoot.applyAuthorStyles = true;
    }

    ReplyDelete
    Replies
    1. Interesting. I'm not getting that error. I even tried upgrading Dart (to 1.0.0.3_r30188) along with my Pub packages. Still, I don't doubt that error is lurking somewhere. I'll keep an eye for it -- especially while trying out the testing stuff...

      Delete
    2. Yup, I did eventually see this -- when I was testing. When I created a new Element.tag(), the shadow DOM had not created itself, resulting in the same error that you noted. I solved it the same way (enteredView).

      Delete
    3. Yes I was wondering why it might be working for you. It happens even with attributes passed to it like this



      It's null in created(), but, again, not so in enteredView().

      Delete
    4. OK didn't realise the replies would convert my tags to entities. Anyway this is what is missing in my last reply

      <poly-el list="{{somelist}}"></poly-el>

      Delete