Thursday, December 12, 2013

Communicating with Child Polymers Redux


I love learning new stuff. I don't mean just playing with new stuff. Or even bending new stuff my will. I mostly enjoy really learning new stuff—how it wants to be used. Really, there is not a ton to be gained just playing with stuff. And bending it to my will is just another way of saying that I'm using a new tool with an old mentality. But when I really learn a new thing, my mentality changes. Then I really have a new tool to use for new and old problems alike.

This is one of the reasons that I am enjoying writing a book on Polymer in JavaScript and Dart. When I think that I have a solution the way that Polymer wants to be used, I can try it out in the other language to see if it still works. I found last night that one approach I took to communicating to child Polymers was not right. It worked in Polymer.dart, but not the JavaScript version of the library.

After a bit more experimentation in the JavaScript version of my code, I found another promising approach. To communicate the initial load value from my parent <store-changes> element down to the child <store-changes-load>, I bound an instance variable in the template that references the child:
<link rel="import" href="store-changes-load.html">
<polymer-element name="store-changes">
  <template>
    <store-changes-load current="{{_initial}}"></store-changes-load>
    <content></content>
  </template>
  <script>
    Polymer('store-changes', {
      // ...
      _fetchCurrent: function() {
        this.record = this.store.value;
        this._initial = this.record;
      },
      // ...
    });
  </script>
</polymer-element>
When the initial value is loaded in _fetchCurrent(), the _initial value is updated. Since that same instance variable is bound in the Polymer's <template>, the <store-changes-load> element can see the new value. This feels like a good solution—not because it works, but because I need to do so little work. It feels like I am using the framework, not fighting it.

And indeed, Polymer provides observer helpers that can watch attribute in the child element to make this kind of thing easy:
<polymer-element name="store-changes-load" attributes="current">
  <script>
    Polymer('store-changes-load', {
      // ...
      currentChanged: function(oldValue, newValue) {
        console.log('old: '+ oldValue);
        console.log('new: '+ newValue);
      },
      // ...
    });
  </script>
</polymer-element>
To see if this really is the Polymer way, I switch back to Dart. I start with the Polymer definition:
<polymer-element name="store-changes">
  <template>
    <store-changes-load current="{{initial}}"></store-changes-load>
    <content></content>
  </template>
  <script type="application/dart" src="store_changes.dart"></script>
</polymer-element>
I am binding the public initial value because private variables do not seem to work in Polymer.dart data binding.

Then, in the backing class, I declare initial as a string with a dummy initial value:
@CustomTag('store-changes')
class StoreChangesElement extends PolymerElement {
  String initial = 'THIS SHOULD CHANGE!!!';
  // ...
  void _fetchCurrent() {
    stores.forEach((store) {
      store.fetch().then((_r) {
        record = _r;
        initial = record['current'];
        print('initial: $initial');
      });
    });
  }
  // ...
}
Just as in the JavaScript version, I set the initial value when the change history record is retrieved from the data store. Updating the value of initial in the object updates the value in the <template>, which in turns updates the value of <store-change-load>'s current attribute. At least that is what it ought to do.

Unfortunately, when I make the attribute changes in <store-changes-load>:
@CustomTag('store-changes-load')
class StoreChangesLoadElement extends PolymerElement {
  @published String current = '';
  // ...
  StoreChangesLoadElement.created(): super.created();
  // ...
  _listenForLoad() {
    new Timer(
      new Duration(milliseconds: 900),
      (){
        if (current == null || current == '') return;
        print('current: $current');
        editable.innerHtml = current;
      }
     );
  }
  // ...
}
I am still seeing the default value:



Judicious print() statements verify that the initial value is being set correctly and that the current attribute is being used after the data is loaded:
initial: 
          <h1>Change 08</h1>
        
current: THIS SHOULD CHANGE!!!
But the child element just does not see the change.

Darn it. I really thought I had this licked. Back to the drawing board? Maybe, or it may be time to dig into the Polymer.dart source code. I really think this ought to work.


Day #963

9 comments:

  1. I think you might need to use the helper function to observable(). It works for lists, haven't tried it on strings though. Of coarse I could be completely off track here.

    ReplyDelete
    Replies
    1. Dam auto correct it's toObservable()

      Delete
    2. I tried that, but no dice. I also tried converting the “initial” instance variable to a List and calling toObservable(), but that does not work -- only strings seems to work in that data binding.

      Delete
    3. Yes well I was totally off track with that toObservable() anyway. It works only for iterables or maps, but not strings. I think the answer does lie in observable though, maybe using a custom change listener, but I feel that's not what you're looking for. I can tell you this does work with lists. You can pass a observable list to another Polymer Element, and have that element add to this list and have the originator parent element pick up and show those changes. I've done it.

      Delete
    4. I gave this a go, and was able to get a child of my custom called unsurprisingly to alter the value of count and increment it, and have the value in to update. https://gist.github.com/terrasea/7954850

      Delete
    5. I keep making that mistake, I was meant to show <store-changes> <store-changes-load> elements

      Delete
    6. Huge thanks for giving that a try! It definitely saves me the trouble of trying to break it down into a small test case :)

      I'm doing something wrong in my copy because I have been unable to get it working regardless of data type. I'll keep at it and (hopefully) report what I've done wrong in tonight's post. Thanks yet again!

      Delete
    7. Arrrrrrrgh! It was a buggy version of observe: http://japhr.blogspot.com/2013/12/wait-second-i-know-this.html. I can't thank you enough for the test case. I would not have figured that out without it :)

      Delete