Wednesday, February 1, 2012

Dart Views for Creating Ajax Records

‹prev | My Chain | next›

I am relatively pleased, so far, with my refactoring of my Dart Ajax powered application into more of an MVC approach. If nothing else, I have cleared away considerable amounts of the callback spaghetti code previously:
#import('dart:html');
#import('dart:json');

main() { /* ... */ }
attach_create_handler() { /* ... */ }
enable_create_form(event) { /* ... */ }
_ensure_create_form(id_selector) { /* ... */ }
_show_form(form_container) { /* ... */ }
_attach_form_handlers(form_container) { /* ... */ }
_submit_create_form(form) { /* ... */ }
_disable_create_form(event) { /* ... */ }
load_comics() { /* ... */ }
attach_handler(parent, event_selector, callback) { /* ... */ }
delete(id, [callback]) { /* ... */ }
graphic_novels_template(list) { /* ... */ }
graphic_novel_template(graphic_novel) { /* ... */ }
form_template([graphic_novel]) { /* ... */ }
I replaced the bulk of that code with a collection class:
#library('Collection View for My Comic Book Collection');

#import('dart:html');
#import('dart:json');

class ComicsCollectionView {
  var get el;
  var get collection;

  ComicsCollectionView([el, collection]) {/*...*/}

  // Be Backbone-lik
  render() {/*...*/}
  template(list) {/*...*/}

  // private helper methods
  _singleComicBookTemplate(comic) {/*...*/}
  _attachUiHandlers() {/*...*/}

  // Need to move these elsewhere
  delete(id, [callback]) {/*...*/}
  attach_handler(parent, event_selector, callback) {/*...*/}
}
And a Collection class:
#library('Collection class to describe my comic book collection');

#import('dart:html');
#import('dart:htmlimpl');
#import('dart:json');

class ComicsCollection {
  var list;
  var get on;

  ComicsCollection() {/*...*/}

  // Be List-like
  forEach(fn) {/*...*/}
  get length() {/*...*/}

  // Be Backbone-like
  fetch() {/*...*/}

  // private helper methods
  _handleOnLoad(event) {/*...*/}
}

// Support MVC events
class CollectionEvents implements Events {/*...*/}
class CollectionEventList implements EventListenerList {/*...*/}
I especially like the Collection class—it does its thing with very few extraneous methods.

The view class could probably benefit from being broken into a collection view plus a sub-view for the individual items in the collection. Aside from that, I also have a bit of Model code (delete()) lurking in there. I will get to that in a bit (probably tomorrow). First, I would like to finish extracting code out of my earlier mess.

Left in the main() entry point of my application is Collection and View instantiation along with a call to attach_create_handler():
main() {
  var my_comics_collection = new ComicsCollection()
    , comics_view = new ComicsCollectionView(
        el:'#comics-list',
        collection: my_comics_collection
      );

  my_comics_collection.fetch();

  attach_create_handler();
}
That handler function is responsible for attaching a handler to the already existing #add-comics element, establishing a click handler to display a form. Say, that sounds an awful lot like View work. So I replace attach_create_handler() with a new view:
#import('ComicsCollection.dart');
#import('ComicsCollectionView.dart');
#import('AddComicView.dart');

main() {
  var my_comics_collection = new ComicsCollection()
    , comics_view = new ComicsCollectionView(
        el:'#comics-list',
        collection: my_comics_collection
      );

  my_comics_collection.fetch();

  new AddComicView(el:'#add-comic');
}
That is now the entire contents of the main comics.dart file. This is the only file that is loaded via a <script> tag. All of the other code comes from those #import() statements at the top the file. That is all kinds of nice.

The AddComicView() view is solely responsible for toggling the form:
#library('Simple view to toggle new comic form view');

#import('dart:html');

#import('AddComicFormView.dart');

class AddComicView {
  var get el;
  var get collection;

  var form_view;

  AddComicView([el]) {
    this.el = document.query(el);

    _attachUiHandlers();
  }

  _attachUiHandlers() {
    el.on.click.add(_toggle_form);
  }

  _toggle_form(event) {
    if (form_view == null) {
      form_view = new AddComicFormView();
      form_view.render();
    }
    else {
      form_view.remove();
      form_view = null;
    }
  }
}
The AddComicFormView is pretty straight-forward (and the guts most copied from the old callback spaghetti):
#library('Form view to add new comic book to my sweet collection.');

#import('dart:html');

class AddComicFormView {
  var get el;

  AddComicFormView() {
    this.el = new Element.html('<div id="add-comic-form"/>');
    this.el.style.opacity = "0";

    document.body.nodes.add(this.el);

    _attachUiHandlers();
  }

  _attachUiHandlers() {
    attach_handler(el, 'submit form', (event) {
      event.preventDefault();
      _submit_create_form(event.target);
    });

    attach_handler(el, 'click a', (event) {
      event.preventDefault();
      _disable_create_form(event);
    });
  }

  render() {...}

  remove() {
    this.el.remove();
  }

  template() {
    return """
<form action="comics" id="new-comic-form">
....
</form>
""";
  }

  _disable_create_form(event) {...}

  attach_handler(parent, event_selector, callback) {...}

  // Thar be model code here...
  _submit_create_form(event) {...}
}
And, yup, it still works:


My not-another-MVC-framework framework is coming along nicely. Except for the distinct lack of "M". And the presence of Model code in the Views (e.g. _submit_create_form()). I will address that tomorrow.

Day #283

1 comment:

  1. Nice post Chris. I have been enjoying the blog posts you have been submitting. Thanks!

    ReplyDelete