Wednesday, September 25, 2013

Reading Attributes from Polymer Tags


After 884 consecutive days of blog posting and 4 resulting books, I begin to fancy myself a testament to the power of small chunks of perseverance. Lucky for me and, more importantly, the size of my head, I have nights like last night to keep me humble. Well, humble is probably stretching it. What's the word for almost declaring myself super-awesome-hacker-extraordinaire, but realizing I might be long on one or two adjectives? Whatever that word is, that's me.

But in all seriousness, I was thwarted in my efforts to create a custom element for an embedded version of the ICE Code Editor. I tried to use Polymer.dart for the effort, but found that I could not bind attributes in the custom element to instance variables in the backing PolymerElement class. Frustrated, I went so far as to initially start tonight's post with:
I find it maddening that there is no way to access the original tag from Polymer.dart code. At the same time, I suspect that this inability speaks to the very heart of Polymer—binding data with elements and all. But for the beginner, it's freaking annoying.
And then I took a moment and a day to think...

And thinking, I realized that I had not done this. I never tried to access the original attributes directly from the page in which my custom element is used:
<ice-code-editor src="embed_a.html" line-number="49"></ice-code-editor>
Actually, before doing anything else, I switch from line-number (previously implemented as a data- attribute before the switch to Polymer). Instead, I will use lineNumber, which will work as a Polymer variable:
<ice-code-editor src="embed_a.html" lineNumber="49"></ice-code-editor>
The Polymer element file remains nearly the same as last night:
<polymer-element name="ice-code-editor" attributes="src,lineNumber">
  <template>
    <h1>The src is "{{src}}"</h1>
    <content></content>
  </template>
  <script type="application/dart" src="ice_polymer.dart"></script>
</polymer-element>
And the backing code referenced in the src attribute still retains the same outline:
import 'package:polymer/polymer.dart';
import 'dart:html';
import 'package:ice_code_editor/ice.dart' as ICE;

@CustomTag('ice-code-editor')
class IceCodeEditorElement extends PolymerElement with ObservableMixin {
  @observable String src;
  @observable int lineNumber;

  void created() {
    super.created();
    // build element here...
  }
}
I will do the bulk of the work in the created() method which is called by Polymer after the custom element is… created.

Tonight's stroke of genius comes courtesy of realizing that my custom IceCodeEditorElement class is extending PolymerElement, which itself extends CustomElement. The thing about CustomElement is that it has an attributes property:
    src = host.attributes['src'];
    lineNumber = int.parse(host.attributes['lineNumber']);
With that I can load the source file and set the line number as needed:
  void created() {
    super.created();

    src = host.attributes['src'];
    lineNumber = int.parse(host.attributes['lineNumber']);

    var container = new DivElement()
      ..id = 'ice-${this.hashCode}'
      ..style.width = '600px'
      ..style.height = '400px';

    host.children.add(container);

    var editor = new ICE.Editor('#${container.id}');

    HttpRequest.getString(src).then((response) {
      editor.content = response;
      editor.editorReady.then((_)=> editor.lineNumber = lineNumber);
    });
  }
After reading the src and lineNumber attributes, I assign them to the observed instance variables of the same name. This will allow the template to include those values if it needs them. In the template above, I just use src as part of the heading (more as a proof-of-concept than for any practical reason). Once I have those values, I create a new instance of Editor. Then I make a HTTP request for the resource specified by src. Once the content of that request comes back, I assign it to the editor's content property. The last thing that I do is to scroll the editor to the proper line number.

And, with that very simple change in place, I have an embedded version of ICE working, thanks to Polymer:



That is a full featured, custom element. And if I had just paid a little bit more attention yesterday, I might have realized how easy it was to create it.

Someday I might achieve that level of awesomeness. Until then, I can only put together my next, small chunk of learning. Tomorrow.


Day #885

4 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. There is a current bug in polymer.dart preventing binding literals in the template. However if you bind it as an expression it does work:

    line-number="{{41}}"



    Theres an issue open for this. Unfortunately if you bind a variable into the template its is not currently updated. At present this doesn't work:

    line-number="{{myInt}"

    The initial value of myInt is passed in but its not updated. Also bare in mind there is conversion between camel case and hyphenated in the template name. It also seems you often need to initialize them to nonnull values to make the binding work. So the variable to bind it to would be:

    @Observable lineNumber = 0;

    This appears to be in active development and keeps changing. A couple of weeks ago you could only bind String variables now ints and bools work fine too.

    ReplyDelete
    Replies
    1. Ah, cool. I never would have thought to try binding a literal like that. I'll play around with it tonight to get a better idea of how it all works. Thanks also for the camelCase / dash conversion tip -- that's a good one to know. I was flying blind since I could barely get basic attribute passing to work, so it's super helpful to know how it is meant to work. Thanks!

      Delete
    2. No problem. I have come up against the same issues fairly recently as you can probably tell!

      Incidentally I found these samples useful. They are fairly up to date and show how polymer.dart is supposed to work in various situations.
      https://github.com/sethladd/dart-polymer-dart-examples

      Delete