Thursday, October 3, 2013

What's Better Than One Polymer Element?

Having gotten the <ice-code-editor> element working in Polymer.dart, I would like to see if it is possible to get two instances of the element on the same page. Something like this would be very useful in a tutorial. The first element could contain an early version of the project, with subsequent elements containing more and more sophistication.

I have some reason to expect this work in the underlying ICE Code Editor. I spent some time getting multiple versions of the editor working to better support testing. Of course none of the tests ever explicitly tried to run two instances at the same time, but what could go wrong?

I kinda feel like spiking this rather than driving a feature via tests. So, in the sample page, I add a second custom element:
asdf asdf as df sadf as df asdf

<ice-code-editor src="embed_a.html" line_number="21"></ice-code-editor>

asdfsadfasd sadf asdf  sdf asdf a sdfsfd

<ice-code-editor src="embed_b.html" line_number="49"></ice-code-editor>

asdfsadfasd sadf asdf  sdf asdf a sdfsfd
Naturally, that does not work. I get a lovely “null object does not have a method” stack trace:

This seems not so much a Polymer issue as it is a js-interop concern. The error is being thrown from the js-interop code that wraps the JavaScript ACE editor:
class Ace extends jsw.TypedProxy {
  static Ace edit(dynamic el) => Ace.cast(js.context['ace'].edit(el));
  // ...
Specifically, the js.context['ace'] property is null in the second Polymer instance. My first thought is to despair that the js-interop context created by the first element is not valid in the second instance. But this does not explain how I can create 150 separate instances in my tests. Even though those test instances are never active at the same time, they all reuse the same js.context['ace'] to create the editor part of ICE.

This leads me to suspect a simple timing issue. Specifically, I believe that when the second <ice-code-editor> tag is created, the ACE JavaScript code is not loaded and evaluated. Even though it is not evaluated, ICE can tell that it has been loaded by virtue of checking for a <script> tag with a source of ace.js. Seeing that tag, ICE assumes that ACE is loaded and evaluated. This is a fine assumption in tests, but is not valid in this case.

To try out my theory, I add a simple 100 millisecond delay to ICE when it believes the JavaScript editor to already be loaded:
_startAce() {
    if (_isAceJsAttached) {
      new Timer(
        new Duration(milliseconds: 100),
    else {
      // Load ace.js for the first time...
And that seems to do the trick. I now have two different <ice-code-editor> elements running two different Three.js simulations and scrolled to different lines in the code:

Not only does it work, but the two tags seem to be completely separate. Updates to one editor only update the simulation in that instance—the preview in the other editor remains unchanged. This ought to be expected when everything is kept in the shadow DOM in Polymer elements, but I cannot keep the JavaScript code in the shadow DOM. So there was a fear that problems would arise thanks to the shared JavaScript. Happily, this does not seem to be the case.

The timer-based solution is inappropriate for real-world usage. There may be high latency cases in which 100ms is not sufficient. I think that I will back out tonight's changes and TDD the correct behavior—possibly with a static completer—tomorrow. But, for now, this was a successful spike—it seems as though this is gonna work quite nicely.

Day #893

No comments:

Post a Comment