After a pleasurable detour in Polymer.dart plumbing, I am ready to get on with original intent with this bit of Dart code: writing my custom
<store-changes>
element such that it will work with either <polymer-localstorage>
or polymer-ajax>
. I am not doing this because I think it is a good idea. Rather I am just curious what my code would look like if it can do either.Currently, I am using the tags as follows:
<store-changes>
<!-- <polymer-localstorage name="store-changes" value="{{value}}"></polymer-localstorage> -->
<polymer-ajax url="http://localhost:31337/widgets/change-history"
handleAs="json">
</polymer-ajax>
<!-- <polymer-xhr></polymer-xhr> -->
<!-- changes will originate from elements here ... -->
</store-changes>
And, in the Dart class that backs the <store-changes>
custom element, I am similarly commenting out any code that works against localstorage:@CustomTag('store-changes') class StoreChangesElement extends PolymerElement { StoreChangesElement.created(): super.created(); // ... void _fetchCurrent() { // if (store.value != null) record = store.value; var subscription; subscription = on['polymerresponse'].listen((e){ // handle http response here.... }); } // ... }I would like either tag, if present, to work. The easiest way to accomplish that is via a common wrapper interface. Both the Ajax and localStorage data store strategies need to be able to fetch records and save records, given a Polymer element:
abstract class ChangeStore { PolymerElement el; ChangeStore(this.el); Future<Map> fetch(); void save(Map record); }The Future return type on
fetch()
is somewhat premature, but I have already implemented it directly in <store-changes>
and know perfectly well that it is asynchronous. So maybe not that premature.Next up, I define my Ajax strategy as extending this abstract baseclass:
class AjaxStore extends ChangeStore { AjaxStore(el): super(el); }The
fetch()
method is the same code as yesterday, but with a Completer to give me the necessary Future:class AjaxStore extends ChangeStore { Future<Map> fetch() { var subscription, completer = new Completer(); subscription = el.on['polymerresponse'].listen((e){ var res = e.detail['response'], data = (res != null && !res.isEmpty) ? res : null; completer.complete(data); subscription.cancel(); }); return completer.future; } }Since I do not care if the save is successful (so what if a little undo history is lost?), the
save()
method is particularly easy to implement:class AjaxStore extends ChangeStore { // ... void save(rec) { el ..method = 'POST' ..xhrArgs = {'body': JSON.encode(rec)} ..go(); } }With that, I have my Ajax strategy ready to roll. I place each strategy present inside a list of stores:
@CustomTag('store-changes') class StoreChangesElement extends PolymerElement { // ... List<ChangeStore> stores = []; StoreChangesElement.created(): super.created(); enteredView() { super.enteredView(); addEventListener('change', storeChange); scheduleMicrotask(_attachStores); // ... } // ... }I keep bumping into the need to schedule a “microtask” as I have done here. Without it, Polymer has not had a chance to do its thing, leaving the code thinking that things like
<polymer-ajax>
and <polymer-localstorage>
are unknown, basic HTML elements.Attaching the stores is where I detect whether the
<polymer-ajax>
tag is present and, if so, adding the corresponding strategy to the list:@CustomTag('store-changes') class StoreChangesElement extends PolymerElement { ListPlacing the storage strategies in a list makes them iterable. In other words, it is easy to callstores = []; StoreChangesElement.created(): super.created(); // ... void _attachStores() { var polymer = children.firstWhere( (el) => el.localName == 'polymer-ajax' ); if (polymer != null) stores.add(new AjaxStore(polymer)); } // ... }
fetch()
and save()
on any strategies present (if any):@CustomTag('store-changes') class StoreChangesElement extends PolymerElement { Map record = {'history': []}; ListAnd that works.stores = []; // ... void _fetchCurrent() { stores.forEach((store) { store.fetch().then((_r) => record = _r); }); } storeChange(e) { // build record from a change event, then .... stores.forEach((store)=> store.save(record)); } }
After a quick and dirty implementing of the localStorage strategy, I am able to swap out
<polymer-ajax>
for <polymer-localstorage>
in the web page and not miss a beat:: <store-changes>
<polymer-localstorage name="store-changes" value="{{value}}"></polymer-localstorage>
<!-- <polymer-ajax url="http://localhost:31337/widgets/change-history" -->
<!-- handleAs="json"> -->
<!-- </polymer-ajax> -->
<!-- changes will originate from elements here ... -->
</store-changes>
And that works as well. In fact, since the strategies are iterable, I can have both <polymer-*>
tags present and everything works just fine. It took a few days of groundwork, but the actual implementation of this multi-strategy approach worked out quite nicely. I don't know that it rises to the level of an actual “pattern” for Patterns in Polymer. Maybe it is a basis pattern or something like that (which is, I suppose a pattern in the true sense of the term). I think I will hold off on this one before it makes an appearance in the book. For now, I am content to have a clean implementation.
Day #957
No comments:
Post a Comment