Monday, January 30, 2012

Psuedo View Classes in Dart

‹prev | My Chain | next›

Today, I think, I am in need of some code re-organization. Currently my simple CRUD-over-XHR Dart application looks like:
#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]) { /* ... */ }
That's quite a mess that I have made for myself.

I have no intention of creating yet another MVC framework, but I would like to at least create two view-like things to bring some semblance of organization to this project. I start by replacing the load_comics() entry point in main():
main() {
  load_comics();
}
I replace that, because I lack imagination, with a very Backbone looking object:
main() {
  var comics = new ComicsCollectionView('#comics-list');
  comics.render();
}
Now to make that work...

I am going to do this entirely in a separate class library, so I make a ComicsCollectionView.dart file with the following contents:
#library('Collection View for My Comic Book Collection');

#import('dart:html');

class ComicsCollectionView {
  var get el;

  ComicsCollectionView(target_el) {
    el = document.query(target_el);
  }

  render() {
    print("Replace me with real code soon");
    print(el);
  }
}
I declare my el property as a getter because I seem to recall having access to that in Backbone. For now, I will not expose a corresponding setter. In the constructor, I query the document for the supplied element (this forces me to import 'dart:html'). Then I expose a simple render() method that serves as tracer bullets for where I need to go next.

Loading it up in the browser, I see that I am in OK shape so far:


I add another getter for the collection. I am not going to attempt a full blown collection class tonight, but will simply stick with an array:
class ComicsCollectionView {
  var get el;
  var get collection;

  ComicsCollectionView(target_el) {
    el = document.query(target_el);
    collection = [];
  }

  render() { /* ... */ }
}
I am not quite sure how to load the collection. In backbone, a collection object would fire an event for which the view could listen. I am not, I will not create a full blown MVC framework. So what to do? I opt for a private method to trigger the collection load in addition to applying the template:
  render() {
    _ensureCollectionLoaded();
    el.innerHTML = template(collection);
  }
The template() method is taken directly from the old procedural code:
  template(list) {
    // This is silly, but [].forEach is broke
    if (list.length == 0) return '';

    var html = '';
    list.forEach((comic) {
      html += _singleComicBookTemplate(comic);
    });
    return html;
  }

  _singleComicBookTemplate(comic) {
    return """
      <li id="${comic['id']}">
        ${comic['title']}
        (${comic['author']})
        <a href="#" class="delete">[delete]</a>
      </li>
    """;
  }
As for the _ensureCollectionLoaded() method, I again adopt the procedural code, but add a guard clause to prevent calling the method if it has already been loaded:

  var _load_requested = false;

  _ensureCollectionLoaded() {
    if (_load_requested) return;
    _load_requested = true;

    var req = new XMLHttpRequest();

    req.on.load.add(_handleOnLoad);
    req.open('get', '/comics', true);
    req.send();
  }
And lastly, I define the _handleOnLoad() callback method. In here, I finally assign the collection to a non-empty array:
  _handleOnLoad(event) {
    var request = event.target;

    // TODO trigger an event for which the view can listen and render
    collection = JSON.parse(request.responseText);

    render();
  }
Interestingly, the request is the event target. Previously, I had been able to re-use the original request object as it was still in-scope. Now I have to extract it from the load event.

Once I have the collection, I re-invoke render. This will by-pass _ensureCollectionLoaded() since it has already been loaded. But the now populated collection will result in the view actually displaying something. And amazingly, it really does display:


I have definitely improved my situation. All of the view code for my comic book collection is now in its own class. But wow, jamming all of that collection code in the view is messy. I may want to extract that out into a proper collection class. Tomorrow.


Day #281

No comments:

Post a Comment