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 {
List stores = [];
StoreChangesElement.created(): super.created();
// ...
void _attachStores() {
var polymer = children.firstWhere(
(el) => el.localName == 'polymer-ajax'
);
if (polymer != null) stores.add(new AjaxStore(polymer));
}
// ...
} Placing the storage strategies in a list makes them iterable. In other words, it is easy to call fetch() and save() on any strategies present (if any):@CustomTag('store-changes')
class StoreChangesElement extends PolymerElement {
Map record = {'history': []};
List 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));
}
} And that works.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