Dart JavaScript output fails with: method not found: 'new ToDos:1:0' Receiver: Instance of 'JsClassMirror'

StackOverflow https://stackoverflow.com/questions/17906821

質問

I've ported a handy JS library to Dart: dartscale. The crucial part of it's functionality can be broken down to:

final Map<Symbol, ClassMirror> _registeredModules = new Map<Symbol, ClassMirror>();

register(module, [String moduleName]) {
   final uniqueModuleName = moduleName != null ? moduleName : module.runtimeType.toString();
   final Symbol uniqueModuleIdentifier = new Symbol(uniqueModuleName);

   if (_registeredModules.containsKey(uniqueModuleName)) {
     throw new StateError("Module ${moduleName} already registered!");
   }

   final ClassMirror mirror = reflect(module).type;
  _registeredModules[uniqueModuleIdentifier] = mirror;
}

start(Symbol moduleName, String id, options) {
   if (!_registeredModules.containsKey(moduleName)) {
      throw new StateError("Module ${moduleName} not registered!");
   }

   final ClassMirror mirror = _registeredModules[moduleName];
   final Symbol moduleId = id != null ? new Symbol(id) : moduleName;
   final Sandbox sandbox = new Sandbox(this.mediator);

   if (_runningModules.containsKey(moduleId)) {
      throw new StateError("Module with id #${moduleId} already running!");
   }

   final InstanceMirror moduleInstance = mirror.newInstance(new Symbol(''), [sandbox], null);
   moduleInstance.invoke(new Symbol("start"), [options]);

   _runningModules[moduleId] = moduleInstance;
}

i also provide an example

part of example;

class ToDos {

    Sandbox _sandbox;

    DivElement _contentEl;
    int _nextToDoId = 0;

    UListElement _todoList;

     ToDos ([Sandbox this._sandbox]);

     start([Map options]) {
         this._initLocalStorage();

         var html = ['<div id="module-todos">',
                       '<form>',
                          '<input type="text" class="input-medium">',
                          '<button type="submit" class="btn">Add</button>',
                       '</form>',
                       '<ul>',
                       '</ul>',
                     '</div>'].join('');

         this._contentEl = new Element.html(html);
         this._todoList = this._contentEl.query('ul');

         options['containerEl'].append(this._contentEl);

         window.localStorage.keys.forEach((key) => this._renderToDo(key));

         this._setupEvents();

         this._sandbox.channel('navigationbar').topic('filter').listen((filterContent) {
             this._filter(filterContent);
         });

        this._sandbox.channel('navigationbar').topic('clearfilter').listen((filterContent) {
           this._todoList.queryAll('li span').forEach((element) => element.parent.classes.remove('hide'));
        });
    }

    stop() {
        this._contentEl.remove();
    }

    _initLocalStorage() {
        if (window.localStorage.keys.length == 0) {
            var map = {
                "1": {
                    "subject": "Groceries: Banas and Apples",
                    "isDone": false
                },
                "2": {
                    "subject": "Taxes: take care of them",
                    "isDone": false
                },
                "3": {
                    "subject": "Bring out trash",
                    "isDone": false
                }  
            };

            for (var key in map.keys) {
                window.localStorage[key] = stringify(map[key]);
                this._nextToDoId++;
            }
        }
        else {
           for (var key in window.localStorage.keys) {
              var intKey = int.parse(key);

              if (intKey > this._nextToDoId) {
                  this._nextToDoId = intKey;
              }

              this._nextToDoId++;
           }
       }
   }

   _setupEvents() {
       var input = this._contentEl.query('input');

       input.onKeyDown.listen((event) {
          if (event.keyCode == KeyCode.ENTER) {
              event.preventDefault();

              this._addToDo(input.value);
              input.value = '';
          }
       });

       this._contentEl.query('button[type="Submit"]').onClick.listen((event) {
           event.preventDefault();

           if (input.value.length > 0) {
               this._addToDo(input.value);
               input.value = '';
           }
       });

       this._todoList.onClick.listen((MouseEvent event) {
           var el = event.target;

           if (el.classes.contains('icon-remove')) {
               this._deleteToDo(el.parent);
           }
           else if (el.classes.contains('icon-ok')) {
               this._toggleToDoDone(el.parent);
           }
       });
   }

   _renderToDo(id) {
       var todoObject = parse(window.localStorage[id.toString()]);

       var html = ['<li class="record-todo ', todoObject["isDone"]?"done":"",'" data-id="', id,'">',
                      '<span>', todoObject["subject"], '</span>',
                      '<i class="icon icon-ok"></i>',
                      '<i class="icon icon-remove"></i>',
                  '</li>'].join('');

       this._todoList.append(new Element.html(html));
   }

   _addToDo(text) {
       var todoJson = stringify({
           "subject": text,
           "isDone": false
       });

       window.localStorage[this._nextToDoId.toString()] = todoJson;
       this._renderToDo(this._nextToDoId);

       this._nextToDoId++;
   }

   _deleteToDo(todoLIElement) {
       window.localStorage.remove(todoLIElement.dataset["id"]);

       todoLIElement.remove();
   }

   _toggleToDoDone(todoLIElement) {
       var done = !todoLIElement.classes.contains('done'); 
       var id = todoLIElement.dataset["id"];
       var todoObject = parse(window.localStorage[id]);
       todoObject["isDone"] = done;
       window.localStorage[id] = stringify(todoObject);

       if (done) {
          todoLIElement.classes.add('done');
       }
       else {
          todoLIElement.classes.remove('done');
       }
   }

   _filter(content) {
       this._todoList.queryAll('li span').forEach((element) {
            if (element.innerHtml.contains(content)) {
                element.parent.classes.remove('hide');
            }
            else {
               element.parent.classes.add('hide');
            }
       });
   }
}

in my App.dart

library example;

import 'dart:html';
import 'dart:json';
import '../lib/dartscale.dart';

part 'dart/ToDos.dart';

main () {
    var core = new Core();
    core.register(new ToDos());

    core.start("ToDos", "ToDos", {
        "containerEl": query('body > .container')
    });
}

bug in dart2js ?

役に立ちましたか?

解決

Solution

Turns out that dart2js has problems with mirrored calls on Methods/Constructors which have optional positional Arguments. So changing

class ToDos {
   Sandbox _sandbox;
   ToDos([Sandbox this._sandbox]);
}

to

class ToDos {
   Sandbox _sandbox;
   ToDos(Sandbox this._sandbox); //make the argument non-optional
}

solved my Problem

他のヒント

This isn't really an answer, since you don't state what's going wrong, but some general advice. Mostly, I'd avoid new Symbol() and mirrors if you can easily avoid them, and in this case you can.

First, you should figure out if you want to register module instances or produce instances on demand, you probably don't want both like you are here. If you register an instance, then can't you just reuse that instance? Does start() need to produce new instances as part of its spec? You turn around and try to make sure that an instance isn't already running anyway.

If you really do need to produce instances, a simple factory function will eliminate the need for mirrors. So instead of:

core.register(new ToDos());

You write:

core.register('ToDos', () => new ToDos());

If you still want to use mirrors, you can clean up the use of new Symbol(). Here's some recommendations:

  • Don't use Symbols as keys unless you're really getting them from reflective APIs like mirrors and noSuchMethod in the first place. Just use the String name or maybe the runtimeType. In your case you're mixing Symbols and Strings as keys in your _registeredModules map, which is probably causing some bugs, like modules will never appear to be registered. (are you testing in checked mode?)
  • Don't use new Symbol('name'), use const Symbol('name')
  • Don't use InstanceMirror.invoke, getField, or setField when you can just call the method directly. In your code you can replace

    moduleInstance.invoke(new Symbol("start"), [options]);
    

    with

    moduleInstance.reflectee.start(options);
    
  • Factories aren't evil. It'd be nice to invoke a constructor from a type instance, but until then registering a factory is pretty lightweight in Dart.

Here's your code with those suggestions:

typedef Object Factory(Sandbox sandbox);

final Map<Symbol, Factory> _registeredModules = new Map<Type, Factory>();

register(Type type, Factory factory) {
   if (_registeredModules.containsKey(type)) {
     throw new StateError("Module $type already registered!");
   }
   _registeredModules[type] = factory;
}

start(Type type, options) {
   if (!_registeredModules.containsKey(type)) {
      throw new StateError("Module $type not registered!");
   }
   if (_runningModules.containsKey(type)) {
      throw new StateError("Module $type already running!");
   }
   Sandbox sandbox = new Sandbox(this.mediator);    
   var module = _runningModules[type](sandbox)..start(options);
   _runningModules[type] = module;
}
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top