Sunday, December 7, 2014

Minimum Viable Polymer Mixin (Dart)


As pointed out in comments last night, there may be yet another minimum viable Polymer.dart mixin than what I identified.

The example mixin that I have been using is <a-form-input>, which enables arbitrary Polymer elements to serve as <input> elements in native HTML forms. Yesterday's solution was for the mixin to define form-like instance variables (e.g. name and value), but not mark them as published properties:
abstract class AFormInputMixin {
  // @PublishedProperty(reflect: true)
  String name;

  // @PublishedProperty(reflect: true)
  String value;

  void attached() { 
    // Use name and value in here...
  }
}
It was then the responsibility of the concrete class to mark them as published after mixing the abstract class in:
import 'package:polymer/polymer.dart';
import 'package:a-form-input/a_form_input_mixin.dart';

@CustomTag('x-pizza')
class XPizza extends PolymerElement with AFormInputMixin {

  @PublishedProperty(reflect: true)
  String name;

  @PublishedProperty(reflect: true)
  String value;
  // ...
}
I like the expressiveness of the class declaration (this is a Polymer element with form-like traits), but needing to declare properties from the mixin in the concrete class stinks.

James Hurford suggested that it is possible to push the published annotations back into the mixin if I use the readValue()/writeValue() version of properties instead of instance variables:
class AFormInputMixin implements Observable, Polymer {
  @PublishedProperty(reflect: true)
  String get name => readValue(#name);
  set name(String n) => writeValue(#name, n);
  // String name;

  @PublishedProperty(reflect: true)
  String get value => readValue(#value);
  set value(String v) => writeValue(#value, v);
  // String value;

  attached() { /* ... */ }
}
And happily, if I remove the @PublishedProperty calls from my concrete class... everything continues to work.

About the only drawback to this approach is some warning about missing methods:
$ dartanalyzer lib/a_form_input_mixin.dart
Analyzing [lib/a_form_input_mixin.dart]...
[warning] Missing concrete implementation of getter 'Element.onStalled', 'Polymer.bindProperty', getter 'Element.clientLeft', getter 'Element.outerHtml' and 272 more (/home/chris/repos/a-form-input-dart/lib/a_form_input_mixin.dart, line 6, col 7)
1 warning found.
Well, OK 276 missing methods.

I can eliminate those by removing the implements modified on my mixin class:
library a_form_input;

import 'package:polymer/polymer.dart';
import 'dart:html' show HiddenInputElement;

abstract class AFormInputMixin  {
  @PublishedProperty(reflect: true)
  String get name => readValue(#name);
  set name(String n) => writeValue(#name, n);

  @PublishedProperty(reflect: true)
  String get value => readValue(#value);
  set value(String v) => writeValue(#value, v);

  void attached() { /* ... */ }
}
As long as I continue to import the polymer package—even if I do not use it—then the mixin continues to work. I still get warnings about readValue()/writeValue() not being defined:
$ dartanalyzer lib/a_form_input_mixin.dart
Analyzing [lib/a_form_input_mixin.dart]...
[warning] The method 'readValue' is not defined for the class 'AFormInputMixin' (/home/chris/repos/a-form-input-dart/lib/a_form_input_mixin.dart, line 8, col 22)
[warning] The method 'writeValue' is not defined for the class 'AFormInputMixin' (/home/chris/repos/a-form-input-dart/lib/a_form_input_mixin.dart, line 9, col 25)
[warning] The method 'readValue' is not defined for the class 'AFormInputMixin' (/home/chris/repos/a-form-input-dart/lib/a_form_input_mixin.dart, line 13, col 23)
[warning] The method 'writeValue' is not defined for the class 'AFormInputMixin' (/home/chris/repos/a-form-input-dart/lib/a_form_input_mixin.dart, line 14, col 26)
4 warnings found.
But I can eliminate those easily enough in my abstract mixin class. I need only declare the method signature and allow concrete classes to provide the definition:
import 'package:polymer/polymer.dart';

abstract class AFormInputMixin  {
  @PublishedProperty(reflect: true)
  String get name => readValue(#name);
  set name(String n) => writeValue(#name, n);

  @PublishedProperty(reflect: true)
  String get value => readValue(#value);
  set value(String v) => writeValue(#value, v);

  readValue(v);
  writeValue(n,v);

  void attached() { /* ... */ }
}
With that, I have no static type warnings:
$ dartanalyzer lib/a_form_input_mixin.dart
Analyzing [lib/a_form_input_mixin.dart]...
No issues found
I also get no compile-time or run-time warnings, and my concrete class continues to function. That is, when my concrete <x-pizza> element updates name and value (that are now completely defined and annotated in my mixin), these changes are reflected on the element attributes and on the hidden <input> injected into the DOM by <a-form-input>:



It is a little awkward to require the mixin author to use the readValue() / writeValue() syntax, but it is a heck of a lot nicer doing that than requiring concrete class authors to publish properties from the mixin. I can live with this.


Day #17

4 comments:

  1. That's weird, as I don't get any of those warnings. Maybe it's the version of Dart or Polymer you're using? Then what you're doing works, so, meh :-)

    ReplyDelete
  2. Wait, I know what you've done. When I created the mixin, it was as an abstract class, so implementing when Observable and Polymer it didn't matter if the methods were defined or not, as the needed methods are in the destination Polymer element class. So you should be defining it as

    abstract class AFormInputMixin implements Observable, Polymer { ... }

    which will work as a mixin.

    ReplyDelete
    Replies
    1. Yup, you are correct again. I had originally started this as an abstract class, but accidentally dropped the abstract keyword while experimenting and wound up with my woes (before switching again to abstract for unrelated reasons).

      Delete
    2. What's annoying is that you can now extend an input element using Polymer in Dart, and it seems to work perfectly in Google Chrome Version 38.0.2125.122 (64-bit), but not in Firefox. Though that may have changed in the latest version of Firefox. Doubt it will work in Safari or IE either.

      Delete