Friday, October 18, 2013

Form Handling Angular.dart Controllers


Life is best on the bleeding edge. Sure the danger is plentiful, but where better to learn about what you're really made of than when death threatens you by the minute? I think I lost track of that metaphor quicker than normal. No matter, I do enjoy the bleeding edge as a great place to explore what's next and, more importantly, why things are changing the way they are.

So far, my exploration of Angular.dart, the Dart port of the awesome AngularJS framework, has not dissappointed. Before moving much further, I think I need to move beyond the bleeding edge (is that going to plaid?). The 0.2 version of Angular.dart has been useful, but things are changing quickly and I would like to be better prepared (and leave more useful posts behind). But first...

I have the first scraps of Angular Calendar in place: the day view can list existing appointments:



This works thanks to an Angular controller linked via the appt-controller attribute in the container <div>:
    <div appt-controller>
      <ul class="unstyled">
        <li ng-repeat="appt in appointments">
          {{appt.time}} {{appt.title}}
        </li>
      </ul>
      <form ng-submit="addAppointment" class="form-inline">
        <input type="text" ng-model="appointmentText" size="30"
               placeholder="15:00 Learn Dart">
        <input class="btn-primary" type="submit" value="add">
      </form>
    </div>
The ng-repeat directive works by reading the property from the controller. But the ng-submit does not work because it is not in Angular.dart yet. That is a bummer, but hope is not lost.

Even though ng-submit is not supported (yet), ng-click is supported. So I have to tell the form not to submit (using a yucky, old onsubmit) and then attaching behavior to the button via ng-click:
    <div appt-controller>
      <ul><!-- ... --></ul>
      <form onsubmit="return false;" class="form-inline">
        <input ng-model="appointmentText" type="text" size="30"
               placeholder="15:00 Learn Dart">
        <input ng-click="addAppointment()" class="btn-primary" type="submit" value="add">
      </form>
    </div>
Note that I am still using appointmentText as the ng-model.

The controller class backing this markup from yesterday is:
@NgDirective(
  selector: '[appt-controller]'
)
class AppointmentCtrl {
  AppointmentCtrl(Scope scope) {
    scope
      ..['appointments'] = [{'time': '08:00', 'title': 'Wake Up'}]
      ..['addAppointment'] = (){print('yo');};
  }
}
The selector in the @NgDirective annotation is what Angular.dart uses to connect the container element with the controller. The constructor for the controller is passed the scope, which it uses to assign the default list of appointments (a single “Wake Up” entry) and define the addAppointment function. And, with the ng-click directive properly in place, I am now seeing the “yo” from that function printed to the console when I click the “add” button:



To get this function to actually add appointments to the list, I write a small fromText() function that converts the appointmentText ng-model from text into a Map. That value can be added to the scope's list of appointments. Lastly, the model is cleared so that other entries can be added:
class AppointmentCtrl {
  AppointmentCtrl(Scope scope) {
    scope
      ..['appointments'] = [{'time': '08:00', 'title': 'Wake Up'}]
      ..['addAppointment'] = (){
            var newAppt = fromText(scope['appointmentText']);
            scope['appointments'].add(newAppt);
            scope['appointmentText'] = '';
          };
  }

  Map fromText(v) { /* ... */ }
}
And that does the trick. I can now add the entire list of appointments to my daily calendar:



Nice! That turned out to be a little easier than expected. For completeness, the fromText method, with some rudimentary support for defaults time and title, is:
  Map fromText(v) {
    var appt = {'time': '00:00', 'title': 'New Appointment'};

    var time_re = new RegExp(r"(\d\d:\d\d)\s+(.*)");
    if (time_re.hasMatch(v)) {
      appt['time'] = time_re.firstMatch(v)[1];
      appt['title'] = time_re.firstMatch(v)[2];
    }
    else {
      appt['title'] = v;
    }

    return appt;
  }
I find the handling of scope awkward when compared to the current AngularJS $scope. This is mostly a function of $scope being an object in JavaScript where it is a Map in Dart. While the Map is the appropriate structure, the bracket lookup looks ugly when compared with the dot notation for accessing the same values in JavaScript. The method cascade helps some, but not enough.

Happily, the Angular.dart folks are already on top of this. Both the recent 0.2 release and the master branch boast a newer, more classical class based approach. I will explore that tomorrow.

For now, I am pleased with the progress that I have made. Despite the lack of ng-submit, it is still possible to create the usual awesome Angular forms, and do so fairly easily. I am looking forward to exploring this stuff more—no matter how dangerous (or plaid) the code may prove.


Day #908

3 comments:

  1. When you go to bleeding edge you should change to:


    @NgController {
    selector: '[ng-controller=AppointmentCtrl]',
    publishAs: 'ctrl'
    }
    class AppointmentCtrl {
    String appointmentText = '';
    List appointments = [{'time': '08:00', 'title': 'Wake Up'}];

    addAppointment() {
    var newAppt = fromText(appointmentText);
    appointments.add(newAppt);
    appointmentText = '';
    };
    }

    Then template to:

    &lt:div ng-controller=AppointmentCtrl>
    &lt:ul class="unstyled">
    &lt:li ng-repeat="appt in ctrl.appointments">
    {{appt.time}} {{appt.title}}
    &lt:/li>
    &lt:/ul>
    &lt:form ng-submit="ctrl.addAppointment" class="form-inline">
    &lt:input type="text" ng-model="ctrl.appointmentText" size="30"
    placeholder="15:00 Learn Dart">
    &lt:input class="btn-primary" type="submit" value="add">
    &lt:/form>
    &lt:/div>

    ReplyDelete
    Replies
    1. Maybe a GIST is better: https://gist.github.com/mhevery/7092098

      Delete
    2. Thanks! The gist definitely shows better - these commenting systems...

      I think I more or less followed your suggested pattern the next night: http://japhr.blogspot.com/2013/10/a-classical-controller-in-angulardart.html. That actually worked with the 0.2 version, which surprised me - I thought I'd have to run against the GitHub repo. I definitely like your approach better than the one in this post. In the future, will Angular.dart support both formats or just the one you've shown here?

      Delete