Tuesday, December 3, 2013

Attempt #1: Swapping <polymer-ajax> for <polymer-localstorage>


Today, I am going to start exploring making Polymer's <polymer-localstorage> and <polymer-ajax> drop-in replacements for each other. If the containing element sees the localStorage element, then changes should be persisted to localStorage. If the containing element sees the Ajax element, then changes should be persisted a REST-like service.

I got the Dart version of <polymer-localstorage> working yesterday. So my first step today is to alter my change-history Polymer element to use <polymer-ajax> instead. I will worry about runtime swapping later.

To ready the code for Ajax, I do a little refactoring to “fetch” the existing records either from localStorage or, soon, Ajax. Since I already have an enteredView() lifecycle method, I place the fetch-current-history in there:
@CustomTag('store-changes')
class StoreChangesElement extends PolymerElement {
  Map record = {'history': []};

  StoreChangesElement.created(): super.created();

  enteredView() {
    super.enteredView();
    addEventListener('change', storeChange);
    scheduleMicroTask(_fetchCurrent);
  }
  // ...
}
There is a bug in there (the “t” in scheduleMicrotask() should not be capitalized), but here I finally take note of what I believe to be a bug in Polymer.dart—the exception that should result is silently swallowed. I only happen to notice it because some tracer bullet code that I had inside _fetchCurrent() was not behaving properly. The fix is easy enough, but, until that bug is fixed (I will report it), I need to be cautious with the entererView() lifecycle event (and wary of others).

Anyhow, I start by adding the <polymer-ajax> definition to my page:
    <!-- Load component(s) -->
    <link rel="import" href="/packages/change_history/change-sink.html">
    <link rel="import" href="/packages/change_history/store-changes.html">
    <link rel="import" href="/packages/polymer_elements/polymer_localstorage/polymer_localstorage.html">
    <link rel="import" href="/packages/polymer_elements/polymer_ajax/polymer_ajax.html">

    <script type="application/dart">export 'package:polymer/init.dart';</script>
    <script src="packages/browser/dart.js"></script>
Then I swap out the old localStorage tag for the new Ajax version:
    <store-changes>
      <!-- <polymer-localstorage name="store-changes" value="{{value}}"></polymer-localstorage> -->
      <polymer-ajax url="http://localhost:31337/widgets/change-history"
                    handleAs="json">
      </polymer-ajax>
      <change-sink>
        <!-- changes happen here -->
      </change-sink>
    </store-changes>
Here, I run into a few problems.

First, the error handling for 404s in <polymer-ajax> seems to have a bug. When that document is first loaded, there is no change history, so a 404 is expected. But either my server is sending back the wrong response headers (which is entirely possible) or <polymer-ajax> has a bug, because I am seeing:
NoSuchMethodError : method not found: '_addFromInteger@0x36924d72'
Receiver: ": "
Arguments: [0]
Stack Trace: 
#0      Object.noSuchMethod (dart:core-patch/object_patch.dart:42)
#1      int.+ (dart:core-patch/integers.dart:16)
#2      PolymerAjax._error (http://localhost:8080/packages/polymer_elements/polymer_ajax/polymer_ajax.dart:129:31)
#3      _receive (http://localhost:8080/packages/polymer_elements/polymer_ajax/polymer_ajax.dart:112:18)
#4      PolymerXhr._makeReadyStateHandler.<anonymous closure> (http://localhost:8080/packages/polymer_elements/polymer_ajax/polymer_xhr.dart:33:17)
I set that aside for the moment because it is not preventing the rest of the code to halt.

Unfortunately. I hit a wall at this point. It seems that <polymer-ajax> (both the Dart and JavaScript versions) is only capable of sending URL-encoded parameter bodies. My server only talks JSON. So, when I try to store a change:
@CustomTag('store-changes')
class StoreChangesElement extends PolymerElement {
  StoreChangesElement.created(): super.created();
  // ...
  storeChange(e) {
    var current = e.detail['current'];
    var update = {'current': current};

    store
      ..params = JSON.encode(update)
      ..method = 'POST'
      ..go();

    record = update;
  }
}
I inadvertently crash my JSON-only server:
FormatException: Unexpected character at 0: 'current=%0A%20%20%20...'
Bother.

At first blush, this seems an example of the limited utility of the various Polymer project <polymer-*> elements. As far as I know, accepting JSON HTTP request bodies is a perfectly normal thing for web APIs to do. Yet I cannot find an obvious way to do this with <polymer-ajax>. This leaves me with several questions. Do I roll my own Ajax element? Do I try to extend <polymer-ajax>? Is there an option to set the body directly that I am missing? Should I give in and teach my server to handle URL encoded parameters?

The last option seems the least satisfying. It may be the one that I choose. I am only interested in swapping behavior at this point and that seems the path of least resistance. Then again, it might be fun to muck in the bowels of <polymer-ajax>. I'll decide which tomorrow.


Day #954

8 comments:

  1. How about sending a patch to the project ;)

    ReplyDelete
    Replies
    1. Heh. There's always that. But I think it would have to go to the JS version of the project first and who really wants to be coding JavaScript? :P

      But yeah, that sounds like good advice. I'll probably play with the underlying <polymer-xhr> today (which does seems to support setting the body) and then look into submitting a new feature. Thanks for the nudge :)

      Delete
  2. polymer-ajax supports a workaround for missing xhr parameters, the xhrArgs object.

    try doing
    ..xhrArgs = {'body': my_json_data}
    before the go()

    ReplyDelete
    Replies
    1. Ahhh. So maybe the JS version does support this. FWIW, I don't think it will work in the Dart version because of https://github.com/ErikGrimes/polymer_elements/blob/master/lib/polymer_ajax/polymer_ajax.dart#L214-L217:

      // TODO polymer.js has something a line
      // 'this.xhrArgs != null ? this.xhrArgs : {};' Not sure what if
      // anything it does or what the dart equiavlent would be.
      // (zoechi) I think it's a bug because xhrArgs is nowhere set

      I'll see if I can get that working... Thanks!

      Delete
    2. Fwiw, here is the line in the JS version (not the same as in the comment in the port):

      https://github.com/Polymer/polymer-ajax/blob/master/polymer-ajax.html#L194

      the general idea is just to pre-populate `args` with whatever happens to be in `xhrArgs`.

      Delete
    3. I was able to get xhrArgs working in a fork of polymer_elements thanks to your pointers: http://japhr.blogspot.com/2013/12/posting-json-payload-with-polymer-ajax.html

      Much thanks!

      Delete
  3. Nicely done!

    Btw, it's true that polymer-* are not generally fully-featured. We started with breadth instead of depth. The kind of feedback you are generating is exactly what we need to determine where to focus the effort.

    Please feel free to send a note to polymer-dev@googlegroups.com whenever you have a problem or suggestion.

    ReplyDelete
    Replies
    1. Oooh, thanks for the suggestion. Just signed up for the mailing list, which I should have done a while ago.

      I totally get the breadth first approach. It'd be insanely easy to over-engineer those things otherwise. I am fascinated by the mere attempt to catalog a list of common elements / activities, let alone the implementation. But if the ajax ones are any indication, my initial thoughts may have been well of the mark. I hadn't planned on really investigating the UI elements, but I may have to do just that...

      Kudos to you and everyone else on the project on the results so far. And thanks for your help here!

      Delete