Thursday, May 2, 2013

Setting ACE Content from Dart

‹prev | My Chain | next›

Up tonight, some more behavior driven development of the Dart version of the ICE Code Editor. The ICE Code Editor is the thing that I have kids use while coding in 3D Game Programming for Kids, so it's got to be right. While adding new features to the original JavaScript version of ICE, it seemed like things were starting to slip: bugs, documentation, browser support—all the things Dart excels at. So why not convert to Dart?

I need to be able to set the content of the actual code editor (ICE has a code editor and preview layer for visualizations). I have js-interop working with ACE code editor to create a running instance of ACE. Hopefully I can continue to use that instance to do things like setting the content.

This is useful when the editor gets embedded on webpages. It is also necessary when switching between projects. In the JavaScript version, I had to use silly setter and getter methods (setContent() / getContent()). In Dart, I can make them real gettters and setters.

I start with a test to verify that I can set the content. The test will exercise both the setter and getter on the editor:
    test("can set the content", () {
      var it = new Editor('ice');
      it.content = 'asdf';
      expect(it.content, equals('asdf'));
    });
When I first run the test, I get a failure because I have not added the necessary code to ICE:
FAIL: content can set the content
  Caught Class 'Editor' has no instance setter 'content='.
  
  NoSuchMethodError : method not found: 'content='
  Receiver: Instance of 'Editor'
  Arguments: ["asdf"] 
I can make the test pass fairly easily:
class Editor {
  // ...
  String _content;
  set content(String data) {
    _content = data;
  }

  get content => _content;
}
Heck, I could have defined a regular instance variable and gotten the default instance variable setter and getter methods. In either case, I am not doing what I want—setting and getting the the ACE content. So, with a passing test, I go about changing the implementation:
class Editor {
  var ace;

  Editor(el, {this.edit_only:false, this.autoupdate:true, this.title}) {
    ace = js.context.ace.edit(el);
  }

  set content(String data) {
    ace.setContent(data);
  }
  // ...
}
When I run the tests again, I get a failure:
FAIL: content can set the content
  Caught NoSuchMethodError : method not found: 'setContent'
  Receiver: Instance of 'Proxy'
  Arguments: ["asdf"]
At first, the reference to Proxy makes me think that I need to do more js-interop setup than I had been doing. Eventually, I realize that this is not the case. I should be able to call JavaScript methods on JavaScript objects from Dart—as long as the methods are actually defined in JavaScript.

I had simply gotten the method name wrong. So I switch to the correct setValue():
import 'dart:html';
import 'package:js/js.dart' as js;

class Editor {
  bool edit_only, autoupdate;
  String title;
  var _ace;

  Editor(el, {this.edit_only:false, this.autoupdate:true, this.title}) {
    _ace = js.context.ace.edit(el);
  }

  set content(String data) {
    _ace.setValue(data, -1);
  }

  String get content => _ace.getValue();
}
With that, I now have four passing tests:
unittest-suite-wait-for-done
PASS: defaults defaults to auto-update the preview
PASS: defaults defaults to disable edit-only mode
PASS: defaults starts an ACE instance
PASS: content can set the content

All 4 tests passed. 
The last thing that I do is try it out in the browser, so I update the example page to set some initial test content:
import 'package:ice_code_editor/editor.dart' as ICE;

main() {
  var ice = new ICE.Editor('ace');
  ice.content = '''
main() {
  print("Dart rocks");
}''';

}
Then, when I reload the page, I see:



I love having the test around to ensure that things continue to work, but there is something exciting about actually seeing it. This will serve as a stopping point for tonight. Up tomorrow: the preview element.



Day #739

5 comments:

  1. I think this has a lot of potential for mixing Dart and JavaScript, esp. if there can be a tool that will take advantage of the many TypeScript interfaces to popular JavaScript libs to create Dart wrappers. The list of TS interfaces will only continue to grow (https://github.com/borisyankov/DefinitelyTyped) Then, we could test the proxy performance hit in various use cases. To run everywhere, the goal would be to use Dart2js and have a pure JavaScript codebase. We need to measure how thin the proxy layer could be between the Dart2js and the native JS. Hopefully, it can be made small and fast.

    ReplyDelete
    Replies
    1. Interesting. Do you think there is significant benefit to using these libraries with a TypeScript wrapper vs. pure JavaScript?

      So far, I don't see much benefit in the additional TypeScript layer. Then again, I'm only just getting started with Dart + JS integration so I'm definitely open to the possibility that there's benefit to be realized once the integration becomes more complicated.

      Delete
    2. I can see I'm having a hard time communicating this because I am not being clear. I'm saying Dart programmers can leverage the work of TypeScript programmers, who are busy creating typed interfaces to many of the JavaScript libs out there, if we had a tool to parse a TypeScript interface and convert it to a Dart wrapper class such as you've done. No TypeScript is involved in the final result. Should be possible, but I don't know how hard it is. The alternative is to write an interface to a JavaScrip lib in Dart by hand, which is obviously time consuming. Automating by leveraging the existing TS interface definitions, even though not in the correct language, would be more accurate. Dart and Js-interop does not provide for interface definitions, so for example, you had your setContent vs setValue problem. I'd like to see Dart provide some sort of interface mechanism to external JavaScript to make all this easier. I don't think this is going to happen. Now I say all this not because I think Dart programmers targeting the Dart VM should do a lot of external JavaScript calls, but for the benefit of those of us using Dart2js to target V8 and coexist with other JS libs. Js-interop should be really be a thin layer in this case.

      Delete
    3. Ahhh... I gotcha. Thanks for the clarification. I think you made sense the first time -- I just didn't think it through :)

      But yeah, that approach seems like it could be all kinds of useful. Somebody should get on that ;-)

      Delete
    4. Well, it's probably not worth doing unless Dart2js strips out most of that js-interop proxy code. Anyone concerned about performance that needs to use existing js libs will just go use TypeScript.

      Delete