Wednesday, February 26, 2014

Best Practice for Assigning a Polymer Strategy


Up tonight, I would like to settle on a way to assign the strategy that a Polymer might use to perform some bit of functionality.

Of late, I have been exploring this idea by swapping two internationalization strategies in a simple <hello-you> Polymer:



Switching languages is easy. The locale variable in the <hello-you> template is bound to the select list and the locale attribute of whichever translation strategy is currently being employed:
<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="polymer-translate.html">
<link rel="import" href="polymer-translate-custom.html">
<polymer-element name="hello-you" attributes="locale">
  <template>
    <p>
      {{labels.language}}:
      <select value="{{locale}}">
        <option value="en">English</option>
        <option value="es">Español</option>
        <option value="fr">Français</option>
      </select>
    </p>
    <!-- <polymer-translate locale={{locale}} labels="{{labels}}"></polymer-translate> -->
    <polymer-translate-custom locale={{locale}} labels="{{labels}}"></polymer-translate>
  </template>
  <script src="hello_you.js"></script>
</polymer-element>
When the locale changes in <hello-you>, the translation strategy immediately knows about it.

What I don't have sussed out is where to assign the translation strategy. In the above, I am doing it in the template—either using the (currently commented out) <polymer-translate> or <polymer-translate-custom>. This has a certain appeal to it—and some implications that I am not sure I fully comprehend.

The appeal is that I am assigning the strategy at the same level as I am importing its code. The implication is that I am effectively importing a library (one or more of the translation libraries). Furthermore, I am instantiating an instance of the imported object inside the template.

In other words, the Polymer template is serving as library glue code and object assignment mechanism. That's a fairly significant shift in thinking—especially in the Dart flavor of Polymer since Dart has proper libraries. But is it appropriate to think of it that way?

The Polymer Elements companion project would seem to have some thoughts on the matter. The <polymer-ajax> element imports the <polymer-xhr> element to serve as strategy for accessing resources with XMLHttpRequest. The <polymer-ajax> element imports the <polymer-xhr> library in the template, but it does not create the <polymer-xhr> instance in the template. Instead it does so in the backing class:
    Polymer('polymer-ajax', {
      // ...
      ready: function() {
        this.xhr = document.createElement('polymer-xhr');
      },
      // ...
    });
I also have to admit that this is more elegant than what I have to do when I want access to the strategy from the backing class. In my case, my i18n strategy contains the string “translate,” so I search all of the children for such an element:
Polymer('hello-you', {
  // ...
  ready: function() {
    var i18n_re = /translate/i;
    for (var i=0; i<this.shadowRoot.children.length; i++) {
      var el = this.shadowRoot.children[i];
      if (i18n_re.test(el.tagName)) this.i18n = el;
    }
  },
  // ...
});
Awful!

So it's an open and shut case, right? I should clean up my code by following the example of the fine Polymer Elements folks.

Or should I?

In assigning the instance in the template, I went out of my way to establish most of the communication between <hello-you> and translation strategy via bound variables and attributes:
    <polymer-translate-custom locale={{locale}} labels="{{labels}}"></polymer-translate>
The only reason that I need to access the translation strategy from the class is because that is currently where the responsibility lies for watching the number-of-balloons <input> field. Once the <hello-you> Polymer sees such a change, it notifies the translation strategy that it needs to translate()—to pick up the latest pluralization translation.

But why not do all communication via bound variables? Why not make a change in state that affects the strategy a simple attribute that can be watched by the strategy? I do just that by syncing the balloon_count variable from the <hello-you> template to a property in the labels that both elements share:
Polymer('hello-you', {
  // ...
  observe: {
    'balloon_count': '_syncBalloonCount'
  },
  _syncBalloonCount: function() {
    this.labels.__count__ = this.balloon_count;
  },
  // ...
});
With that, the translation strategy can now watch for changes to this __count__ variable that it needs to pluralize:
Polymer('polymer-translate-custom', {
  observe: {
    'locale': 'translate',
    'labels.__count__': 'translate'
  },
  // ...
  translate: function() {
    // ...
    var count = this.labels.__count__;
    if (count == "" || count == "0" || count == undefined)
      this.labels.count_message = l10n.balloon_zero;
    else if (count == "1")
      this.labels.count_message = l10n.balloon_one;
    else
      this.labels.count_message = l10n.balloon_plural.replace('__count__', count);
  }
});
I must say that I fancy communicating via shared primitives like this. My <hello-you> backing class is completely unaware of the strategy, save for the need to synchronize values, leaving it to focus on the business of being <hello-you>.

That said, shared state is usually a recipe for disaster. I would feel safer if the primitive could be marked as immutable on whichever side of the communication channel it is appropriate. The effective convention of <hello-you> only changing properties surrounded by double underscores seems a reasonable convention. But is that a long-term recipe for Polymer success?

Regardless, I appreciate this new thought model of Polymer templates as library and instance glue. Even if I wind up sticking with my crazy instance finder or switching to the <polymer-ajax> approach of instantiating the instance in the backing class, I think there is fertile ground to be tilled by limiting communication—and especially method calling—between parent and child Polymers.


Day #1,038

No comments:

Post a Comment