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

Tuesday, January 31, 2012

Custom Events in Dart

‹prev | My Chain | next›

I have a deadline for the alpha release for Dart for Hipsters, but before I can get to that, I must appease the gods of my chain. I have yet to meet these gods, but they have been kind to me, so I must do what I can to keep them happy.

Last night I got started breaking my Ajax based Dart application into real View classes (somewhat along the lines of Backbone.js). Today, I hope to extract collection code from my view into a proper collection class.

The heart of the collection, the fetching from the backend datastore, I can already do:
class ComicsCollection {
  var list;

  ComicsCollection() {
  }

  // Be Backbone like
  fetch() {
    var req = new XMLHttpRequest();

    req.on.load.add(_handleOnLoad);
    req.open('get', '/comics', true);
    req.send();
  }

  _handleOnLoad(event) {
    var request = event.target;

    list = JSON.parse(request.responseText);

    // How to tell the view that I'm done?
  }
}
But how can I communicate to my view that the collection has loaded new data?

I would like to instantiate a collection and a collection view something along the lines of:
  var my_comics_collection = new ComicsCollection()
    , comics_view = new ComicsCollectionView(
        el:'#comics-list',
        collection: my_comics_collection
      );

  my_comics_collection.fetch();
In the collection view, I could then subscribe to the collection view's on-load event:
  ComicsCollectionView([el, collection]) {
    this.el = document.query(el);
    this.collection = collection;

    collection.on.load.add((event) {
      render();
    });
  });
To get that to work, I will at least need an on getter in the Collection:
class ComicsCollection {
  var list;
  var get on;

  ComicsCollection() {
    on = new CollectionEvents();
  }
}
I could probably re-use one of the DOM Events classes here, but it is not too hard to define my own:
class CollectionEvents implements Events {
  var load_list;

  CollectionEvents() {
    load_list = new CollectionEventList();
  }

  get load() {
    return load_list;
  }
}
I defined a load getter that returns an EventList object. This CollectionEventList class that I define will be the thing that holds event listeners. It needs to define two methods: add() to add listeners and dispatch() to send events to all listeners:
class CollectionEventList implements EventListenerList {
  var listeners;

  CollectionEventList() {
    listeners = [];
  }

  add(fn) {
    listeners.add(fn);
  }

  bool dispatch(Event event) {
    listeners.forEach((fn) {fn(event);});
    return true;
  }
}
No doubt I can be a little more elegant about that, but that should meet the spirit of what is needed. Amazingly, that does work. To be sure that I am not somehow faking it, I add another debugging on-load handler to listen for my custom event:
class ComicsCollectionView {
  var get el;
  var get collection;

  ComicsCollectionView([el, collection]) {
    this.el = document.query(el);
    this.collection = collection;

    collection.on.load.add((event) {
      render();
    });

    collection.on.load.add((event) {
      print("This really did fire in response to a custom event");
    });

    _attachUiHandlers();
  }

  // ...
}
And indeed, it does work:


Nice. That almost seems a little too easy. I will take a look at this in the fresh light of a new day to make sure that I am not cheating somehow. If custom event handlers in Dart really are this easy, then I am thrilled. Bust mostly I'm sleepy.


Day #282

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

Sunday, January 29, 2012

Removing DOM Elements with Dart (A Tale of Null)

‹prev | My Chain | next›

For the next couple of days, I hope to keep it simple as I push toward a usable alpha release for Dart for Hipsters. While mucking about with my Dart sample application, I notice that cancelling the create form was not removing the form element from the page.

To remove the element, I had been:
if (form_div) form_div.remove();
That is not how the removeChild() works in Javascript (operates on a parent node, removing the specified child). I had tried working off of the API reference for Dart's remove(), which does not require an argument.

It may not require an argument, but it does state that it renames removeChild(), which would seem to indicate that it does operate on the parent. So I give it a try:
if (form_div) form_div.parent.remove(form_div);
But that fails to work as well.

OK, this is getting a bit annoying. When annoying, break it down to simplest case, I add a test_remove() method:
main() {
  test_remove();
  // ...
}

test_remove() {
  var test_div = new Element.html('<div id="test"/>');
  document.body.nodes.add(test_div);

  test_div = document.query('#test');
  test_div.parent.remove(test_div);
}
For that, I get a not-too-helpful backtrace:
Exception: NoSuchMethodException - receiver: 'Instance of 'BodyElementWrappingImplementation'' function name: 'remove' arguments: [Instance of 'DivElementWrappingImplementation']]
Stack Trace:  0. Function: 'Object.noSuchMethod' url: 'bootstrap' line:356 col:3
 1. Function: '::test_remove' url: 'http://localhost:3000/scripts/comics.dart' line:17 col:25
 2. Function: '::main' url: 'http://localhost:3000/scripts/comics.dart' line:5 col:14
So there is no remove() method on the test_div Element/Node? Surely there is because it is in the documentation. Ah, but it's in there with zero arity. So maybe:
test_remove() {
  var test_div = new Element.html('<div id="test"/>');
  document.body.nodes.add(test_div);

  test_div = document.query('#test');
  test_div.remove();
}
And, indeed that does work. But wait, why did that fail to remove an element in my original version?
if (form_div) form_div.remove();
Ah, dang it. I have a conditional in there. Sure enough, if I switch my test case to include that conditional:
test_remove() {
  var test_div = new Element.html('<div id="test"/>');
  document.body.nodes.add(test_div);

  test_div = document.query('#test');
  if (test_div) test_div.remove();
}
Then the test DIV is not removed:


It turns out that Dart very much wants a boolean in its conditionals. Lacking a conditional, it would appear to assume false. I cannot say that I am a fan of this, especially since the only resolution seems to be comparing to null:
test_remove() {
  var test_div = new Element.html('<div id="test"/>');
  document.body.nodes.add(test_div);

  test_div = document.query('#test');
  if (test_div != null) test_div.remove();
}
Ew. Not even the old !!test_div trick works. I seem to be stuck with this less than desirable null comparison.

I may not like this conditional structure (hopefully that can change), but I do much prefer the simple remove() in Dart to the traditional child.parent.removeChild(child) dance. All-in-all, I am willing to call this a net win for Dart. But it's a narrow win.

Day #280

Saturday, January 28, 2012

Dart HTML Templates and insertAdjacentElement

‹prev | My Chain | next›

Picking back up with my web app now that my laptop is back (yay!), tonight I would like to get back to my Dart web application. I hope to figure out how to handle checkbox elements in my form, but first, I need to get it displaying (again).

The form is now in a template method:
form_template([graphic_novel]) {
  return """
<form action="comics" id="new-comic-form">
<p>
<label>Title<br/>
<input type="text" name="title" id="comic-title"/>
</label></p>

<p>
<label>Author<br/>
<input type="text" name="author" id="comic-author"/>
</label></p>

<p>Format
<p>
<label>
<input type="checkbox" name="format" value="tablet" id="comic-table"/>
Tablet</label></p>
<p>
<label>
<input type="checkbox" name="format" value="dead-tree" id="comic-dead-tree"/>
Dead Tree</label></p>
</p>

<p>
<input type="submit" value="Bazinga!"/></p>

</form>
""";
}
To insert that into that DOM, I need the Dart equivalent of jQuery's prepend(), append(), before(), or after(). Looking through the API reference for Element, however, I do not see anything of the sort. I do come across an odd little beastie named insertAdjacentElement.

I had never heard of that one, but apparently insertAdjacentElement is an Internet Explorer thing. Instead of invoking before(), after, etc. on an element, I am supposed to invoke insertAdjacentElement() with the first argument of "beforeBegin", "afterBegin", "beforeEnd", or "afterEnd". Ew.

I do not see any obvious better way to accomplish this in the Dart HTML library, so I get started. First, in the method responsible for enabling the form, I add a call to a separate method that ensure the DOM element is present:
enable_add_form(event) {
  final form_div = _ensure_add_form('#add-comic-form');

  // effect and handlers here
}
Then, I create a new <div> tag with that selector and insert the result of the form template:
_ensure_add_form(id_selector) {
  var form_div = document.query(id_selector);
  if (form_div) form_div.remove();

  form_div = new Element.html("""
<div id="$id_selector">
${form_template()}
</div>
""");

  document.body.insertAdjacentElement('beforeEnd', form_div);

  return form_div;
}
I do rather like the string interpolation that is possible in Dart. It is wonderful for working with strings.

I really do not care for that instertAdjacentElement() method. It is way too long which does not lend itself to clear intent. It does work, but... no.

So instead, I stick with the add() method available to nodeList objects:
_ensure_add_form(id_selector) {
  var form_div = document.query(id_selector);
  if (form_div) form_div.remove();

  form_div = new Element.html("""
<div id="$id_selector">
${form_template()}
</div>
""");

  document.body.nodes.add(form_div);

  return form_div;
}
Hopefully the HTML API will continue to evolve. That insertAjacentElement() is just crazy noisy. Fortunately, the add() method covers the primary use-case of the various jQuery DOM manipulation methods.

Regardless, the ability to perform string interpolation and the support for multi-line strings in Dart is a big win.


Day #279

Friday, January 27, 2012

Regular Expressions in Dart

‹prev | My Chain | next›

Up tonight, I am going to explore regular expressions in Dart. As an old time Perl hacker, I love regular expressions. Even thought it has been years since I last coded Perl, I still think of its regular expressions as the best implementation that I have ever used. I do not care for the Ruby implementation--I never got the hang of the API and still have to refer to the documentation when using them. Javascript is better, but I forget whether matches() or test() is a string method or a regular expression method.

Unfortunately for me, the Dart implementation looks to be closer to Ruby's than Javascript's. But I will give it the benefit of the doubt.

Last night, I was working with simple greetings (e.g. "Howdy, Bob!"). In the end, I stuck with string splitting in order to identify the two parts:
  set greeting(greeting) {
    var parts = greeting.split(',');

    if (parts.length == 2) {
      _greeting_opening = parts[0];
      name = parts[1].replaceAll(' ', '').replaceAll('!', '');
    }
    else {
      _full_greeting = greeting;
    }
  }
That is a little noisy (because it does not use regular expressions!), but fairly straight forward. I take the greeting (e.g. "Howdy, Bob!") and split on commas. In the default case, I am left with two parts: "Howdy" and " Bob!". This falls into the first conditional in which I set the _greeting_opening and name to the two parts. The second part, in particular is painful since I have to strip spaces and punctuation.

As noisy as this solution is, it does not even cover even simple deviations from the default case. When I attempted to set a greeting of "Yo, Yo, Yo, Alice!", my scheme was thwarted.

The regular expression that ought to cover both cases is: /^(.+)\s*,\s*([\w\s]+)\W?$/. The two parentheses indicate the portions of the string that I want to extract. The greeting opening is all characters starting at the beginning of the line (^) all the way up to the last comma, ignoring any space around the comma (\s*,\s*). The period in (.+) matches any character and the plus indicates one or more of them.

The second part of the string in which I am interested starts after the comma and any spaces (\s*,\s*). Here I try to match one or more characters or spaces: ([\w\s]+). I match any number of those characters until the end of the string ($) with the exception of a possible non-alphanumeric at the end of the line (\W?).

So let's see how this works in Dart. Regular expressions in Dart are built with the RegExp class. I try this against my default case:
main() {
  var matcher = new RegExp('^(.+)\s*,\s*([\w\s]+)\W?$');

  matcher.allMatches("Howdy, Bob!").forEach((m) {
    print(m[0]);
  });
}
I am not quite sure about the allMatches() usage--I am just trying that as my first attempt. It hardly matters because I am greeted by:
$ dart regexp.dart
'/home/cstrom/repos/dart-book/samples/regexp.dart': Error: line 2 pos 54: illegal character after $ in string interpolation
  var matcher = new RegExp('^(.+)\s*,\s*([\w\s]+)\W?$');
                                                     ^
Well that is unfortunate. The dollar sign character in Dart strings is special--it signifies a string interpolation ("$name" might produce "Bob"). But here I am not attempting to interpolate a variable--I just want to match the end of the line. Ugh.

The API reference for RegExp puts an at sign before strings in RegExp's. Perhaps that prevents interpolation? There is one way to find out:
main() {
  var matcher = new RegExp(@'^(.+)\s*,\s*([\w\s]+)\W?$');

  matcher.allMatches("Howdy, Bob!").forEach((m) {
    print(m[0]);
  });
}
That does appear to prevent interpolation because I now get output:
$ dart regexp.dart
Howdy, Bob!
Aha! I get allMatches() now. That is used when there is an expectation that there are multiple matches within a string. Since I do not have to worry about that here, I should use firstMatch. I also see that the zeroth match is the entire string that matched the whole regular expression. I want the first and second sub-expressions. Something like:
main() {
  var matcher = new RegExp(@'^(.+)\s*,\s*([\w\s]+)\W?$');

  matcher.firstMatch("Howdy, Bob!").tap((m) {
    print(m[1]);
    print(m[2]);
  });
}
This results in:
$ dart regexp.dart
Unhandled exception:
NoSuchMethodException - receiver: 'Instance of 'JSRegExpMatch'' function name: 'tap' arguments: [Closure]]
 0. Function: 'Object.noSuchMethod' url: 'bootstrap' line:360 col:3
 1. Function: '::main' url: '/home/cstrom/repos/dart-book/samples/regexp.dart' line:4 col:40
Aw, man! No tap() in Dart? Say it ain't so!

Ah well, I can manage with:
main() {
  final matcher = new RegExp(@'^(.+)\s*,\s*([\w\s]+)\W?$');

  var m = matcher.firstMatch("Howdy, Bob!");
  print(m[1]);
  print(m[2]);
}
This results in:
$ dart regexp.dart
Howdy
Bob
Trying this out against the very challenging "Yo, yo, yo, Alice!":
main() {
  final matcher = new RegExp(@'^(.+)\s*,\s*([\w\s]+)\W?$');

  var m = matcher.firstMatch("Howdy, Bob!");
  print(m[1]);
  print(m[2]);

  m = matcher.firstMatch("Yo, yo, yo, Alice!");
  print(m[1]);
  print(m[2]);
}
I find:
$ dart regexp.dart                                  ~/repos/dart-book/samples
Howdy
Bob
Yo, Yo, Yo
Alice
Hunh. I think I might like that. That makes sense and I can definitely see myself remembering how to use those.

What about the match vs. test from Javascript? It seems that Dart improves on that. Instead of test, Dart uses contains. It still uses the matches() method on regular expressions. I try this out with a generalized version of my regular expression exploration code:
main() {
  final matcher = new RegExp(@'^(.+)\s*,\s*([\w\s]+)\W?$');

  try_regexp(matcher, "Howdy, Bob!");

  try_regexp(matcher, "Yo, yo, yo, Alice!");

  try_regexp(matcher, "Hey. You.");
}

try_regexp(regexp, str) {
  print('----');
  print(str);

  print("contains? ${str.contains(regexp)}");
  print("hasMatch? ${regexp.hasMatch(str)}");

  var m = regexp.firstMatch(str);
  print(m[1]);
  print(m[2]);
  print('');
}
I have also added a new string, "Hey. You.", which should not match. And indeed it does not:
dart regexp.dart                                  ~/repos/dart-book/samples
----
Howdy, Bob!
contains? true
hasMatch? true
Howdy
Bob

----
Yo, yo, yo, Alice!
contains? true
hasMatch? true
Yo, yo, yo
Alice

----
Hey. You.
contains? false
hasMatch? false
Unhandled exception:
NullPointerException
 0. Function: 'Object.noSuchMethod' url: 'bootstrap' line:360 col:3
 1. Function: '::try_regexp' url: '/home/cstrom/repos/dart-book/samples/regexp.dart' line:19 col:10
 2. Function: '::main' url: '/home/cstrom/repos/dart-book/samples/regexp.dart' line:8 col:13
(try.dartlang.org)


In the end, I am forced to admit that I rather like Dart's regular expressions. They will even improve my Javascript coding (I will always remember that Dart has a saner contains() method, which is analogous to the silly test()). It may not be Perl, and it would be nice if there were regular expression literals, but this is pretty darn nice.

Day #278

Thursday, January 26, 2012

Dart Setters and Getters

‹prev | My Chain | next›

I remain stuck without a proper web development environment (c'mon Apple geniuses!), so tonight I continue to explore some Dart internals. I played about with factory constructors last night, and have to admit they are pretty darn cool. Tonight, I take a look at setters and getters.

On the face of it, setters and getters in objects is a pretty simple thing. Instead of needing full blown method syntax to retrieve a value:
var bob = new Person("Bob");
bob.greeting(); // => "Howdy, Bob!"
In Dart, we should be able to do something like:
var bob = Person.new("Bob");
bob.greeting; // => "Howdy, Bob!"
It is a simple enough thing, but over the long haul, removing parenthesis can only help with readability / maintainability.

So let's see if I know how to do this:
class Person {
  var name;
  var _greeting_opening = "Howdy";

  Person(this.name);

  get greeting() => "${_greeting_opening}, ${name}!";
}
I declare two instance variables--one public, one private--to hold the name of the person and the word used to open the greeting. My constructor is simple enough (assign the supplied string as the name of the person), so the constructor declaration can make use of the nifty Dart shortcut that assign the argument to an instance variable.

Lastly, I declare a greeting method that puts the two variables together in a string. Here again, I have a simple method, so I can use the compact () => function syntax.

To use this class, I print out the greeting to two different people:
main() {
  var bob = new Person("Bob")
    , alice = new Person("Alice");

  print(bob.greeting);
  print(alice.greeting);
}
Running this results in:
$ dart setters_getters.dart
Howdy, Bob!
Howdy, Alice!
What I really like about this is that I do not have to follow a convention for these beasties (e.g. getGreeting, setGreeting). I never stick with whatever convention I start and end up with way too much confusion over a simple thing.

Setting things is similarly easy in Dart. In the class defintion, I need to use set instead of get. The usage of setters looks like any other property being assigned:
bob.greeting = "Yo, Bob!";
To make this work, the class would then need the set method:
class Person {
  var name;
  var _greeting_opening = "Howdy"
    , _full_greeting;

  Person(this.name);

  get greeting() {
    (_full_greeting != null) ?
      _full_greeting :
      "${_greeting_opening}, ${name}!";
  }
  set greeting(greeting) {
    _full_greeting = greeting;
  }
}
I have introduced a new, private instance variable _full_greeting. If set, the greeting getting will use this as the return value, otherwise it will stick with the regular "Howdy <<your name here>>!" version.

When I run this code:
main() {
  var bob = new Person("Bob")
    , alice = new Person("Alice");

  print(bob.greeting);
  print(alice.greeting);

  bob.greeting = "Yo, Bob!";
  print(bob.greeting);
}
I am greeted with:
null
null
null
Ah. I missed a return statement:
class Person {
  var name;
  var _greeting_opening = "Howdy"
    , _full_greeting;

  Person(this.name);

  get greeting() {
    return (_full_greeting != null) ?
      _full_greeting :
      "${_greeting_opening}, ${name}!";
  }
  set greeting(greeting) {
    _full_greeting = greeting;
  }
}
Now, I get:
Howdy, Bob!
Howdy, Alice!
Yo, Bob!
Of course, the real fun starts when you begin making calculations based on setters:
  set greeting(greeting) {
    var parts = greeting.split(',');

    if (parts.length == 2) {
      _greeting_opening = parts[0];
      name = parts[1].replaceAll(' ', '').replaceAll('!', '');
    }
    else {
      _full_greeting = greeting;
    }
  }
Running the following:
main() {
  var bob = new Person("Bob")
    , alice = new Person("Alice");

  print(bob.greeting);
  print(alice.greeting);

  bob.greeting = "Yo, Fred!";
  print(bob.greeting);
  print(bob.name);

  alice.greeting = "Yo, yo, yo, Alice!";
  print(alice.greeting);
  print(alice.name);
}
Results in:
Howdy, Bob!
Howdy, Alice!
Yo, Fred!
Fred
Yo, yo, yo, Alice!
Alice
(try.dartlang.org)


There is some definite power there.


Day #276