Thursday, May 29, 2014

Waiting for Polymer Assets


I really should have done this before I went through all the SVG craziness, but it would be super helpful if I could specify toppings via attribute of my <x-pizza> Polymer element, which enables building pizzas via web component:



That is, I would like to specify that a two topping special starts as something akin to:
  <body>
    <div class="container">
      <h1>Ye Olde Dart Pizza Shoppe</h1>
      <x-pizza toppings="sausage,pepperoni"></x-pizza>
    </div>
  </body>
I expect that it will be fairly easy to push this information into my Polymer element, complicated only slightly by the model-driven view approach taken inside the element. Slightly more difficult is reflecting internal changes back on this attribute.

Anyhow, getting started, I try a simple split() to break the attribute into a list of attributes that can be added to the model. I do this in the created() life-cycle method of the Polymer class:
@CustomTag('x-pizza')
class XPizza extends PolymerElement {
  //...
  @published String toppings = '';
  XPizza.created(): super.created() {
    model = new Pizza();

    toppings.
      split(new RegExp(r'\s*,\s*')).
      forEach((t){ model.wholeToppings.add(t); });
  }
  //...
}
When I load the page, however, I get all manner of errors related to adding empty SVG content. It turns out that my <polymer-ajax> strategy for loading SVG assets finally bit me. By starting the drawing off with toppings, I am no longer giving my assets time to load.

Async Dart to the rescue. I add an svgLoaded completed in the created() method:
Completer svgLoaded;
  XPizza.created(): super.created() {
    // ...
    svgLoaded = new Completer();
  }
The _updateGraphic() method now needs to honor that completer. If the completer is still in progress, then updating the graphics needs to wait for the assets to fully load and, only then, actually update the graphic:
  _updateGraphic() {
    if (!svgLoaded.isCompleted) {
      svgLoaded.future.then((_) => _updateGraphic());
      return;
    }

    svg = new SvgElement.svg(svgContent['pizza.svg']);
    $['pizza']
      ..children.clear()
      ..append(svg);

    _addWholeToppings();
    _addFirstHalfToppings();
    _addSecondHalfToppings();
  }
Once svgLoaded has completed for the first time, the svgLoaded.isComplete check will always return true. In other words, _updateGraphic() will wait at first but once the assets have loaded, the check for svgLoaded.isComplete will be ignored for the remainder of the element's lifetime.

Deciding when the assets are fully loaded will be the responsibility of the recently created responseReceived() method:
  responseReceived(e, detail, node){
    svgContent[node.id] = detail['response'];
    if (node.id == 'pizza.svg') _updateGraphic();
  }
This method accepts what the <polymer-ajax> method supplies when invoked from an on-polymer-response callback. Each of the assets in the <x-pizza> template call this method:
<polymer-element name="x-pizza">
  <template>
    <polymer-ajax
       id="pizza.svg"
       url="/packages/svg_example/images/pizza.svg"
       on-polymer-response="{{responseReceived}}"></polymer-ajax>
    <polymer-ajax
       id="pepperoni.svg"
       url="/packages/svg_example/images/pepperoni.svg"
       on-polymer-response="{{responseReceived}}"></polymer-ajax>
    <polymer-ajax
       id="sausage.svg"
       url="/packages/svg_example/images/sausage.svg"
       on-polymer-response="{{responseReceived}}"></polymer-ajax>
    <polymer-ajax
       id="green_pepper.svg"
       url="/packages/svg_example/images/green_pepper.svg"
       on-polymer-response="{{responseReceived}}"></polymer-ajax>
     <!-- ... -->
  </template>
  <script type="application/dart" src="x_pizza.dart"></script>
</polymer-element>
So, by the time all assets have loaded, responseReceived() will have been invoked 4 times. I could hard-code this, but that would not be future-proof. Instead, I query my element's shadow DOM for the number of <polymer-ajax> elements:
  responseReceived(e, detail, node){
    svgContent[node.id] = detail['response'];
    if (svgContent.length == shadowRoot.queryAll('polymer-ajax').length) {
      svgLoaded.complete();
    }

    if (node.id == 'pizza.svg') _updateGraphic();
  }
With that, I have <x-pizza> accepting start values:



That seems a good stopping point for tonight. Tomorrow, I will pick back up with reflecting changes back from the model onto topping attributes. There may be little more than watching attribute changes, but if tonight is any indication, it is a good bet that some previous silly assumption on my part will come back to bite me. Stayed tuned!


Day #78

No comments:

Post a Comment