Friday, March 30, 2012

Dart Templates (Bleeding Edge)

‹prev | My Chain | next›

When I wake up later today, I plan to do nothing but edit Dart for Hipsters. But of course, now is not the time to displease the gods of my chain for they have been so good to me. So before I get a few hours of sleep, I make a small offering in the form of messing about with the template stuff in Dart's bleeding edge.

I already have much of bleeding edge checked out from the other night when I was trying out dartdoc. But I do not have the utils sub-directory. So I again install subversion:
sudo apt-get install subversion
Then in the same directory in which I checked out the other bits of the SDK, I grab utils:
➜  dart  svn checkout http://dart.googlecode.com/svn/branches/bleeding_edge/dart/utils
A    utils/apidoc
A    utils/apidoc/html_diff.dart
A    utils/apidoc/.gitignore
...
Checked out revision 6072.
Then I remove subversion on principle:
sudo apt-get remove subversion
With that, I am ready to templatize something!

In my sweet Hipster MVC-based comic book application, I already have a poor man's template defined:
  _singleComicBookTemplate(comic) {
    return """
      <li id="${comic['id']}">
        ${comic['title']}
        (${comic['author']})
        <a href="#" class="delete">[delete]</a>
      </li>
    """;
  }
This seems like a nice candidate on which to experiment.

So I extract that out into ComicBook.tmpl as:
template ComicBook(comic) {
  <li id="${comic['id']}">
    ${comic['title']}
    (${comic['author']})
    <a href="#" class="delete">[delete]</a>
  </li>
}
Next, I try compiling that into Dart code with the template utility:
➜  dart-comics git:(modal-dialog) ✗ ~/repos/dart/utils/template/template public/scripts/ComicBook.tmpl 
:1:20: fatal: Template paramter missing type and name
template ComicBook(comic) {
                   ^^^^^
Parsed /home/cstrom/repos/dart-comics/public/scripts/ComicBook.tmpl in 63 msec.
Codegen /home/cstrom/repos/dart-comics/public/scripts/ComicBook.tmpl in null msec.
Wrote file /home/cstrom/repos/dart-comics/public/scripts/ComicBook.dart in null msec.
Ugh. Does it really need to know that code is an instance of HipsterModel? Can it even find that class if it needs it? There is an easy way to find out and that is adding the type declaration:
template ComicBook(HispterModel comic) {
  <li id="${comic['id']}">
    ${comic['title']}
    (${comic['author']})
    <a href="#" class="delete">[delete]</a>
  </li>
}
With that, it compiles:
➜  dart-comics git:(modal-dialog) ✗ ~/repos/dart/utils/template/template public/scripts/ComicBook.tmpl
Parsed /home/cstrom/repos/dart-comics/public/scripts/ComicBook.tmpl in 60 msec.
Codegen /home/cstrom/repos/dart-comics/public/scripts/ComicBook.tmpl in 12 msec.
Wrote file /home/cstrom/repos/dart-comics/public/scripts/ComicBook.dart in 12 msec.
To get that back into my comic book view, I need to #source() (pull directly into library namespace) the generated template code:
#source('ComicBook.dart');
Looking through the generated code, it seems like I need to access the result of the template on the root property:
Element get root() => _fragment;
The problem with that is that, at the point that I extracted this from a working application, I was building HTML strings, not Element objects. So, to use this, I need to replace my previous String function:
    var html = '';
    list.forEach((comic) {
      html += _singleComicBookTemplate(comic);
    });
With an outerHTML call on the root Element:
    var html = '';
    list.forEach((comic) {
      html += (new ComicBook(comic)).root.outerHTML;
    });
Loading that up in Dartium, however throws an error:
Internal error: 'http://localhost:3000/scripts/ComicBook.dart': Error: line 20 pos 56: unterminated string literal
    var e0 = new Element.html('<li id="${comic['id']}">
                                                       ^
Looking at the generated output, I see that it does seem incorrect in that a multi-line string is declared with single quotes:
  ComicBook(this.comic) : _scopes = new Map<String, Object>() {
    // Insure stylesheet for template exist in the document.
    add_ComicBook_templatesStyles();

    _fragment = new DocumentFragment();
    var e0 = new Element.html('<li id="${comic['id']}">
    ${inject_0()}
    (${inject_1()})
    </li>');
    _fragment.elements.add(e0);
    var e1 = new Element.html('<a href="#" class="delete">[delete]</a>');
    e0.elements.add(e1);
  }
And, indeed, adding triple quotes fixes that problem:
  ComicBook(this.comic) : _scopes = new Map<String, Object>() {
    // Insure stylesheet for template exist in the document.
    add_ComicBook_templatesStyles();

    _fragment = new DocumentFragment();
    var e0 = new Element.html('''<li id="${comic['id']}">
    ${inject_0()}
    (${inject_1()})
    </li>''');
    _fragment.elements.add(e0);
    var e1 = new Element.html('<a href="#" class="delete">[delete]</a>');
    e0.elements.add(e1);
  }
I am unsure if that single quote would work in bleeding_edge Dart or not. Regardless, triple quotes definitely eliminates the error.

That eliminated the unterminated string problem, but now I get:
Internal error: 'http://localhost:3000/scripts/ComicBook.dart': Error: line 19 pos 21: type 'DocumentFragment' is not loaded
    _fragment = new DocumentFragment();
                    ^
My Hipster MVC is so well encapsulated, that I have not needed the HTML library yet, which is why DocumentFragment is not defined. To gain access to it, I #import() the HTML library into the view class:
#import('dart:html');
#source('ComicBook.dart');
With that, I have my compiled template working in my Hipster MVC view:


Problems with bleeding edge aside, I cannot say that I am sold on these templates. This was a somewhat contrived, small example. The pseudo-template with which I started seems more appropriate. No doubt this would have worked better on a larger example, but I really go out of my way to keep my views small.

The use of safeHMTL under the template covers is encouraging. That said, the compilation step is painful. Perhaps templates will get first-class support before long. I would also prefer to define most templates inside my view classes. Assuming both of those concerns are addressed, then I can see using these templates. Until then, I will stick with multi-line strings.


Day #342

Making HipsterModel Even Easier

‹prev | My Chain | next›

Up tonight, I document and, if necessary, polish off HispterModel, the model class in my Dart Hipster MVC library.

I see that I am optionally allowing the collection to be set on the model (it's useful for delegating the backend URL):
class HipsterModel implements Hashable {
  // ...
  HipsterModel(this.attributes, [this.collection]) { /* ... */ }
}
This was a bit of premature optimization on my part as I am not using the option collection parameter anywhere. Even when the collection creates a model, it assigns itself after the model is built:
class HipsterCollection implements Collection {
  // ...
  _buildModel(attrs) {
    var new_model = modelMaker(attrs);
    new_model.collection = this;
    return new_model;
  }
}
If this ever becomes useful, I can add it back or define a named constructor (e.g. new ComicBook.withCollection(comic_collection)). For now, I remove it.

This leads me to wonder again about the model subclass. Currently, I have to define a redirection to super() in all subclasses:
class ComicBook extends HipsterModel {
  ComicBook(attributes) : super(attributes);
}
I would prefer to only define that as necessary. In other words, I would like to reduce the minimal HipsterModel subclass to:
class ComicBook extends HipsterModel {}
In this case, Dart has an implied redirection constructor that effectively calls the superclass constructor with no arguments. In other words, the above is the same as defining the subclass as:
class ComicBook extends HipsterModel {
  ComicBook(): super();
}
As defined now, HispterModel will not support this because I still require attributes. This will work if I make attributes optional:
class HipsterModel implements Hashable {
  /// The internal representation of the record.
  Map attributes;

  HipsterModel([this.attributes]) {
    on = new ModelEvents();
    if (attributes == null) attributes = {};
  }
  // ...
}
I still have the option to define a constructor on ComicBook that takes an argument, but now I do not have to do so. Since I do not, my modelMaker() factory function can no longer pass arguments to the constructor:
class Comics extends HipsterCollection {
  get url() => '/comics';
  modelMaker(_) => new ComicBook();
}
That modelMaker() function still needs to accept an argument in case another subclass wants to use it. Here, I use the convention of an underscore parameter to signify that it is being discarded.

One last hoop that I have to go through is for the collection to assign attributes when they are discarded like that. A simple isEmpty() suffices:
  _buildModel(attrs) {
    var new_model = modelMaker(attrs);
    // Give the factory a chance to define attributes on the model, if it does
    // not, explicitly set them.
    if (new_model.attributes.isEmpty()) new_model.attributes = attrs;
    new_model.collection = this;
    return new_model;
  }
With that, my empty model definition now works, as evidenced by my sample comic book application still working:


That might seem like a lot of work to go through just to eliminate one line, but I am eliminating one line that users of my library need to define. Forcing potential users to write extra code is never cool.

So... win!

Lastly, I notice that I have a TODO in HipsterModel#save():
  // TODO: update
Back before I switched the data sync layer to HipsterSync, this must have seemed to me a difficult task. Now, all that I need to do is pass in 'create' or 'update' depending on whether or not the model has been previously saved to the backend:
  Future save() {
    Completer completer = new Completer();
    String operation = isSaved() ? 'update' : 'create';
    Future after_call = HipsterSync.call(operation, this);
    // ...
  }
With that, I have a well-documented, fully operational Death S... er, model class in Hipster MVC.


Day #341

Thursday, March 29, 2012

Don't Dartdoc the Core API

‹prev | My Chain | next›

Up tonight, I take the advice of John Evans to rework my hard fork of the dart-sdk to use a saner command-line option in dart-doc. Specifically, it makes sense to include an option to enable / disable the inclusion of core Dart API documentation in the output. I do not want it in Hipster MVC, and already have any references to core types pointing to api.dartlang.org.

The problem with my current solution is that it whitelists the files that I want documented and assumes that everything else is located on api.dartlang.org. It works when I can use the string "hipster" to include only libraries in my Hipster MVC package. If I wanted to add another library without the name "Hipster" embedded in it, then I would be out of luck. Besides, I am not really interested in only including certain libraries. I really want to enable / disable the inclusion of core documentation.

After much thought, I settle on a command line switch --no-dart-api. This seems to capture what I want and conform to the standard dartdoc options (e.g. --node-code). If this switch is not provided, then core documentation will be generated—just like it does in upstream. If it is provided....

I set a boolean instance variable accordingly:
// ...
    switch (arg) {
      case '--no-code':
        includeSource = false;
        break;

      case '--no-dart-api':
        includeDartApi = false;
        break;

      // ...
    }
// ...
The value of includeDartApi is passed into the DartDoc class. In there, I filter the list of libraries that will be documented:
// ...
      // Sort the libraries by name (not key).
      _sortedLibraries = world.
        libraries.
        getValues().
        filter(_includeLibrary);
// ...
That _includeLibrary() filter method can then make use of the includeDartApi instance variable:
  bool _includeLibrary(Library library) {
    // Exclude main.dart that might be index of library
    if (library.name == 'main') return false;

    if (includeDartApi) return true;

    if (library.isDartApi) return false;

    return true;
  }
If includeDartApi is true, then all documentation should be generated. If false, evaluation moves down to library.isDartApi. That isDartApi method is new to the Library. In there, I use a Set to describe the entire list of libraries that are considered part of the Dart API:
/** Represents a Dart library. */
class Library extends Element {
  // ...
  static Set get dartApiLibraries() =>
    new Set.from([
      'core',
      'dart:core',
      'coreimpl',
      'dart:coreimpl',
      'dart:isolate',
      'html',
      'dart:html',
      'io',
      'json',
      'uri'
    ]);
  
  bool get isDartApi() => Library.dartApiLibraries.contains(this.name);
}
The nice thing about Set is that it give me the contains() method, which I use in isDartApi to decide if the current library is one of the Dart API libraries.

Easy-peasy and I have dartdoc again generating only documentation for my 6 Hispter MVC libraries:
dart ~/src/dart-sdk/lib/dartdoc/dartdoc.dart \
    --title='Hipster MVC Documentation' \
    --description='API documentation for the very excellent MVC library...' \
  main.dart
Documented 6 libraries, 16 types, and 75 members.
With that working fairly well, I take a little time to actually document Hipster MVC, starting with the router. The dartdoc format is markdown:
class HipsterRouter {
  /// The router exposes named events on which listeners can wait. For example,
  /// if the router has a "page" route, the event will be available on the
  /// 'route:page' event:
  ///     HipsterRouter app = new MyRouter();
  ///       app.
  ///         on['route:page'].
  ///         add((num) {
  ///           print("Routed to page: $num");
  ///         });
  RouterEvents on;
  // ...
}
The result is some very pretty documentation on HipsterRouter. Looking that good, I may not be able to resist documenting the heck out of Hipster MVC.


Day #340

Wednesday, March 28, 2012

Link to External Dart Documentation

‹prev | My Chain | next›

As pointed out to me by Michael Haubenwallner, my dartdoc tweaks still have a bit of a gap in them. When I removed the core documentation from the output, I neglected to point links to the core documentation at api.dartlang.org.

The dartdoc output is pretty slick. It does a very nice job parsing my code and generating basic docs. Eventually, I need to add some details, but the type information alone makes for a good start:


The problem, of course, is the link to List. I want this to be public documentation on the GitHubs, but the most up-to-date information on List is always going to be at api.dartlang.org/dart_core/List.html, not whatever version of Dart I happened to have installed when I compiled the documentation.

The output from dartdoc is something along the lines of:
<h4 id="get:routes">
  <span class="show-code">Code</span>
  <a href="../dart_core/List.html">List</a>
  <strong>get routes</strong>() 
  <a class="anchor-link" href="#get:routes"
     title="Permalink to HipsterRouter.get routes">#</a>
</h4>
Instead of the relative URL "../dart_core/List.html", I need the api.dartlang.org version.

The easiest thing to do would be to jam a conditional into dartdocs's a() function:
  String a(String href, String contents, [String css]) {
    // Mark outgoing external links, mainly so we can style them.
    final rel = isAbsolute(href) ? ' ref="external"' : '';
    final cssClass = css == null ? '' : ' class="$css"';
    return '<a href="${relativePath(href)}"$cssClass$rel>$contents</a>';
  }
But both a() and its dependent relativePath() have checks for absolute paths. Best to keep these functions doing one thing rather than expanding their responsibility.

This means that the calling context needs to supply the necessary absolute, api.dartlang.org URL. Rooting through the dartdoc source, it seems that typeUrl() is the place that I need to check:
// ...
    write(a(typeUrl(type), type.genericType.name));
// ...
Indeed, I need only prepend a path to the return library as necessary:
  /** Gets the URL for the documentation for [type]. */
  String typeUrl(Type type) {
    String library = sanitize(type.library.name);

    // If limiting docs to libraries containing a substring, then point all
    // other docs to api.dartlang.org
    String path = type.library.name.contains(contains) ?
      "" : "http://api.dartlang.org/";

    if (type.isTop) return '$path$library.html';
    // Always get the generic type to strip off any type parameters or
    // arguments. If the type isn't generic, genericType returns `this`, so it
    // works for non-generic types too.
    return '$path$library/${type.genericType.name}.html';
  }
With that, I have my api.dartlang.org URLs for core types:
<h4 id="get:routes">
  <span class="show-code">Code</span>
  <a href="http://api.dartlang.org/dart_core/List.html" ref="external">List</a>
  <strong>get routes</strong>() 
  <a class="anchor-link" href="#get:routes"
     title="Permalink to HipsterRouter.get routes">#</a>
</h4>
But, by virtue of containing the supplied string ("hipster" in this case), my actual library documentation retains the relative URLs:
<h4 id="on">
  <span class="show-code">Code</span>
  <a href="../hipster_router/RouterEvents.html">RouterEvents</a>
  <strong>on</strong>
  <a class="anchor-link" href="#on"
     title="Permalink to HipsterRouter.on">#</a>
</h4>
I am reasonably satisfied with that. I kept the a() function appropriately dumb. Similarly, the typeUrl() feels like a good place to determine whether or not a relative URL is proper. The contains(contains) is a little awkward, so I may have to reconsider the name. But not today.

After regenerating the API documentation for Hipster MVC, I have core types coming from api.dartlang.org and the rest coming from the GitHubs—even HipsterRouter (where the problem was originally noted).

Hopefully nothing else is missing: eee-c.github.com/hipster-mvc.

Day #339

Tuesday, March 27, 2012

Pretty Dartdoc

‹prev | My Chain | next›

I was eventually able to get my dartdoc for Hipster MVC published to GitHub pages:


This is a bit of a pain to accomplish. In my master branch, I dartdoc Hipster MVC into the default docs directory. I use .gitignore to ignore that directory both in master and in the gh-pages branch. Then I switch to the gh-pages branch and copy the generated contents of docs into the top-level of the branch:
➜  hipster-mvc git:(master) dart ~/src/dart-sdk/lib/dartdoc/dartdoc.dart main.dart
Documented 11 libraries, 677 types, and 5544 members.
➜  hipster-mvc git:(master) git co gh-pages
Switched to branch 'gh-pages'
➜  hipster-mvc git:(gh-pages) cp -r docs/* .
➜  hipster-mvc git:(gh-pages) ✗ gst
# On branch gh-pages
# Changes not staged for commit:
#   (use "git add ..." to update what will be committed)
#   (use "git checkout -- ..." to discard changes in working directory)
#
#       modified:   index.html
#
no changes added to commit (use "git add" and/or "git commit -a")
It would be nice if the wiki tab in GitHub supported HTML (and if dartdoc didn't delete and recreate the target directory), but this will do for now.

There are a couple of problems with the generated documentation. First off, the documentation title:


And the page title as well:


The title should read "Hipster MVC Documentation", not "Dart Documentation".

Next, there are several unnecessary core Dart libraries included in the documentation:


If I want to lookup methods on Map, I will head straight for the online Dart API documentation. I will not recall that "I make use of that library in Hipster MVC so why not look there?" Besides, it clutters things up.

Lastly, and somewhat related to the previous point, the sidebar navigation also includes references to the core Dart libraries:


This is different than the inline documentation because the sidebar navigation is not hard-coded on every page of dartdoc. Rather it is defined in a nav.json file which is loaded over Ajax. This saves space (the sidebar contains a lot of redundant information) and lets dartdoc dynamically open sub-menus.

One other nice-to-have with the documentation is a place to stick a small blurb about the project (including a link back to the main GitHub repository).

To verify that I my understanding of nav.json is correct, I hand-edit it, removing the "core", "coreimpl", "html", etc. entries. For good measure, I also hand-edit index.html removing the same information. I then publish to GitHub and... I now have just the Hipster docs:


That is all well and good, but I am not going to hand edit my documentation every time I make a code change. I need to either post-process the generated documentation or modify the code that generates it. Either way I need do it in Dart—this is a Dart chain after all. In the end, I opt to modify the dartdoc code in-place. I have the feeling that I will learn more Dart by working with someone else's code.

Looking through the dart-sdk's lib/dartdoc/dartdoc.dart, it seems that most of the heavy lifting is done in Dartdoc#document(). In there, a list of sorted files is pulled from the frogc compiler's "world":
// ...
      world.resolveAll();

      // Sort the libraries by name (not key).
      _sortedLibraries = world.libraries.getValues();
// ...
If I change that to be the list of values with a name that contains "hipster":
//  ...
      // Sort the libraries by name (not key).
      _sortedLibraries = world.
        libraries.
        getValues().
        filter((library) {
          return library.name.contains('hipster');
        });
// ...
Then regenerating and pushing to GitHub pages, I get auto-generated API docs for just Hipster MVC:


The mainTitle is already an instance variable on Dartdoc (meaning there is an instance getter). I do the same for contains so that I do not have to hard-code 'hipster':
/** Only generate documentation for libraries whose names contains this */
  Pattern contains = '';
Then I add some additional poor-man's command-line processing:
// ...
    switch (arg) {
      // ...
      default:
        if (arg.startsWith('--out=')) {
          outputDir = arg.substring('--out='.length);
        }
        else if (arg.startsWith('--title=')) {
          title = arg.substring('--title='.length);
        }
        else if (arg.startsWith('--contains=')) {
          contains = arg.substring('--contains='.length);
        }
        else {
          print('Unknown option: $arg');
          return;
        }
        break;
    }
// ...
And use them to drive Dartdoc:
// ...
  if (title != null) dartdoc.mainTitle = title;
  if (contains != null) dartdoc.contains = contains;
// ...
Now, I can pass the title and a string that all libraries should match:
dart ~/src/dart-sdk/lib/dartdoc/dartdoc.dart \
   --contains=hipster \
   --title='Hipster MVC Documentation' \
   main.dart
And now I have some pretty decent Hipster MVC documentation:


For good measure, I add a description command line switch and make Hipster MVC that much better.

For what it's worth, I have documented my changes to dartdoc in a github repository.


Day #338

Monday, March 26, 2012

Publishing Dartdoc to Github

‹prev | My Chain | next›

Thanks to a helpful comment on last night's work, I now know that dartdoc is part of the standard Dart SDK. Indeed, had I checked the contents of the SDK that I downloaded yesterday, I would have seen it amongst the other Dart goodness:
➜  src  unzip -l ~/Downloads/dart-linux\ \(3\).zip | grep dartdoc
        0  2012-03-19 17:31   dart-sdk/lib/dartdoc/
     2295  2012-03-19 17:31   dart-sdk/lib/dartdoc/utils.dart
     3856  2012-03-13 13:15   dart-sdk/lib/dartdoc/comment_map.dart
     1096  2012-03-13 13:15   dart-sdk/lib/dartdoc/client-shared.dart
    38064  2012-03-25 16:24   dart-sdk/lib/dartdoc/dartdoc.dart
...
I unzip that into $HOME/src (that's where I store source code that is not under SCM). Then I try generating dartdoc for Hipster MVC:
➜  hipster-mvc git:(master) ✗ dart ~/src/dart-sdk/lib/dartdoc/dartdoc.dart Hipster*dart          
Unknown option: HipsterCollection.dart
Bummer. It seems that dartdoc only accepts a single argument—anything else is treated as an option.

If I generate documentation for HipsterRouter (which depends on HipsterHistory) and then HipsterView (which depends on HipsterCollection and HipsterModel), it seems to work:
➜  hipster-mvc git:(master) ✗ dart ~/src/dart-sdk/lib/dartdoc/dartdoc.dart HipsterRouter.dart  
Documented 5 libraries, 665 types, and 5480 members.
➜  hipster-mvc git:(master) ✗ dart ~/src/dart-sdk/lib/dartdoc/dartdoc.dart HipsterView.dart  
Documented 8 libraries, 672 types, and 5531 members.
But, of course, I have overwritten the results from my first run with my second, leaving me no documentation for HipsterRouter or HipsterHistory:


This will not do.

To get around this, I create an index.dart file that contains nothing other than imports of my two highest level classes:
#import('HipsterView.dart');
#import('HipsterRouter.dart');
When I run dartdoc against that, it seems to pick up the correct number of libraries:
➜  hipster-mvc git:(master) ✗ dart ~/src/dart-sdk/lib/dartdoc/dartdoc.dart index.dart 
Documented 11 libraries, 677 types, and 5544 members.
Unfortunately, "index" was probably a poor choice as this seems to have resulted in an overwritten index.html file:


So I rename that to main.dart (even though there is no main() entry block) and regenerate my documentation:
➜  hipster-mvc git:(master) ✗ dart ~/src/dart-sdk/lib/dartdoc/dartdoc.dart main.dart
Documented 11 libraries, 677 types, and 5544 members.
That seems to have done the trick:


Local documentation does not do much good. I need some way to get that information up to the github repository. At first I try pushing the HTML docs into the wiki, but that has no effect. The wiki tab only houses wiki pages written in one of the various github recognized markups.

So it seems that I need to use github's other documentation vehicle: github pages. To create the github pages, I follow along with the github pages instructions:
➜  hipster-mvc git:(master) ✗ mv docs /tmp/hipster-mvc-docs
➜  hipster-mvc git:(master) git symbolic-ref HEAD refs/heads/gh-pages
➜  hipster-mvc git:(gh-pages) ✗ rm .git/index
➜  hipster-mvc git:(gh-pages) ✗ git clean -fdx
Removing .gitignore
Removing HipsterCollection.dart
Removing HipsterHistory.dart  
Removing HipsterModel.dart
Removing HipsterRouter.dart   
Removing HipsterSync.dart
Removing HipsterView.dart
Removing README.asciidoc
Removing wiki/
➜  hipster-mvc git:(gh-pages) ✗ mv /tmp/hipster-mvc-docs/* .
➜  hipster-mvc git:(gh-pages) ✗ git add .
➜  hipster-mvc git:(gh-pages) ✗ git ci -m "dartdoc generated HTML" -a
[gh-pages (root-commit) 3364e2a] dartdoc generated HTML
 703 files changed, 107930 insertions(+), 0 deletions(-)
 create mode 100644 body-bg.png
 create mode 100644 class.png 
...
➜  hipster-mvc git:(gh-pages) gp origin gh-pages
With that, I have my Hipster MVC API documentation. There is still much work to be done to whip that into a consumable state, but this is a pretty good start.

Day #337

Sunday, March 25, 2012

Dartdoc (not Apidoc)

‹prev | My Chain | next›

If Hipster MVC is to rule the world, I need some documentation. Well, that and it would help if it could perform the "Update" part of CRUD, but I'll worry about that another day.

Bleeding edge Dart has a dartdoc tool that I have yet to play with—tonight seems like a fine time to start. The dartdoc code has moved about, but currently it resides in /bleeding_edge/dart/lib. I could check out all of bleeding_edge/dart, but that seems overkill. It contains the Dart Editor and third-party tools like Ant and Rhino. If I can avoid downloading that, I will.

So first, I start by installing subversion (really?):
sudo apt-get install subversion
With that, I can create a dart working directory to hold all of this and then proceed to checkout dart/lib:
➜  repos  mkdir dart
➜  dart  svn checkout http://dart.googlecode.com/svn/branches/bleeding_edge/dart/lib
A    lib/uri
A    lib/uri/uri.dart
A    lib/dartdoc
...
Checked out revision 5808.
Next, I try to generate documentation for the HipsterHistory class in Hipster MVC:
➜  dart  dart ./lib/dartdoc/dartdoc.dart /home/cstrom/repos/hipster-mvc/HipsterHistory.dart
Unable to open file: /home/cstrom/repos/dart/frog/lang.dart'file:///home/cstrom/repos/dart/lib/dartdoc/dartdoc.dart': Error: line 21 pos 1: library handler failed
#import('../../frog/lang.dart');
^
So it seems that I need to have dart/frog installed for this:
➜  dart  svn checkout http://dart.googlecode.com/svn/branches/bleeding_edge/dart/frog
A    frog/file_system_node.dart
A    frog/scripts
A    frog/scripts/tree_gen.py
...
Checked out revision 5808.
Now I get:
➜  dart  dart ./lib/dartdoc/dartdoc.dart /home/cstrom/repos/hipster-mvc/HipsterHistory.dart
error: File not found: /home/cstrom/repos/dart/corelib/src/bool.dart
error: File not found: /home/cstrom/repos/dart/corelib/src/collection.dart
error: File not found: /home/cstrom/repos/dart/corelib/src/comparable.dart
...
compilation failed with 378 errors
There are a ton of errors there, but I start with the first few lines. It seem clear that, in addition to dart/lib I also need dart/corelib:
➜  dart  svn checkout http://dart.googlecode.com/svn/branches/bleeding_edge/dart/corelib
A    corelib/src
A    corelib/src/iterable.dart
A    corelib/src/expect.dart
...
Checked out revision 5808.
Now, when I try to run dartdoc, I find:
➜  dart  dart ./lib/dartdoc/dartdoc.dart /home/cstrom/repos/hipster-mvc/HipsterHistory.dart
error: File not found: /home/cstrom/repos/dart/client/html/frog/html_frog.dart
Documented 4 libraries, 93 types, and 711 members.
error: File not found: /home/cstrom/repos/dart/client/html/frog/html_frog.dart
...
compilation failed with 8 errors
Eight errors is certainly an improvement over 378. Still, it is not quite there yet so I install dart/client:
➜  dart  svn checkout http://dart.googlecode.com/svn/branches/bleeding_edge/dart/client
A    client/dom
A    client/dom/src
A    client/dom/src/EventListener.dart
...
Checked out revision 5808.
Now when I run dartdoc it completes successfully:
➜  dart  dart ./lib/dartdoc/dartdoc.dart /home/cstrom/repos/hipster-mvc/HipsterHistory.dart
Documented 4 libraries, 661 types, and 5470 members.
Compilation succeded. Code generated in: docs/client-live-nav.js
Four libraries? I only supplied one. That is odd. Loading up the index of the documentation I see:


Ah, so it got four libraries by including dart:core, dart:core, and dart:html in addition to my library. I need to figure out how to suppress those. Also, I need a better name for my library. I had been using a full description of the library after realizing that the dart2javascript compiler used the #library() declaration to comment the code in Javascript. So it seems that my current declaration:
#library('Manage pushState, usually through HipsterRouter.');

#import('dart:html');

class HipsterHistory {
  // ...
}
Would be better written as:
#library('Hipster MVC: Hipster History');

#import('dart:html');

class HipsterHistory {
  // ...
}
With that, my documentation now has a more proper name:


Looking through the dartdoc source code, I figure out what that "Code generated in: docs/client-live-nav.js" statement means -- it means that sidebar navigation is stored in JSON to be pulled in via Ajax by the browser. Since I am viewing the documentation locally, the Ajax load will not work. Still, that is a nice space savings optimization done by dartdoc. Of course, even more space would be saved if it was not documenting core libraries...

In addition to dartdoc, bleeding edge Dart also includes an apidoc command. Perhaps I am meant to use that? To test that theory, I checkout dart/utils:
➜  dart  svn checkout http://dart.googlecode.com/svn/branches/bleeding_edge/dart/utils
A    utils/apidoc
A    utils/apidoc/html_diff.dart
A    utils/apidoc/.gitignore
...
Checked out revision 5810.
My first attempt at running it is not a success:
➜  dart  dart ./utils/apidoc/apidoc.dart /home/cstrom/repos/hipster-mvc/HipsterHistory.dart 
Unknown option: /home/cstrom/repos/hipster-mvc/HipsterHistory.dart
So I check the source code, which seems to indicate that I should be running without any arguments. So I change directories into my Hipster MVC library to try again:
➜  hipster-mvc git:(master) ✗ dart ../dart/utils/apidoc/apidoc.dart          
Unhandled exception:
Unresolved static method: url 'file:///home/cstrom/repos/dart/utils/apidoc/apidoc.dart' line 53 pos 7
  doc.compileScript(frogPath,

 0. Function: 'StaticResolutionException._throwNew@127eafe4' url: 'bootstrap' line:620 col:3
 1. Function: '::main' url: 'file:///home/cstrom/repos/dart/utils/apidoc/apidoc.dart' line:53 col:7
Yikes!

Why on earth does Dart think that doc.compileScript() is a static method? My understanding was that classes needed to be upper-case....

It turns out that apidoc it importing dartdoc into the doc prefix:
#import('../../lib/dartdoc/dartdoc.dart', prefix: 'doc');
So, although doc looks like an object, it is really a prefix. That seems... deceptive.

As for why the static method is unknown, it seems that I am a victim of the bleeding edge here. In dartdoc, the compileScript function takes four arguments, but apidoc is supplying 3:
// ...
  // Compile the client-side code to JS.
  // TODO(bob): Right path.
  doc.compileScript(frogPath,
      '${doc.scriptDir}/../../lib/dartdoc/client-live-nav.dart',
      '${outputDir}/client-live-nav.js');
// ...
This is not a long term solution, but I copy the fourth argument from dartdoc to get past this:
// ...
  final libDir = joinPaths(frogPath, 'lib');
  doc.compileScript(frogPath, 
       libDir,
       '${doc.scriptDir}/../../lib/dartdoc/client-live-nav.dart',
       '${outputDir}/client-live-nav.js');
// ...
I also have to modify the Mozilla Documentation Path to get this to work:
// ...
  print('Parsing MDN data...');
  final mdnPath = joinPaths(doc.scriptDir, 'mdn');
 
  final mdn = JSON.parse(files.readAll('$mdnPath/database.json'));
// ...
With that, I get new errors:
➜  hipster-mvc git:(master) ✗ dart ../dart/utils/apidoc/apidoc.dart
Parsing MDN data...
Cross-referencing dart:dom and dart:html...
error: File not found: /home/cstrom/repos/dart/runtime/bin/buffer_list.dart
error: File not found: /home/cstrom/repos/dart/runtime/bin/common.dart
...
So it seem that I need dart/runtime for apidoc:
➜  dart  svn checkout http://dart.googlecode.com/svn/branches/bleeding_edge/dart/runtime
A    runtime/tools
A    runtime/tools/gyp
A    runtime/tools/gyp/runtime-configurations.gypi
...
Checked out revision 5810.
Trying again, I get:
➜  hipster-mvc git:(master) ✗ dart ../dart/utils/apidoc/apidoc.dart
Parsing MDN data...
Cross-referencing dart:dom and dart:html...
Generating docs...
Failed to compile /home/cstrom/repos/dart/utils/apidoc/../../lib/dartdoc/client-live-nav.dart. Error:
ProcessException: Permission denied
Uhh.... "Permission Denied"? What the heck was it trying to do?

Regardless, what is generated has nothing to do with my Hipster MVC library. Rather it is the actual API documentation for Dart itself:


So, in the end, it seems that dartdoc is what I want, not apidoc. I see no reason to include the dependent core Dart libraries along with my own API documentation, so I still have some work ahead. I will either need to post-process the generated dartdoc, dig deeper for some as-yet unseen flag to suppress core libraries, or roll my own dartdoc sans core libraries. Tomorrow.


Day #336

Saturday, March 24, 2012

Trouble Mixing Remote and Local Dart Libraries

‹prev | My Chain | next›

While messing about with static variables in Dart, I ran into some annoying behavior with Dart libraries. The code in question was imported into the main application space from the Hipster MVC github repository:
#import("dart:html");

#import("https://raw.github.com/eee-c/hipster-mvc/master/HipsterRouter.dart");
#import("https://raw.github.com/eee-c/hipster-mvc/master/HipsterHistory.dart");

main() {
  // ...
}
I need to make changes to the HipsterHistory library, so I switched that import to point to my local copy:
#import("dart:html");

#import("https://raw.github.com/eee-c/hipster-mvc/master/HipsterRouter.dart");
// #import("https://raw.github.com/eee-c/hipster-mvc/master/HipsterHistory.dart");
#import("/home/cstrom/repos/hipster-mvc/HipsterHistory.dart");

main() {
  // ...
}
The main() entry point remained unchanged—it creates a route and starts the pushState History mechanism:
main() {
  HipsterRouter app = new MyRouter();
  HipsterHistory.startHistory();
}
Only now it did not work. When I loaded my page, it was behaving as if there was no route present at all.

At first I suspected that the versions of HipsterRouter and HipsterHistory between my local copy and github had somehow grown out of sync (or at least that I had forgotten to push a change to github). But a quick check revealed that they were, in fact, identical.

My next instinct was of a select-is-broken nature. That this was not my first instinct proves that I am growing as a developer. Maybe. But select is not broken and neither is mixed local vs. remote import in Dart.

It turns out that main() and HipsterRouter were using two different versions of HipsterHistory. As can be seen, main() is explicitly using HipsterHistory locally. The problem is that HipsterRouter contains the following import:
#library('Router for MVC pages.');

#import("dart:html");

#import("HipsterHistory.dart");

class HipsterRouter {
  //...
}
That is, HipsterRouter imports HipsterHistory from the same location in which it exists. In this case, HipsterRouter comes from github, so it imports HipsterHistory from github. The main() entry point, on the other hand, gets HipsterHistory locally.

Unfortunately for me, Dart treats these two definitions of HipsterHistory as separate. So, when the router registers itself with HipsterHistory, it is doing so with a HipsterHistory that has not been started.

The solution, of course, is to import both from the local filesystem:
// #import("https://raw.github.com/eee-c/hipster-mvc/master/HipsterRouter.dart");
#import("/home/cstrom/repos/hipster-mvc/HipsterRouter.dart");

// #import("https://raw.github.com/eee-c/hipster-mvc/master/HipsterHistory.dart");
#import("/home/cstrom/repos/hipster-mvc/HipsterHistory.dart");

main() {
  HipsterRouter app = new MyRouter();
  HipsterHistory.startHistory();
}
This was a somewhat subtle "bug" that took me a bit to identify. I could have solved this, perhaps, by moving all of Hispter MVC into a single file, but I really prefer the separate file approach. Maybe this is something that Dart packaging system might help somehow—making it easy to switch between local and remote versions of a library. For now, however, I will have to be wary of getting in this situation again.


Day #325

Plan for Laziness in Dart

‹prev | My Chain | next›

With exactly one week to go until the release of the first edition of Dart for Hipsters, the Dart team released a new version of the language specification. To this, I can only say, guys, seriously, we definitely need better coordination for the second edition. I kid!

Reading through the spec, I do not see much that requires changes to the beta version of the book. To be sure, method cascades would require a new section, possibly even a new chapter, but they are not yet baked, let alone implemented.

One thing that does bear investigation is lazy evaluation of static variables. I have been bit by "must be a compile time constant" enough to see it in my sleep. Yes, my dreams are just that boring—but (hopefully) no more!

For example, in Hipster MVC's pushState manager, HipsterHistory, I cannot simply initialize the routes class variable like so:
class HipsterHistory {
  static List routes = [];
  // ...
}
The empty array to which I am attempting to assign routes is not a compile time constant, so I would get a compile time error.

Instead, I have needed to make a pretend routes static variable via a getter. That getter can initialize a private _routes class variable if needed and return the current value:
class HipsterHistory {
  static List _routes;

  static get routes() {
    if (_routes == null) _routes = [];
    return _routes;
  }
  // ...
}
That, of course, it is a silly amount of work to go through for what ought to be a one-liner. Hence the updated spec. I download the latest copy of Dartium and give the simpler version a go. Sadly, I still get the same error:


Ah well, at least I know that help is on the way. I think for first edition purposes, I will likely describe lazy evaluation as the One True Way™, but with an aside briefly describing the old behavior.



Day #235

Friday, March 23, 2012

Dart Switch

‹prev | My Chain | next›

There are several places in my Dart code that could benefit from a good switch statement. The question is, does Dart have a good switch statement?

In my simple canvas player animation, I respond to event key codes with a series of if statements:
attachMover(me, context) {
  // Move on key down
  document.
    on.
    keyDown.
    add((event) {
      String direction;

      // Listen for arrow keys
      if (event.keyCode == 37) direction = 'left';
      if (event.keyCode == 38) direction = 'up';
      if (event.keyCode == 39) direction = 'right';
      if (event.keyCode == 40) direction = 'down';

      if (direction != null) {
        event.preventDefault();
        me.move(direction);
        draw(me, context);
      }
    });
Assigning direction in each of those if statements feels un-DRY. I would prefer assigning the direction variable once—to the result of a switch statement:
      String direction = switch(event.keyCode) {
        case 37: 'left';
        case 38: 'up';
        case 39: 'right';
        case 40: 'down';
      };
Only that does not work in Dart.

When I run that statement, Dart informs me that:
Internal error: 'file:///home/cstrom/repos/dart-book/book/includes/animation/main.dart': Error: line 65 pos 26: unexpected token 'switch'
      String direction = switch(event.keyCode) {
                         ^
So it seems that I am stuck setting the direction inside the case statements just as I did with the if statements:
      String direction;
      switch(event.keyCode) {
        case 37: direction = 'left';
        case 38: direction = 'up';
        case 39: direction = 'right';
        case 40: direction = 'down';
      };
I suppose that is a slight improvement over the equivalent if statements—at least with switch, I no longer need to check event.keyCode on each line.

So yes, an improvement except that it does not work. Working is kind of a big deal.

The crash message:
Exception: 'file:///home/cstrom/repos/dart-book/book/includes/animation/main.dart': Switch case fall-through at line 69.
Stack Trace:  0. Function: 'FallThroughError._throwNew@127eafe4' url: 'bootstrap' line:595 col:3
 1. Function: '::function' url: 'file:///home/cstrom/repos/dart-book/book/includes/animation/main.dart' line:69 col:9
 2. Function: '_EventListenerListImpl@33cc944a.function' url: 'dart:html' line:8653 col:35
Fallthrough? You mean that I have to break each statement in the switch()?:
      String direction;
      switch(event.keyCode) {
        case 37: direction = 'left'; break;
        case 38: direction = 'up'; break;
        case 39: direction = 'right'; break;
        case 40: direction = 'down'; break;
      };
Indeed, that is exactly what I have to do because, not only does that compile again, but also my player animation is again working:

And then:

But in the end, I think I prefer if-statement:
      if (event.keyCode == 37) direction = 'left';
      if (event.keyCode == 38) direction = 'up';
      if (event.keyCode == 39) direction = 'right';
      if (event.keyCode == 40) direction = 'down';
The equivalent case-statement-break buries the assignment inside too much code:
      switch(event.keyCode) {
        case 37: direction = 'left'; break;
        case 38: direction = 'up'; break;
        case 39: direction = 'right'; break;
        case 40: direction = 'down'; break;
      };
I really do not care for that trailing break. It obscures the intent of the code (the direction assignment), decreasing long-term maintainability. I suppose that I could move it to a separate line, but my switch() statement already has two more lines of code than the equivalent if-statement structure.

The only time that I can think to use the switch statement in Dart code is when I have significant, multi-line calculation to perform for each case. Even then it might be better to break those mulii-line statements out into separate functions.

Which begs the question: is there a use-case for Dart's switch()? Unless I can think of one, I may give it a miss in Dart for Hipsters.


Day #334

Thursday, March 22, 2012

Really, Really Removing Event Handlers in Dart

‹prev | My Chain | next›

Up tonight a quick follow-up to yesterday's post on Dart event handlers. It was pointed out in comments by Ernesto Oltra that the manner in which I was removing handlers might not be doing what I thought it was doing.

That is easy enough to test, I add a print statement to my draw/resize hander:
  _removeHandlers() {
    window.
      on.
      resize.
      remove(_drawBackground);
  }

  _drawBackground([_]) {
    print('_drawBackground');
    // ...
  }
Then I create the modal dialog to which these handlers are attached and remove it it immediately. Then I resize the browser window. If the events had been removed, then I should not see any print() output. But I do see it:


Dang it.

This seems to be one of those interesting edge cases of a new language. The problem is that Dart has not yet decided how to tell if functions / methods equal each other. It seems a reasonable expectation that the following two should refer to the same method (_drawBackground):
  _attachHanders() {
    // ...
    window.
      on.
      resize.
      add(_drawBackground);
  }

  _removeHandlers() {
    window.
      on.
      resize.
      remove(_drawBackground);
  }
Currently they do not. The reasoning is that whatever closures are created by the method might make it different. In other words _drawBackground == _drawBackground // => false because of the potential for closures.

Until this issue is resolved, I need an alternate approach to force Dart to think these are the same handler. Per that ticket, this involves assigning the handler to a variable for which the equality it less ambiguous:
class ModalDialog implements Element {
  Element el, bg;
  var resizeHandler;

  ModalDialog(): this.tag('div');

  ModalDialog.tag(String tag) {
    el = new Element.tag(tag);
    resizeHandler = _drawBackground;
  }

  _attachHanders() {
    // ...
    window.
      on.
      resize.
      add(resizeHandler);
  }

  _removeHandlers() {
    window.
      on.
      resize.
      remove(resizeHandler);
  }
  //...
}
The value of the variable resizeHandler is a reference to the method, not the method itself. It seems a subtle distinction, but it does the trick. After closing my modal, no amount of resizing generates a call to the resizing handler:


It will be interesting to see how Dart team resolves this. My original expectation really seems reasonable, but I have no qualms with the bug languishing. In fact, I rather appreciate that there are no rushes to address gnarly cases like this. I am happy to work around it for now if that means a better ultimate solution. Still, it will make for interesting writing in Dart for Hipsters' Events chapter.


Day #333

Wednesday, March 21, 2012

Useful Event Handlers for Modal Dialogs

‹prev | My Chain | next›

Tonight I revisit the Dart from a few nights back. There are a couple of minor things that don't quite work or I would like to add.

First up, I would like to remove the modal dialog if I press the escape key. This is simple enough:
window.
      on.
      keyDown.
      add((event) {
        if (event.keyCode == 27) remove();
      });
I add that to an _attachHandlers() private method and invoke that when the dialog is first shown. Easy peasy.

Next a bug. When I look at the page with the Dart console open (which is nearly all the time), it looks somewhat OK:


But when I remove the Dart console, I find:


Ugh.

There has to be a built-in event handler that will fire when the viewport size changes like that, right? For my first try, I put tracer bullets into window.on.resize and, amazingly that seems to hit the target. So I add another handler to _attachHandlers():
  _attachHanders() {
    // handle ESC here...

    window.
      on.
      resize.
      add((event) {
        _drawBackground();
      });
  }
This is only half the solution however. It will no do to leave this handler in place after the modal dialog is closed. After close, the handler needs to be removed. In Dart, I have to use an explicitly named function in order to be able to remove it. I could make a wrapper around _drawBackground(), but that seems superfluous. I cannot add _drawBackground() directly to add() because all event listeners are required to accept a single parameter (then event).

Oooh.... but wait. I can define _drawBackground() such that it takes an optional parameter. I will never care about that optional parameter so, by convention, I use the _ parameter:
  _drawBackground([_]) {
    //  do normal draw stuff here...
  }

  _attachHanders() {
    // Handle ESC here ...

    window.
      on.
      resize.
      add(_drawBackground);
  }
With that, I can add a corresponding _removeHandlers() to the remove() method, which removed the named _drawBackground() "listener":
  Node hide() {
    _removeHandlers();
    bg.remove();
    return el.remove();
  }

  _removeHandlers() {
    window.
      on.
      resize.
      remove(_drawBackground);
  }
Nice. Either this is fairly intuitive or I am getting the hang of Dart. Either way, implementing these handlers was quite easy.


Day #332

Tuesday, March 20, 2012

Dart Typedef

‹prev | My Chain | next›

While skimming through the Dart language specification tonight, I came across the entry for typedef. I am putting out a book on the subject in less than two weeks and I have no idea what typedef does, so let's rectify.

The spec is not terribly illuminating as to purpose, but it definitely applies to functions. I eventually grep through the dart-sdk source code and find a couple of examples. Typedefs seem to be used to constrain callbacks to a specific fingerprint.

Say, I could use that to limit the acceptable data sync callbacks in Hipster MVC. Currently, I allow any method to be injected into HipsterSync.sync=:
class HipsterSync {
  // private class variable to hold an application injected sync behavior
  static Function _injected_sync;

  // setter for the injected sync behavior
  static set sync(fn) {
    _injected_sync = fn;
  }
  // ...
}
To swap out the default data sync layer, I can, for example, use localStorage:
main() {
  HipsterSync.sync = localSync;
  // ...
}

localSync(method, model) {
  // localStorage awesomeness here
}
That works just fine, but I have no real way of ensuring (or at least strongly suggesting) that the function assigned via the HipsterSync.sync setter is of arity 2, returns a Future, and accepts a String as the first argument.

This is where typedef comes in handy.

In my HipsterSync library, I declare a SyncCallback typedef:
typedef Future<HashMap> SyncCallback(String _method, Dynamic model);
As a long time type hater, I am trying to resist the allure of that statement, but it's hard—so very hard to resist this temptation. Especially in my MVC library, that is a big deal. With that line, I declare that any callbacks need to return a Future callback—which will, in turn, supply a HashMap (i.e. the return value of a JSON.parse()). Moreover, the first argument needs to be a String (to describe the CRUD operation). I am lax with the second argument because it can be either a HipsterModel or a HipsterCollection (I should really create a common interface for the two).

With that, I can declare that my _injected_sync function needs to be a "SyncCallback":
typedef Future<HashMap> SyncCallback(String method, Dynamic model);

class HipsterSync {
  // private class variable to hold an application injected sync behavior
  static SyncCallback _injected_sync;

  // setter for the injected sync behavior
  static set sync(SyncCallback fn) {
    _injected_sync = fn;
  // ...
}
And, if I give my localStorage sync method an arity of 3 instead of SyncCallback's 2:
main() {
  HipsterSync.sync = localSync;
  // ...
}

localSync(method, model, asdf) {
  // localStorage awesomeness here
}
Then I get a nice exception:


That is a type-checked mode exception only, but it is a compile time error, ensuring that I will know that there is a problem immediately. No test suite run, no smoke tests. Nice.

As a long time dynamic programmer, I really want to dislike this, but I cannot find good cause. Even if I can come up with a good excuse to hate, it is not as if Dart is forcing me to use this feature—I didn't even know about it until tonight. Dart is really shaping up to be a gateway drug for strongly typed programming. Dammit.


Day #331

Monday, March 19, 2012

Obscure Dart Constructors

‹prev | My Chain | next›

I have myself a nice, Dart modal dialog. It is an adapter for Element with the following constructors:
class ModalDialog implements Element {
  Element el, bg;

  factory ModalDialog() => new ModalDialog.tag('div');

  ModalDialog.tag(String tag) {
    el = new Element.tag(tag);
  }

  ModalDialog.html(String html) {
    el = new Element.tag('div');
    el.innerHTML = html;
  }
  // ...
}
The factory constructor allows me to instantiate a modal dialog thusly:
el = new ModalDialog();
el.innerHTML = '<form>...</form>';
That works, but the factory constructor is more ceremony than Dart requires. In fact, I can use a redirecting constructor, as Kasper Lund pointed out. Thus I can still instantiate simple modal dialogs the same way if I change the constructor to:
class ModalDialog implements Element {
  Element el, bg;

  ModalDialog(): this.tag('div');
  // ...
}
That is some compact syntax—the vanilla ModalDialog() constructor redirects to the ModalDialog.tag() constructor with an argument of 'div'. I really like that.

Something else that Kasper mentioned was that redirection also works in constant constructors. Upon hearing that, I could only wonder what the heck is a constant constructor? To answer that, I return to my Player class:
class Player {
  int x, y;

  Player.xy(this.x, this.y) {
    if (x == null) x = 0;
    if (y == null) y = 0;
  }
}
I can instantiate a player as new Player.xy(25, 25); and the player is positioned at x=25 and y=25. The thing about most Dart classes is that you cannot simply assign them outside of functions:
// this won't work...
var me = new Player.xy(25, 25);

main() {
  // do stuff here ...
}
The problem is that new Player.xy(25, 25) is not a compile time constant. I can declare the me variable in a Dart libraries scope, but I cannot assign it—unless the assignment is a constant expression. Actual assignment is deferred until runtime (e.g. inside the main() function).

To convert Player into a compile time constant, I need to make all instance variables final:
class Player {
  final int x, y;
  // ...
}
Admittedly, there is not much sense to a game in which all player positions are final, but why let that get in the way of a good topic exploration?

In addition to final instance variables, I also need a constant constructor. Here, I use a redirecting, constant constructor to point the vanilla Player() to the Player.xy() version:
class Player {
  final int x, y;

  const Player(): this.xy(0,0);

  Player.xy([this.x, this.y]) {
    if (x == null) x = 0;
    if (y == null) y = 0;
  }
}
With that, I can now instantiate a constant object at compile time:
final me = const Player();

main() {
  // Not much to do here since players don't move
}
Clearly I need a better use-case for constant objects that an immovable Player if I am to include a treatment of this topic in Dart for Hipsters. And I do need to include it—these redirecting constructors are quite nice.


Day #330

Sunday, March 18, 2012

A ModalDialog Element Class for Dart

‹prev | My Chain | next›

Yesterday, I was able to create a reasonable facsimile of a modal dialog in Dart. The only drawback was that I was forced to handle styling for both the background and the modal dialog element inside a Hipster MVC form view that should really only be concerned with drawing the form.

So tonight, I will attempt to create an adapter class for a modal dialog element. If I can pull it off, the ModalDialog class should behave like any other Dart Element, but should result in a modal dialog.

In the pre-modal version of the code in which the form simply displayed below the list of items, the container <div> was added to the DOM when the form was created:
el = new Element.html('<div id="add-comic-form"/>');
The form itself, as defined in the template() method, is added when the view is rendered:
  render() {
    el.innerHTML = template();
    // additional styling...
  }
I start by declaring ModalDialog as a sub-class of Element:
#library("Modal Dialog");
#import("dart:html");
class ModalDialog implements Element {
  Element el, bg;
}
The el element will serve as the container for the dialog. It will also server as the adaptee of this attempted adapter. That is, many of the methods and fields for ModalDialog will be delegated to el while others will be adapted for modal purposes. The other element, bg will describe the background.

I think it makes a certain amount of sense that, when ModalDialog's innerHTML setter is invoked with HTML describing a form, then the modal dialog displays. If innerHTML is invoked with no data, then the modal dialog should be removed. In other words:
  void set innerHTML(String html) {
    el.innerHTML = html;

    if (html == null || html == '')
      remove();
    else
      show();
  }
As for the class constructor, I implement Element's Element.tag() constructor and use that to define a factory constructor:
class ModalDialog implements Element {
  Element el, bg;

  factory ModalDialog() => new ModalDialog.tag('div');

  ModalDialog.tag(String tag) {
    el = new Element.tag(tag);
  }

  void set innerHTML(String html) { /* ... */ }
  // ...
}
It is nice to implement the usual tag constructor so that I could new ModalDialog.tag('ul') if I felt the need. Still, I think the typical use-case will likely not care about the container, but instead will concern itself with the HTML for a <form>. In such a case a <div> container will suffice. In this case a factory constructor (returns an object rather than manipulating internal state) using the tag constructor makes for a fine default.

As for the remove() method invoked by innerHTML, I make that an alias for another method that seems likely to be called from the outside, hide():
  Node remove() => hide();

  Node hide() {
    bg.remove();
    return el.remove();
  }
This removes the background and element from the DOM, but retains a reference to both for future use. Lastly, show() does most of the magic CSS manipulation from last night.

My add-comic-book view class is then ready to make use of this:
class AddComicForm extends HipsterView {
  AddComicForm([collection, model, el]):
    super(collection:collection, model:model, el:el);

  post_initialize() {
    el = new ModalDialog();
    _attachUiHandlers();
  }

  void render() { el.innerHTML = template(); }

  void remove() { el.innerHTML = ''; }

  template() { /* ... */ }

  _attachUiHandlers() { /* ... */  }
}
And that almost works.

It turns out that a few more Element methods are needed in ModalDialog in order to make it enough like an Element to trick my view:
class ModalDialog implements Element {
  // ...
  get on() => el.on;
  get parent() => el.parent;
  ElementList queryAll(String selectors) => el.queryAll(selectors);
}
With that, I have a modal dialog being generated from an adapter class:


That worked out rather nicely. My form view is now completely concerned with doing form things. The ModalDialog is sufficiently like Element for it to be used effectively by the Hipster MVC. Displaying and hiding the dialog based on the value passed to innerHTML almost feels a little too cute, but I could always supply HTML directly to a ModalDialog.html() constructor and then use show() and hide() directly.


Day #329

Saturday, March 17, 2012

Modal Dialogs with Dart

‹prev | My Chain | next›

Today, I switch back to my comic book collection app written in Dart with Hipster MVC. Currently, I have a very simple fade-in animation that displays the create comic book form:
#library('Form view to add new comic book to my sweet collection.');

#import('dart:html');
#import('https://raw.github.com/eee-c/hipster-mvc/master/HipsterView.dart');

class AddComicForm extends HipsterView {
  // ...
  render() {
    el.style.opacity = '0';
    el.innerHTML = template();

    window.setTimeout(() {
      el.style.transition = '1s';
      el.style.opacity = '1';
    }, 1);
  }
}
There is nothing fancy there—the form element is simply placed in the normal document flow. The 1 millisecond timeout seems to be necessary to give Dart a chance to render the element with the form from the template() function. Without it, the transition is ignored.

The first step for a modal dialog is the black, semi-opaque background that covers the rest of the document, ensuring that nothing else can be clicked. For that, I create a new <div> tag, position it absolutely, guess at the appropriate size, make the background color black with a slight opacity, and render it above the rest of the document. All of this can be done with the style property on Element:
  render() {
    // Black background
    Element bg = new Element.tag('div');
    document.body.nodes.add(bg);

    bg.style.position = 'absolute';
    bg.style.top = '0px';
    bg.style.left = '0px';

    bg.style.width = "1000px";
    bg.style.height = "1000px";

    bg.style.backgroundColor = 'black';
    bg.style.opacity = '0.8';
    bg.style.zIndex = '1000';

    // Display the dialog
    // ...
  }
The problem is the guess at the dimensions. Not surprisingly they only cover a portion of the document:


I could make the dimensions larger, but that will only end up adding scrollbars when they are not warranted. This means that I have to calculate dimensions. Interestingly, Dart does this inside a future (to prevent blocking during a sometimes expensive calculation). So I move the background styling into the rect property's future so that I can use the document's width and height for the modal dialog's background:
  render() {
    // Black background
    Element bg = new Element.tag('div');
    document.body.nodes.add(bg);

    window.document.rect.then((document) {
      bg.style.position = 'absolute';
      bg.style.top = '0px';
      bg.style.left = '0px';

      bg.style.width = "${document.offset.width}px";
      bg.style.height = "${document.client.height}px";

      bg.style.backgroundColor = 'black';
      bg.style.opacity = '0.8';
      bg.style.zIndex = '1000';


      // Display the dialog
      // ...

    });
  }
With that, I am ready to place my dialog:
  render() {
    // Black background
    Element bg = new Element.tag('div');
    document.body.nodes.add(bg);

    window.document.rect.then((document) {
      // Place the background above the document
      // ...

      // Place the modal dialog on top of the background
      el.style.opacity = '0';
      el.innerHTML = template();

      el.style.position = 'absolute';
      el.style.top = '0px';
      el.style.left = '0px';

      el.style.backgroundColor = 'white';
      el.style.padding = '20px';
      el.style.borderRadius = '10px';

      el.style.transition = 'opacity 1s ease-in-out';
      el.style.opacity = '1';
      el.style.zIndex = '1001';
    });
  }
That works, but the dialog is somewhat poorly placed:


To fix that, I need another rect future. This time, I grab the rect for the form dialog element so that I can use that to calculate the distance from the left side of the page:
  render() {
    // Black background
    Element bg = new Element.tag('div');
    document.body.nodes.add(bg);

    window.document.rect.then((document) {
      // Place the background above the document
      // ...

      // Place the modal dialog on top of the background
      // ...
      el.rect.then((dialog) {
        el.style.top = '10px';
        int offset_left = document.offset.width/2 - dialog.offset.width/2;

        el.style.left = "${offset_left}px";
      });
    });
  }
And that seems to do the trick:


I am undecided on how much I like the rect property. It is a little awkward, but I do appreciate the performance consideration. As for the rest, this was pretty easy to build. Best of all, I can rely on Dart to generate compatible Javascript and not have to worry (much) about IE, Mozilla, and Safari. I do think that a separate class is needed to do a modal right. Right now, I have to manually remove both the background and dialog. Something for another day.

Day #328