Skip to content

Instantly share code, notes, and snippets.

@dam0vm3nt
Last active January 18, 2018 08:01
Show Gist options
  • Save dam0vm3nt/d50b4862ccfdacd91d11 to your computer and use it in GitHub Desktop.
Save dam0vm3nt/d50b4862ccfdacd91d11 to your computer and use it in GitHub Desktop.
Auto notify support for new polymer
import "package:polymer/polymer.dart";
import "package:observe/observe.dart";
import "package:smoke/smoke.dart";
import "package:logging/logging.dart";
class ObservablePolymerNotifier {
static final Logger _logger = new Logger("polymer.auto.notify");
String _path="";
PolymerElement _element;
Observable _target;
StreamSubscription _sub;
Map<String,ObservablePolymerNotifier> _subNotifiers = {};
ObservablePolymerNotifier(PolymerElement element, var target,[String path=""]) {
_element = element;
_path = path;
_target = target;
if (target is Observable) {
_sub = target.changes.listen((List<ChangeRecord> recs) {
recs.where((ChangeRecord cr) => cr is PropertyChangeRecord).forEach((PropertyChangeRecord pcr) {
var val = pcr.newValue;
_notifySymbol(symbolToName(pcr.name), val);
});
});
} else if (target is ObservableList) {
_sub = (target as ObservableList).listChanges.listen((List<ListChangeRecord> rc){
// Notify splice
rc.forEach((ListChangeRecord lc) {
_notifySplice(target,"${_path}${symbolToName(pcr.name)}",lc.index,lc.addedCount,lc.removed);
// Fix observers
if(lc.removed!=null) {
for(int i=0;i<lc.removed.length;i++) {
_subNotifiers.remove((lc.index+i).toString()).close();
}
// fix path on the rest
for (int i=lc.index;i<target.length;i++) {
_subNotifiers[i.toString()]=_subNotifiers.remove((i+lc.removed.length).toString())
.._path="${_path}${symbolToName(pcr.name)}.${i}";
}
}
if (lc.addedCount>0) {
// Fix path on tail
for (int i=lc.index+lc.addedCount;i<target.length;i++) {
_subNotifiers[i.toString()] =_subNotifiers.remove((i-lc.addedCount).toString())
.._path="${_path}${symbolToName(pcr.name)}.${i}";
}
// Add new observers
for (int i=lc.index;i<lc.addedCount+lc.index;i++) {
_notifySymbol(i.toString(),target[i]);
}
}
});
});
}
// Add Sub
if (target is List) {
for (int i=0;i<target.length;i++) {
_notifySplice(target,"${_path}".substring(0,_path.length-1),0,target.length,[]);
_notifySymbol(i.toString(),target[i]);
}
} else {
List<Declaration> fields = query(target.runtimeType,new QueryOptions(includeFields:true,includeProperties:true,includeInherited:false));
fields.forEach((Declaration f) {
if (f.annotations.any((a)=> a is ObservableProperty)) {
var fieldValue=read(target,f.name);
_notifySymbol(symbolToName(f.name),fieldValue);
}
});
}
}
void _notifySymbol(String name,var value) {
String path = "${_path}${name}";
_logger.fine("Notify ${path} with ${value} for ${_element}");
_element.notifyPath(path,value);
_installSubNotifier(name,value);
}
void _installSubNotifier(String name,var target) {
// Attach a new sub notifier for observable objects
_logger.fine("Installing subnotifier for ${name} with ${target}");
ObservablePolymerNotifier subNotifier = _subNotifiers[name];
if (subNotifier!=null) {
subNotifier.close();
}
String subPath = "${_path}${name}";
if (target!=null && (target is Observable || target is List)) {
subNotifier = new ObservablePolymerNotifier(_element,target,"${subPath}.");
_subNotifiers[name] = subNotifier;
}
}
void close() {
_subNotifiers.values.forEach((ObservablePolymerNotifier x) => x.close());
_subNotifiers.clear();
if (_sub!=null) {
_sub.cancel();
_sub=null;
}
}
void _notifySplice(List array, String path, int index, int added, List removed) =>
_element.jsElement.callMethod('_notifySplice', [jsValue(array),path,index,added, jsValue(removed)]);
}
@behavior
abstract class PolymerAutoNotifySupportMixin {
ObservablePolymerNotifier _observablePolymerNotifier;
static created(mixin) {
print("MIXIN CREATED CALLED !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!11");
}
void attached() {
_observablePolymerNotifier = new ObservablePolymerNotifier(this,this);
}
void detached() {
_observablePolymerNotifier.close();
_observablePolymerNotifier=null;
}
}
@jakemac53
Copy link

fyi, if you make your mixin be a behavior (annotate it with @behavior) then you can call startNotify in a static created method. Something like this:

@behavior
abstract class PolymerAutoNotifySupportMixin implements PolymerElement {
  ObservablePolymerNotifier _observablePolimerNotifier;

  // This would only be invoked with instances that mix in PolymerAutoNotifySupportMixin,
  // so you can reliably put that type here. I also added the implements clause above
  // which will give you access to all PolymerElement methods :). Best of both worlds!
  static created(PolymerAutoNotifySupportMixin instance) {
    instance.startNotify();
  }

  void startNotify() {
    _observablePolimerNotifier = new ObservablePolymerNotifier(this, this);
  }

  void stopNotify() {
    _observablePolimerNotifier.close();
    _observablePolimerNotifier=null;
  }
}

@dam0vm3nt
Copy link
Author

Great! I will definitely follow your advice.

@dam0vm3nt
Copy link
Author

I made some changes using attached and detached callback and polishing a bit.
I'm going to write a smoke transformer to make the thing work with dart2js too and/or patch observable to carry String field info (shouldn't be too difficult to modify the PropertyChangeEvent and the transformer) along with symbol thus to garantee retrocompatibilty and to submit a pull request.
I will package this little thing and submit to pub for public utility.

@dam0vm3nt
Copy link
Author

I turns out smoke and some reflection is indeed necessary otherwise we don't get notifications for initial values.

@dam0vm3nt
Copy link
Author

Added _notifySplice and ObservableList support.

@jakemac53
Copy link

I would also use the static attached and detached methods, instead of the instance methods, otherwise they could be overridden by other mixins.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment