Saturday, September 28, 2013

Generic GUI Library in Dart (Part 3)

I could run the generic GUI lib application on browsers(in javascript mode).
Since Dart does not support mirror in JavaScript mode, I needed to do this kind of things(see previous post).
The following snapshot shows running the app on Dartium , Chrome and Firefox:


In order to achieve this, I re-factored the code so that it eliminates the direct reference to 'dart:mirrors' by introducing minimal mirror interfaces.

 library mirror_api;

typedef IClassMirror CMirrorFun(Type type);

class ClassMirrorFactory {
  static CMirrorFun _cmf;
  static IClassMirror create(Type type) => _cmf(type);
  static void register(CMirrorFun cmf) { _cmf = cmf; }
}

//
abstract class IClassMirror {
  IInstanceMirror newInstance();
  IInstanceMirror reflect(Object obj);
 
  Map<Symbol, IFieldType> get fieldTypes;
}

abstract class IFieldType {
  Symbol get symbol;
  String get name; // convert symol to string
  Type get type;
}

//
abstract class IInstanceMirror {
  IClassMirror get cmirror;
  Object get reflectee;
 
  IField getField(Symbol name);
}

abstract class IField {
  Symbol get symbol;
  String get name; // convert symol to string
 
  Object get value;
  void set value(Object obj);
 
  Type get type;
}
 Then I provided two implementations of this interfaces:
  •  mirror_dynamic_lib.dart
  •  mirror_static_lib.dart
 mirror_dynamic_lib will use 'dart:mirrors', so this will be useful to run on server side, and no extra information are required to use this lib.
the second one is for javascript mode, it require additional meta  information for the class which is to be used in a generic class.
Following dart file is to provide such information for Expense class.  As shown in the last part, there is a dart2js compiler bug, I could not use the simpler version(commented one), but if we use this code, I could avoid the compiler bug(compiler was crashed).(this bug was fixed after I reported this issue in rev 28044)

library sample_mirror;

import 'mirror_libs/mirror_api_lib.dart';
import 'mirror_libs/mirror_static_lib.dart';
import 'shared/models.dart';

/*
 * This must be invoked before calling any of these simple mirror APIs.
 */

void initClassMirrorFactory() {
  ClassMirrorFactory.register((Type type)=>(type == Expense)?
      new StaticClassMirror(()=>new Expense.Default(), expenseFieldInfo)
  :null);
}

Type _String = String;
Type _ExpenseType = ExpenseType;
Type _DateTime = DateTime;
Type _num = num;
Type _bool = bool;

Map<Symbol, FieldInfo> expenseFieldInfo =
[
 new FieldInfo(const Symbol('id'), _String, (Expense e)=>e.id, (Expense e, String value) { e.id = value; }),
 new FieldInfo(const Symbol('rev'), _String, (Expense e)=>e.rev, (Expense e, String value) { e.rev = value; }),
 new FieldInfo(const Symbol('expenseType'), _ExpenseType, (Expense e)=>e.expenseType, (Expense e, ExpenseType value) { e.expenseType = value; }),
 new FieldInfo(const Symbol('date'), _DateTime, (Expense e)=>e.date, (Expense e, DateTime value) { e.date = value; }),
 new FieldInfo(const Symbol('amount'), _num, (Expense e)=>e.amount, (Expense e, num value) { e.amount = value; }),
 new FieldInfo(const Symbol('detail'), _String, (Expense e)=>e.detail, (Expense e, String value) { e.detail = value; }),
 new FieldInfo(const Symbol('isClaimed'), _bool, (Expense e)=>e.isClaimed, (Expense e, bool value) { e.isClaimed = value; })
 ].fold({}, (Map map, FieldInfo fh) => map..[fh.symbol] = fh);

/*
//
// This works for DartVM, but it fails dart2js.
//
Map<Symbol, FieldInfo> expenseFieldInfo =
    [
     new FieldInfo(const Symbol('id'), String, (Expense e)=>e.id, (Expense e, String value) { e.id = value; }),
     new FieldInfo(const Symbol('rev'), String, (Expense e)=>e.rev, (Expense e, String value) { e.rev = value; }),
     new FieldInfo(const Symbol('expenseType'), ExpenseType, (Expense e)=>e.expenseType, (Expense e, ExpenseType value) { e.expenseType = value; }),
     new FieldInfo(const Symbol('date'), DateTime, (Expense e)=>e.date, (Expense e, DateTime value) { e.date = value; }),
     new FieldInfo(const Symbol('amount'), num, (Expense e)=>e.amount, (Expense e, num value) { e.amount = value; }),
     new FieldInfo(const Symbol('detail'), String, (Expense e)=>e.detail, (Expense e, String value) { e.detail = value; }),
     new FieldInfo(const Symbol('isClaimed'), bool, (Expense e)=>e.isClaimed, (Expense e, bool value) { e.isClaimed = value; })
     ].fold({}, (Map map, FieldInfo fh) => map..[fh.symbol] = fh);
*/
From the top level dart file, we can change the  library by just switching to import file as following. I'll attach the entire main dart file for this app. this is  all required codes  to run this application using the generic GUI library. the layout of several components like table, buttons etc, and the controlling logic  are defined here.


 library sample_generic_gui;

import 'dart:html';
import "shared/models.dart";
import "gui_generic_lib/gui_generic_lib.dart";
//import "mirror_libs/mirror_dynamic_lib.dart"; // <== use 'dart:mirrors'
import "sample_mirror_impl.dart";; // <== no use of 'dart:mirrors'

class AppController extends Component {
  static const String APP = "g_app_ctlr";
  final DivElement _uiRoot;
  DivElement _content;
  DivElement _actions;
 
  Table<Expense> table;
  Form<Expense> form;
  ButtonComp newButtom;
  ButtonComp saveButtom;
  ButtonComp deleteButtom;
 
  AppController(Component parent, this._uiRoot, {List<Expense> expenses}): super(parent, const[APP]) {
    table = new Table<Expense>.fromModelType(this, Expense, formatFunctionMap: {ExpenseType: ExpenseTypeComp.format});
    form = new Form<Expense>(this, Expense,
        specialInputCompMap: {ExpenseType: ExpenseTypeComp.inputCompFactory},
        adhocCompMap: {'detail': 'textarea'}  // temp
    );
  
    Expense e = null;
    newButtom = new ButtonComp(this, "New", (_) {
      e = form.create();
      _changeButtonState(false, create: true);
    
    });
    saveButtom = new ButtonComp(this, "Save", (_) {
      Expense e0 = form.save();
      if (e0 != null) {
        e = e0;
        table.addOrUpdate(e);
        _changeButtonState(true);
      }
    });
    deleteButtom = new ButtonComp(this, "Delete", (_) {
      Expense e = form.delete();
      if (e != null) {
        table.delete(e);
        _changeButtonState(true);
      }
    });
    _changeButtonState(true);
  
  
   table.row_listeners.add((ev, row){
      //print("row clicked.. ${ev}, row: ${row}");
      e = row.e;
      form.load(e);
      _changeButtonState(false);
    });
  
    if (expenses != null) {
      table.load(expenses);
    }
    _uiRoot.nodes.add(element); // this 'element' tiggers DOM node creation!
  }
 
  void _changeButtonState(bool isEmpty, {create: false}) {
    (newButtom.element as ButtonElement).disabled = !isEmpty;
    (saveButtom.element as ButtonElement).disabled = isEmpty;
    (deleteButtom.element as ButtonElement).disabled = (create)?true:isEmpty;  
  }

  Element createElement() => addSubComponents0(newElem("div"));
 
  Element update() => addSubComponents0(initElem());
    
  Element addSubComponents0(Element elm) => addListeners(
      elm
      ..classes.add("section")
      ..nodes.add(new Element.html("<header class='section'>${table.modelType} Table</header>"))
      ..nodes.add(_content = new Element.tag("div")
        ..nodes.add(table.element))
      ..nodes.add(_actions = new Element.tag("div")
        ..id = "actions"
        ..classes.add("section")
        ..nodes.add(newButtom.element)
        ..nodes.add(saveButtom.element)
        ..nodes.add(deleteButtom.element))
      ..nodes.add(form.element)
      ..nodes.add(new Element.html("<footer class='section' id='footer'></footer>")));
}

class ExpenseTypeComp extends SelectComp<ExpenseType> {
  static const String EXPENSE_TYPE_INPUT = "g_expense_type_input";
 
  static final Map<String, ExpenseType> _expenseTypes = {
    "TRV": const ExpenseType("Travel","TRV"),
    "BK": const ExpenseType("Books","BK"),
    "HT": const ExpenseType("Hotel","HT")                        
  };
 
  ExpenseTypeComp(Component parent, String label, {List<String> classes: const [SelectComp.SELECT_INPUT, EXPENSE_TYPE_INPUT]})
    : super(parent, label, ExpenseType, classes: classes);
 
  static ExpenseTypeComp inputCompFactory(Component c, String name, Type t)=>new ExpenseTypeComp(c, name);
  static String format(ExpenseType et) => (et == null)?'':et.name;
 
  Map<String, ExpenseType> get entityTypes => _expenseTypes;
 
  String getCode(ExpenseType et) => et.code;
  String getName(ExpenseType et) => et.name;
}

main() {
  // register reflection factory
  initClassMirrorFactory();
 
  List<Expense> expenses = [
                            new Expense.random(),
                            new Expense.random(),
                            new Expense.random(),
                            new Expense.random(),
                            new Expense.random()];
  Element uiContainer = document.query("#sample_generic_gui");
  AppController app = new AppController(null, uiContainer);
  app.table.load(expenses);
-------------------------------

here is the implementation classes:

library dynamic_mirror;

import 'dart:mirrors';
import 'mirror_api_lib.dart';

/*
 * This must be invoked before calling any of these simple mirror APIs.
 */
void initClassMirrorFactory() {
  ClassMirrorFactory.register((Type type)=>new DynamicClassMirror.reflectClass(type));
}

//
// IClassMirror implementations
//
class DynamicClassMirror implements IClassMirror {
  static Map<Type, DynamicClassMirror>  cmirrs = {};
 
  final Type type;
  ClassMirror _cmirror;
  MethodMirror _ctor;
  Map<Symbol, IFieldType> _fieldTypes = {};
 
  DynamicClassMirror(this.type) {
    _cmirror = reflectClass(type);
   
    reflectClass(type).constructors.forEach((k, v){
      if (v.parameters.length == 0 && (_ctor == null || getSymbolName(k) == "${type}.Default")) {
        _ctor = v;
      }
    });
   
    _cmirror.getters.forEach((Symbol symbol, MethodMirror md){
      _fieldTypes[symbol] = new DynamicFieldType(symbol, md);
    });
  }
 
  factory DynamicClassMirror.reflectClass(Type type) {
    DynamicClassMirror cmirr = cmirrs[type];
    if (cmirr == null) {
      cmirr = new DynamicClassMirror(type);
    }
    return cmirr;
  }
 
  IInstanceMirror newInstance() =>
      reflect(_cmirror.newInstance(_ctor.constructorName, []).reflectee);

  IInstanceMirror reflect(Object obj) => new DynamicInstanceMirror(this, obj);

  Map<Symbol, IFieldType> get fieldTypes => _fieldTypes;

}

class DynamicFieldType implements IFieldType {
  Symbol _symbol;
  String _name;
  MethodMirror _md;
 
  DynamicFieldType(this._symbol, this._md) {
    _name = getSymbolName(_symbol);
  }
 
  Symbol get symbol => _symbol;
  String get name => _name;
  Type get type => (_md.returnType as ClassMirror).reflectedType;
}

//
// IInstanceMirror implementations
//
class DynamicInstanceMirror implements IInstanceMirror {
  Map<Symbol, DynamicField>  dfs = {};
 
  final IClassMirror _cmirror;
  InstanceMirror _imirror;
 
  DynamicInstanceMirror(this._cmirror, Object obj) {
    _imirror = reflect(obj);
  }
 
  IClassMirror get cmirror => _cmirror;
 
  Object get reflectee => _imirror.reflectee;
  IField getField(Symbol name) => new DynamicField.create(name, this);
}

class DynamicField implements IField {
  DynamicInstanceMirror _parent;
  Symbol _symbol;
  String _name;
 
  DynamicField(Symbol this._symbol, this._parent) {
    _name = getSymbolName(_symbol);
  }
 
  factory DynamicField.create(Symbol symbol, DynamicInstanceMirror _parent) {
    DynamicField df = _parent.dfs[symbol];
    if (df == null) {
      _parent.dfs[symbol] = df = new DynamicField(symbol, _parent);
    }
    return df;
  }
 
  Symbol get symbol => _symbol;
  String get name => _name;
 
  Object get value => _parent._imirror.getField(_symbol).reflectee;
  void set value(Object obj) { _parent._imirror.setField(_symbol, obj); }
 
  Type get type => _parent._cmirror.fieldTypes[_symbol].type;
 }

//
// utils
//
String getSymbolName(Symbol symbol) => symbol.toString().substring('Symbol("'.length, symbol.toString().length-2);
 ----------------------------
 library static_mirror;

import 'mirror_api_lib.dart';

//
// IClassMirror implementations
//
typedef Object Getter(Object entity);
typedef void Setter(Object entity, Object value);

class FieldInfo implements IFieldType {
  final Symbol _symbol;
  String _name;
  final Type _type;
 
  final Getter getter;
  final Setter setter;
 
  FieldInfo(this._symbol, this._type, this.getter, this.setter) {
    _name = getSymbolName(_symbol);
  }
 
  Symbol get symbol => _symbol;
  String get name => _name;
  Type get type => _type;
}

typedef Object DefaultConstructor();

class StaticClassMirror implements IClassMirror {
  final DefaultConstructor ctor;
  final Map<Symbol, FieldInfo> fieldInfos;
 
  StaticClassMirror(this.ctor, this.fieldInfos);
 
  IInstanceMirror newInstance() => reflect(ctor());
  IInstanceMirror reflect(Object obj) => new StaticInstanceMirror(this, obj);

  Map<Symbol, IFieldType> get fieldTypes => fieldInfos;
}

//
// IInstanceMirror implementations
//
class StaticInstanceMirror implements IInstanceMirror {
  Map<Symbol, StaticField>  dfs = {};
 
  final IClassMirror _cmirror;
 
  final Object _obj;
 
  StaticInstanceMirror(this._cmirror, Object this._obj);
 
  IClassMirror get cmirror => _cmirror;
 
  Object get reflectee => _obj;
  IField getField(Symbol name) => new StaticField.create(name, this);
}

class StaticField implements IField {
  StaticInstanceMirror _parent;
  Symbol _symbol;
  String _name;
 
  StaticField(Symbol this._symbol, this._parent) {
    _name = getSymbolName(_symbol);
  }
 
  factory StaticField.create(Symbol symbol, StaticInstanceMirror _parent) {
    StaticField df = _parent.dfs[symbol];
    if (df == null) {
      _parent.dfs[symbol] = df = new StaticField(symbol, _parent);
    }
    return df;
  }
 
  Symbol get symbol => _symbol;
  String get name => _name;
 
  Object get value => _cmirror.fieldInfos[symbol].getter(_parent.reflectee);
 
  void set value(Object obj) { _cmirror.fieldInfos[symbol].setter(_parent.reflectee, obj); }
 
  Type get type => _cmirror.fieldInfos[symbol].type;
 
  StaticClassMirror get _cmirror => (_parent._cmirror as StaticClassMirror);
 }

//
// utils
//
String getSymbolName(Symbol symbol) => symbol.toString().substring('Symbol("'.length, symbol.toString().length-2);

No comments:

Post a Comment