Wednesday, February 8, 2012

Stupid Sub-Class Tricks in Dart

‹prev | My Chain | next›

I have model, view and collection base classes factored out of my Dart web application. I have a minor issue that I would like to see if I can resolve tonight. When I add a model to my collection, I would like to have it show up in the UI. Currently I have the add-from-form bit working, but I have to refresh the page ot actually see the new record.

In order for this to work, I will need a create() method on the collection class. The create() method will create a new record and add it to the collection. Adding it to the collection will, in turn, generate an "add" event for which the collection view can listen and do its thing. I have some of the parts necessary for this, but not all.

First up, the create() method:
class HipsterCollection {
  // ...
  create(attrs) {
    var new_model = model(attrs);
    new_model.save(callback:(event) {
      this.add(new_model);
    });
  }
  // ...
}
Nothing fancy there. I create a new model with the model function constructor, then save it. My model class already supports an optional callback that is used here to add the newly created model to the collection.

As for the add() method, I add the model to the models property (which is a simple array) and then dispatch an 'add' event:
class HipsterCollection {
  // ...
  add(model) {
    models.add(model);
    on.add.
      dispatch(new CollectionEvent('add', this, model:model));
  }
  // ...
}
With that, I need to modify the view class to allow a collection to be injected into it. I start in the view base class:
class HipsterView {
  var model, el, collection;

  HipsterView([this.model, this.el, this.collection]) {
    if (this.el is String) this.el = document.query(this.el);
    this._initialize();
  }
  // ...
}
Here I am making use of Dart's very nice "generative constructors" with optional parameters. The constructor HipsterView([this.model, this.el, this.collection]) does just what one would expect: assign this.model if a model parameter is supplied, assign this.el if el is supplied, etc. That is, I could assign the model and collection properties of a HipsterView object with the following:
new HipsterView(model: model_object, collection: collection_object);
That is definitely nice. Except I do not want to create a HipsterView, I want to create a object that sub-classes a HipsterView like the AddComic view:
#import('HipsterView.dart');

class AddComic extends HipsterView {
  // ...
}
Back in my main() entry point, I do this like this:
#import('Collections.Comics.dart', prefix: 'Collections');
#import('Views.Comics.dart', prefix: 'ViewsFIXME01');
#import('Views.AddComic.dart', prefix: 'ViewsFIXME02');

main() {

  var my_comics_collection = new Collections.Comics()
    , comics_view = new ViewsFIXME01.Comics(
        el:'#comics-list',
        collection: my_comics_collection
      );

  my_comics_collection.fetch();

  new ViewsFIXME02.AddComic(
    el:'#add-comic',
    collection: my_comics_collection
  );
}
(the ViewsFIXME01 and ViewsFIXME02 prefixes are a reminder to fix these once a Dart bug has been fixed).

Unfortunately, this does not work. When I load the page, I get the following error:
Internal error: 'http://localhost:3000/scripts/main.dart': Error: line 15 pos 3: invalid arguments passed to constructor 'AddComic' for class 'AddComic'
  new ViewsFIXME02.AddComic(
  ^
Ugh. It seems that I cannot rely on the constructor definition in my base class. Instead, I have to reproduce the same constructor signature in my sub-class:
class AddComic extends HipsterView {
  AddComic([collection, model, el]): super(collection:collection, model:model, el:el);
  // ...
}
Ew. I am not a fan of that. It seems that, if I do not define a constructor in my sub-class, all that Dart does for me is forward an empty sub-class constructor to super() (without arguments). If I want anything more sophisticated, I have to manually transcribe the parameters list like this.

Unfortunately, my trouble does not end there. In my sub-class, I need to perform some post-instantiation initialization:
class AddComic extends HipsterView {
  AddComic([collection, model, el]): super(collection:collection, model:model, el:el);

  void _initialize() {
    print("sub initialize");
    el.on.click.add(_toggle_form);
  }
  // ...
}
In my base class, I had defined my constructor to invoke the _initialize() method:
class HipsterView {
  var model, el, collection;

  HipsterView([this.model, this.el, this.collection]) {
    if (this.el is String) this.el = document.query(this.el);
    print(this.el);
    this._initialize();
  }

  void _initialize() { print("super initialize"); }
  // ...
}
But when I load the page, I am not seeing my "sub initialize" message—I only see "super initialize". I can get this working by declaring _initialize() as an abstract method and removing the function body, but then I have to define _initialize() in all sub-classes. I want this to be an optional thing.

It seems crazy that I cannot access the _initialize() method from the super class. And, after much fiddling, I find that I can access the sub-class's method, but only if it is public. That is, if I remove the underscore from the method name:
class HipsterView {
  var model, el, collection;

  HipsterView([this.model, this.el, this.collection]) {
    if (this.el is String) this.el = document.query(this.el);
    print(this.el);
    this.post_initialize();
  }

  void post_initialize() { print("super initialize"); }
  // ....
}
If I then define post_initialize() in the sub-class:
class AddComic extends HipsterView {
  AddComic([collection, model, el]): super(collection:collection, model:model, el:el);

  void post_initialize() {
    print("sub initialize");
    el.on.click.add(_toggle_form);
  }
  // ...
}
Then I finally see the "sub initialize" message. More importantly, on-click handler is again working.

I think that is enough for one night. I hope to complete the dynamic UI add tomorrow. It was a bit rough today, but I got myself pretty close. I am none too sure about Dart's requirement to explicitly support the super class's parameters. I am definitely not a fan of not being able to access an over-ridden private method from the super class (this seems like a bug). I am a fan of the optional generative constructor format. And, since I made progress in my MVC framework, I will count tonight as a win.


Day #290

No comments:

Post a Comment