Tuesday, September 16, 2014

Updating Published Polymer Attributes


If a codebase's tests don't run regularly, the codebase is not tested. A team can spend hundreds of person-hours writing hundreds of tests. But if those tests are not executed at least once a day, the team might just as well delete them. They occupy space with no purpose.

This seems utterly obvious, yet I see it time and time again. And as much as I detest this poor excuse for programming, I am guilty of no less with the Patterns in Polymer project code. As I work through each chapter adding at least rudimentary Karma / Jasmine tests to the custom Polymer elements therein, I am surprised… at how many tests I actually did write.

And so far, the tests have been easy to fix once I updated the element definitions to the latest Polymer library. Until tonight. Tonight, I find that some of the console logging that I expect from a Polymer observer is not being called:
$ karma start
INFO [karma]: Karma v0.12.23 server started at http://localhost:9876/
INFO [launcher]: Starting browser Chrome
INFO [Chrome 37.0.2062 (Linux)]: Connected on socket wjIrCJXi9b1e1VLgybGG with id 7435382
Chrome 37.0.2062 (Linux)  sees changes to your_name FAILED
        Expected spy Console Log to have been called with [ 'your_name is now: Bob' ] but it was never called.
            at Object.<anonymous> (/home/chris/repos/polymer-book/book/code-js/changes_from_outside/test/HelloYouSpec.js:106:9)
Chrome 37.0.2062 (Linux): Executed 1 of 5 (1 FAILED) ERROR (0.065 secs / 0.061 secs)
At first I assumed than an API had changed since I originally wrote this code and test (it was all the way back in March, after all). Upon closer examination, something else seems to be going wrong. I am hesitant to say that Polymer is broken, but my understanding of it is.

Regardless, it sure would have been nice to have a regular build running so that I knew exactly when this did break.

First, the problem. I am spying on console.log() which is called by a MutationObserver:
function watchPolymer(el) {
  var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
      console.log(
        mutation.attributeName +
        ' is now: ' +
        mutation.target[mutation.attributeName]
      );
    });
  });
  observer.observe(el, {attributes: true});
}
By virtue of the attributes: true on the observe() method, this MutationObserver should fire whenever an attribute changes. And it does, if I find the <hello-you> Polymer element and update one of its attributes, the MutationObserver fires:
> el = document.querySelector('hello-you')
    <hello-you color=​"greenish">​…​</hello-you>​
> el.attributes.getNamedItem('color').value = 'orange'
    color is now: orange watcher.js:14
    "orange"
But if the value of the color property changes in the element, say when the feelingLucky() button is pressed, nothing happens:
<link rel="import" href="../bower_components/polymer/polymer.html">
<polymer-element name="hello-you" attributes="your_name color" color>
  <template><!-- ... --></template>
  <script>
Polymer('hello-you', {
  your_name: '',
  color: '',

  feelingLucky: function() {
    console.log(this.color);

    var colors = ['red', 'blue', 'green'];
    var num = Math.floor(colors.length*Math.random());
    this.color = colors[num];
    this.$.hello.style.color = this.color;
  }
});
</script>
</polymer-element>
Dang it. I am publishing the color attribute by including it in the space separated list of published attributes in the aptly named attributes attribute of the definition. But no matter what I do, changing the property is not reflected in the element's attribute.

What is even stranger is that, the console.log() on the feelingLucky() does see the initial value of the attribute. If I use this custom <hello-you> element with a “greenish” color:
<hello-you color="greenish"></hello-you>
Then, when I first press the feelingLucky() button, I see greenish logged in the JavaScript console:
color is now: greenish     watcher.js:14
greenish                   hello-you.html:27
It is almost like data binding is one way. That, or Polymer has some kind of internal setting that prevents published attributes from getting updated.

Wait....

I have seen this before. I know I have. Despite numerous searches and banging my head against this code for quite some time, I had no luck identifying this problem. But I know that I either getAttribute() or setAttribute()'d before to work this exact problem. And indeed I did.

I have now learned through bitter experience not once, but twice that there is special undocumented syntax that needs to be applied if one wants attributes of a Polymer element to be updated when the underlying property value is updated. I cannot set the published attributes via the attribute attribute of polymer-element anymore. Instead I have to use the published property and set reflect to true:
<link rel="import" href="../bower_components/polymer/polymer.html">
<polymer-element name="hello-you">
  <template><!-- ... --></template>
  <script>
Polymer('hello-you', {
  publish: {
    your_name: {value: '', reflect: true},
    color: {value: '', reflect: true},
  },
  // ...
});
</script>
</polymer-element>
I was nearing my wits' end with this problem. That I have now been forced to solve this twice is a source of much annoyance. Hopefully the title of this post will ensure that I will have better search luck the next time that I have to do battle with this feature.



Day #185

No comments:

Post a Comment