فهرست منبع

Trapped{Data=>Processing}Chain, data-bind / one-run alternatives

When a TrappedProcessingChain contains at least one element
that isExpectingModelData, blackboard connector is added at the beginning
and it is presumed that on the model ->view direction,
the chain is observing data from the model and it is left
to the blackboard connector to start it when data changes.

When a TrappedProcessingChain does not contain any element
that isExpectingModelData, dataTerminator is added at the end
and it is presumed that on the model ->view direction,
the chain is one-time sequence of actions and
it is run immediately with `true` as carried piece of data.
Herbert Vojčík 11 سال پیش
والد
کامیت
d3b78c6af0
2فایلهای تغییر یافته به همراه304 افزوده شده و 227 حذف شده
  1. 209 161
      js/Trapped-Frontend.js
  2. 95 66
      st/Trapped-Frontend.st

+ 209 - 161
js/Trapped-Frontend.js

@@ -1,4 +1,4 @@
-define("gh_herby_trapped/Trapped-Frontend", ["amber_vm/smalltalk", "amber_vm/nil", "amber_vm/_st", "amber_core/Kernel-Objects", "amber_core/Canvas", "amber_core/Kernel-Collections"], function(smalltalk,nil,_st){
+define("gh_herby_trapped/Trapped-Frontend", ["amber_vm/smalltalk", "amber_vm/nil", "amber_vm/_st", "amber_core/Kernel-Objects", "amber_core/Kernel-Collections", "amber_core/Canvas"], function(smalltalk,nil,_st){
 smalltalk.addPackage('Trapped-Frontend');
 smalltalk.addPackage('Trapped-Frontend');
 smalltalk.packages["Trapped-Frontend"].transport = {"type":"amd","amdNamespace":"gh_herby_trapped"};
 smalltalk.packages["Trapped-Frontend"].transport = {"type":"amd","amdNamespace":"gh_herby_trapped"};
 
 
@@ -7,18 +7,35 @@ smalltalk.addMethod(
 smalltalk.method({
 smalltalk.method({
 selector: "chain:",
 selector: "chain:",
 category: 'accessing',
 category: 'accessing',
-fn: function (aDataChain){
+fn: function (aProcessingChain){
 var self=this;
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
 return smalltalk.withContext(function($ctx1) { 
-self["@chain"]=aDataChain;
-return self}, function($ctx1) {$ctx1.fill(self,"chain:",{aDataChain:aDataChain},smalltalk.TrappedDataCarrier)})},
-args: ["aDataChain"],
-source: "chain: aDataChain\x0a\x09chain := aDataChain",
+self["@chain"]=aProcessingChain;
+return self}, function($ctx1) {$ctx1.fill(self,"chain:",{aProcessingChain:aProcessingChain},smalltalk.TrappedDataCarrier)})},
+args: ["aProcessingChain"],
+source: "chain: aProcessingChain\x0a\x09chain := aProcessingChain",
 messageSends: [],
 messageSends: [],
 referencedClasses: []
 referencedClasses: []
 }),
 }),
 smalltalk.TrappedDataCarrier);
 smalltalk.TrappedDataCarrier);
 
 
+smalltalk.addMethod(
+smalltalk.method({
+selector: "initialize",
+category: 'initialization',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+smalltalk.TrappedDataCarrier.superclass.fn.prototype._initialize.apply(_st(self), []);
+self["@model"]=true;
+return self}, function($ctx1) {$ctx1.fill(self,"initialize",{},smalltalk.TrappedDataCarrier)})},
+args: [],
+source: "initialize\x0a\x09super initialize.\x0a\x09model := true",
+messageSends: ["initialize"],
+referencedClasses: []
+}),
+smalltalk.TrappedDataCarrier);
+
 smalltalk.addMethod(
 smalltalk.addMethod(
 smalltalk.method({
 smalltalk.method({
 selector: "modifyTarget",
 selector: "modifyTarget",
@@ -204,19 +221,19 @@ smalltalk.addMethod(
 smalltalk.method({
 smalltalk.method({
 selector: "on:target:",
 selector: "on:target:",
 category: 'not yet classified',
 category: 'not yet classified',
-fn: function (aDataChain,anObject){
+fn: function (aProcessingChain,anObject){
 var self=this;
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
 return smalltalk.withContext(function($ctx1) { 
 var $2,$3,$1;
 var $2,$3,$1;
 $2=self._new();
 $2=self._new();
-_st($2)._chain_(aDataChain);
+_st($2)._chain_(aProcessingChain);
 _st($2)._target_(anObject);
 _st($2)._target_(anObject);
 $3=_st($2)._yourself();
 $3=_st($2)._yourself();
 $1=$3;
 $1=$3;
 return $1;
 return $1;
-}, function($ctx1) {$ctx1.fill(self,"on:target:",{aDataChain:aDataChain,anObject:anObject},smalltalk.TrappedDataCarrier.klass)})},
-args: ["aDataChain", "anObject"],
-source: "on: aDataChain target: anObject\x0a\x09^self new\x0a\x09\x09chain: aDataChain;\x0a\x09\x09target: anObject;\x0a\x09\x09yourself",
+}, function($ctx1) {$ctx1.fill(self,"on:target:",{aProcessingChain:aProcessingChain,anObject:anObject},smalltalk.TrappedDataCarrier.klass)})},
+args: ["aProcessingChain", "anObject"],
+source: "on: aProcessingChain target: anObject\x0a\x09^self new\x0a\x09\x09chain: aProcessingChain;\x0a\x09\x09target: anObject;\x0a\x09\x09yourself",
 messageSends: ["chain:", "new", "target:", "yourself"],
 messageSends: ["chain:", "new", "target:", "yourself"],
 referencedClasses: []
 referencedClasses: []
 }),
 }),
@@ -275,7 +292,7 @@ smalltalk.TrappedDataCarrierToView);
 
 
 
 
 
 
-smalltalk.addClass('TrappedDataChain', smalltalk.Object, ['processors'], 'Trapped-Frontend');
+smalltalk.addClass('TrappedProcessingChain', smalltalk.Object, ['processors'], 'Trapped-Frontend');
 smalltalk.addMethod(
 smalltalk.addMethod(
 smalltalk.method({
 smalltalk.method({
 selector: "firstProcessorNo",
 selector: "firstProcessorNo",
@@ -284,13 +301,13 @@ fn: function (){
 var self=this;
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
 return smalltalk.withContext(function($ctx1) { 
 return (1);
 return (1);
-}, function($ctx1) {$ctx1.fill(self,"firstProcessorNo",{},smalltalk.TrappedDataChain)})},
+}, function($ctx1) {$ctx1.fill(self,"firstProcessorNo",{},smalltalk.TrappedProcessingChain)})},
 args: [],
 args: [],
 source: "firstProcessorNo\x0a\x09^1",
 source: "firstProcessorNo\x0a\x09^1",
 messageSends: [],
 messageSends: [],
 referencedClasses: []
 referencedClasses: []
 }),
 }),
-smalltalk.TrappedDataChain);
+smalltalk.TrappedProcessingChain);
 
 
 smalltalk.addMethod(
 smalltalk.addMethod(
 smalltalk.method({
 smalltalk.method({
@@ -302,6 +319,7 @@ var toViewCarrier,toModelCarrier;
 function $TrappedDataCarrierToView(){return smalltalk.TrappedDataCarrierToView||(typeof TrappedDataCarrierToView=="undefined"?nil:TrappedDataCarrierToView)}
 function $TrappedDataCarrierToView(){return smalltalk.TrappedDataCarrierToView||(typeof TrappedDataCarrierToView=="undefined"?nil:TrappedDataCarrierToView)}
 function $TrappedDataCarrierToModel(){return smalltalk.TrappedDataCarrierToModel||(typeof TrappedDataCarrierToModel=="undefined"?nil:TrappedDataCarrierToModel)}
 function $TrappedDataCarrierToModel(){return smalltalk.TrappedDataCarrierToModel||(typeof TrappedDataCarrierToModel=="undefined"?nil:TrappedDataCarrierToModel)}
 return smalltalk.withContext(function($ctx1) { 
 return smalltalk.withContext(function($ctx1) { 
+var $1;
 toViewCarrier=_st($TrappedDataCarrierToView())._on_target_(self,aTagBrush);
 toViewCarrier=_st($TrappedDataCarrierToView())._on_target_(self,aTagBrush);
 $ctx1.sendIdx["on:target:"]=1;
 $ctx1.sendIdx["on:target:"]=1;
 toModelCarrier=_st($TrappedDataCarrierToModel())._on_target_(self,aSnapshot);
 toModelCarrier=_st($TrappedDataCarrierToModel())._on_target_(self,aSnapshot);
@@ -309,13 +327,17 @@ _st(self["@processors"])._do_((function(each){
 return smalltalk.withContext(function($ctx2) {
 return smalltalk.withContext(function($ctx2) {
 return _st(each)._installToView_toModel_(toViewCarrier,toModelCarrier);
 return _st(each)._installToView_toModel_(toViewCarrier,toModelCarrier);
 }, function($ctx2) {$ctx2.fillBlock({each:each},$ctx1,1)})}));
 }, function($ctx2) {$ctx2.fillBlock({each:each},$ctx1,1)})}));
-return self}, function($ctx1) {$ctx1.fill(self,"forSnapshot:andBrush:",{aSnapshot:aSnapshot,aTagBrush:aTagBrush,toViewCarrier:toViewCarrier,toModelCarrier:toModelCarrier},smalltalk.TrappedDataChain)})},
+$1=_st(_st(toViewCarrier)._value()).__eq(true);
+if(smalltalk.assert($1)){
+_st(_st(toViewCarrier)._copy())._proceed();
+};
+return self}, function($ctx1) {$ctx1.fill(self,"forSnapshot:andBrush:",{aSnapshot:aSnapshot,aTagBrush:aTagBrush,toViewCarrier:toViewCarrier,toModelCarrier:toModelCarrier},smalltalk.TrappedProcessingChain)})},
 args: ["aSnapshot", "aTagBrush"],
 args: ["aSnapshot", "aTagBrush"],
-source: "forSnapshot: aSnapshot andBrush: aTagBrush\x0a\x09| toViewCarrier toModelCarrier |\x0a\x09toViewCarrier := TrappedDataCarrierToView on: self target: aTagBrush.\x0a\x09toModelCarrier := TrappedDataCarrierToModel on: self target: aSnapshot.\x0a\x09processors do: [ :each | each installToView: toViewCarrier toModel: toModelCarrier ]",
-messageSends: ["on:target:", "do:", "installToView:toModel:"],
+source: "forSnapshot: aSnapshot andBrush: aTagBrush\x0a\x09| toViewCarrier toModelCarrier |\x0a\x09toViewCarrier := TrappedDataCarrierToView on: self target: aTagBrush.\x0a\x09toModelCarrier := TrappedDataCarrierToModel on: self target: aSnapshot.\x0a\x09processors do: [ :each | each installToView: toViewCarrier toModel: toModelCarrier ].\x0a\x09toViewCarrier value = true ifTrue: [ toViewCarrier copy proceed ]",
+messageSends: ["on:target:", "do:", "installToView:toModel:", "ifTrue:", "=", "value", "proceed", "copy"],
 referencedClasses: ["TrappedDataCarrierToView", "TrappedDataCarrierToModel"]
 referencedClasses: ["TrappedDataCarrierToView", "TrappedDataCarrierToModel"]
 }),
 }),
-smalltalk.TrappedDataChain);
+smalltalk.TrappedProcessingChain);
 
 
 smalltalk.addMethod(
 smalltalk.addMethod(
 smalltalk.method({
 smalltalk.method({
@@ -327,13 +349,13 @@ return smalltalk.withContext(function($ctx1) {
 var $1;
 var $1;
 $1=_st(self["@processors"])._size();
 $1=_st(self["@processors"])._size();
 return $1;
 return $1;
-}, function($ctx1) {$ctx1.fill(self,"lastProcessorNo",{},smalltalk.TrappedDataChain)})},
+}, function($ctx1) {$ctx1.fill(self,"lastProcessorNo",{},smalltalk.TrappedProcessingChain)})},
 args: [],
 args: [],
 source: "lastProcessorNo\x0a\x09^processors size",
 source: "lastProcessorNo\x0a\x09^processors size",
 messageSends: ["size"],
 messageSends: ["size"],
 referencedClasses: []
 referencedClasses: []
 }),
 }),
-smalltalk.TrappedDataChain);
+smalltalk.TrappedProcessingChain);
 
 
 smalltalk.addMethod(
 smalltalk.addMethod(
 smalltalk.method({
 smalltalk.method({
@@ -345,13 +367,13 @@ return smalltalk.withContext(function($ctx1) {
 var $1;
 var $1;
 $1=_st(self["@processors"])._at_(aNumber);
 $1=_st(self["@processors"])._at_(aNumber);
 return $1;
 return $1;
-}, function($ctx1) {$ctx1.fill(self,"processorNo:",{aNumber:aNumber},smalltalk.TrappedDataChain)})},
+}, function($ctx1) {$ctx1.fill(self,"processorNo:",{aNumber:aNumber},smalltalk.TrappedProcessingChain)})},
 args: ["aNumber"],
 args: ["aNumber"],
 source: "processorNo: aNumber\x0a\x09^processors at: aNumber",
 source: "processorNo: aNumber\x0a\x09^processors at: aNumber",
 messageSends: ["at:"],
 messageSends: ["at:"],
 referencedClasses: []
 referencedClasses: []
 }),
 }),
-smalltalk.TrappedDataChain);
+smalltalk.TrappedProcessingChain);
 
 
 smalltalk.addMethod(
 smalltalk.addMethod(
 smalltalk.method({
 smalltalk.method({
@@ -361,13 +383,13 @@ fn: function (anArray){
 var self=this;
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
 return smalltalk.withContext(function($ctx1) { 
 self["@processors"]=anArray;
 self["@processors"]=anArray;
-return self}, function($ctx1) {$ctx1.fill(self,"processors:",{anArray:anArray},smalltalk.TrappedDataChain)})},
+return self}, function($ctx1) {$ctx1.fill(self,"processors:",{anArray:anArray},smalltalk.TrappedProcessingChain)})},
 args: ["anArray"],
 args: ["anArray"],
 source: "processors: anArray\x0a\x09processors := anArray",
 source: "processors: anArray\x0a\x09processors := anArray",
 messageSends: [],
 messageSends: [],
 referencedClasses: []
 referencedClasses: []
 }),
 }),
-smalltalk.TrappedDataChain);
+smalltalk.TrappedProcessingChain);
 
 
 
 
 smalltalk.addMethod(
 smalltalk.addMethod(
@@ -381,13 +403,32 @@ return smalltalk.withContext(function($ctx1) {
 var $1;
 var $1;
 $1=_st($TrappedProcessorBlackboard())._new();
 $1=_st($TrappedProcessorBlackboard())._new();
 return $1;
 return $1;
-}, function($ctx1) {$ctx1.fill(self,"blackboardReaderWriter",{},smalltalk.TrappedDataChain.klass)})},
+}, function($ctx1) {$ctx1.fill(self,"blackboardReaderWriter",{},smalltalk.TrappedProcessingChain.klass)})},
 args: [],
 args: [],
 source: "blackboardReaderWriter\x0a\x09^TrappedProcessorBlackboard new",
 source: "blackboardReaderWriter\x0a\x09^TrappedProcessorBlackboard new",
 messageSends: ["new"],
 messageSends: ["new"],
 referencedClasses: ["TrappedProcessorBlackboard"]
 referencedClasses: ["TrappedProcessorBlackboard"]
 }),
 }),
-smalltalk.TrappedDataChain.klass);
+smalltalk.TrappedProcessingChain.klass);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "dataTerminator",
+category: 'private',
+fn: function (){
+var self=this;
+function $TrappedProcessorTerminator(){return smalltalk.TrappedProcessorTerminator||(typeof TrappedProcessorTerminator=="undefined"?nil:TrappedProcessorTerminator)}
+return smalltalk.withContext(function($ctx1) { 
+var $1;
+$1=_st($TrappedProcessorTerminator())._new();
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"dataTerminator",{},smalltalk.TrappedProcessingChain.klass)})},
+args: [],
+source: "dataTerminator\x0a\x09^TrappedProcessorTerminator new",
+messageSends: ["new"],
+referencedClasses: ["TrappedProcessorTerminator"]
+}),
+smalltalk.TrappedProcessingChain.klass);
 
 
 smalltalk.addMethod(
 smalltalk.addMethod(
 smalltalk.method({
 smalltalk.method({
@@ -396,19 +437,31 @@ category: 'instance creation',
 fn: function (anArray){
 fn: function (anArray){
 var self=this;
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
 return smalltalk.withContext(function($ctx1) { 
-var $2,$3,$1;
-$2=self._new();
-_st($2)._processors_(_st([self._blackboardReaderWriter()]).__comma(anArray));
-$3=_st($2)._yourself();
-$1=$3;
-return $1;
-}, function($ctx1) {$ctx1.fill(self,"new:",{anArray:anArray},smalltalk.TrappedDataChain.klass)})},
+var $1,$3,$4,$2;
+$1=_st(anArray)._detect_ifNone_((function(each){
+return smalltalk.withContext(function($ctx2) {
+return _st(each)._isExpectingModelData();
+}, function($ctx2) {$ctx2.fillBlock({each:each},$ctx1,1)})}),(function(){
+return smalltalk.withContext(function($ctx2) {
+return nil;
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1,2)})}));
+if(($receiver = $1) == nil || $receiver == null){
+_st(anArray)._add_(self._dataTerminator());
+} else {
+_st(anArray)._addFirst_(self._blackboardReaderWriter());
+};
+$3=self._new();
+_st($3)._processors_(anArray);
+$4=_st($3)._yourself();
+$2=$4;
+return $2;
+}, function($ctx1) {$ctx1.fill(self,"new:",{anArray:anArray},smalltalk.TrappedProcessingChain.klass)})},
 args: ["anArray"],
 args: ["anArray"],
-source: "new: anArray\x0a\x09^self new\x0a\x09\x09processors: { self blackboardReaderWriter }, anArray;\x0a\x09\x09yourself",
-messageSends: ["processors:", "new", ",", "blackboardReaderWriter", "yourself"],
+source: "new: anArray\x0a\x09(anArray detect: [ :each | each isExpectingModelData ] ifNone: [ nil ])\x0a\x09\x09ifNil: [ anArray add: self dataTerminator ]\x0a\x09\x09ifNotNil: [ anArray addFirst: self blackboardReaderWriter ].\x0a\x09^self new\x0a\x09\x09processors: anArray;\x0a\x09\x09yourself",
+messageSends: ["ifNil:ifNotNil:", "detect:ifNone:", "isExpectingModelData", "add:", "dataTerminator", "addFirst:", "blackboardReaderWriter", "processors:", "new", "yourself"],
 referencedClasses: []
 referencedClasses: []
 }),
 }),
-smalltalk.TrappedDataChain.klass);
+smalltalk.TrappedProcessingChain.klass);
 
 
 smalltalk.addMethod(
 smalltalk.addMethod(
 smalltalk.method({
 smalltalk.method({
@@ -450,37 +503,17 @@ return _st($TrappedProcessor())._perform_withArguments_(selector,args);
 };
 };
 }, function($ctx2) {$ctx2.fillBlock({each:each},$ctx1,2)})})));
 }, function($ctx2) {$ctx2.fillBlock({each:each},$ctx1,2)})})));
 return $1;
 return $1;
-}, function($ctx1) {$ctx1.fill(self,"newFromProcessorSpecs:",{anArray:anArray},smalltalk.TrappedDataChain.klass)})},
+}, function($ctx1) {$ctx1.fill(self,"newFromProcessorSpecs:",{anArray:anArray},smalltalk.TrappedProcessingChain.klass)})},
 args: ["anArray"],
 args: ["anArray"],
 source: "newFromProcessorSpecs: anArray\x0a\x09^self new: ((anArray ifEmpty: [ #(contents) ]) collect: [ :each | each isString\x0a\x09\x09ifTrue: [ TrappedProcessor perform: each ]\x0a\x09\x09ifFalse: [\x0a\x09\x09\x09| selector args |\x0a\x09\x09\x09selector := ''.\x0a\x09\x09\x09args := #().\x0a\x09\x09\x09each withIndexDo: [ :element :index | index odd\x0a\x09\x09\x09\x09ifTrue: [ selector := selector, element ]\x0a\x09\x09\x09\x09ifFalse: [ selector := selector, ':'. args add: element ] ].\x0a\x09\x09\x09TrappedProcessor perform: selector withArguments: args ] ])",
 source: "newFromProcessorSpecs: anArray\x0a\x09^self new: ((anArray ifEmpty: [ #(contents) ]) collect: [ :each | each isString\x0a\x09\x09ifTrue: [ TrappedProcessor perform: each ]\x0a\x09\x09ifFalse: [\x0a\x09\x09\x09| selector args |\x0a\x09\x09\x09selector := ''.\x0a\x09\x09\x09args := #().\x0a\x09\x09\x09each withIndexDo: [ :element :index | index odd\x0a\x09\x09\x09\x09ifTrue: [ selector := selector, element ]\x0a\x09\x09\x09\x09ifFalse: [ selector := selector, ':'. args add: element ] ].\x0a\x09\x09\x09TrappedProcessor perform: selector withArguments: args ] ])",
 messageSends: ["new:", "collect:", "ifEmpty:", "ifTrue:ifFalse:", "isString", "perform:", "withIndexDo:", "odd", ",", "add:", "perform:withArguments:"],
 messageSends: ["new:", "collect:", "ifEmpty:", "ifTrue:ifFalse:", "isString", "perform:", "withIndexDo:", "odd", ",", "add:", "perform:withArguments:"],
 referencedClasses: ["TrappedProcessor"]
 referencedClasses: ["TrappedProcessor"]
 }),
 }),
-smalltalk.TrappedDataChain.klass);
-
-
-smalltalk.addClass('TrappedDumbView', smalltalk.Widget, [], 'Trapped-Frontend');
-smalltalk.TrappedDumbView.comment="I just read and show an actual path.";
-smalltalk.addMethod(
-smalltalk.method({
-selector: "renderOn:",
-category: 'rendering',
-fn: function (html){
-var self=this;
-return smalltalk.withContext(function($ctx1) { 
-_st(_st(html)._root())._trap_([]);
-return self}, function($ctx1) {$ctx1.fill(self,"renderOn:",{html:html},smalltalk.TrappedDumbView)})},
-args: ["html"],
-source: "renderOn: html\x0a\x09html root trap: #()",
-messageSends: ["trap:", "root"],
-referencedClasses: []
-}),
-smalltalk.TrappedDumbView);
-
+smalltalk.TrappedProcessingChain.klass);
 
 
 
 
 smalltalk.addClass('TrappedProcessor', smalltalk.Object, [], 'Trapped-Frontend');
 smalltalk.addClass('TrappedProcessor', smalltalk.Object, [], 'Trapped-Frontend');
-smalltalk.TrappedProcessor.comment="I process data in TrappedDataChain.\x0aI am stateless flyweight (aka servant)\x0aand will get all necessary data as arguments in API calls.\x0a\x0aMy public API is:\x0a - installToView:toModel:\x0a   This gets two TrappedDataCarriers set up without actual data\x0a   and at the beginning of their chains. It should do one-time\x0a   installation task needed (install event handlers etc.).\x0a   To start a chain, do: dataCarrier copy value: data; proceed.\x0a - toView:\x0a   This performs transformation of TrappedDataCarrier on its way from model to view.\x0a   Should call aDataCarrier proceed to proceed to subsequent step.\x0a - toModel:\x0a   This performs transformation of TrappedDataToken on its way from view to model.\x0a   Should call aDataCarrier proceed to proceed to subsequent step.\x0a";
+smalltalk.TrappedProcessor.comment="I am a processing step in TrappedProcessingChain.\x0aI am stateless flyweight (aka servant)\x0aand will get all necessary data as arguments in API calls.\x0a\x0aMy public API is:\x0a - installToView:toModel:\x0a   This gets two TrappedDataCarriers set up without actual data\x0a   and at the beginning of their chains. It should do one-time\x0a   installation task needed (install event handlers etc.).\x0a   To start a chain, do: dataCarrier copy value: data; proceed.\x0a - toView:\x0a   This performs transformation of TrappedDataCarrier on its way from model to view.\x0a   Should call aDataCarrier proceed to proceed to subsequent step.\x0a - toModel:\x0a   This performs transformation of TrappedDataCarrier on its way from view to model.\x0a   Should call aDataCarrier proceed to proceed to subsequent step.\x0a";
 smalltalk.addMethod(
 smalltalk.addMethod(
 smalltalk.method({
 smalltalk.method({
 selector: "installToView:toModel:",
 selector: "installToView:toModel:",
@@ -496,6 +529,22 @@ referencedClasses: []
 }),
 }),
 smalltalk.TrappedProcessor);
 smalltalk.TrappedProcessor);
 
 
+smalltalk.addMethod(
+smalltalk.method({
+selector: "isExpectingModelData",
+category: 'testing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+return false;
+}, function($ctx1) {$ctx1.fill(self,"isExpectingModelData",{},smalltalk.TrappedProcessor)})},
+args: [],
+source: "isExpectingModelData\x0a\x09^false",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.TrappedProcessor);
+
 smalltalk.addMethod(
 smalltalk.addMethod(
 smalltalk.method({
 smalltalk.method({
 selector: "toModel:",
 selector: "toModel:",
@@ -663,61 +712,27 @@ referencedClasses: ["TrappedProcessorWidget"]
 smalltalk.TrappedProcessor.klass);
 smalltalk.TrappedProcessor.klass);
 
 
 
 
-smalltalk.addClass('TrappedProcessorBlackboard', smalltalk.TrappedProcessor, [], 'Trapped-Frontend');
-smalltalk.TrappedProcessorBlackboard.comment="I am used internally to fetch data from blackboard\x0aor write it back.";
-smalltalk.addMethod(
-smalltalk.method({
-selector: "installToView:toModel:",
-category: 'installation',
-fn: function (aDataCarrier,anotherDataCarrier){
-var self=this;
-var snap;
-function $KeyedPubSubUnsubscribe(){return smalltalk.KeyedPubSubUnsubscribe||(typeof KeyedPubSubUnsubscribe=="undefined"?nil:KeyedPubSubUnsubscribe)}
-return smalltalk.withContext(function($ctx1) { 
-var $1,$2,$3;
-snap=_st(anotherDataCarrier)._target();
-$ctx1.sendIdx["target"]=1;
-_st(snap)._watch_((function(data){
-return smalltalk.withContext(function($ctx2) {
-$1=_st(_st(_st(_st(_st(aDataCarrier)._target())._asJQuery())._closest_("html"))._toArray())._isEmpty();
-if(smalltalk.assert($1)){
-_st($KeyedPubSubUnsubscribe())._signal();
-};
-return _st(snap)._do_((function(){
-return smalltalk.withContext(function($ctx3) {
-$2=_st(aDataCarrier)._copy();
-_st($2)._value_(data);
-$3=_st($2)._proceed();
-return $3;
-}, function($ctx3) {$ctx3.fillBlock({},$ctx2,3)})}));
-}, function($ctx2) {$ctx2.fillBlock({data:data},$ctx1,1)})}));
-return self}, function($ctx1) {$ctx1.fill(self,"installToView:toModel:",{aDataCarrier:aDataCarrier,anotherDataCarrier:anotherDataCarrier,snap:snap},smalltalk.TrappedProcessorBlackboard)})},
-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 ] ]",
-messageSends: ["target", "watch:", "ifTrue:", "isEmpty", "toArray", "closest:", "asJQuery", "signal", "do:", "value:", "copy", "proceed"],
-referencedClasses: ["KeyedPubSubUnsubscribe"]
-}),
-smalltalk.TrappedProcessorBlackboard);
-
+smalltalk.addClass('TrappedDataExpectingProcessor', smalltalk.TrappedProcessor, [], 'Trapped-Frontend');
+smalltalk.TrappedDataExpectingProcessor.comment="I answer true to isExpectingModelData and serve as a base class\x0afor processor that present / change model data.\x0a\x0aWhen at least one of my instances is present in the chain,\x0aautomatic databinding processor is added at the beginning\x0a(the data-binding scenario); otherwise, the chain\x0ais run immediately with true as data (run-once scenario).";
 smalltalk.addMethod(
 smalltalk.addMethod(
 smalltalk.method({
 smalltalk.method({
-selector: "toModel:",
-category: 'data transformation',
-fn: function (aDataCarrier){
+selector: "isExpectingModelData",
+category: 'testing',
+fn: function (){
 var self=this;
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
 return smalltalk.withContext(function($ctx1) { 
-_st(aDataCarrier)._modifyTarget();
-return self}, function($ctx1) {$ctx1.fill(self,"toModel:",{aDataCarrier:aDataCarrier},smalltalk.TrappedProcessorBlackboard)})},
-args: ["aDataCarrier"],
-source: "toModel: aDataCarrier\x0a\x09aDataCarrier modifyTarget",
-messageSends: ["modifyTarget"],
+return true;
+}, function($ctx1) {$ctx1.fill(self,"isExpectingModelData",{},smalltalk.TrappedDataExpectingProcessor)})},
+args: [],
+source: "isExpectingModelData\x0a\x09^true",
+messageSends: [],
 referencedClasses: []
 referencedClasses: []
 }),
 }),
-smalltalk.TrappedProcessorBlackboard);
+smalltalk.TrappedDataExpectingProcessor);
 
 
 
 
 
 
-smalltalk.addClass('TrappedProcessorContents', smalltalk.TrappedProcessor, [], 'Trapped-Frontend');
+smalltalk.addClass('TrappedProcessorContents', smalltalk.TrappedDataExpectingProcessor, [], 'Trapped-Frontend');
 smalltalk.TrappedProcessorContents.comment="I put data into target via contents: in toView:";
 smalltalk.TrappedProcessorContents.comment="I put data into target via contents: in toView:";
 smalltalk.addMethod(
 smalltalk.addMethod(
 smalltalk.method({
 smalltalk.method({
@@ -737,7 +752,7 @@ smalltalk.TrappedProcessorContents);
 
 
 
 
 
 
-smalltalk.addClass('TrappedProcessorInputChecked', smalltalk.TrappedProcessor, [], 'Trapped-Frontend');
+smalltalk.addClass('TrappedProcessorInputChecked', smalltalk.TrappedDataExpectingProcessor, [], 'Trapped-Frontend');
 smalltalk.TrappedProcessorInputChecked.comment="I bind to checkbox checked attribute.";
 smalltalk.TrappedProcessorInputChecked.comment="I bind to checkbox checked attribute.";
 smalltalk.addMethod(
 smalltalk.addMethod(
 smalltalk.method({
 smalltalk.method({
@@ -782,7 +797,7 @@ smalltalk.TrappedProcessorInputChecked);
 
 
 
 
 
 
-smalltalk.addClass('TrappedProcessorInputValue', smalltalk.TrappedProcessor, [], 'Trapped-Frontend');
+smalltalk.addClass('TrappedProcessorInputValue', smalltalk.TrappedDataExpectingProcessor, [], 'Trapped-Frontend');
 smalltalk.TrappedProcessorInputValue.comment="I bind to input value.";
 smalltalk.TrappedProcessorInputValue.comment="I bind to input value.";
 smalltalk.addMethod(
 smalltalk.addMethod(
 smalltalk.method({
 smalltalk.method({
@@ -827,6 +842,62 @@ smalltalk.TrappedProcessorInputValue);
 
 
 
 
 
 
+smalltalk.addClass('TrappedProcessorBlackboard', smalltalk.TrappedProcessor, [], 'Trapped-Frontend');
+smalltalk.TrappedProcessorBlackboard.comment="I am used internally to fetch data from blackboard\x0aor write it back.\x0a\x0aI am added to the beginning of the chain\x0awhen the chain contains at least one element\x0athat isExpectingModelData (see TrappedDataExpectingProcessor).";
+smalltalk.addMethod(
+smalltalk.method({
+selector: "installToView:toModel:",
+category: 'installation',
+fn: function (aDataCarrier,anotherDataCarrier){
+var self=this;
+var snap;
+function $KeyedPubSubUnsubscribe(){return smalltalk.KeyedPubSubUnsubscribe||(typeof KeyedPubSubUnsubscribe=="undefined"?nil:KeyedPubSubUnsubscribe)}
+return smalltalk.withContext(function($ctx1) { 
+var $1,$2,$3;
+snap=_st(anotherDataCarrier)._target();
+$ctx1.sendIdx["target"]=1;
+_st(snap)._watch_((function(data){
+return smalltalk.withContext(function($ctx2) {
+$1=_st(_st(_st(_st(_st(aDataCarrier)._target())._asJQuery())._closest_("html"))._toArray())._isEmpty();
+if(smalltalk.assert($1)){
+_st($KeyedPubSubUnsubscribe())._signal();
+};
+return _st(snap)._do_((function(){
+return smalltalk.withContext(function($ctx3) {
+$2=_st(aDataCarrier)._copy();
+_st($2)._value_(data);
+$ctx3.sendIdx["value:"]=1;
+$3=_st($2)._proceed();
+return $3;
+}, function($ctx3) {$ctx3.fillBlock({},$ctx2,3)})}));
+}, function($ctx2) {$ctx2.fillBlock({data:data},$ctx1,1)})}));
+_st(aDataCarrier)._value_(false);
+return self}, function($ctx1) {$ctx1.fill(self,"installToView:toModel:",{aDataCarrier:aDataCarrier,anotherDataCarrier:anotherDataCarrier,snap:snap},smalltalk.TrappedProcessorBlackboard)})},
+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",
+messageSends: ["target", "watch:", "ifTrue:", "isEmpty", "toArray", "closest:", "asJQuery", "signal", "do:", "value:", "copy", "proceed"],
+referencedClasses: ["KeyedPubSubUnsubscribe"]
+}),
+smalltalk.TrappedProcessorBlackboard);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "toModel:",
+category: 'data transformation',
+fn: function (aDataCarrier){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+_st(aDataCarrier)._modifyTarget();
+return self}, function($ctx1) {$ctx1.fill(self,"toModel:",{aDataCarrier:aDataCarrier},smalltalk.TrappedProcessorBlackboard)})},
+args: ["aDataCarrier"],
+source: "toModel: aDataCarrier\x0a\x09aDataCarrier modifyTarget",
+messageSends: ["modifyTarget"],
+referencedClasses: []
+}),
+smalltalk.TrappedProcessorBlackboard);
+
+
+
 smalltalk.addClass('TrappedProcessorSignal', smalltalk.TrappedProcessor, ['selector'], 'Trapped-Frontend');
 smalltalk.addClass('TrappedProcessorSignal', smalltalk.TrappedProcessor, ['selector'], 'Trapped-Frontend');
 smalltalk.TrappedProcessorSignal.comment="Instead of writing data directly to model,\x0aI instead modify it by sending a message specified when instantiating me.";
 smalltalk.TrappedProcessorSignal.comment="Instead of writing data directly to model,\x0aI instead modify it by sending a message specified when instantiating me.";
 smalltalk.addMethod(
 smalltalk.addMethod(
@@ -899,6 +970,25 @@ referencedClasses: []
 smalltalk.TrappedProcessorSignal.klass);
 smalltalk.TrappedProcessorSignal.klass);
 
 
 
 
+smalltalk.addClass('TrappedProcessorTerminator', smalltalk.TrappedProcessor, [], 'Trapped-Frontend');
+smalltalk.TrappedProcessorTerminator.comment="I do not proceed in toView:.\x0a\x0aI am added automatically to end of chain when it does not contain\x0aany element that isExpectingModelData (see TrappedDataExpectingProcessor).";
+smalltalk.addMethod(
+smalltalk.method({
+selector: "toView:",
+category: 'data transformation',
+fn: function (aDataCarrier){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+return self}, function($ctx1) {$ctx1.fill(self,"toView:",{aDataCarrier:aDataCarrier},smalltalk.TrappedProcessorTerminator)})},
+args: ["aDataCarrier"],
+source: "toView: aDataCarrier\x0a\x09\x22stop\x22",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.TrappedProcessorTerminator);
+
+
+
 smalltalk.addClass('TrappedProcessorWhenClicked', smalltalk.TrappedProcessor, [], 'Trapped-Frontend');
 smalltalk.addClass('TrappedProcessorWhenClicked', smalltalk.TrappedProcessor, [], 'Trapped-Frontend');
 smalltalk.TrappedProcessorWhenClicked.comment="I bind to an element and send true to blackboard when clicked.";
 smalltalk.TrappedProcessorWhenClicked.comment="I bind to an element and send true to blackboard when clicked.";
 smalltalk.addMethod(
 smalltalk.addMethod(
@@ -908,19 +998,15 @@ category: 'installation',
 fn: function (aDataCarrier,anotherDataCarrier){
 fn: function (aDataCarrier,anotherDataCarrier){
 var self=this;
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
 return smalltalk.withContext(function($ctx1) { 
-var $1,$2;
 _st(_st(aDataCarrier)._target())._onClick_((function(){
 _st(_st(aDataCarrier)._target())._onClick_((function(){
 return smalltalk.withContext(function($ctx2) {
 return smalltalk.withContext(function($ctx2) {
-$1=_st(anotherDataCarrier)._copy();
-_st($1)._value_(true);
-$2=_st($1)._proceed();
-$2;
+_st(_st(anotherDataCarrier)._copy())._proceed();
 return false;
 return false;
 }, function($ctx2) {$ctx2.fillBlock({},$ctx1,1)})}));
 }, function($ctx2) {$ctx2.fillBlock({},$ctx1,1)})}));
 return self}, function($ctx1) {$ctx1.fill(self,"installToView:toModel:",{aDataCarrier:aDataCarrier,anotherDataCarrier:anotherDataCarrier},smalltalk.TrappedProcessorWhenClicked)})},
 return self}, function($ctx1) {$ctx1.fill(self,"installToView:toModel:",{aDataCarrier:aDataCarrier,anotherDataCarrier:anotherDataCarrier},smalltalk.TrappedProcessorWhenClicked)})},
 args: ["aDataCarrier", "anotherDataCarrier"],
 args: ["aDataCarrier", "anotherDataCarrier"],
-source: "installToView: aDataCarrier toModel: anotherDataCarrier\x0a\x09aDataCarrier target onClick: [ anotherDataCarrier copy value: true; proceed. false ]",
-messageSends: ["onClick:", "target", "value:", "copy", "proceed"],
+source: "installToView: aDataCarrier toModel: anotherDataCarrier\x0a\x09aDataCarrier target onClick: [ anotherDataCarrier copy proceed. false ]",
+messageSends: ["onClick:", "target", "proceed", "copy"],
 referencedClasses: []
 referencedClasses: []
 }),
 }),
 smalltalk.TrappedProcessorWhenClicked);
 smalltalk.TrappedProcessorWhenClicked);
@@ -936,61 +1022,23 @@ category: 'installation',
 fn: function (aDataCarrier,anotherDataCarrier){
 fn: function (aDataCarrier,anotherDataCarrier){
 var self=this;
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
 return smalltalk.withContext(function($ctx1) { 
-var $1,$2;
 _st(_st(aDataCarrier)._target())._onSubmit_((function(){
 _st(_st(aDataCarrier)._target())._onSubmit_((function(){
 return smalltalk.withContext(function($ctx2) {
 return smalltalk.withContext(function($ctx2) {
-$1=_st(anotherDataCarrier)._copy();
-_st($1)._value_(true);
-$2=_st($1)._proceed();
-$2;
+_st(_st(anotherDataCarrier)._copy())._proceed();
 return false;
 return false;
 }, function($ctx2) {$ctx2.fillBlock({},$ctx1,1)})}));
 }, function($ctx2) {$ctx2.fillBlock({},$ctx1,1)})}));
 return self}, function($ctx1) {$ctx1.fill(self,"installToView:toModel:",{aDataCarrier:aDataCarrier,anotherDataCarrier:anotherDataCarrier},smalltalk.TrappedProcessorWhenSubmitted)})},
 return self}, function($ctx1) {$ctx1.fill(self,"installToView:toModel:",{aDataCarrier:aDataCarrier,anotherDataCarrier:anotherDataCarrier},smalltalk.TrappedProcessorWhenSubmitted)})},
 args: ["aDataCarrier", "anotherDataCarrier"],
 args: ["aDataCarrier", "anotherDataCarrier"],
-source: "installToView: aDataCarrier toModel: anotherDataCarrier\x0a\x09aDataCarrier target onSubmit: [ anotherDataCarrier copy value: true; proceed. false ]",
-messageSends: ["onSubmit:", "target", "value:", "copy", "proceed"],
+source: "installToView: aDataCarrier toModel: anotherDataCarrier\x0a\x09aDataCarrier target onSubmit: [ anotherDataCarrier copy proceed. false ]",
+messageSends: ["onSubmit:", "target", "proceed", "copy"],
 referencedClasses: []
 referencedClasses: []
 }),
 }),
 smalltalk.TrappedProcessorWhenSubmitted);
 smalltalk.TrappedProcessorWhenSubmitted);
 
 
 
 
 
 
-smalltalk.addClass('TrappedStoppingProcessor', smalltalk.TrappedProcessor, [], 'Trapped-Frontend');
-smalltalk.TrappedStoppingProcessor.comment="I do not proceed in toView: nor in toModel:\x0a\x0aI am therefore only interesting for my side-effects from install step.";
-smalltalk.addMethod(
-smalltalk.method({
-selector: "toModel:",
-category: 'data transformation',
-fn: function (aDataCarrier){
-var self=this;
-return smalltalk.withContext(function($ctx1) { 
-return self}, function($ctx1) {$ctx1.fill(self,"toModel:",{aDataCarrier:aDataCarrier},smalltalk.TrappedStoppingProcessor)})},
-args: ["aDataCarrier"],
-source: "toModel: aDataCarrier\x0a\x09\x22stop\x22",
-messageSends: [],
-referencedClasses: []
-}),
-smalltalk.TrappedStoppingProcessor);
-
-smalltalk.addMethod(
-smalltalk.method({
-selector: "toView:",
-category: 'data transformation',
-fn: function (aDataCarrier){
-var self=this;
-return smalltalk.withContext(function($ctx1) { 
-return self}, function($ctx1) {$ctx1.fill(self,"toView:",{aDataCarrier:aDataCarrier},smalltalk.TrappedStoppingProcessor)})},
-args: ["aDataCarrier"],
-source: "toView: aDataCarrier\x0a\x09\x22stop\x22",
-messageSends: [],
-referencedClasses: []
-}),
-smalltalk.TrappedStoppingProcessor);
-
-
-
-smalltalk.addClass('TrappedProcessorWidget', smalltalk.TrappedStoppingProcessor, ['viewName'], 'Trapped-Frontend');
-smalltalk.TrappedProcessorWidget.comment="When installed, I insert a widget instance of the class specified when creating me.";
+smalltalk.addClass('TrappedProcessorWidget', smalltalk.TrappedProcessor, ['viewName'], 'Trapped-Frontend');
+smalltalk.TrappedProcessorWidget.comment="I insert a widget instance of the class specified when creating me.";
 smalltalk.addMethod(
 smalltalk.addMethod(
 smalltalk.method({
 smalltalk.method({
 selector: "installToView:toModel:",
 selector: "installToView:toModel:",
@@ -1680,17 +1728,17 @@ selector: "trap:processors:",
 category: '*Trapped-Frontend',
 category: '*Trapped-Frontend',
 fn: function (path,anArray){
 fn: function (path,anArray){
 var self=this;
 var self=this;
-function $TrappedDataChain(){return smalltalk.TrappedDataChain||(typeof TrappedDataChain=="undefined"?nil:TrappedDataChain)}
+function $TrappedProcessingChain(){return smalltalk.TrappedProcessingChain||(typeof TrappedProcessingChain=="undefined"?nil:TrappedProcessingChain)}
 return smalltalk.withContext(function($ctx1) { 
 return smalltalk.withContext(function($ctx1) { 
 _st(path)._trapDescend_((function(snap){
 _st(path)._trapDescend_((function(snap){
 return smalltalk.withContext(function($ctx2) {
 return smalltalk.withContext(function($ctx2) {
-return _st(_st($TrappedDataChain())._newFromProcessorSpecs_(anArray))._forSnapshot_andBrush_(snap,self);
+return _st(_st($TrappedProcessingChain())._newFromProcessorSpecs_(anArray))._forSnapshot_andBrush_(snap,self);
 }, function($ctx2) {$ctx2.fillBlock({snap:snap},$ctx1,1)})}));
 }, function($ctx2) {$ctx2.fillBlock({snap:snap},$ctx1,1)})}));
 return self}, function($ctx1) {$ctx1.fill(self,"trap:processors:",{path:path,anArray:anArray},smalltalk.TagBrush)})},
 return self}, function($ctx1) {$ctx1.fill(self,"trap:processors:",{path:path,anArray:anArray},smalltalk.TagBrush)})},
 args: ["path", "anArray"],
 args: ["path", "anArray"],
-source: "trap: path processors: anArray\x0a\x09path trapDescend: [ :snap |\x0a\x09\x09(TrappedDataChain newFromProcessorSpecs: anArray)\x0a\x09\x09\x09forSnapshot: snap andBrush: self ]",
+source: "trap: path processors: anArray\x0a\x09path trapDescend: [ :snap |\x0a\x09\x09(TrappedProcessingChain newFromProcessorSpecs: anArray)\x0a\x09\x09\x09forSnapshot: snap andBrush: self ]",
 messageSends: ["trapDescend:", "forSnapshot:andBrush:", "newFromProcessorSpecs:"],
 messageSends: ["trapDescend:", "forSnapshot:andBrush:", "newFromProcessorSpecs:"],
-referencedClasses: ["TrappedDataChain"]
+referencedClasses: ["TrappedProcessingChain"]
 }),
 }),
 smalltalk.TagBrush);
 smalltalk.TagBrush);
 
 

+ 95 - 66
st/Trapped-Frontend.st

@@ -5,8 +5,8 @@ Object subclass: #TrappedDataCarrier
 
 
 !TrappedDataCarrier methodsFor: 'accessing'!
 !TrappedDataCarrier methodsFor: 'accessing'!
 
 
-chain: aDataChain
-	chain := aDataChain
+chain: aProcessingChain
+	chain := aProcessingChain
 !
 !
 
 
 target
 target
@@ -47,11 +47,18 @@ toTargetValue
 	self target asJQuery val: (self value ifNotNil: [ :o | o value ] ifNil: [[]])
 	self target asJQuery val: (self value ifNotNil: [ :o | o value ] ifNil: [[]])
 ! !
 ! !
 
 
+!TrappedDataCarrier methodsFor: 'initialization'!
+
+initialize
+	super initialize.
+	model := true
+! !
+
 !TrappedDataCarrier class methodsFor: 'not yet classified'!
 !TrappedDataCarrier class methodsFor: 'not yet classified'!
 
 
-on: aDataChain target: anObject
+on: aProcessingChain target: anObject
 	^self new
 	^self new
-		chain: aDataChain;
+		chain: aProcessingChain;
 		target: anObject;
 		target: anObject;
 		yourself
 		yourself
 ! !
 ! !
@@ -78,11 +85,11 @@ proceed
 	(chain processorNo: index) toView: self
 	(chain processorNo: index) toView: self
 ! !
 ! !
 
 
-Object subclass: #TrappedDataChain
+Object subclass: #TrappedProcessingChain
 	instanceVariableNames: 'processors'
 	instanceVariableNames: 'processors'
 	package: 'Trapped-Frontend'!
 	package: 'Trapped-Frontend'!
 
 
-!TrappedDataChain methodsFor: 'accessing'!
+!TrappedProcessingChain methodsFor: 'accessing'!
 
 
 firstProcessorNo
 firstProcessorNo
 	^1
 	^1
@@ -100,20 +107,24 @@ processors: anArray
 	processors := anArray
 	processors := anArray
 ! !
 ! !
 
 
-!TrappedDataChain methodsFor: 'action'!
+!TrappedProcessingChain methodsFor: 'action'!
 
 
 forSnapshot: aSnapshot andBrush: aTagBrush
 forSnapshot: aSnapshot andBrush: aTagBrush
 	| toViewCarrier toModelCarrier |
 	| toViewCarrier toModelCarrier |
 	toViewCarrier := TrappedDataCarrierToView on: self target: aTagBrush.
 	toViewCarrier := TrappedDataCarrierToView on: self target: aTagBrush.
 	toModelCarrier := TrappedDataCarrierToModel on: self target: aSnapshot.
 	toModelCarrier := TrappedDataCarrierToModel on: self target: aSnapshot.
-	processors do: [ :each | each installToView: toViewCarrier toModel: toModelCarrier ]
+	processors do: [ :each | each installToView: toViewCarrier toModel: toModelCarrier ].
+	toViewCarrier value = true ifTrue: [ toViewCarrier copy proceed ]
 ! !
 ! !
 
 
-!TrappedDataChain class methodsFor: 'instance creation'!
+!TrappedProcessingChain class methodsFor: 'instance creation'!
 
 
 new: anArray
 new: anArray
+	(anArray detect: [ :each | each isExpectingModelData ] ifNone: [ nil ])
+		ifNil: [ anArray add: self dataTerminator ]
+		ifNotNil: [ anArray addFirst: self blackboardReaderWriter ].
 	^self new
 	^self new
-		processors: { self blackboardReaderWriter }, anArray;
+		processors: anArray;
 		yourself
 		yourself
 !
 !
 
 
@@ -130,29 +141,21 @@ newFromProcessorSpecs: anArray
 			TrappedProcessor perform: selector withArguments: args ] ])
 			TrappedProcessor perform: selector withArguments: args ] ])
 ! !
 ! !
 
 
-!TrappedDataChain class methodsFor: 'private'!
+!TrappedProcessingChain class methodsFor: 'private'!
 
 
 blackboardReaderWriter
 blackboardReaderWriter
 	^TrappedProcessorBlackboard new
 	^TrappedProcessorBlackboard new
-! !
-
-Widget subclass: #TrappedDumbView
-	instanceVariableNames: ''
-	package: 'Trapped-Frontend'!
-!TrappedDumbView commentStamp!
-I just read and show an actual path.!
-
-!TrappedDumbView methodsFor: 'rendering'!
+!
 
 
-renderOn: html
-	html root trap: #()
+dataTerminator
+	^TrappedProcessorTerminator new
 ! !
 ! !
 
 
 Object subclass: #TrappedProcessor
 Object subclass: #TrappedProcessor
 	instanceVariableNames: ''
 	instanceVariableNames: ''
 	package: 'Trapped-Frontend'!
 	package: 'Trapped-Frontend'!
 !TrappedProcessor commentStamp!
 !TrappedProcessor commentStamp!
-I process data in TrappedDataChain.
+I am a processing step in TrappedProcessingChain.
 I am stateless flyweight (aka servant)
 I am stateless flyweight (aka servant)
 and will get all necessary data as arguments in API calls.
 and will get all necessary data as arguments in API calls.
 
 
@@ -166,7 +169,7 @@ My public API is:
    This performs transformation of TrappedDataCarrier on its way from model to view.
    This performs transformation of TrappedDataCarrier on its way from model to view.
    Should call aDataCarrier proceed to proceed to subsequent step.
    Should call aDataCarrier proceed to proceed to subsequent step.
  - toModel:
  - toModel:
-   This performs transformation of TrappedDataToken on its way from view to model.
+   This performs transformation of TrappedDataCarrier on its way from view to model.
    Should call aDataCarrier proceed to proceed to subsequent step.!
    Should call aDataCarrier proceed to proceed to subsequent step.!
 
 
 !TrappedProcessor methodsFor: 'data transformation'!
 !TrappedProcessor methodsFor: 'data transformation'!
@@ -187,6 +190,12 @@ installToView: aDataCarrier toModel: anotherDataCarrier
 	"by default, do nothing"
 	"by default, do nothing"
 ! !
 ! !
 
 
+!TrappedProcessor methodsFor: 'testing'!
+
+isExpectingModelData
+	^false
+! !
+
 !TrappedProcessor class methodsFor: 'factory'!
 !TrappedProcessor class methodsFor: 'factory'!
 
 
 contents
 contents
@@ -217,30 +226,25 @@ widget: aString
 	^TrappedProcessorWidget new: aString
 	^TrappedProcessorWidget new: aString
 ! !
 ! !
 
 
-TrappedProcessor subclass: #TrappedProcessorBlackboard
+TrappedProcessor subclass: #TrappedDataExpectingProcessor
 	instanceVariableNames: ''
 	instanceVariableNames: ''
 	package: 'Trapped-Frontend'!
 	package: 'Trapped-Frontend'!
-!TrappedProcessorBlackboard commentStamp!
-I am used internally to fetch data from blackboard
-or write it back.!
-
-!TrappedProcessorBlackboard methodsFor: 'data transformation'!
+!TrappedDataExpectingProcessor commentStamp!
+I answer true to isExpectingModelData and serve as a base class
+for processor that present / change model data.
 
 
-toModel: aDataCarrier
-	aDataCarrier modifyTarget
-! !
+When at least one of my instances is present in the chain,
+automatic databinding processor is added at the beginning
+(the data-binding scenario); otherwise, the chain
+is run immediately with true as data (run-once scenario).!
 
 
-!TrappedProcessorBlackboard methodsFor: 'installation'!
+!TrappedDataExpectingProcessor methodsFor: 'testing'!
 
 
-installToView: aDataCarrier toModel: anotherDataCarrier
-	| snap |
-	snap := anotherDataCarrier target.
-	snap watch: [ :data |
-		(aDataCarrier target asJQuery closest: 'html') toArray isEmpty ifTrue: [ KeyedPubSubUnsubscribe signal ].
-        snap do: [ aDataCarrier copy value: data; proceed ] ]
+isExpectingModelData
+	^true
 ! !
 ! !
 
 
-TrappedProcessor subclass: #TrappedProcessorContents
+TrappedDataExpectingProcessor subclass: #TrappedProcessorContents
 	instanceVariableNames: ''
 	instanceVariableNames: ''
 	package: 'Trapped-Frontend'!
 	package: 'Trapped-Frontend'!
 !TrappedProcessorContents commentStamp!
 !TrappedProcessorContents commentStamp!
@@ -252,7 +256,7 @@ toView: aDataCarrier
 	aDataCarrier toTargetContents
 	aDataCarrier toTargetContents
 ! !
 ! !
 
 
-TrappedProcessor subclass: #TrappedProcessorInputChecked
+TrappedDataExpectingProcessor subclass: #TrappedProcessorInputChecked
 	instanceVariableNames: ''
 	instanceVariableNames: ''
 	package: 'Trapped-Frontend'!
 	package: 'Trapped-Frontend'!
 !TrappedProcessorInputChecked commentStamp!
 !TrappedProcessorInputChecked commentStamp!
@@ -272,7 +276,7 @@ installToView: aDataCarrier toModel: anotherDataCarrier
 	brush onChange: [ anotherDataCarrier copy value: (brush asJQuery attr: 'checked') notNil; proceed ]
 	brush onChange: [ anotherDataCarrier copy value: (brush asJQuery attr: 'checked') notNil; proceed ]
 ! !
 ! !
 
 
-TrappedProcessor subclass: #TrappedProcessorInputValue
+TrappedDataExpectingProcessor subclass: #TrappedProcessorInputValue
 	instanceVariableNames: ''
 	instanceVariableNames: ''
 	package: 'Trapped-Frontend'!
 	package: 'Trapped-Frontend'!
 !TrappedProcessorInputValue commentStamp!
 !TrappedProcessorInputValue commentStamp!
@@ -292,6 +296,34 @@ installToView: aDataCarrier toModel: anotherDataCarrier
 	brush onChange: [ anotherDataCarrier copy value: brush asJQuery val; proceed ]
 	brush onChange: [ anotherDataCarrier copy value: brush asJQuery val; proceed ]
 ! !
 ! !
 
 
+TrappedProcessor subclass: #TrappedProcessorBlackboard
+	instanceVariableNames: ''
+	package: 'Trapped-Frontend'!
+!TrappedProcessorBlackboard commentStamp!
+I am used internally to fetch data from blackboard
+or write it back.
+
+I am added to the beginning of the chain
+when the chain contains at least one element
+that isExpectingModelData (see TrappedDataExpectingProcessor).!
+
+!TrappedProcessorBlackboard methodsFor: 'data transformation'!
+
+toModel: aDataCarrier
+	aDataCarrier modifyTarget
+! !
+
+!TrappedProcessorBlackboard methodsFor: 'installation'!
+
+installToView: aDataCarrier toModel: anotherDataCarrier
+	| snap |
+	snap := anotherDataCarrier target.
+	snap watch: [ :data |
+		(aDataCarrier target asJQuery closest: 'html') toArray isEmpty ifTrue: [ KeyedPubSubUnsubscribe signal ].
+        snap do: [ aDataCarrier copy value: data; proceed ] ].
+	aDataCarrier value: false
+! !
+
 TrappedProcessor subclass: #TrappedProcessorSignal
 TrappedProcessor subclass: #TrappedProcessorSignal
 	instanceVariableNames: 'selector'
 	instanceVariableNames: 'selector'
 	package: 'Trapped-Frontend'!
 	package: 'Trapped-Frontend'!
@@ -323,6 +355,21 @@ new: aString
 		yourself
 		yourself
 ! !
 ! !
 
 
+TrappedProcessor subclass: #TrappedProcessorTerminator
+	instanceVariableNames: ''
+	package: 'Trapped-Frontend'!
+!TrappedProcessorTerminator commentStamp!
+I do not proceed in toView:.
+
+I am added automatically to end of chain when it does not contain
+any element that isExpectingModelData (see TrappedDataExpectingProcessor).!
+
+!TrappedProcessorTerminator methodsFor: 'data transformation'!
+
+toView: aDataCarrier
+	"stop"
+! !
+
 TrappedProcessor subclass: #TrappedProcessorWhenClicked
 TrappedProcessor subclass: #TrappedProcessorWhenClicked
 	instanceVariableNames: ''
 	instanceVariableNames: ''
 	package: 'Trapped-Frontend'!
 	package: 'Trapped-Frontend'!
@@ -332,7 +379,7 @@ I bind to an element and send true to blackboard when clicked.!
 !TrappedProcessorWhenClicked methodsFor: 'installation'!
 !TrappedProcessorWhenClicked methodsFor: 'installation'!
 
 
 installToView: aDataCarrier toModel: anotherDataCarrier
 installToView: aDataCarrier toModel: anotherDataCarrier
-	aDataCarrier target onClick: [ anotherDataCarrier copy value: true; proceed. false ]
+	aDataCarrier target onClick: [ anotherDataCarrier copy proceed. false ]
 ! !
 ! !
 
 
 TrappedProcessor subclass: #TrappedProcessorWhenSubmitted
 TrappedProcessor subclass: #TrappedProcessorWhenSubmitted
@@ -344,32 +391,14 @@ I bind to a form and send true to blackboard when submitted.!
 !TrappedProcessorWhenSubmitted methodsFor: 'installation'!
 !TrappedProcessorWhenSubmitted methodsFor: 'installation'!
 
 
 installToView: aDataCarrier toModel: anotherDataCarrier
 installToView: aDataCarrier toModel: anotherDataCarrier
-	aDataCarrier target onSubmit: [ anotherDataCarrier copy value: true; proceed. false ]
-! !
-
-TrappedProcessor subclass: #TrappedStoppingProcessor
-	instanceVariableNames: ''
-	package: 'Trapped-Frontend'!
-!TrappedStoppingProcessor commentStamp!
-I do not proceed in toView: nor in toModel:
-
-I am therefore only interesting for my side-effects from install step.!
-
-!TrappedStoppingProcessor methodsFor: 'data transformation'!
-
-toModel: aDataCarrier
-	"stop"
-!
-
-toView: aDataCarrier
-	"stop"
+	aDataCarrier target onSubmit: [ anotherDataCarrier copy proceed. false ]
 ! !
 ! !
 
 
-TrappedStoppingProcessor subclass: #TrappedProcessorWidget
+TrappedProcessor subclass: #TrappedProcessorWidget
 	instanceVariableNames: 'viewName'
 	instanceVariableNames: 'viewName'
 	package: 'Trapped-Frontend'!
 	package: 'Trapped-Frontend'!
 !TrappedProcessorWidget commentStamp!
 !TrappedProcessorWidget commentStamp!
-When installed, I insert a widget instance of the class specified when creating me.!
+I insert a widget instance of the class specified when creating me.!
 
 
 !TrappedProcessorWidget methodsFor: 'accessing'!
 !TrappedProcessorWidget methodsFor: 'accessing'!
 
 
@@ -596,7 +625,7 @@ trap: path
 
 
 trap: path processors: anArray
 trap: path processors: anArray
 	path trapDescend: [ :snap |
 	path trapDescend: [ :snap |
-		(TrappedDataChain newFromProcessorSpecs: anArray)
+		(TrappedProcessingChain newFromProcessorSpecs: anArray)
 			forSnapshot: snap andBrush: self ]
 			forSnapshot: snap andBrush: self ]
 !
 !