Browse Source

Extraction of Axon blackboard event runner

Herbert Vojčík 9 years ago
parent
commit
df8c96a59e

+ 6 - 6
example-counter/src/Trapped-Counter.js

@@ -14,7 +14,7 @@ selector: "initialize",
 protocol: 'initialization',
 fn: function (){
 var self=this;
-function $SimpleKeyedPubSub(){return $globals.SimpleKeyedPubSub||(typeof SimpleKeyedPubSub=="undefined"?nil:SimpleKeyedPubSub)}
+function $SimpleAxon(){return $globals.SimpleAxon||(typeof SimpleAxon=="undefined"?nil:SimpleAxon)}
 function $AppModel(){return $globals.AppModel||(typeof AppModel=="undefined"?nil:AppModel)}
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
@@ -28,11 +28,11 @@ $globals.App.superclass.fn.prototype._initialize.apply($recv(self), []));
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 $ctx1.supercall = false;
 //>>excludeEnd("ctx");;
-$1=$recv($SimpleKeyedPubSub())._new();
+$1=$recv($SimpleAxon())._new();
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 $ctx1.sendIdx["new"]=1;
 //>>excludeEnd("ctx");
-self._dispatcher_($1);
+self._axon_($1);
 self._model_($recv($AppModel())._new());
 return self;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
@@ -41,10 +41,10 @@ return self;
 },
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: [],
-source: "initialize\x0a\x09super initialize.\x0a    self dispatcher: SimpleKeyedPubSub new.\x0a    self model: AppModel new",
-referencedClasses: ["SimpleKeyedPubSub", "AppModel"],
+source: "initialize\x0a\x09super initialize.\x0a    self axon: SimpleAxon new.\x0a    self model: AppModel new",
+referencedClasses: ["SimpleAxon", "AppModel"],
 //>>excludeEnd("ide");
-messageSends: ["initialize", "dispatcher:", "new", "model:"]
+messageSends: ["initialize", "axon:", "new", "model:"]
 }),
 $globals.App);
 

+ 1 - 1
example-counter/src/Trapped-Counter.st

@@ -35,7 +35,7 @@ function TodoCtrl($scope) {
 
 initialize
 	super initialize.
-    self dispatcher: SimpleKeyedPubSub new.
+    self axon: SimpleAxon new.
     self model: AppModel new
 ! !
 

+ 7 - 7
example-todo/src/Trapped-Todo.js

@@ -14,7 +14,7 @@ selector: "initialize",
 protocol: 'initialization',
 fn: function (){
 var self=this;
-function $SimpleKeyedPubSub(){return $globals.SimpleKeyedPubSub||(typeof SimpleKeyedPubSub=="undefined"?nil:SimpleKeyedPubSub)}
+function $SimpleAxon(){return $globals.SimpleAxon||(typeof SimpleAxon=="undefined"?nil:SimpleAxon)}
 function $AppModel(){return $globals.AppModel||(typeof AppModel=="undefined"?nil:AppModel)}
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
@@ -28,17 +28,17 @@ $globals.App.superclass.fn.prototype._initialize.apply($recv(self), []));
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 $ctx1.supercall = false;
 //>>excludeEnd("ctx");;
-$1=$recv($SimpleKeyedPubSub())._new();
+$1=$recv($SimpleAxon())._new();
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 $ctx1.sendIdx["new"]=1;
 //>>excludeEnd("ctx");
-self._dispatcher_($1);
+self._axon_($1);
 self._model_($recv($recv($AppModel())._new())._title_("Todo"));
 self._watch_do_([["todos"], nil],(function(){
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx2) {
 //>>excludeEnd("ctx");
-return $recv(self._dispatcher())._changed_([["remaining"]]);
+return self._changed_([["remaining"]]);
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 }, function($ctx2) {$ctx2.fillBlock({},$ctx1,1)});
 //>>excludeEnd("ctx");
@@ -62,10 +62,10 @@ return self;
 },
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: [],
-source: "initialize\x0a\x09super initialize.\x0a    self dispatcher: SimpleKeyedPubSub new.\x0a    self model: (AppModel new title: 'Todo').\x0a\x09self watch: #((todos) nil) do: [ self dispatcher changed: #((remaining)) ].\x0a    [ self modify: #((todos)) do: [{\x0a        #{'text'->'learn trapped'. 'done'->true}.\x0a        #{'text'->'build a trapped app'. 'done'->false}\x0a    }]] valueWithTimeout: 2000",
-referencedClasses: ["SimpleKeyedPubSub", "AppModel"],
+source: "initialize\x0a\x09super initialize.\x0a    self axon: SimpleAxon new.\x0a    self model: (AppModel new title: 'Todo').\x0a\x09self watch: #((todos) nil) do: [ self changed: #((remaining)) ].\x0a    [ self modify: #((todos)) do: [{\x0a        #{'text'->'learn trapped'. 'done'->true}.\x0a        #{'text'->'build a trapped app'. 'done'->false}\x0a    }]] valueWithTimeout: 2000",
+referencedClasses: ["SimpleAxon", "AppModel"],
 //>>excludeEnd("ide");
-messageSends: ["initialize", "dispatcher:", "new", "model:", "title:", "watch:do:", "changed:", "dispatcher", "valueWithTimeout:", "modify:do:"]
+messageSends: ["initialize", "axon:", "new", "model:", "title:", "watch:do:", "changed:", "valueWithTimeout:", "modify:do:"]
 }),
 $globals.App);
 

+ 2 - 2
example-todo/src/Trapped-Todo.st

@@ -35,9 +35,9 @@ function TodoCtrl($scope) {
 
 initialize
 	super initialize.
-    self dispatcher: SimpleKeyedPubSub new.
+    self axon: SimpleAxon new.
     self model: (AppModel new title: 'Todo').
-	self watch: #((todos) nil) do: [ self dispatcher changed: #((remaining)) ].
+	self watch: #((todos) nil) do: [ self changed: #((remaining)) ].
     [ self modify: #((todos)) do: [{
         #{'text'->'learn trapped'. 'done'->true}.
         #{'text'->'build a trapped app'. 'done'->false}

+ 1 - 0
local.amd.json

@@ -1,6 +1,7 @@
 {
     "paths": {
         "trapped": "src",
+        "axon": "src",
         "trapped-todo": "example-todo/src",
         "trapped-counter": "example-counter/src"
     }

+ 530 - 0
src/Axon.js

@@ -0,0 +1,530 @@
+define("axon/Axon", ["amber/boot", "amber_core/Kernel-Objects", "amber_core/Kernel-Exceptions"], function($boot){
+var $core=$boot.api,nil=$boot.nil,$recv=$boot.asReceiver,$globals=$boot.globals;
+$core.addPackage('Axon');
+$core.packages["Axon"].innerEval = function (expr) { return eval(expr); };
+$core.packages["Axon"].transport = {"type":"amd","amdNamespace":"axon"};
+
+$core.addClass('AxonBase', $globals.Object, ['factory'], 'Axon');
+//>>excludeStart("ide", pragmas.excludeIdeData);
+$globals.AxonBase.comment="I represent a pub-sub based on a key (called 'aspect').\x0aI manage aspect-block subscriptions (called 'interests') as well as run blocks of dirtied interests.\x0aThe interest objects are responsible of decision if the change of an aspect is relevant for them.\x0aInterest object must be subclasses of `AxonInterestBase`.\x0a\x0aMy subclasses must provide implementation for:\x0a\x0a - add:\x0a - do:\x0a - clean\x0a - (optionally) run\x0a\x0aand issue this call before actual use:\x0a\x0a\x09interestFactory: [ :description :block | ... factory that creates appropriate AxonInterest ... ]";
+//>>excludeEnd("ide");
+$core.addMethod(
+$core.method({
+selector: "changed:",
+protocol: 'action',
+fn: function (anAspect){
+var self=this;
+var needsToRun;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $1;
+needsToRun=false;
+self._do_((function(each){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+$1=$recv(each)._accepts_(anAspect);
+if($core.assert($1)){
+$recv(each)._flag();
+needsToRun=true;
+return needsToRun;
+};
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({each:each},$ctx1,1)});
+//>>excludeEnd("ctx");
+}));
+self._dirty_(needsToRun);
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"changed:",{anAspect:anAspect,needsToRun:needsToRun},$globals.AxonBase)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["anAspect"],
+source: "changed: anAspect\x0a\x09| needsToRun |\x0a    needsToRun := false.\x0a\x09self do: [ :each |\x0a\x09\x09(each accepts: anAspect) ifTrue: [\x0a\x09\x09\x09each flag.\x0a            needsToRun := true.\x0a\x09\x09]\x0a\x09].\x0a\x09self dirty: needsToRun",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["do:", "ifTrue:", "accepts:", "flag", "dirty:"]
+}),
+$globals.AxonBase);
+
+$core.addMethod(
+$core.method({
+selector: "dirty:",
+protocol: 'action',
+fn: function (aBoolean){
+var self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+if($core.assert(aBoolean)){
+$recv((function(){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+return self._run();
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1,2)});
+//>>excludeEnd("ctx");
+}))._fork();
+};
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"dirty:",{aBoolean:aBoolean},$globals.AxonBase)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["aBoolean"],
+source: "dirty: aBoolean\x0a\x09aBoolean ifTrue: [[ self run ] fork]",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["ifTrue:", "fork", "run"]
+}),
+$globals.AxonBase);
+
+$core.addMethod(
+$core.method({
+selector: "interestFactory:",
+protocol: 'action',
+fn: function (aBlock){
+var self=this;
+self["@factory"]=aBlock;
+return self;
+
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["aBlock"],
+source: "interestFactory: aBlock\x0a    factory := aBlock",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: []
+}),
+$globals.AxonBase);
+
+$core.addMethod(
+$core.method({
+selector: "on:hook:",
+protocol: 'action',
+fn: function (description,aBlock){
+var self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+self._add_($recv($recv(self["@factory"])._value_value_(description,aBlock))._flag());
+self._dirty_(true);
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"on:hook:",{description:description,aBlock:aBlock},$globals.AxonBase)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["description", "aBlock"],
+source: "on: description hook: aBlock\x0a\x09self add: (factory value: description value: aBlock) flag.\x0a   \x09self dirty: true",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["add:", "flag", "value:value:", "dirty:"]
+}),
+$globals.AxonBase);
+
+$core.addMethod(
+$core.method({
+selector: "run",
+protocol: 'action',
+fn: function (){
+var self=this;
+function $Error(){return $globals.Error||(typeof Error=="undefined"?nil:Error)}
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $1,$2,$3;
+$recv((function(){
+var needsClean;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+needsClean=false;
+needsClean;
+self._do_((function(each){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx3) {
+//>>excludeEnd("ctx");
+$1=$recv(each)._isFlagged();
+if($core.assert($1)){
+$recv(each)._run();
+};
+$2=$recv(each)._isEnabled();
+if(!$core.assert($2)){
+needsClean=true;
+return needsClean;
+};
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx3) {$ctx3.fillBlock({each:each},$ctx2,2)});
+//>>excludeEnd("ctx");
+}));
+$3=needsClean;
+if($core.assert($3)){
+return self._clean();
+};
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({needsClean:needsClean},$ctx1,1)});
+//>>excludeEnd("ctx");
+}))._on_do_($Error(),(function(){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+return self._dirty_(true);
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1,6)});
+//>>excludeEnd("ctx");
+}));
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"run",{},$globals.AxonBase)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "run\x0a\x09[\x0a\x09\x09| needsClean |\x0a\x09    needsClean := false.\x0a\x09\x09self do: [ :each |\x0a\x09\x09\x09each isFlagged ifTrue: [ each run ].\x0a\x09        each isEnabled ifFalse: [ needsClean := true ]\x0a\x09\x09].\x0a    \x09needsClean ifTrue: [ self clean ]\x0a\x09] on: Error do: [ self dirty: true ]",
+referencedClasses: ["Error"],
+//>>excludeEnd("ide");
+messageSends: ["on:do:", "do:", "ifTrue:", "isFlagged", "run", "ifFalse:", "isEnabled", "clean", "dirty:"]
+}),
+$globals.AxonBase);
+
+
+
+$core.addClass('SimpleAxon', $globals.AxonBase, ['queue'], 'Axon');
+$core.addMethod(
+$core.method({
+selector: "add:",
+protocol: 'accessing',
+fn: function (aSubscription){
+var self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+$recv(self["@queue"])._add_(aSubscription);
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"add:",{aSubscription:aSubscription},$globals.SimpleAxon)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["aSubscription"],
+source: "add: aSubscription\x0a\x09queue add: aSubscription.",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["add:"]
+}),
+$globals.SimpleAxon);
+
+$core.addMethod(
+$core.method({
+selector: "clean",
+protocol: 'bookkeeping',
+fn: function (){
+var self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+self["@queue"]=$recv(self["@queue"])._select_((function(each){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+return $recv(each)._isEnabled();
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({each:each},$ctx1,1)});
+//>>excludeEnd("ctx");
+}));
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"clean",{},$globals.SimpleAxon)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "clean\x0a\x09queue := queue select: [ :each | each isEnabled ]",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["select:", "isEnabled"]
+}),
+$globals.SimpleAxon);
+
+$core.addMethod(
+$core.method({
+selector: "do:",
+protocol: 'enumeration',
+fn: function (aBlock){
+var self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+$recv(self["@queue"])._do_(aBlock);
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"do:",{aBlock:aBlock},$globals.SimpleAxon)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["aBlock"],
+source: "do: aBlock\x0a\x09queue do: aBlock",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["do:"]
+}),
+$globals.SimpleAxon);
+
+$core.addMethod(
+$core.method({
+selector: "initialize",
+protocol: 'initialization',
+fn: function (){
+var self=this;
+function $OrderedCollection(){return $globals.OrderedCollection||(typeof OrderedCollection=="undefined"?nil:OrderedCollection)}
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+(
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx1.supercall = true, 
+//>>excludeEnd("ctx");
+$globals.SimpleAxon.superclass.fn.prototype._initialize.apply($recv(self), []));
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx1.supercall = false;
+//>>excludeEnd("ctx");;
+self["@queue"]=$recv($OrderedCollection())._new();
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"initialize",{},$globals.SimpleAxon)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "initialize\x0a    super initialize.\x0a\x09queue := OrderedCollection new",
+referencedClasses: ["OrderedCollection"],
+//>>excludeEnd("ide");
+messageSends: ["initialize", "new"]
+}),
+$globals.SimpleAxon);
+
+
+
+$core.addClass('AxonInterestBase', $globals.Object, ['aspect', 'actionBlock', 'flagged'], 'Axon');
+$core.addMethod(
+$core.method({
+selector: "accepts:",
+protocol: 'testing',
+fn: function (anAspect){
+var self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+self._subclassResponsibility();
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"accepts:",{anAspect:anAspect},$globals.AxonInterestBase)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["anAspect"],
+source: "accepts: anAspect\x0a    \x22Should return true if change for anAspect is relevant for this AxonInterest\x22\x0a    self subclassResponsibility",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["subclassResponsibility"]
+}),
+$globals.AxonInterestBase);
+
+$core.addMethod(
+$core.method({
+selector: "aspect:block:",
+protocol: 'accessing',
+fn: function (anAspect,aBlock){
+var self=this;
+self["@aspect"]=anAspect;
+self["@actionBlock"]=aBlock;
+return self;
+
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["anAspect", "aBlock"],
+source: "aspect: anAspect block: aBlock\x0a\x09aspect := anAspect.\x0a    actionBlock := aBlock",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: []
+}),
+$globals.AxonInterestBase);
+
+$core.addMethod(
+$core.method({
+selector: "flag",
+protocol: 'accessing',
+fn: function (){
+var self=this;
+self["@flagged"]=true;
+return self;
+
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "flag\x0a\x09flagged := true",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: []
+}),
+$globals.AxonInterestBase);
+
+$core.addMethod(
+$core.method({
+selector: "initialize",
+protocol: 'initialization',
+fn: function (){
+var self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+(
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx1.supercall = true, 
+//>>excludeEnd("ctx");
+$globals.AxonInterestBase.superclass.fn.prototype._initialize.apply($recv(self), []));
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+$ctx1.supercall = false;
+//>>excludeEnd("ctx");;
+self["@aspect"]=nil;
+self["@actionBlock"]=nil;
+self["@flagged"]=false;
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"initialize",{},$globals.AxonInterestBase)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "initialize\x0a\x09super initialize.\x0a    aspect := nil.\x0a    actionBlock := nil.\x0a    flagged := false.",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["initialize"]
+}),
+$globals.AxonInterestBase);
+
+$core.addMethod(
+$core.method({
+selector: "isEnabled",
+protocol: 'testing',
+fn: function (){
+var self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $1;
+$1=$recv(self["@actionBlock"])._notNil();
+return $1;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"isEnabled",{},$globals.AxonInterestBase)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "isEnabled\x0a\x09^actionBlock notNil",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["notNil"]
+}),
+$globals.AxonInterestBase);
+
+$core.addMethod(
+$core.method({
+selector: "isFlagged",
+protocol: 'testing',
+fn: function (){
+var self=this;
+var $1;
+$1=self["@flagged"];
+return $1;
+
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "isFlagged\x0a\x09^flagged",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: []
+}),
+$globals.AxonInterestBase);
+
+$core.addMethod(
+$core.method({
+selector: "run",
+protocol: 'action',
+fn: function (){
+var self=this;
+function $AxonOff(){return $globals.AxonOff||(typeof AxonOff=="undefined"?nil:AxonOff)}
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+$recv((function(){
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx2) {
+//>>excludeEnd("ctx");
+self["@flagged"]=false;
+self["@flagged"];
+return $recv(self["@actionBlock"])._value();
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1,1)});
+//>>excludeEnd("ctx");
+}))._on_do_($AxonOff(),(function(){
+self["@actionBlock"]=nil;
+return self["@actionBlock"];
+
+}));
+return self;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"run",{},$globals.AxonInterestBase)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: [],
+source: "run\x0a\x09[ flagged := false. actionBlock value ]\x0a    on: AxonOff do: [ actionBlock := nil ]",
+referencedClasses: ["AxonOff"],
+//>>excludeEnd("ide");
+messageSends: ["on:do:", "value"]
+}),
+$globals.AxonInterestBase);
+
+
+
+$core.addClass('InterestedInEqual', $globals.AxonInterestBase, [], 'Axon');
+$core.addMethod(
+$core.method({
+selector: "accepts:",
+protocol: 'testing',
+fn: function (anAspect){
+var self=this;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+return $core.withContext(function($ctx1) {
+//>>excludeEnd("ctx");
+var $1;
+$1=$recv(anAspect).__eq(self["@aspect"]);
+return $1;
+//>>excludeStart("ctx", pragmas.excludeDebugContexts);
+}, function($ctx1) {$ctx1.fill(self,"accepts:",{anAspect:anAspect},$globals.InterestedInEqual)});
+//>>excludeEnd("ctx");
+},
+//>>excludeStart("ide", pragmas.excludeIdeData);
+args: ["anAspect"],
+source: "accepts: anAspect\x0a    ^ anAspect = aspect",
+referencedClasses: [],
+//>>excludeEnd("ide");
+messageSends: ["="]
+}),
+$globals.InterestedInEqual);
+
+
+
+$core.addClass('AxonOff', $globals.Error, [], 'Axon');
+//>>excludeStart("ide", pragmas.excludeIdeData);
+$globals.AxonOff.comment="SIgnal me from the subscription block to unsubscribe it.";
+//>>excludeEnd("ide");
+
+});

+ 151 - 0
src/Axon.st

@@ -0,0 +1,151 @@
+Smalltalk createPackage: 'Axon'!
+Object subclass: #AxonBase
+	instanceVariableNames: 'factory'
+	package: 'Axon'!
+!AxonBase commentStamp!
+I represent a pub-sub based on a key (called 'aspect').
+I manage aspect-block subscriptions (called 'interests') as well as run blocks of dirtied interests.
+The interest objects are responsible of decision if the change of an aspect is relevant for them.
+Interest object must be subclasses of `AxonInterestBase`.
+
+My subclasses must provide implementation for:
+
+ - add:
+ - do:
+ - clean
+ - (optionally) run
+
+and issue this call before actual use:
+
+	interestFactory: [ :description :block | ... factory that creates appropriate AxonInterest ... ]!
+
+!AxonBase methodsFor: 'action'!
+
+changed: anAspect
+	| needsToRun |
+    needsToRun := false.
+	self do: [ :each |
+		(each accepts: anAspect) ifTrue: [
+			each flag.
+            needsToRun := true.
+		]
+	].
+	self dirty: needsToRun
+!
+
+dirty: aBoolean
+	aBoolean ifTrue: [[ self run ] fork]
+!
+
+interestFactory: aBlock
+    factory := aBlock
+!
+
+on: description hook: aBlock
+	self add: (factory value: description value: aBlock) flag.
+   	self dirty: true
+!
+
+run
+	[
+		| needsClean |
+	    needsClean := false.
+		self do: [ :each |
+			each isFlagged ifTrue: [ each run ].
+	        each isEnabled ifFalse: [ needsClean := true ]
+		].
+    	needsClean ifTrue: [ self clean ]
+	] on: Error do: [ self dirty: true ]
+! !
+
+AxonBase subclass: #SimpleAxon
+	instanceVariableNames: 'queue'
+	package: 'Axon'!
+
+!SimpleAxon methodsFor: 'accessing'!
+
+add: aSubscription
+	queue add: aSubscription.
+! !
+
+!SimpleAxon methodsFor: 'bookkeeping'!
+
+clean
+	queue := queue select: [ :each | each isEnabled ]
+! !
+
+!SimpleAxon methodsFor: 'enumeration'!
+
+do: aBlock
+	queue do: aBlock
+! !
+
+!SimpleAxon methodsFor: 'initialization'!
+
+initialize
+    super initialize.
+	queue := OrderedCollection new
+! !
+
+Object subclass: #AxonInterestBase
+	instanceVariableNames: 'aspect actionBlock flagged'
+	package: 'Axon'!
+
+!AxonInterestBase methodsFor: 'accessing'!
+
+aspect: anAspect block: aBlock
+	aspect := anAspect.
+    actionBlock := aBlock
+!
+
+flag
+	flagged := true
+! !
+
+!AxonInterestBase methodsFor: 'action'!
+
+run
+	[ flagged := false. actionBlock value ]
+    on: AxonOff do: [ actionBlock := nil ]
+! !
+
+!AxonInterestBase methodsFor: 'initialization'!
+
+initialize
+	super initialize.
+    aspect := nil.
+    actionBlock := nil.
+    flagged := false.
+! !
+
+!AxonInterestBase methodsFor: 'testing'!
+
+accepts: anAspect
+    "Should return true if change for anAspect is relevant for this AxonInterest"
+    self subclassResponsibility
+!
+
+isEnabled
+	^actionBlock notNil
+!
+
+isFlagged
+	^flagged
+! !
+
+AxonInterestBase subclass: #InterestedInEqual
+	instanceVariableNames: ''
+	package: 'Axon'!
+
+!InterestedInEqual methodsFor: 'testing'!
+
+accepts: anAspect
+    ^ anAspect = aspect
+! !
+
+Error subclass: #AxonOff
+	instanceVariableNames: ''
+	package: 'Axon'!
+!AxonOff commentStamp!
+SIgnal me from the subscription block to unsubscribe it.!
+

File diff suppressed because it is too large
+ 144 - 616
src/Trapped-Backend.js


+ 46 - 178
src/Trapped-Backend.st

@@ -37,6 +37,28 @@ initialize
 	putBlock := [ self error: 'No putter block.' ].
 ! !
 
+AxonInterestBase subclass: #InterestedInTrapPath
+	instanceVariableNames: ''
+	package: 'Trapped-Backend'!
+
+!InterestedInTrapPath methodsFor: 'testing'!
+
+accepts: anAspect
+    ^anAspect size <= aspect size and: [anAspect = (aspect copyFrom: 1 to: anAspect size)]
+! !
+
+AxonInterestBase subclass: #InterestedInTrapPathSubtree
+	instanceVariableNames: ''
+	package: 'Trapped-Backend'!
+
+!InterestedInTrapPathSubtree methodsFor: 'testing'!
+
+accepts: anAspect
+    ^anAspect size <= aspect size
+		ifTrue: [anAspect = (aspect copyFrom: 1 to: anAspect size)]
+		ifFalse: [aspect = (anAspect copyFrom: 1 to: aspect size)]
+! !
+
 Object subclass: #Isolator
 	instanceVariableNames: 'root'
 	package: 'Trapped-Backend'!
@@ -73,207 +95,53 @@ on: anObject
 ^self new root: anObject
 ! !
 
-Object subclass: #KeyedPubSubBase
-	instanceVariableNames: 'factory'
-	package: 'Trapped-Backend'!
-!KeyedPubSubBase commentStamp!
-I represent a pub-sub based on a key.
-I manage key-block subscriptions as well as running blocks that are dirty.
-The subscription objects are reponsible of decision if the change is relevant for them.
-Subscription object must be subclasses of KeyedSubscriptionBase.
-
-My subclasses must provide implementation for:
-	add:
-    do:
-    clean
-    (optionally) run
-
-and issue this call before actual use:
-	subscritionFactory: (setting [:key:block|...] factory that creates appropriate subscription)!
-
-!KeyedPubSubBase methodsFor: 'action'!
-
-changed: key
-	| needsToRun |
-    needsToRun := false.
-	self do: [ :each |
-		(each accepts: key) ifTrue: [
-			each flag.
-            needsToRun := true.
-		]
-	].
-	self dirty: needsToRun
-!
-
-dirty: aBoolean
-	aBoolean ifTrue: [[ self run ] fork]
-!
-
-on: key hook: aBlock
-	self add: (factory value: key value: aBlock) flag.
-   	self dirty: true
-!
-
-run
-	[
-		| needsClean |
-	    needsClean := false.
-		self do: [ :each |
-			each isFlagged ifTrue: [ each run ].
-	        each isEnabled ifFalse: [ needsClean := true ]
-		].
-    	needsClean ifTrue: [ self clean ]
-	] on: Error do: [ self dirty: true ]
-!
-
-subscriptionFactory: aBlock
-    factory := aBlock
-! !
-
-KeyedPubSubBase subclass: #SimpleKeyedPubSub
-	instanceVariableNames: 'queue'
-	package: 'Trapped-Backend'!
-
-!SimpleKeyedPubSub methodsFor: 'accessing'!
-
-add: aSubscription
-	queue add: aSubscription.
-! !
-
-!SimpleKeyedPubSub methodsFor: 'bookkeeping'!
-
-clean
-	queue := queue select: [ :each | each isEnabled ]
-! !
-
-!SimpleKeyedPubSub methodsFor: 'enumeration'!
-
-do: aBlock
-	queue do: aBlock
-! !
-
-!SimpleKeyedPubSub methodsFor: 'initialization'!
-
-initialize
-    super initialize.
-	queue := OrderedCollection new
-! !
-
-Error subclass: #KeyedPubSubUnsubscribe
-	instanceVariableNames: ''
-	package: 'Trapped-Backend'!
-!KeyedPubSubUnsubscribe commentStamp!
-SIgnal me from the subscription block to unsubscribe it.!
-
-Object subclass: #KeyedSubscriptionBase
-	instanceVariableNames: 'key actionBlock flagged'
-	package: 'Trapped-Backend'!
-
-!KeyedSubscriptionBase methodsFor: 'accessing'!
-
-flag
-	flagged := true
-!
-
-key: anObject block: aBlock
-	key := anObject.
-    actionBlock := aBlock
-! !
-
-!KeyedSubscriptionBase methodsFor: 'action'!
-
-run
-	[ flagged := false. actionBlock value ]
-    on: KeyedPubSubUnsubscribe do: [ actionBlock := nil ]
-! !
-
-!KeyedSubscriptionBase methodsFor: 'initialization'!
-
-initialize
-	super initialize.
-    key := nil.
-    actionBlock := nil.
-    flagged := false.
-! !
-
-!KeyedSubscriptionBase methodsFor: 'testing'!
-
-accepts: aKey
-    "Should return true if change for aKey is relevant for this subscription"
-    self subclassResponsibility
-!
-
-isEnabled
-	^actionBlock notNil
-!
-
-isFlagged
-	^flagged
-! !
-
-KeyedSubscriptionBase subclass: #ListKeyedSubscription
-	instanceVariableNames: ''
-	package: 'Trapped-Backend'!
-
-!ListKeyedSubscription methodsFor: 'testing'!
-
-accepts: aKey
-    ^aKey size <= key size and: [aKey = (key copyFrom: 1 to: aKey size)]
-! !
-
-KeyedSubscriptionBase subclass: #TwoWayListKeyedSubscription
-	instanceVariableNames: ''
-	package: 'Trapped-Backend'!
-
-!TwoWayListKeyedSubscription methodsFor: 'testing'!
-
-accepts: aKey
-    ^aKey size <= key size
-		ifTrue: [aKey = (key copyFrom: 1 to: aKey size)]
-		ifFalse: [key = (aKey copyFrom: 1 to: key size)]
-! !
-
 Object subclass: #ListKeyedEntity
-	instanceVariableNames: 'dispatcher payload'
+	instanceVariableNames: 'axon payload'
 	package: 'Trapped-Backend'!
 !ListKeyedEntity commentStamp!
 I am base class for #('string-at-index' #selector numeric-at-index)-array-path-keyed entities,
 that moderate access to the wrapped model object via read;do and modify:do:
 and allow pub-sub via watch:do:.
-This wrapped model can be any smalltalk object.
+The wrapped model can be any smalltalk object.
 
 My subclasses need to provide implementation for:
-	read:do:
-    modify:do:
+
+ - read:do:
+ - modify:do:
 
 and must issue these calls when initializing:
-	model: (with a wrapped object)
-	dispatcher: (with a subclass of KeyedPubSubBase)!
+
+ - model: (with a wrapped object)
+ - axon: (with a subclass of `AxonBase`)!
 
 !ListKeyedEntity methodsFor: 'accessing'!
 
-dispatcher
-	^dispatcher
+axon
+	^ axon
 !
 
-dispatcher: aDispatcher
-	dispatcher := aDispatcher
-        subscriptionFactory: [ :key :block |
-			(key notEmpty and: [ key last isNil ])
-				ifTrue: [ TwoWayListKeyedSubscription new key: key allButLast block: block; yourself ]
-				ifFalse: [ ListKeyedSubscription new key: key block: block; yourself ]];
+axon: anAxon
+	axon := anAxon
+		interestFactory: [ :description :block |
+			(description notEmpty and: [ description last isNil ])
+				ifTrue: [ InterestedInTrapPathSubtree new aspect: description allButLast block: block; yourself ]
+				ifFalse: [ InterestedInTrapPath new aspect: description block: block; yourself ]];
         yourself
 !
 
 model: anObject
 	payload := anObject.
-    self dispatcher changed: #()
+    self changed: #()
 ! !
 
 !ListKeyedEntity methodsFor: 'action'!
 
+changed: anAspect
+	self axon changed: anAspect
+!
+
 watch: path do: aBlock
-	self dispatcher on: path hook: [ self read: path do: aBlock ]
+	self axon on: path hook: [ self read: path do: aBlock ]
 ! !
 
 ListKeyedEntity subclass: #ListKeyedDirectEntity
@@ -289,7 +157,7 @@ modify: path do: aBlock
     | newValue eavModel |
     eavModel := path asEavModel.
     newValue := aBlock value: (eavModel on: payload).
-    [ eavModel on: payload put: newValue ] ensure: [ self dispatcher changed: path ]
+    [ eavModel on: payload put: newValue ] ensure: [ self changed: path ]
 !
 
 read: path do: aBlock
@@ -316,7 +184,7 @@ model: anObject
 modify: path do: aBlock
     | eavModel |
     eavModel := ({{#root}},path) asEavModel.
-    [ payload model: eavModel modify: aBlock ] ensure: [ self dispatcher changed: path ]
+    [ payload model: eavModel modify: aBlock ] ensure: [ self changed: path ]
 !
 
 read: path do: aBlock

+ 8 - 8
src/Trapped-Frontend.js

@@ -785,7 +785,7 @@ protocol: 'installation',
 fn: function (aDataCarrier,anotherDataCarrier){
 var self=this;
 var snap;
-function $KeyedPubSubUnsubscribe(){return $globals.KeyedPubSubUnsubscribe||(typeof KeyedPubSubUnsubscribe=="undefined"?nil:KeyedPubSubUnsubscribe)}
+function $AxonOff(){return $globals.AxonOff||(typeof AxonOff=="undefined"?nil:AxonOff)}
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
@@ -800,7 +800,7 @@ return $core.withContext(function($ctx2) {
 //>>excludeEnd("ctx");
 $1=$recv($recv($recv($recv($recv(aDataCarrier)._target())._asJQuery())._closest_("html"))._toArray())._isEmpty();
 if($core.assert($1)){
-$recv($KeyedPubSubUnsubscribe())._signal();
+$recv($AxonOff())._signal();
 };
 return $recv(snap)._do_((function(){
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
@@ -829,8 +829,8 @@ return self;
 },
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: ["aDataCarrier", "anotherDataCarrier"],
-source: "installToView: aDataCarrier toModel: anotherDataCarrier\x0a\x09| snap |\x0a\x09snap := anotherDataCarrier target.\x0a\x09snap watch: [ :data |\x0a\x09\x09(aDataCarrier target asJQuery closest: 'html') toArray isEmpty ifTrue: [ KeyedPubSubUnsubscribe signal ].\x0a        snap do: [ aDataCarrier copy value: data; proceed ] ].\x0a\x09aDataCarrier value: false",
-referencedClasses: ["KeyedPubSubUnsubscribe"],
+source: "installToView: aDataCarrier toModel: anotherDataCarrier\x0a\x09| snap |\x0a\x09snap := anotherDataCarrier target.\x0a\x09snap watch: [ :data |\x0a\x09\x09(aDataCarrier target asJQuery closest: 'html') toArray isEmpty ifTrue: [ AxonOff signal ].\x0a        snap do: [ aDataCarrier copy value: data; proceed ] ].\x0a\x09aDataCarrier value: false",
+referencedClasses: ["AxonOff"],
 //>>excludeEnd("ide");
 messageSends: ["target", "watch:", "ifTrue:", "isEmpty", "toArray", "closest:", "asJQuery", "signal", "do:", "value:", "copy", "proceed"]
 }),
@@ -2141,7 +2141,7 @@ selector: "trap:read:",
 protocol: '*Trapped-Frontend',
 fn: function (path,aBlock){
 var self=this;
-function $KeyedPubSubUnsubscribe(){return $globals.KeyedPubSubUnsubscribe||(typeof KeyedPubSubUnsubscribe=="undefined"?nil:KeyedPubSubUnsubscribe)}
+function $AxonOff(){return $globals.AxonOff||(typeof AxonOff=="undefined"?nil:AxonOff)}
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
@@ -2156,7 +2156,7 @@ return $core.withContext(function($ctx3) {
 //>>excludeEnd("ctx");
 $1=$recv($recv($recv(self._asJQuery())._closest_("html"))._toArray())._isEmpty();
 if($core.assert($1)){
-$recv($KeyedPubSubUnsubscribe())._signal();
+$recv($AxonOff())._signal();
 };
 return $recv(snap)._do_((function(){
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
@@ -2190,8 +2190,8 @@ return self;
 },
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: ["path", "aBlock"],
-source: "trap: path read: aBlock\x0a\x09path trapDescend: [ :snap |\x0a        snap watch: [ :data |\x0a            (self asJQuery closest: 'html') toArray isEmpty ifTrue: [ KeyedPubSubUnsubscribe signal ].\x0a        \x09snap do: [ self with: [ :html | aBlock value: data value: html ] ]\x0a    \x09]\x0a    ]",
-referencedClasses: ["KeyedPubSubUnsubscribe"],
+source: "trap: path read: aBlock\x0a\x09path trapDescend: [ :snap |\x0a        snap watch: [ :data |\x0a            (self asJQuery closest: 'html') toArray isEmpty ifTrue: [ AxonOff signal ].\x0a        \x09snap do: [ self with: [ :html | aBlock value: data value: html ] ]\x0a    \x09]\x0a    ]",
+referencedClasses: ["AxonOff"],
 //>>excludeEnd("ide");
 messageSends: ["trapDescend:", "watch:", "ifTrue:", "isEmpty", "toArray", "closest:", "asJQuery", "signal", "do:", "with:", "value:value:"]
 }),

+ 2 - 2
src/Trapped-Frontend.st

@@ -246,7 +246,7 @@ installToView: aDataCarrier toModel: anotherDataCarrier
 	| snap |
 	snap := anotherDataCarrier target.
 	snap watch: [ :data |
-		(aDataCarrier target asJQuery closest: 'html') toArray isEmpty ifTrue: [ KeyedPubSubUnsubscribe signal ].
+		(aDataCarrier target asJQuery closest: 'html') toArray isEmpty ifTrue: [ AxonOff signal ].
         snap do: [ aDataCarrier copy value: data; proceed ] ].
 	aDataCarrier value: false
 ! !
@@ -522,7 +522,7 @@ trap: path processors: anArray
 trap: path read: aBlock
 	path trapDescend: [ :snap |
         snap watch: [ :data |
-            (self asJQuery closest: 'html') toArray isEmpty ifTrue: [ KeyedPubSubUnsubscribe signal ].
+            (self asJQuery closest: 'html') toArray isEmpty ifTrue: [ AxonOff signal ].
         	snap do: [ self with: [ :html | aBlock value: data value: html ] ]
     	]
     ]

Some files were not shown because too many files changed in this diff