2013. március 10., vasárnap

Kikerült a GitHub-ra az AngularGWT első változata, ami a Google két nagy sikernek örvendő technológiáját, az...

Kikerült a GitHub-ra az AngularGWT első változata, ami a Google két nagy sikernek örvendő technológiáját, az AngularJS-t és a GWT-t ötvözi. Így a Model-View összerendelést deklaratív úton az AngularJS-el végezhetjük, míg a controllert Java-ban fejleszthetjük. Szerintem ha rendesen kiforrja magát a technológia, akkor nagyon hatékony eszköz lehet a fejlesztők kezében.

Amikor Ray Cormwell (aki a GWT fejlesztésének vezetője) a Bombermine-ról posztolt (https://plus.google.com/110412141990454266397/posts/FxRjuNDUuN1), felvetettem, hogy kellene valami szép megoldás gwt-s angularjs controllerek írására. Persze lehet, hogy akkor már megvolt a fejében az ötlet, az is lehet, hogy ez már azelőtt több ezer embernek eszébe jutott. Minden esetre hátha esetleg volt ennek valami halvány köze ahhoz, hogy megszületett ez a projekt. :)

Originally shared by Ray Cromwell

Ok, my hacked up AngularJS + GWT integration is available on GitHub. Uses a ton of code-generators and 'configuration by convention' to try and keep boilerplate to a minimum while preserving AngularJS idiomatic feel.

Works great with SuperDevMode, probably doesn't work at all with regular DevMode. I haven't tried it yet, but a lot of JSO and compiler-casting magic is going on that will probably break a JVM hosted module.

Lots of goodies: Use interfaces to define object literals with typed getters/setters, using either JavaBean style, or fluent style. Implementation backed by JSO is auto-generated. Two kinds: Model (just plain old json object), and Scope, which is backed by a AngularJs $scope object and has extra methods.

You can register a watch function just by annotating any function as @NgWatch. You can make something injectable by adding @NgInject to its type, and then when you refer to it in methods, like a controller method, $inject will be automatically set up.

You can register controller actions just by adding public methods to a controller. That's it. You can compare with JS here: https://github.com/addyosmani/todomvc/tree/gh-pages/architecture-examples/angularjs/js

Here's how you configure an Angular module.
@NgName("todomvc")
@NgDepends({TodoStorage.class, TodoFilter.class, TodoController.class, TodoBlurDirective.class,  TodoFocusDirective.class})
public class TodoAppModule implements AngularModule {}

That's about the most boilerplate config you need. This tells the code generator what dependency injections to register.

Here's how you define a storage service:
@NgInject(name= "todoStorage")
public class TodoStorage {
  private static final String STORAGE_ID = "todos-angularjs";

  public ArrayOf get() {
    ArrayOf todos = reinterpret_cast(Json.parse(Browser.getWindow().getLocalStorage().getItem
        (STORAGE_ID)));
    return todos == null ? JsArrayOf.create() : todos;
  }

  public void put(ArrayOf todos) {
    Browser.getWindow().getLocalStorage().setItem(STORAGE_ID, toJson(todos));
  }
}

Here's how you define a directive:
public class TodoBlurDirective implements Directive {

 @NgDirective("todoBlur")
  public void blur(final TodoScope scope, final NgElement element,
                   final JsonObject attrs) {
    element.bind("blur", new Runnable() {
      public void run() {
        scope.$apply(attrs.getString("todoBlur"));
      }
    });
  }
}

Here's how you define a controller:
@NgInject(name= "TodoCtrl")
public class TodoController extends AngularController {

  private ArrayOf todos;
  private Location location;
  private TodoStorage store;
  private TodoFilter filterFilter;

  public void onInit(TodoScope scope, Location location, TodoStorage store, TodoFilter filter) {
    this.location = location;
    this.store = store;
    this.filterFilter = filter;
    scope.newTodo("")
        .editedTodo(null)
        .location(location);
    this.todos = store.get();
    scope.todos(todos);
    if ("".equals(location.path())) {
      location.path("/");
    }
  }

 @NgWatch(value= "todos", objEq = true)
  public void $watchTodos() {
    Todo todoPredicate = makeTodo();
    todoPredicate.setCompleted(false);
    scope.remainingCount(filterFilter.filter(todos, todoPredicate).length())
        .doneCount(todos.length() - scope.remainingCount())
        .allChecked(scope.remainingCount() != 0);
    store.put(todos);
  }

 @NgWatch("location.path()")
  public void $watchPath(String path) {
    scope.statusFilter("/active".equals(path) ?
        makeTodo().setCompleted(false) :
        "/completed".equals(path) ?
            makeTodo().setCompleted(true) : null);
  }

  private Todo makeTodo() {
    return make(GWT.create(Todo.class));
  }

  public void addTodo() {
    if (scope.newTodo().length() == 0) {
      return;
    }
    Todo newTodo = makeTodo();
    newTodo.setTitle(scope.newTodo())
        .setCompleted(false);
    todos.push(newTodo);
    scope.newTodo("");
  }

  public void doneEditing(Todo todo) {
    scope.editedTodo(null);
    if ("".equals(todo.getTitle())) {
      removeTodo(todo);
    }
  }

  public void editTodo(Todo todo) {
    scope.editedTodo(todo);
  }

  public void removeTodo(Todo todo) {
    todos.splice(todos.indexOf(todo), 1);
  }

  public void markAll(boolean done) {
    for (Todo todo : iterable(todos)) {
      todo.setCompleted(done);
    }
  }

  public void clearDoneTodos() {
    ArrayOf result = JsArrayOf.create();
    for (Todo todo : iterable(todos)) {
      if (!todo.getCompleted()) {
        result.push(todo);
      }
    }
    scope.todos(result);
  }
}
https://github.com/cromwellian/angulargwt/

Nincsenek megjegyzések:

Megjegyzés küldése