Tuesday, October 15, 2013

Reflective Interceptor, Monitor in Dart (Part 2)

Here is a sample code using Monitor. This sample was created from dart-tutorials-samples-Master/web/namebadge so that we can compare the differences.

In order to have getter/setter interception, we need to use Mixin with Monitor or extend Monitor.
We need to define monitored private fields, and public getter/setter corresponding to these private annotated fields.
The public getter/setter will call inherited methods get/set defined in Monitor.
This part is sort of mechanical, and may be a bit annoying to repeat, but it is better than using  code generator.
Also we need to define actions to be triggered when setter/getter are called using addSetListener, addGetListner.

When we use setter for private variables, like '_male = true;', then no actions are triggered. but if we use public setter, like 'male = true; ' then, it will trigger the registered actions.

Compared the both code size, Polymer looks better. But this approach is simpler and flexible. we don't have to rely on some convention between Polymer html and dart class bound to the polymer element.
Also there is no need to maintain two files system closely associated with each others. That will become maintenance night mare.

Since this is normal dart code, much more complicated things can be done without struggling with the restriction posed by Polymer.

I don't like ugly JSP style mixing of control in html file.
If we use cascading style to build html Element, the size of code is comparable(may be smaller?) to HTML. And we can do much more flexible thing in dart.

One advantage of using HTML is it uses less dart code. the code size can be smaller than program based one.
but if we develop dynamic web site, such a difference may be ignorable.

And we want to take generic web component approach like Form, Table, Polymer style approach does not work.

This is using my portable_mirror lib,  it works using both dart VM using dart:mirrors lib, and javascript mode with static stubs.

import 'dart:html';
import 'dart:math';

import "../../gui_component/lib/monitor_lib.dart";
import "package:gui_component/gui_component_lib.dart";

class NameBadge extends Component with Monitor {
  static const String APP = "g_namebage";
  TextInputComp textInput;
  ButtonComp button;
  RadioComp maleRadio;
  RadioComp femaleRadio;
  
  Element _div0;
  Element _initDiv0(Element e) => _div0 = e;

  @Monitored()
  String _badgename = 'Bob';
  @Monitored()
  bool _male = false;
  @Monitored()
  bool _female = true;

  String get badgename => get(const Symbol('badgename'));
  void set badgename(String v) => set(const Symbol('badgename'), v);
  
  bool get male => get(const Symbol('male'));
  void set male(bool v) => set(const Symbol('male'), v);
  
  bool get female => get(const Symbol('female'));
  void set female(bool v) => set(const Symbol('female'), v);
  
  NameBadge(Component parent): super(parent, const[APP]) {
    this.textInput = new TextInputComp(this, "Enter New Name:", String);
    this.button = new ButtonComp(this, "Generate pirate name", (e) { badgename = pirateName(); });
    this.maleRadio = new RadioComp(this, "Male", "male", _male, name:"maleOrFemale")..onClick((_, comp){ male = true; });
    this.femaleRadio = new RadioComp(this, "Female", "female", _female, name:"maleOrFemale")..onClick((_, comp){ female = true; });
    
    addSetListener(const Symbol("badgename"), (NameBadge target, String old_value, String new_value){
      textInput.value = new_value;
      _div0.text = new_value;
    });
    addSetListener(const Symbol("male"), (NameBadge target, bool old_value, bool new_value){
      if (old_value == new_value) return;
      if (_male && _female) female = false;
    });
    addSetListener(const Symbol("female"), (NameBadge target, bool old_value, bool new_value){
      if (old_value == new_value) return;
      if (_female && _male) male = false;
    });
  }

  String pirateName() => (_female)?new PirateName.female().name:new PirateName.male().name;
  
  Element createElement() => addSubComponents0(newElem("div"));
  
  Element update() => addSubComponents0(initElem());
  
  Element addSubComponents0(Element elm) => addListeners(
      elm
        ..nodes.add(
            new Element.div()
              ..classes.add("entry")
              ..nodes.add(textInput.element)
              ..nodes.add(button.element)
              ..nodes.add(maleRadio.element)
              ..nodes.add(femaleRadio.element))
        ..nodes.add(
            new Element.div()
              ..classes.add("outer")
              ..nodes.add(new Element.div()..classes.add('boilerplate')..text = 'Hi! My name is')
              ..nodes.add(_initDiv0(new Element.div()..classes.add('name')..text = _badgename))));
}

Following codes are from original dart-tutorials-samples-Master/web/namebadge.
In polymer version, we need to write both dart program and corresponding polymer html files. So this dart part is short compared to my monitor based implementation, but if we compare with dart and html, the code size difference will become less.

import 'dart:html';
import 'dart:math';
import 'package:polymer/polymer.dart';

@CustomTag('name-badge')
class NameBadge extends PolymerElement with ObservableMixin {
  @observable String badgename = 'Bob';
  @observable bool female = true;
  @observable bool male = false;
    
  NameBadge() {
    bindProperty(this, const Symbol('female'), () {
        if (female) {
          male = false;
        }
        notifyProperty(this, const Symbol('name'));
      });
    bindProperty(this, const Symbol('male'), () {
        if (male) {
          female = false;
        }
        notifyProperty(this, const Symbol('name'));
      });
  }

  void pirateName(Event event, var detail, Node target) {
    if (female) {
      badgename = new PirateName.female().name;
    } else {
      badgename = new PirateName.male().name;
    }
  }
}

<polymer-element name="name-badge">
  <template>
    <style>
    .entry {
      padding-bottom: 20pt;
    }
    .outer {
      border: 2px solid brown;
      border-radius: 1em;
      background: red;
      font-size: 20pt;
      width: 12em;
      height: 7em;
      text-align: center;
    }
    .boilerplate {
      color: white;
      font-family: sans-serif;
      padding: 0.5em;
    }
    .name {
      color: black;
      background: white;
      font-family: "Marker Felt", cursive;
      font-size: 45pt;
      padding-top: 0.5em;
      padding-bottom: 0.3em;
    }
    </style>
    <div class="entry">
      <label for="newName">Enter New Name:</label>
      <input type="text" id="newName" value="{{badgename}}">
      <button on-click="pirateName">Generate pirate name</button>
      <input name="maleOrFemale" type="radio" value="male" checked={{male}}>Male
      <input name="maleOrFemale" type="radio" value="female" checked={{female}}>Female
    </div>
    <div class="outer">
      <div class="boilerplate">
        Hi! My name is
      </div>
      <div class="name">
        {{badgename}}
      </div>
    </div>
  </template>
  <script type="application/dart" src="name_badge_element.dart"></script>
</polymer-element>



No comments:

Post a Comment