Now for the fun. Now for Dart.
I kid (a little). One of the joys of writing Patterns in Polymer is slipping back and forth between JavaScript and Dart. If I am being honest, I prefer Dart, but both languages have their joys. More than experiencing those joys, I appreciate trying solutions for Polymer in one language and then another. If a solution is pleasant in both languages, then I feel like I have found a nice solution that transcends language—making it a true Polymer pattern.
In some ways, switch back and forth between languages feels a little like spiking solutions, then doing it for real. In real programming that is loads of fun (not to mention is leads to better solutions). Surprisingly, I have found this to be a bad idea writing the book. It is easy to mistake tinghe post-spike flow for a transcendent pattern. Of course it works when switching languages, I already pushed through the gaps in knowledge in the other language.
Instead, I have been making a concerted effort to push the solution forward when switching between languages. When I got my initial spike of
<a-form-input> (it allows Polymer elements to work in native HTML forms) working in Dart, I did not try to get it working in JavaScript. Instead I tried building a base class / element in JavaScript. Now that I have that working in JavaScript, I try something a little different in Dart. Instead of just making <a-form-input> a base class in Dart, I am going to see if I can make it work with the is="a-form-input" syntax.I start by reproducing my latest JavaScript work in Dart. That is, I create a Dart package for
<a-form-input>. In a new git repository, I add a simple Dart Pub pubspec.yaml:name: a-form-input dependencies: polymer: anyThen, I copy in
a_form_element.dart from my initial spike in some of the book's exploratory code into lib/a_form_input.dart. For the most part, the JavaScript version of this library looks identical to the Dart solution—aside from the code annotations:@CustomTag('a-form-input')
class AFormInput extends PolymerElement {
@PublishedProperty(reflect: true)
String name;
@PublishedProperty(reflect: true)
String value;
// ...
}The same class in JavaScript is the more compact:Polymer('a-form-input', {
publish: {
name: {reflect: true},
value: {reflect: true}
},
// ...
});I am going to be honest here: I love the Polymer.dart annotations. If you would have told this old dynamic language aficionado a year ago that I'd love annotations, I'd have thought you were crazy. But now I really appreciate how they distinguish meta-information in the class from actual code. These published properties are core to the functionality of <a-form-input>, but in JavaScript that information gets lost in the rest of the code:Polymer('a-form-input', {
publish: {
name: {value: 0, reflect: true},
value: {value: 0, reflect: true}
},
attached: function(){
this.lightInput = document.createElement('input');
this.lightInput.type = 'hidden';
this.lightInput.name = this.getAttribute('name');
this.parentElement.appendChild(this.lightInput);
},
nameChanged: function(){
this.lightInput.name = this.name;
},
valueChanged: function(){
this.lightInput.value = this.value;
}
});Still, the task at hand tonight is not language comparing. Instead, I want to change the test element in Dart from extending the base. So. in the smoke test page for the book example, I add the is attribute:<!DOCTYPE html>
<html lang="en">
<head>
<link rel="import" href="packages/form_example/elements/x-pizza.html">
<link rel="import" href="packages/a-form-input/a-form-input.html">
<script type="application/dart">export 'package:polymer/init.dart';</script>
</head>
<body>
<form action="test" method="post">
<h1>Plain-old Form</h1>
<input type=text
name="plain_old_param"
placeholder="Plain-old form field">
<x-pizza name="pizza_toppings" is="a-form-input"></x-pizza>
<button>Order my pizza!</button>
</form>
</body>
</html>
Which almost works… except not at all (some of the functionality works, but ultimately crashes ugly). I finally read the actual documentation to find that this is not how "is" works at all! It is meant to extends native DOM elements, not arbitrary elements like I am trying to do here. Sometimes there is nothing for it, but to admit that I am an idiot. OK, much times.
But while working this, I ran into an interesting problem. My
AFormInput class does not seem to see attributes changing when moved into a package. That is, the same exact a_form_input.dart when used locally or used via a package… behaves differently. And it is an important difference. The attributeChanged() callback is never invoked in the super class via a package:library a_form_input;
import 'package:polymer/polymer.dart';
import 'dart:html';
@CustomTag('a-form-input')
class AFormInput extends PolymerElement {
@PublishedProperty(reflect: true)
String name;
@PublishedProperty(reflect: true)
String value;
// ...
AFormInput.created(): super.created();
void attached() {
print('attached!');
super.attached();
}
void attributeChanged(String name, String oldValue, String newValue) {
print('$name: $oldValue → $newValue');
if (name == 'name') lightInput.name = newValue;
if (name == 'value') lightInput.value = newValue;
}
}If my test element (the old <x-pizza> standby) uses a local copy of this same file, then it can update value:import 'package:polymer/polymer.dart';
import 'a_form_input.dart';
// import 'package:a-form-input/a_form_input.dart';
@CustomTag('x-pizza')
class XPizza extends AFormInput {
// ...
_updateText() {
value = 'First Half: ${model.firstHalfToppings}\n'
'Second Half: ${model.secondHalfToppings}\n'
'Whole: ${model.wholeToppings}';
}
// ...
}But if I swap the a_form_input.dart lines, then changes to the value attribute (or the name attribute) never trigger the attributeChanged() callback. I play with this for some time, but I cannot solve this. Everything else works—a hidden input is generated by <a-form-input> in either case. The attached() lifecycle method is called in either case. But I am clearly missing something in the packaged version. Something to solve tomorrow.Day #13
No comments:
Post a Comment