Sunday, December 8, 2013

Dynamically Generating Polymer Elements (A Separation of Concerns)


I am fairly pleased with the element-in-the-middle approach last night that enabled me to link two Polymer elements. I still doubt this is a panacea, but might prove a useful tool in the Polymer toolbelt. That said, I see a disturbing trend in my custom elements, <polymer-*> or otherwise:
    <store-changes>
      <polymer-localstorage name="store-changes" value="{{value}}">
      </polymer-localstorage>
      <polymer-ajax url="http://localhost:31337/widgets/change-history"
                    handleAs="json">
      </polymer-ajax>
      <store-changes-load></store-changes-load>
      <change-sink>
        <div contenteditable>
          <!-- actual changes occur here -->
        </div>
      </change-sink>
    </store-changes>
The two <polymer-*> elements are there solely to enable <store-changes> to persist change history. Last night's <store-changes> is there solely to use that persisted data to initialize the contenteditable tag. In other words, each of these three tags are there solely to support the topmost <store-changes> element. And while they do an admirable job of doing that, they tend to get in the way of readability.

So tonight, I am going to take a hint from the <polymer-ajax> code. Like my <store-changes> tag, the <polymer-ajax> relies on another element—<polymer-xhr>—to do its thing. Unlike my <store-changes> tag, <polymer-ajax> never requires the programmer to manually create the tag on which it relies. Instead <polymer-ajax> itself creates the <polymer-xhr> tag, keeping the HTML clean and maintaining a separation of concerns. That seems a worthy ideal to which to aspire.

I start by removing last night's <store-changes-load> from the web page. Instead of manually creating it, I will have <store-changes> do so when it is, itself, ready:
@CustomTag('store-changes')
class StoreChangesElement extends PolymerElement {
  // ...
  StoreChangesElement.created(): super.created();
  ready() {
    super.ready();
    new Element.tag('store-changes-load');
  }
  // ...
}
Here, I am only creating the <store-changes-load> tag, not actually adding it to the document. Since it is not being added to the page, it never “enters the view." This in turn necessitates one other change. In the <store-changes-load> class, I have to start linking the store and content-editable elements when the Polymer is ready, not when it enters the view. Instead of:
@CustomTag('store-changes-load')
class StoreChangesLoadElement extends PolymerElement {
  // ...
  StoreChangesLoadElement.created(): super.created();
  enteredView() {
    super.enteredView();
    scheduleMicrotask(_linkElements);
  }
  // ...
}>
I do nearly the same, but in the ready() lifecycle method:
@CustomTag('store-changes-load')
class StoreChangesLoadElement extends PolymerElement {
  // ...
  StoreChangesLoadElement.created(): super.created();
  ready() {
    super.ready();
    scheduleMicrotask(_linkElements);
  }
  // ...
}
And that actually works. This surprises me a little because <store-changes-load> is synchronizing two elements in the web page. In order to do so, I had been querying the ownerDocument (to find the <store-changes> element) and querying the children of the <store-changes> element for the content-editable. At least, that's what I thought. It turns out that I had been querying for the content-editable in the entire document with the dart:html top-level query() method:
  // ...
  _findElements() {
    store = ownerDocument.query('store-changes');
    editable = query('[contenteditable]');
  }
  // ...
I had expected that to really be performing a this.query() which, in reality would return nothing since there are no child elements.

To make this perfectly explicit, I query instead from the storage element:
  // ....
  _findElements() {
    store = ownerDocument.query('store-changes');
    editable = store.query('[contenteditable]');
  }
With that, everything still works and I am a little more explicit in how I find the content-editable element. Really, I probably ought to add a property to the <store-changes-load> element so it know the <store-changes> element that created it. That would eliminate potential confusion when multiple change stores are in active use.

I will leave that for another day. For now, I am happy to have applied a lesson learned from the Polymer Elements code in my own code. It seems a valuable one.


Day #959

3 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Very interesting to follow your experiments.
    It seems Emacs doesn't show that query is deprecated and querySelector should be used instead.
    Cheers

    ReplyDelete
    Replies
    1. It doesn't, but dartanalyzer does (I run that as part of the test suite). I ignore that one since it's only deprecated until the new query() comes along, which will have nearly identical functionality. The renaming is done to follow the standard and is only described as “semi-breaking”:

      https://groups.google.com/a/dartlang.org/d/msg/misc/dJnz6DbyPbs/eDe5BwAm140J

      Delete