A Programmer's Blog

JS-Labs PT.3: Schlankere Controller durch Bindings mit AngularJS!

Apr
26

In meinem letzten Post habe ich gezeigt wie Jasmine und Coffeescript schönen Test-Code schreiben kann. In diesem Post werde ich zeigen wie sich Controller-Code durch Bindings mit AngularJS vereinfachen lässt.

Mein bisheriger Controller

Meine Arbeiten an der jQuery-Version des Wakmarks-Projekts neigen sich dem Ende zu. Ein Anti-Pattern was sich herauskristallisiert ist, dass der Controller bei diversen Events sowohl Model als auch View aktualisieren muss.

Als Beispiel soll hier der ‘click’-Handler auf dem “gelesen”-Button eines Bookmarks dienen. Mit dem Button lässt sich der Zustand eines Bookmarks zwischen “gelesen” und “ungelesen” wechseln:

onBookmarkReadClick : (event) ->
 bookmarkView = $(event.target).parents(".bookmark")
 # update the model ...
 bookmark = Bookmark.find(bookmarkView.data('id'))
 bookmark.read = !bookmark.read
 # ... and the view :(
 if(bookmark.read)
   $(event.target).addClass('read')
   $(event.target).removeClass('unread')
 else
   $(event.target).addClass('unread')
   $(event.target).removeClass('read')

Das “M” und das “V” im MVC

Schön wäre es, wenn der Controller nur das Model aktualisieren müsste und sich der View dann automatisch aktuallisiert. Im klassischen MVC wird das Problem durch das Observer-Muster gelöst.

(original von Wikipedia)

In meinem Beispiel besteht der View aber aus purem HTML:

<div data-id="1">
 ...
   <button class="unread">&nbsp;</button>
 ...
</div>

Was also tun?

Bindings

Glücklicherweise gibt es Frameworks die sogenannte Bindings anbieten. Durch Bindings lassen sich Attribute des Models an HTML-Elemente des Views binden und automatisch damit synchronisieren. Letztendlich handelt es sich um vereinfachte Variante um mit dem Observer-Musters zu arbeiten.

Mit AngularJS kann der Code des Controllers damit stark vereinfacht werden:

$scope.onBookmarkReadClick = (bookmark) ->
  bookmark.read = !bookmark.read
$scope.getBookmarkReadClass = (bookmark) ->
  if bookmark.read
    "read"
  else
    "unread"

Eigentlich wäre es ein Zweizeiler gewesen, aber leider leider unterstützten AngularJS-Bindings keine Kontrollstrukturen, weshalb es die weitere Methode “getBookmarkReadClass” benötigt um den read-Flag in einen CSS-Klassennamen umzuwandeln.

Der View sieht dann so aus:

<div class="bookmark" data-id="{{bookmark.id}}">
 ...
   <button class="{{getReadClassName(bookmark)}}">&nbsp;</button>
 ...
</div>

Filter

Mir gefällt diese Variante schon deutlich besser, aber mich stört die Methode “getBookmarkReadClass” in meinem Controller. Es gibt in AngularJS auch die Möglichkeit eigene Filter zu definieren, damit liesse sich der View in etwa so schreiben:

<div data-id="{{bookmark.id}}">
 ...
   <button class="bookmark.read | to_bookmark_class">&nbsp;</button>
 ...
</div>

Die Methode “getBookmarkReadClass” könnte damit entfernt werden. Allerdings habe ich es nicht geschafft einen solchen Filter zu schreiben, da die Dokumenation nicht aktuell ist.

Weitere Frameworks mit Binding-Support

Morgen werde ich mir noch Batman-JS anschauen. Ein weiteres Framework mit Bindings wäre noch Ember-JS, welches aber leider nicht zu CoffeeScript kompatibel ist. Ein Vergleich verschiedener MVC Frameworks gibt es hier.

 

Scala and the Observer Pattern

Jun
17

I just started coding my first scala snippets. I played a bit around with lwjgl library to create some random graphic stuff.

The first problem I faced was to find a generic implementation to dispatch events such as render- or input-events from one place and handle them in another place. In OO-Languages this problem is usually solved using the observer pattern.

After studying some existing scala solutions,  I did not find the one that fits my coding style. Since I come from the Flash-world, I would really appreciate a solution where observers are just callback-functions instead of implementations of traits. I also don’t like to have my observables cluttered with code for dispatching the events to the observers.

My Solution consists of a single generic class, the “Event”:

class Event[T] {
 private var listeners: List[T] = List();

 def apply(listener: T): Unit = register(listener);
 def register(listener : T) : Unit = listeners ::= listener;
 def unregisterAll() : Unit = listeners = List();
 def fire(f : (T) => Unit) = {
   for(l <- listeners) {
     f(l)
   }
 }
}

All an observable has to do now is to create one field for each event-type they offer and to call fire() on them. The following example defines one event that gets fired as soon as the position of a DisplayObject changes. The callback-method takes the coordinates as paramters containing the new values.

class DisplayObject {

 val onPositionChangeEvent = new Event[(Int, Int) => Unit];

 private var x:Int = 0;
 private var y:Int = 0;

 def changePosition(x:Int, y:Int) = {
   this.x = x;
   this.y = y;
   onPositionChangeEvent.fire(_(x, y));
 }
}

Now we can register a callback-function directly at the onPositionChangeEvent-field. In the following example we print the new value to the console.

var observable = new DisplayObject();
observable.onPositionChangeEvent((x, y) => println(x + ":" + y));
observable.changePosition(10, 15);

One problem I faced is that I could not find any possibility to reliably unregister from an event. So I decided to implement an unsubscribeAll method.

observable.onPositionChangeEvent.unregisterAll();

If someone has an idea how this could be improved, please leave me a comment.