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

No comments:

Post a Comment