Browse Source

KeyedPubSub... generic classes extracted in backend.

Herbert Vojčík 12 years ago
parent
commit
9201e5d742
6 changed files with 252 additions and 192 deletions
  1. 40 57
      js/Trapped-Backend.deploy.js
  2. 60 83
      js/Trapped-Backend.js
  3. 38 1
      js/Trapped-Frontend.deploy.js
  4. 52 4
      js/Trapped-Frontend.js
  5. 30 45
      st/Trapped-Backend.st
  6. 32 2
      st/Trapped-Frontend.st

+ 40 - 57
js/Trapped-Backend.deploy.js

@@ -129,18 +129,18 @@ return $1;
 smalltalk.Isolator.klass);
 
 
-smalltalk.addClass('TrappedDispatcher', smalltalk.Object, [], 'Trapped-Backend');
+smalltalk.addClass('KeyedPubSubBase', smalltalk.Object, [], 'Trapped-Backend');
 smalltalk.addMethod(
 "_changed_",
 smalltalk.method({
 selector: "changed:",
-fn: function (path){
+fn: function (key){
 var self=this;
 var $1;
 var needsToRun;
 needsToRun=false;
 smalltalk.send(self,"_do_",[(function(each){
-$1=smalltalk.send(each,"_accepts_",[path]);
+$1=smalltalk.send(each,"_accepts_",[key]);
 if(smalltalk.assert($1)){
 smalltalk.send(each,"_flag",[]);
 needsToRun=true;
@@ -150,7 +150,7 @@ return needsToRun;
 smalltalk.send(self,"_dirty_",[needsToRun]);
 return self}
 }),
-smalltalk.TrappedDispatcher);
+smalltalk.KeyedPubSubBase);
 
 smalltalk.addMethod(
 "_dirty_",
@@ -165,19 +165,19 @@ return smalltalk.send(self,"_run",[]);
 };
 return self}
 }),
-smalltalk.TrappedDispatcher);
+smalltalk.KeyedPubSubBase);
 
 smalltalk.addMethod(
 "_on_hook_",
 smalltalk.method({
 selector: "on:hook:",
-fn: function (path,aBlock){
+fn: function (key,aBlock){
 var self=this;
-smalltalk.send(self,"_add_",[smalltalk.send(smalltalk.send((smalltalk.TrappedSubscription || TrappedSubscription),"_path_action_",[path,aBlock]),"_flag",[])]);
+smalltalk.send(self,"_add_",[smalltalk.send(smalltalk.send(self,"_subscriptionKey_block_",[key,aBlock]),"_flag",[])]);
 smalltalk.send(self,"_dirty_",[true]);
 return self}
 }),
-smalltalk.TrappedDispatcher);
+smalltalk.KeyedPubSubBase);
 
 smalltalk.addMethod(
 "_run",
@@ -204,25 +204,35 @@ smalltalk.send(self,"_clean",[]);
 };
 return self}
 }),
-smalltalk.TrappedDispatcher);
+smalltalk.KeyedPubSubBase);
 
+smalltalk.addMethod(
+"_subscriptionKey_block_",
+smalltalk.method({
+selector: "subscriptionKey:block:",
+fn: function (key,aBlock){
+var self=this;
+smalltalk.send(self,"_subclassReponsibility",[]);
+return self}
+}),
+smalltalk.KeyedPubSubBase);
+
+
+
+smalltalk.addClass('KeyedPubSubUnsubscribe', smalltalk.Error, [], 'Trapped-Backend');
 
 
-smalltalk.addClass('TrappedSubscription', smalltalk.Object, ['path', 'actionBlock', 'flagged'], 'Trapped-Backend');
+smalltalk.addClass('KeyedSubscriptionBase', smalltalk.Object, ['key', 'actionBlock', 'flagged'], 'Trapped-Backend');
 smalltalk.addMethod(
 "_accepts_",
 smalltalk.method({
 selector: "accepts:",
-fn: function (aPath){
+fn: function (aKey){
 var self=this;
-var $1;
-$1=smalltalk.send(smalltalk.send(smalltalk.send(aPath,"_size",[]),"__lt_eq",[smalltalk.send(self["@path"],"_size",[])]),"_and_",[(function(){
-return smalltalk.send(aPath,"__eq",[smalltalk.send(self["@path"],"_copyFrom_to_",[(1),smalltalk.send(aPath,"_size",[])])]);
-})]);
-return $1;
-}
+smalltalk.send(self,"_subclassResponsibility",[]);
+return self}
 }),
-smalltalk.TrappedSubscription);
+smalltalk.KeyedSubscriptionBase);
 
 smalltalk.addMethod(
 "_flag",
@@ -233,7 +243,7 @@ var self=this;
 self["@flagged"]=true;
 return self}
 }),
-smalltalk.TrappedSubscription);
+smalltalk.KeyedSubscriptionBase);
 
 smalltalk.addMethod(
 "_initialize",
@@ -242,12 +252,12 @@ selector: "initialize",
 fn: function (){
 var self=this;
 smalltalk.send(self,"_initialize",[],smalltalk.Object);
-self["@path"]=nil;
+self["@key"]=nil;
 self["@actionBlock"]=nil;
 self["@flagged"]=false;
 return self}
 }),
-smalltalk.TrappedSubscription);
+smalltalk.KeyedSubscriptionBase);
 
 smalltalk.addMethod(
 "_isEnabled",
@@ -260,7 +270,7 @@ $1=smalltalk.send(self["@actionBlock"],"_notNil",[]);
 return $1;
 }
 }),
-smalltalk.TrappedSubscription);
+smalltalk.KeyedSubscriptionBase);
 
 smalltalk.addMethod(
 "_isFlagged",
@@ -271,19 +281,19 @@ var self=this;
 return self["@flagged"];
 }
 }),
-smalltalk.TrappedSubscription);
+smalltalk.KeyedSubscriptionBase);
 
 smalltalk.addMethod(
-"_path_actionBlock_",
+"_key_block_",
 smalltalk.method({
-selector: "path:actionBlock:",
-fn: function (anArray,aBlock){
+selector: "key:block:",
+fn: function (anObject,aBlock){
 var self=this;
-self["@path"]=anArray;
+self["@key"]=anObject;
 self["@actionBlock"]=aBlock;
 return self}
 }),
-smalltalk.TrappedSubscription);
+smalltalk.KeyedSubscriptionBase);
 
 smalltalk.addMethod(
 "_run",
@@ -298,41 +308,14 @@ return smalltalk.send(self["@actionBlock"],"_value",[]);
 self["@flagged"]=false;
 return self["@flagged"];
 })]);
-}),"_on_do_",[(smalltalk.TrappedUnwatch || TrappedUnwatch),(function(){
+}),"_on_do_",[(smalltalk.KeyedPubSubUnsubscribe || KeyedPubSubUnsubscribe),(function(){
 self["@actionBlock"]=nil;
 return self["@actionBlock"];
 })]);
 return self}
 }),
-smalltalk.TrappedSubscription);
-
-
-smalltalk.addMethod(
-"_new",
-smalltalk.method({
-selector: "new",
-fn: function (){
-var self=this;
-smalltalk.send(self,"_shouldNotImplement",[]);
-return self}
-}),
-smalltalk.TrappedSubscription.klass);
-
-smalltalk.addMethod(
-"_path_action_",
-smalltalk.method({
-selector: "path:action:",
-fn: function (anArray,aBlock){
-var self=this;
-var $1;
-$1=smalltalk.send(smalltalk.send(self,"_new",[],smalltalk.Object.klass),"_path_actionBlock_",[anArray,aBlock]);
-return $1;
-}
-}),
-smalltalk.TrappedSubscription.klass);
-
+smalltalk.KeyedSubscriptionBase);
 
-smalltalk.addClass('TrappedUnwatch', smalltalk.Error, [], 'Trapped-Backend');
 
 
 smalltalk.addMethod(

+ 60 - 83
js/Trapped-Backend.js

@@ -180,20 +180,19 @@ referencedClasses: []
 smalltalk.Isolator.klass);
 
 
-smalltalk.addClass('TrappedDispatcher', smalltalk.Object, [], 'Trapped-Backend');
-smalltalk.TrappedDispatcher.comment="I am base class for change event dispatchers.\x0aI manage changed path - action block subscriptions.\x0aThese subscription must be three-element arrays\x0a\x09{ dirty. path. block }\x0a\x0aMy subclasses need to provide implementation for:\x0a\x09add:\x0a    do:\x0a    clean\x0a    (optionally) run\x0a"
+smalltalk.addClass('KeyedPubSubBase', smalltalk.Object, [], 'Trapped-Backend');
 smalltalk.addMethod(
 "_changed_",
 smalltalk.method({
 selector: "changed:",
 category: 'action',
-fn: function (path){
+fn: function (key){
 var self=this;
 var $1;
 var needsToRun;
 needsToRun=false;
 smalltalk.send(self,"_do_",[(function(each){
-$1=smalltalk.send(each,"_accepts_",[path]);
+$1=smalltalk.send(each,"_accepts_",[key]);
 if(smalltalk.assert($1)){
 smalltalk.send(each,"_flag",[]);
 needsToRun=true;
@@ -202,12 +201,12 @@ return needsToRun;
 })]);
 smalltalk.send(self,"_dirty_",[needsToRun]);
 return self},
-args: ["path"],
-source: "changed: path\x0a\x09| needsToRun |\x0a    needsToRun := false.\x0a\x09self do: [ :each |\x0a\x09\x09(each accepts: path) ifTrue: [\x0a\x09\x09\x09each flag.\x0a            needsToRun := true.\x0a\x09\x09]\x0a\x09].\x0a\x09self dirty: needsToRun",
+args: ["key"],
+source: "changed: key\x0a\x09| needsToRun |\x0a    needsToRun := false.\x0a\x09self do: [ :each |\x0a\x09\x09(each accepts: key) ifTrue: [\x0a\x09\x09\x09each flag.\x0a            needsToRun := true.\x0a\x09\x09]\x0a\x09].\x0a\x09self dirty: needsToRun",
 messageSends: ["do:", "ifTrue:", "flag", "accepts:", "dirty:"],
 referencedClasses: []
 }),
-smalltalk.TrappedDispatcher);
+smalltalk.KeyedPubSubBase);
 
 smalltalk.addMethod(
 "_dirty_",
@@ -227,24 +226,24 @@ source: "dirty: aBoolean\x0a\x09aBoolean ifTrue: [[ self run ] fork]",
 messageSends: ["ifTrue:", "fork", "run"],
 referencedClasses: []
 }),
-smalltalk.TrappedDispatcher);
+smalltalk.KeyedPubSubBase);
 
 smalltalk.addMethod(
 "_on_hook_",
 smalltalk.method({
 selector: "on:hook:",
 category: 'action',
-fn: function (path,aBlock){
+fn: function (key,aBlock){
 var self=this;
-smalltalk.send(self,"_add_",[smalltalk.send(smalltalk.send((smalltalk.TrappedSubscription || TrappedSubscription),"_path_action_",[path,aBlock]),"_flag",[])]);
+smalltalk.send(self,"_add_",[smalltalk.send(smalltalk.send(self,"_subscriptionKey_block_",[key,aBlock]),"_flag",[])]);
 smalltalk.send(self,"_dirty_",[true]);
 return self},
-args: ["path", "aBlock"],
-source: "on: path hook: aBlock\x0a\x09self add: (TrappedSubscription path: path action: aBlock) flag.\x0a   \x09self dirty: true",
-messageSends: ["add:", "flag", "path:action:", "dirty:"],
-referencedClasses: ["TrappedSubscription"]
+args: ["key", "aBlock"],
+source: "on: key hook: aBlock\x0a\x09self add: (self subscriptionKey: key block: aBlock) flag.\x0a   \x09self dirty: true",
+messageSends: ["add:", "flag", "subscriptionKey:block:", "dirty:"],
+referencedClasses: []
 }),
-smalltalk.TrappedDispatcher);
+smalltalk.KeyedPubSubBase);
 
 smalltalk.addMethod(
 "_run",
@@ -276,30 +275,46 @@ source: "run\x0a\x09| needsClean |\x0a    needsClean := false.\x0a\x09self do: [
 messageSends: ["do:", "ifTrue:", "run", "ifFalse:", "isEnabled", "isFlagged", "clean"],
 referencedClasses: []
 }),
-smalltalk.TrappedDispatcher);
+smalltalk.KeyedPubSubBase);
+
+smalltalk.addMethod(
+"_subscriptionKey_block_",
+smalltalk.method({
+selector: "subscriptionKey:block:",
+category: 'action',
+fn: function (key,aBlock){
+var self=this;
+smalltalk.send(self,"_subclassReponsibility",[]);
+return self},
+args: ["key", "aBlock"],
+source: "subscriptionKey: key block: aBlock\x0a    \x22Should return subclass of KeyedSubscriptionBase\x22\x0a    self subclassReponsibility\x0a",
+messageSends: ["subclassReponsibility"],
+referencedClasses: []
+}),
+smalltalk.KeyedPubSubBase);
+
 
 
+smalltalk.addClass('KeyedPubSubUnsubscribe', smalltalk.Error, [], 'Trapped-Backend');
+smalltalk.KeyedPubSubUnsubscribe.comment="SIgnal me from the subscription block to unsubscribe it."
 
-smalltalk.addClass('TrappedSubscription', smalltalk.Object, ['path', 'actionBlock', 'flagged'], 'Trapped-Backend');
+
+smalltalk.addClass('KeyedSubscriptionBase', smalltalk.Object, ['key', 'actionBlock', 'flagged'], 'Trapped-Backend');
 smalltalk.addMethod(
 "_accepts_",
 smalltalk.method({
 selector: "accepts:",
 category: 'testing',
-fn: function (aPath){
+fn: function (aKey){
 var self=this;
-var $1;
-$1=smalltalk.send(smalltalk.send(smalltalk.send(aPath,"_size",[]),"__lt_eq",[smalltalk.send(self["@path"],"_size",[])]),"_and_",[(function(){
-return smalltalk.send(aPath,"__eq",[smalltalk.send(self["@path"],"_copyFrom_to_",[(1),smalltalk.send(aPath,"_size",[])])]);
-})]);
-return $1;
-},
-args: ["aPath"],
-source: "accepts: aPath\x0a    ^aPath size <= path size and: [aPath = (path copyFrom: 1 to: aPath size)]",
-messageSends: ["and:", "=", "copyFrom:to:", "size", "<="],
+smalltalk.send(self,"_subclassResponsibility",[]);
+return self},
+args: ["aKey"],
+source: "accepts: aKey\x0a    \x22Should return true if change for aKey is relevant for this subscription\x22\x0a    self subclassResponsibility",
+messageSends: ["subclassResponsibility"],
 referencedClasses: []
 }),
-smalltalk.TrappedSubscription);
+smalltalk.KeyedSubscriptionBase);
 
 smalltalk.addMethod(
 "_flag",
@@ -315,7 +330,7 @@ source: "flag\x0a\x09flagged := true",
 messageSends: [],
 referencedClasses: []
 }),
-smalltalk.TrappedSubscription);
+smalltalk.KeyedSubscriptionBase);
 
 smalltalk.addMethod(
 "_initialize",
@@ -325,16 +340,16 @@ category: 'initialization',
 fn: function (){
 var self=this;
 smalltalk.send(self,"_initialize",[],smalltalk.Object);
-self["@path"]=nil;
+self["@key"]=nil;
 self["@actionBlock"]=nil;
 self["@flagged"]=false;
 return self},
 args: [],
-source: "initialize\x0a\x09super initialize.\x0a    path := nil.\x0a    actionBlock := nil.\x0a    flagged := false.",
+source: "initialize\x0a\x09super initialize.\x0a    key := nil.\x0a    actionBlock := nil.\x0a    flagged := false.",
 messageSends: ["initialize"],
 referencedClasses: []
 }),
-smalltalk.TrappedSubscription);
+smalltalk.KeyedSubscriptionBase);
 
 smalltalk.addMethod(
 "_isEnabled",
@@ -352,7 +367,7 @@ source: "isEnabled\x0a\x09^actionBlock notNil",
 messageSends: ["notNil"],
 referencedClasses: []
 }),
-smalltalk.TrappedSubscription);
+smalltalk.KeyedSubscriptionBase);
 
 smalltalk.addMethod(
 "_isFlagged",
@@ -368,24 +383,24 @@ source: "isFlagged\x0a\x09^flagged",
 messageSends: [],
 referencedClasses: []
 }),
-smalltalk.TrappedSubscription);
+smalltalk.KeyedSubscriptionBase);
 
 smalltalk.addMethod(
-"_path_actionBlock_",
+"_key_block_",
 smalltalk.method({
-selector: "path:actionBlock:",
+selector: "key:block:",
 category: 'accessing',
-fn: function (anArray,aBlock){
+fn: function (anObject,aBlock){
 var self=this;
-self["@path"]=anArray;
+self["@key"]=anObject;
 self["@actionBlock"]=aBlock;
 return self},
-args: ["anArray", "aBlock"],
-source: "path: anArray actionBlock: aBlock\x0a\x09path := anArray.\x0a    actionBlock := aBlock",
+args: ["anObject", "aBlock"],
+source: "key: anObject block: aBlock\x0a\x09key := anObject.\x0a    actionBlock := aBlock",
 messageSends: [],
 referencedClasses: []
 }),
-smalltalk.TrappedSubscription);
+smalltalk.KeyedSubscriptionBase);
 
 smalltalk.addMethod(
 "_run",
@@ -401,56 +416,18 @@ return smalltalk.send(self["@actionBlock"],"_value",[]);
 self["@flagged"]=false;
 return self["@flagged"];
 })]);
-}),"_on_do_",[(smalltalk.TrappedUnwatch || TrappedUnwatch),(function(){
+}),"_on_do_",[(smalltalk.KeyedPubSubUnsubscribe || KeyedPubSubUnsubscribe),(function(){
 self["@actionBlock"]=nil;
 return self["@actionBlock"];
 })]);
 return self},
 args: [],
-source: "run\x0a\x09[[ actionBlock value ] ensure: [ flagged := false ]]\x0a    on: TrappedUnwatch do: [ actionBlock := nil ]",
+source: "run\x0a\x09[[ actionBlock value ] ensure: [ flagged := false ]]\x0a    on: KeyedPubSubUnsubscribe do: [ actionBlock := nil ]",
 messageSends: ["on:do:", "ensure:", "value"],
-referencedClasses: ["TrappedUnwatch"]
-}),
-smalltalk.TrappedSubscription);
-
-
-smalltalk.addMethod(
-"_new",
-smalltalk.method({
-selector: "new",
-category: 'instance creation',
-fn: function (){
-var self=this;
-smalltalk.send(self,"_shouldNotImplement",[]);
-return self},
-args: [],
-source: "new\x0a\x09self shouldNotImplement",
-messageSends: ["shouldNotImplement"],
-referencedClasses: []
+referencedClasses: ["KeyedPubSubUnsubscribe"]
 }),
-smalltalk.TrappedSubscription.klass);
-
-smalltalk.addMethod(
-"_path_action_",
-smalltalk.method({
-selector: "path:action:",
-category: 'instance creation',
-fn: function (anArray,aBlock){
-var self=this;
-var $1;
-$1=smalltalk.send(smalltalk.send(self,"_new",[],smalltalk.Object.klass),"_path_actionBlock_",[anArray,aBlock]);
-return $1;
-},
-args: ["anArray", "aBlock"],
-source: "path: anArray action: aBlock\x0a\x09^super new path: anArray actionBlock: aBlock",
-messageSends: ["path:actionBlock:", "new"],
-referencedClasses: []
-}),
-smalltalk.TrappedSubscription.klass);
-
+smalltalk.KeyedSubscriptionBase);
 
-smalltalk.addClass('TrappedUnwatch', smalltalk.Error, [], 'Trapped-Backend');
-smalltalk.TrappedUnwatch.comment="SIgnal me from the watch: block to unwatch it."
 
 
 smalltalk.addMethod(

+ 38 - 1
js/Trapped-Frontend.deploy.js

@@ -1,4 +1,23 @@
 smalltalk.addPackage('Trapped-Frontend', {});
+smalltalk.addClass('TrappedDispatcher', smalltalk.KeyedPubSubBase, [], 'Trapped-Frontend');
+smalltalk.addMethod(
+"_subscriptionKey_block_",
+smalltalk.method({
+selector: "subscriptionKey:block:",
+fn: function (key,aBlock){
+var self=this;
+var $2,$3,$1;
+$2=smalltalk.send((smalltalk.TrappedSubscription || TrappedSubscription),"_new",[]);
+smalltalk.send($2,"_key_block_",[key,aBlock]);
+$3=smalltalk.send($2,"_yourself",[]);
+$1=$3;
+return $1;
+}
+}),
+smalltalk.TrappedDispatcher);
+
+
+
 smalltalk.addClass('TrappedDumbView', smalltalk.Widget, [], 'Trapped-Frontend');
 smalltalk.addMethod(
 "_renderOn_",
@@ -339,6 +358,24 @@ smalltalk.TrappedPathStack);
 
 
 
+smalltalk.addClass('TrappedSubscription', smalltalk.KeyedSubscriptionBase, [], 'Trapped-Frontend');
+smalltalk.addMethod(
+"_accepts_",
+smalltalk.method({
+selector: "accepts:",
+fn: function (aKey){
+var self=this;
+var $1;
+$1=smalltalk.send(smalltalk.send(smalltalk.send(aKey,"_size",[]),"__lt_eq",[smalltalk.send(self["@key"],"_size",[])]),"_and_",[(function(){
+return smalltalk.send(aKey,"__eq",[smalltalk.send(self["@key"],"_copyFrom_to_",[(1),smalltalk.send(aKey,"_size",[])])]);
+})]);
+return $1;
+}
+}),
+smalltalk.TrappedSubscription);
+
+
+
 smalltalk.addMethod(
 "_trapDescend_",
 smalltalk.method({
@@ -378,7 +415,7 @@ model;
 return smalltalk.send(model,"_watch_do_",[smalltalk.send(actual,"_allButFirst",[]),(function(data){
 $1=smalltalk.send(smalltalk.send(smalltalk.send(smalltalk.send(self,"_asJQuery",[]),"_closest_",["html"]),"_toArray",[]),"_isEmpty",[]);
 if(smalltalk.assert($1)){
-smalltalk.send((smalltalk.TrappedUnwatch || TrappedUnwatch),"_signal",[]);
+smalltalk.send((smalltalk.KeyedPubSubUnsubscribe || KeyedPubSubUnsubscribe),"_signal",[]);
 };
 return smalltalk.send(actual,"_trapDescend_",[(function(){
 return smalltalk.send(self,"_with_",[(function(html){

+ 52 - 4
js/Trapped-Frontend.js

@@ -1,4 +1,29 @@
 smalltalk.addPackage('Trapped-Frontend', {});
+smalltalk.addClass('TrappedDispatcher', smalltalk.KeyedPubSubBase, [], 'Trapped-Frontend');
+smalltalk.TrappedDispatcher.comment="I am base class for change event dispatchers.\x0aI manage changed path - action block subscriptions.\x0aThese subscription are instances of TrappedSubscription\x0a\x0aMy subclasses need to provide implementation for:\x0a\x09add:\x0a    do:\x0a    clean\x0a    (optionally) run\x0a"
+smalltalk.addMethod(
+"_subscriptionKey_block_",
+smalltalk.method({
+selector: "subscriptionKey:block:",
+category: 'action',
+fn: function (key,aBlock){
+var self=this;
+var $2,$3,$1;
+$2=smalltalk.send((smalltalk.TrappedSubscription || TrappedSubscription),"_new",[]);
+smalltalk.send($2,"_key_block_",[key,aBlock]);
+$3=smalltalk.send($2,"_yourself",[]);
+$1=$3;
+return $1;
+},
+args: ["key", "aBlock"],
+source: "subscriptionKey: key block: aBlock\x0a\x09^TrappedSubscription new key: key block: aBlock; yourself\x0a",
+messageSends: ["key:block:", "new", "yourself"],
+referencedClasses: ["TrappedSubscription"]
+}),
+smalltalk.TrappedDispatcher);
+
+
+
 smalltalk.addClass('TrappedDumbView', smalltalk.Widget, [], 'Trapped-Frontend');
 smalltalk.TrappedDumbView.comment="I just read and show an actual path."
 smalltalk.addMethod(
@@ -20,7 +45,7 @@ smalltalk.TrappedDumbView);
 
 
 smalltalk.addClass('TrappedModelWrapper', smalltalk.Object, ['dispatcher', 'payload'], 'Trapped-Frontend');
-smalltalk.TrappedModelWrapper.comment="I am base class for model wrappers.\x0aI wrap a model which can be any object.\x0a\x0aMy subclasses need to provide implementation for:\x0a\x09read:do:\x0a    modify:do:\x0a\x09(optionally) name\x0a\x0aand must initialize:\x0a\x09payload\x0a\x09dispatcher\x0a"
+smalltalk.TrappedModelWrapper.comment="I am base class for model wrappers.\x0aI wrap a model which can be any object.\x0a\x0aMy subclasses need to provide implementation for:\x0a\x09read:do:\x0a    modify:do:\x0a\x09(optionally) name\x0a\x0aand must initialize:\x0a\x09payload\x0a\x09dispatcher (with a subclass of TrappedDispatcher)\x0a"
 smalltalk.addMethod(
 "_dispatcher",
 smalltalk.method({
@@ -451,6 +476,29 @@ smalltalk.TrappedPathStack);
 
 
 
+smalltalk.addClass('TrappedSubscription', smalltalk.KeyedSubscriptionBase, [], 'Trapped-Frontend');
+smalltalk.addMethod(
+"_accepts_",
+smalltalk.method({
+selector: "accepts:",
+category: 'testing',
+fn: function (aKey){
+var self=this;
+var $1;
+$1=smalltalk.send(smalltalk.send(smalltalk.send(aKey,"_size",[]),"__lt_eq",[smalltalk.send(self["@key"],"_size",[])]),"_and_",[(function(){
+return smalltalk.send(aKey,"__eq",[smalltalk.send(self["@key"],"_copyFrom_to_",[(1),smalltalk.send(aKey,"_size",[])])]);
+})]);
+return $1;
+},
+args: ["aKey"],
+source: "accepts: aKey\x0a    ^aKey size <= key size and: [aKey = (key copyFrom: 1 to: aKey size)]",
+messageSends: ["and:", "=", "copyFrom:to:", "size", "<="],
+referencedClasses: []
+}),
+smalltalk.TrappedSubscription);
+
+
+
 smalltalk.addMethod(
 "_trapDescend_",
 smalltalk.method({
@@ -501,7 +549,7 @@ model;
 return smalltalk.send(model,"_watch_do_",[smalltalk.send(actual,"_allButFirst",[]),(function(data){
 $1=smalltalk.send(smalltalk.send(smalltalk.send(smalltalk.send(self,"_asJQuery",[]),"_closest_",["html"]),"_toArray",[]),"_isEmpty",[]);
 if(smalltalk.assert($1)){
-smalltalk.send((smalltalk.TrappedUnwatch || TrappedUnwatch),"_signal",[]);
+smalltalk.send((smalltalk.KeyedPubSubUnsubscribe || KeyedPubSubUnsubscribe),"_signal",[]);
 };
 return smalltalk.send(actual,"_trapDescend_",[(function(){
 return smalltalk.send(self,"_with_",[(function(html){
@@ -512,9 +560,9 @@ return smalltalk.send(aBlock,"_value_value_",[data,html]);
 })]);
 return self},
 args: ["path", "aBlock"],
-source: "trap: path read: aBlock\x0a\x09path trapDescend: [ | actual model |\x0a    \x09actual := Trapped path.\x0a        model := Trapped current byName: actual first.\x0a        model watch: actual allButFirst do: [ :data |\x0a            (self asJQuery closest: 'html') toArray isEmpty ifTrue: [ TrappedUnwatch signal ].\x0a        \x09actual trapDescend: [ self with: [ :html | aBlock value: data value: html ] ]\x0a    \x09]\x0a    ]",
+source: "trap: path read: aBlock\x0a\x09path trapDescend: [ | actual model |\x0a    \x09actual := Trapped path.\x0a        model := Trapped current byName: actual first.\x0a        model watch: actual allButFirst do: [ :data |\x0a            (self asJQuery closest: 'html') toArray isEmpty ifTrue: [ KeyedPubSubUnsubscribe signal ].\x0a        \x09actual trapDescend: [ self with: [ :html | aBlock value: data value: html ] ]\x0a    \x09]\x0a    ]",
 messageSends: ["trapDescend:", "path", "byName:", "first", "current", "watch:do:", "allButFirst", "ifTrue:", "signal", "isEmpty", "toArray", "closest:", "asJQuery", "with:", "value:value:"],
-referencedClasses: ["Trapped", "TrappedUnwatch"]
+referencedClasses: ["Trapped", "KeyedPubSubUnsubscribe"]
 }),
 smalltalk.TagBrush);
 

+ 30 - 45
st/Trapped-Backend.st

@@ -73,28 +73,17 @@ on: anObject
 ^self new root: anObject
 ! !
 
-Object subclass: #TrappedDispatcher
+Object subclass: #KeyedPubSubBase
 	instanceVariableNames: ''
 	package: 'Trapped-Backend'!
-!TrappedDispatcher commentStamp!
-I am base class for change event dispatchers.
-I manage changed path - action block subscriptions.
-These subscription must be three-element arrays
-	{ dirty. path. block }
 
-My subclasses need to provide implementation for:
-	add:
-    do:
-    clean
-    (optionally) run!
+!KeyedPubSubBase methodsFor: 'action'!
 
-!TrappedDispatcher methodsFor: 'action'!
-
-changed: path
+changed: key
 	| needsToRun |
     needsToRun := false.
 	self do: [ :each |
-		(each accepts: path) ifTrue: [
+		(each accepts: key) ifTrue: [
 			each flag.
             needsToRun := true.
 		]
@@ -106,8 +95,8 @@ dirty: aBoolean
 	aBoolean ifTrue: [[ self run ] fork]
 !
 
-on: path hook: aBlock
-	self add: (TrappedSubscription path: path action: aBlock) flag.
+on: key hook: aBlock
+	self add: (self subscriptionKey: key block: aBlock) flag.
    	self dirty: true
 !
 
@@ -121,43 +110,55 @@ run
         ]
 	].
     needsClean ifTrue: [ self clean ]
+!
+
+subscriptionKey: key block: aBlock
+    "Should return subclass of KeyedSubscriptionBase"
+    self subclassReponsibility
 ! !
 
-Object subclass: #TrappedSubscription
-	instanceVariableNames: 'path actionBlock flagged'
+Error subclass: #KeyedPubSubUnsubscribe
+	instanceVariableNames: ''
 	package: 'Trapped-Backend'!
+!KeyedPubSubUnsubscribe commentStamp!
+SIgnal me from the subscription block to unsubscribe it.!
 
-!TrappedSubscription methodsFor: 'accessing'!
+Object subclass: #KeyedSubscriptionBase
+	instanceVariableNames: 'key actionBlock flagged'
+	package: 'Trapped-Backend'!
+
+!KeyedSubscriptionBase methodsFor: 'accessing'!
 
 flag
 	flagged := true
 !
 
-path: anArray actionBlock: aBlock
-	path := anArray.
+key: anObject block: aBlock
+	key := anObject.
     actionBlock := aBlock
 ! !
 
-!TrappedSubscription methodsFor: 'action'!
+!KeyedSubscriptionBase methodsFor: 'action'!
 
 run
 	[[ actionBlock value ] ensure: [ flagged := false ]]
-    on: TrappedUnwatch do: [ actionBlock := nil ]
+    on: KeyedPubSubUnsubscribe do: [ actionBlock := nil ]
 ! !
 
-!TrappedSubscription methodsFor: 'initialization'!
+!KeyedSubscriptionBase methodsFor: 'initialization'!
 
 initialize
 	super initialize.
-    path := nil.
+    key := nil.
     actionBlock := nil.
     flagged := false.
 ! !
 
-!TrappedSubscription methodsFor: 'testing'!
+!KeyedSubscriptionBase methodsFor: 'testing'!
 
-accepts: aPath
-    ^aPath size <= path size and: [aPath = (path copyFrom: 1 to: aPath size)]
+accepts: aKey
+    "Should return true if change for aKey is relevant for this subscription"
+    self subclassResponsibility
 !
 
 isEnabled
@@ -168,22 +169,6 @@ isFlagged
 	^flagged
 ! !
 
-!TrappedSubscription class methodsFor: 'instance creation'!
-
-new
-	self shouldNotImplement
-!
-
-path: anArray action: aBlock
-	^super new path: anArray actionBlock: aBlock
-! !
-
-Error subclass: #TrappedUnwatch
-	instanceVariableNames: ''
-	package: 'Trapped-Backend'!
-!TrappedUnwatch commentStamp!
-SIgnal me from the watch: block to unwatch it.!
-
 !Object methodsFor: '*Trapped-Backend'!
 
 reverseTrapAt: anObject

+ 32 - 2
st/Trapped-Frontend.st

@@ -1,4 +1,24 @@
 Smalltalk current createPackage: 'Trapped-Frontend' properties: #{}!
+KeyedPubSubBase subclass: #TrappedDispatcher
+	instanceVariableNames: ''
+	package: 'Trapped-Frontend'!
+!TrappedDispatcher commentStamp!
+I am base class for change event dispatchers.
+I manage changed path - action block subscriptions.
+These subscription are instances of TrappedSubscription
+
+My subclasses need to provide implementation for:
+	add:
+    do:
+    clean
+    (optionally) run!
+
+!TrappedDispatcher methodsFor: 'action'!
+
+subscriptionKey: key block: aBlock
+	^TrappedSubscription new key: key block: aBlock; yourself
+! !
+
 Widget subclass: #TrappedDumbView
 	instanceVariableNames: ''
 	package: 'Trapped-Frontend'!
@@ -25,7 +45,7 @@ My subclasses need to provide implementation for:
 
 and must initialize:
 	payload
-	dispatcher!
+	dispatcher (with a subclass of TrappedDispatcher)!
 
 !TrappedModelWrapper methodsFor: 'accessing'!
 
@@ -172,6 +192,16 @@ initialize
 	elements := #().
 ! !
 
+KeyedSubscriptionBase subclass: #TrappedSubscription
+	instanceVariableNames: ''
+	package: 'Trapped-Frontend'!
+
+!TrappedSubscription methodsFor: 'testing'!
+
+accepts: aKey
+    ^aKey size <= key size and: [aKey = (key copyFrom: 1 to: aKey size)]
+! !
+
 !Array methodsFor: '*Trapped-Frontend'!
 
 trapDescend: aBlock
@@ -191,7 +221,7 @@ trap: path read: aBlock
     	actual := Trapped path.
         model := Trapped current byName: actual first.
         model watch: actual allButFirst do: [ :data |
-            (self asJQuery closest: 'html') toArray isEmpty ifTrue: [ TrappedUnwatch signal ].
+            (self asJQuery closest: 'html') toArray isEmpty ifTrue: [ KeyedPubSubUnsubscribe signal ].
         	actual trapDescend: [ self with: [ :html | aBlock value: data value: html ] ]
     	]
     ]